Using new file structure. Use new regexir display method. Fix line dependency error in prefix analysing.

Signed-off-by: Nitload <YoumuLiu_NSI@whv0t.onmicrosoft.com>
This commit is contained in:
Nitload
2026-07-12 02:04:57 +08:00
parent ae8a09efc2
commit b6cd2fc256
476 changed files with 16440 additions and 502 deletions
@@ -0,0 +1,204 @@
// -----------------------------------------------------------------------------
// file="BidirectionalDictionary"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace StgSharp.Collections
{
public sealed class BidirectionalDictionary<TFirst, TSecond> : IBidirectionalDictionary<TFirst, TSecond>
where TFirst : notnull
where TSecond : notnull
{
private readonly Dictionary<TFirst, TSecond> _forward;
private readonly Dictionary<TSecond, TFirst> _reverse;
public BidirectionalDictionary()
{
_forward = [];
_reverse = [];
}
public TFirst this[
TSecond key
]
{
get => _reverse.TryGetValue(key, out TFirst value) ?
value :
throw new ArgumentException($"Cannot find key {key} in dictionary.");
set
{
if (_reverse.TryGetValue(key, out TFirst origin))
{
_reverse[key] = value;
_ = _forward.Remove(origin);
} else
{
_reverse.Add(key, value);
}
_forward.Add(value, key);
}
}
public TSecond this[
TFirst key
]
{
get => _forward.TryGetValue(key, out TSecond value) ?
value :
throw new ArgumentException($"Cannot find key {key} in dictionary.");
set
{
if (_forward.TryGetValue(key, out TSecond origin))
{
_forward[key] = value;
_ = _reverse.Remove(origin);
} else
{
_forward.Add(key, value);
}
_reverse.Add(value, key);
}
}
public bool IsReadOnly => false;
public ICollection<TFirst> FirstIndex => _forward.Keys;
public ICollection<TSecond> SecondIndex => _reverse.Keys;
public int Count => _forward.Count;
public IReadOnlyDictionary<TFirst, TSecond> Forward => new ReadOnlyDictionary<TFirst, TSecond>(
_forward);
public IReadOnlyDictionary<TSecond, TFirst> Reverse => new ReadOnlyDictionary<TSecond, TFirst>(
_reverse);
public void Add(
KeyValuePair<TFirst, TSecond> item
)
{
if (_forward.ContainsKey(item.Key) || _reverse.ContainsKey(item.Value)) {
throw new ArgumentException("Duplicate key or value.");
}
_forward.Add(item.Key, item.Value);
_reverse.Add(item.Value, item.Key);
}
public void Add(
TFirst key,
TSecond value
)
{
if (_forward.ContainsKey(key) || _reverse.ContainsKey(value)) {
throw new ArgumentException("Duplicate key or value.");
}
_forward.Add(key, value);
_reverse.Add(value, key);
}
public void Clear()
{
_forward.Clear();
_reverse.Clear();
}
public bool Contains(
KeyValuePair<TFirst, TSecond> item
)
{
return _forward.Contains(item) &&
_reverse.TryGetValue(item.Value, out TFirst var) &&
var!.Equals(item.Key);
}
public bool Contains(
TSecond key
)
{
return _reverse.ContainsKey(key);
}
public bool Contains(
TFirst key
)
{
return _forward.ContainsKey(key);
}
public void CopyTo(
KeyValuePair<TFirst, TSecond>[] array,
int arrayIndex
)
{
if (array == null) {
throw new ArgumentNullException(nameof(array));
}
if (arrayIndex < 0 || arrayIndex > array.Length) {
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
if (array.Length - arrayIndex < _forward.Count) {
throw new ArgumentException("The array is too small to copy the elements.");
}
foreach (KeyValuePair<TFirst, TSecond> pair in _forward) {
array[arrayIndex++] = pair;
}
}
public IEnumerator<KeyValuePair<TFirst, TSecond>> GetEnumerator()
{
foreach (KeyValuePair<TFirst, TSecond> pair in _forward) {
yield return pair;
}
}
public bool Remove(
TFirst key
)
{
throw new NotImplementedException();
}
public bool Remove(
KeyValuePair<TFirst, TSecond> item
)
{
if (_forward.Remove(item.Key)) {
return _reverse.Remove(item.Value);
}
return false;
}
public bool TryGetValue(
TFirst key,
out TSecond value
)
{
return _forward.TryGetValue(key, out value);
}
public bool TryGetValue(
TSecond key,
out TFirst value
)
{
return _reverse.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,80 @@
// -----------------------------------------------------------------------------
// file="CapacityFixedBag"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
namespace StgSharp.Collections
{
public sealed class CapacityFixedBag<T> : IEnumerable<T>
{
private readonly T[] _buffer;
public CapacityFixedBag(
int capacity
)
{
_buffer = new T[capacity];
}
public int Count { get; set; }
public int Capacity => _buffer.Length;
public void Add(
T item
)
{
if (Count >= _buffer.Length) {
throw new InvalidOperationException("Bag capacity exceeded.");
}
_buffer[Count] = item;
Count++;
}
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < Count; i++) {
yield return _buffer[i];
}
}
public void Remove(
T item
)
{
int index = Array.IndexOf(_buffer, item, 0, Count);
if (index >= 0)
{
_buffer[index] = _buffer[Count - 1];
_buffer[Count - 1] = default!;
Count--;
}
}
public void RemoveAt(
int index
)
{
if (index >= 0)
{
_buffer[index] = _buffer[Count - 1];
_buffer[Count - 1] = default!;
Count--;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,203 @@
// -----------------------------------------------------------------------------
// file="CapacityFixedList"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace StgSharp.Collections
{
public static class CapacityFixedListBuilder
{
internal static CapacityFixedList<T> Create<T>(
ReadOnlySpan<T> values
)
{
return new CapacityFixedList<T>(values);
}
}
[CollectionBuilder(typeof(CapacityFixedListBuilder), nameof(CapacityFixedListBuilder.Create))]
public class CapacityFixedList<T> : IList<T>
{
private T[] _values;
private readonly int _capacity;
private int _index;
public CapacityFixedList(
int size
)
{
_values = new T[size];
_capacity = size;
}
public CapacityFixedList(
ReadOnlySpan<T> values
)
{
_values = values.ToArray();
}
public T this[
int index
]
{
get => index < _index ?
_values[index] :
throw new ArgumentOutOfRangeException(nameof(index));
set => _values[index] =
index < _index ? value : throw new ArgumentOutOfRangeException(nameof(index));
}
public bool IsReadOnly => false;
public int Count => _index + 1;
public void Add(
T item
)
{
if (_index < _capacity - 1)
{
_index++;
this[_index] = item;
}
}
public void Clear()
{
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) {
Array.Fill(_values, default);
}
_index = 0;
}
public bool Contains(
T item
)
{
if (item is null) {
return false;
}
for (int i = 0; i <= _index; i++)
{
if (item.Equals(_values[i])) {
return true;
}
}
return false;
}
public void CopyTo(
T[] array,
int arrayIndex
)
{
_values.CopyTo(array, arrayIndex);
}
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i <= _index; i++) {
yield return _values[i];
}
}
public int IndexOf(
T item
)
{
T v;
for (int i = 0; i <= _index; i++)
{
if ((v = _values[i]) != null && v.Equals(item)) {
return i;
}
}
return -1;
}
public bool Remove(
T item
)
{
T v;
for (int i = 0; i <= _index; i++)
{
if ((v = _values[i]) != null && v.Equals(item))
{
_values[i] = _values[_index];
_index--;
return true;
}
}
return false;
}
public void RemoveAll(
Predicate<T> predicate
)
{
if (predicate is null) {
return;
}
for (int i = _index; i > 0; i--)
{
if (predicate(_values[i]))
{
_values[i] = _values[_index];
_index--;
}
}
}
public void RemoveAt(
int index
)
{
if (index <= _index)
{
Array.Copy(_values, index + 1, _values, index, _index - index);
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) {
_values[_index] = default!;
}
_index--;
}
}
public void Resize(
int newSize
)
{
Array.Resize(ref _values, newSize);
}
internal Span<T> ToSpan()
{
return _values;
}
IEnumerator IEnumerable.GetEnumerator()
{
yield return GetEnumerator();
}
void IList<T>.Insert(
int index,
T item
)
{
throw new NotSupportedException();
}
}//------------------------------------- End of Class ------------------------------------------
}
@@ -0,0 +1,166 @@
// -----------------------------------------------------------------------------
// file="CapacityFixedStack"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
namespace StgSharp.Collections
{
[CollectionBuilder(typeof(CapacityFixedStackBuilder), nameof(CapacityFixedStackBuilder.Create))]
public class CapacityFixedStack<T> : IEnumerable<T>
{
private readonly T[] _values;
// _index points to the next free slot (top empty position). Therefore Count == _index.
private int _index;
public CapacityFixedStack(
int size
)
{
ArgumentOutOfRangeException.ThrowIfNegative(size);
_values = new T[size];
_index = 0; // empty stack
}
public T? First => Volatile.Read(ref _index) > 0 ? _values[0] : default;
public bool IsEmpty => Volatile.Read(ref _index) == 0;
public int Count => Volatile.Read(ref _index);
public int Capacity => _values.Length;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<T> AsSpan()
{
return _values.AsSpan(0, _index);
}
public void Clear()
{
if (_index > 0)
{
Array.Clear(_values, 0, _index);
_index = 0;
}
}
public IEnumerator<T> GetEnumerator()
{
for (int i = _index - 1; i >= 0; i--) {
yield return _values[i];
}
}
public T Peek()
{
if (_index == 0) {
throw new InvalidOperationException("Stack is empty.");
}
return _values[_index - 1];
}
public T Pop()
{
if (_index == 0) {
throw new InvalidOperationException("Stack is empty.");
}
_index--;
T item = _values[_index];
_values[_index] = default!; // Clear reference for GC
return item;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] PopRange(
int count
)
{
if (count <= 0 || _index == 0) {
return Array.Empty<T>();
}
int n = Math.Min(count, _index);
int start = _index - n;
T[] ret = new T[n];
Array.Copy(_values, start, ret, 0, n);
Array.Clear(_values, start, n);
_index -= n;
return ret;
}
public void Push(
T item
)
{
if (_index >= _values.Length) {
throw new InvalidOperationException("Stack is full.");
}
_values[_index] = item;
_index++;
}
public void PushRange(
ReadOnlySpan<T> span
)
{
if (span.Length == 0) {
return;
}
if (_index + span.Length > _values.Length) {
throw new InvalidOperationException("Not enough space in the stack to push the entire span.");
}
span.CopyTo(_values.AsSpan(_index, span.Length));
_index += span.Length;
}
public bool TryPop(
out T value
)
{
if (_index == 0)
{
value = default!;
return false;
}
_index--;
value = _values[_index];
_values[_index] = default!;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Span<T> GetValueLayout()
{
return _values.AsSpan();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public static class CapacityFixedStackBuilder
{
public static CapacityFixedStack<T> Create<T>(
ReadOnlySpan<T> values
)
{
CapacityFixedStack<T> stack = new(values.Length);
stack.PushRange(values); // Push in order
return stack;
}
}
}
@@ -0,0 +1,284 @@
// -----------------------------------------------------------------------------
// file="ConcurrentCapacityFixedStack"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
namespace StgSharp.Collections
{
/// <summary>
/// Lock-free, fixed-capacity stack implemented on top of an array using atomic index
/// operations. - No linked list; only array slots - Uses CAS on a shared top index to
/// reserve/release slots - Per-slot state flag to safely publish/consume values under races
/// </summary>
public class ConcurrentCapacityFixedStack<T> : IEnumerable<T>
{
private readonly int[] _states; // 0 = empty, 1 = full
private readonly T[] _values;
private readonly int _capacity;
// Number of full slots (observable count)
private int _count;
// Next push position (0.._capacity). Only modified via CAS.
private int _top;
public ConcurrentCapacityFixedStack(
int capacity
)
{
if (capacity <= 0) {
throw new ArgumentOutOfRangeException(nameof(capacity));
}
_capacity = capacity;
_values = new T[capacity];
_states = new int[capacity];
_top = 0;
_count = 0;
}
public bool IsEmpty => Count == 0;
public int Capacity => _capacity;
public int Count => Volatile.Read(ref _count);
public void Clear()
{
// Pop all elements to return slots to empty state
while (TryPop(out _)) { }
}
public IEnumerator<T> GetEnumerator()
{
T[] snap = ToArraySnapshot();
for (int i = 0; i < snap.Length; i++) {
yield return snap[i];
}
}
public T Peek()
{
if (TryPeek(out T? v)) {
return v;
}
throw new InvalidOperationException("Stack is empty.");
}
public T Pop()
{
if (TryPop(out T value)) {
return value;
}
throw new InvalidOperationException("Stack is empty.");
}
public T[] PopRange(
int count
)
{
if (count <= 0) {
return [];
}
T[] result = new T[count];
int i = 0;
for (; i < count; i++)
{
if (!TryPop(out result[i]))
{
break;
}
}
if (i == count) {
return result;
}
if (i == 0) {
return [];
}
T[] trimmed = new T[i];
Array.Copy(result, trimmed, i);
return trimmed;
}
public void Push(
T item
)
{
SpinWait sw = default;
while (true)
{
int oldTop = Volatile.Read(ref _top);
if (oldTop >= _capacity) {
throw new InvalidOperationException("Stack is full.");
}
if (Interlocked.CompareExchange(ref _top, oldTop + 1, oldTop) != oldTop)
{
sw.SpinOnce();
continue; // failed to reserve slot
}
// We exclusively own slot oldTop. Publish value then state.
_values[oldTop] = item;
Volatile.Write(ref _states[oldTop], 1); // publish
_ = Interlocked.Increment(ref _count);
return;
}
}
public void PushRange(
ReadOnlySpan<T> span
)
{
for (int i = 0; i < span.Length; i++) {
Push(span[i]);
}
}
/// <summary>
/// Best-effort snapshot for enumeration; not strongly consistent under concurrent
/// mutations.
/// </summary>
public T[] ToArraySnapshot()
{
int expected = Math.Min(Volatile.Read(ref _count), _capacity);
if (expected == 0) {
return Array.Empty<T>();
}
T[] snap = new T[expected];
int copied = 0;
int idx = Volatile.Read(ref _top) - 1;
while (idx >= 0 && copied < expected)
{
if (Volatile.Read(ref _states[idx]) == 1) {
snap[copied++] = _values[idx];
}
idx--;
}
if (copied == snap.Length) {
return snap;
}
T[] trimmed = new T[copied];
Array.Copy(snap, trimmed, copied);
return trimmed;
}
public bool TryPeek(
out T value
)
{
value = default!;
SpinWait sw = default;
while (true)
{
int top = Volatile.Read(ref _top);
if (top <= 0) {
return false;
}
int idx = top - 1;
// If not yet published or racing, retry/spin
if (Volatile.Read(ref _states[idx]) == 0)
{
if (Volatile.Read(ref _top) != top)
{
continue; // head moved, retry
}
sw.SpinOnce();
continue;
}
T v = _values[idx];
// Validate consistency
if (Volatile.Read(ref _top) == top && Volatile.Read(ref _states[idx]) == 1)
{
value = v;
return true;
}
}
}
public bool TryPop(
out T value
)
{
value = default!;
SpinWait sw = default;
while (true)
{
int oldTop = Volatile.Read(ref _top);
if (oldTop <= 0)
{
return false; // empty
}
int newTop = oldTop - 1; // index to consume
if (Interlocked.CompareExchange(ref _top, newTop, oldTop) != oldTop)
{
sw.SpinOnce();
continue; // failed to reserve element
}
// If producer hasn't published yet, roll back our reservation and retry.
if (Volatile.Read(ref _states[newTop]) == 0)
{
// attempt to restore top; ok if it fails due to concurrent operations
_ = Interlocked.CompareExchange(ref _top, newTop + 1, newTop);
sw.SpinOnce();
continue;
}
value = _values[newTop];
_values[newTop] = default!; // avoid ref retention
Volatile.Write(ref _states[newTop], 0); // mark empty
_ = Interlocked.Decrement(ref _count);
return true;
}
}
public int TryPopRange(
Span<T> destination
)
{
if (destination.IsEmpty) {
return 0;
}
int i = 0;
for (; i < destination.Length; i++)
{
if (!TryPop(out destination[i]))
{
break;
}
}
return i;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,98 @@
// -----------------------------------------------------------------------------
// file="ConcurrentFlexibleArray"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.Collections
{
public class ConcurrentFlexibleArray<T>(
int capacity = 4
)
{
private volatile T[] _array = new T[capacity];
private readonly ReaderWriterLockSlim _rwLock = new();
public T this[
int index
]
{
get
{
_rwLock.EnterReadLock();
try
{
if ((uint)index >= (uint)_array.Length) {
throw new ArgumentOutOfRangeException(nameof(index));
}
return _array[index];
}
finally
{
_rwLock.ExitReadLock();
}
}
set
{
if ((uint)index >= (uint)_array.Length)
{
_rwLock.EnterWriteLock();
try
{
if ((uint)index >= (uint)_array.Length)
{
int newSize = Math.Max(_array.Length * 2, index + 1);
T[] newArray = new T[newSize];
Array.Copy(_array, newArray, _array.Length);
_array = newArray;
_array[index] = value;
}
}
finally
{
_rwLock.ExitWriteLock();
}
} else
{
_rwLock.EnterReadLock();
try
{
_array[index] = value;
}
finally
{
_rwLock.ExitReadLock();
}
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Span<T> AsSpan()
{
return _array.AsSpan();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ReleaseSpanOperation()
{
_rwLock.ExitReadLock();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RequestSpanOperation()
{
_rwLock.EnterReadLock();
}
}
}
@@ -0,0 +1,75 @@
// -----------------------------------------------------------------------------
// file="IBidirectionalDictionary"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System.Collections.Generic;
namespace StgSharp.Collections
{
public interface IBidirectionalDictionary<TFirst, TSecond> : IDictionary<TFirst, TSecond>
{
TFirst this[
TSecond key
] { get; set; }
ICollection<TFirst> FirstIndex { get; }
ICollection<TSecond> SecondIndex { get; }
IReadOnlyDictionary<TFirst, TSecond> Forward { get; }
IReadOnlyDictionary<TSecond, TFirst> Reverse { get; }
/// <summary>
/// Determines whether the <see cref="IBidirectionalDictionary{TFirst, TSecond}" /> contains the
/// specified key.
/// </summary>
/// <param _label="key">
/// The value to locate in the <see cref="IBidirectionalDictionary{TFirst, TSecond}" />.
/// </param>
/// <returns>
/// true if the <see cref="IBidirectionalDictionary{TFirst, TSecond}" /> contains an element with
/// the specified key; otherwise, false.
/// </returns>
bool Contains(
TSecond key
);
/// <summary>
/// Determines whether the <see cref="IBidirectionalDictionary{TFirst, TSecond}" /> contains the
/// specified key.
/// </summary>
/// <param _label="key">
/// The value to locate in the <see cref="IBidirectionalDictionary{TFirst, TSecond}" />.
/// </param>
/// <returns>
/// true if the <see cref="IBidirectionalDictionary{TFirst, TSecond}" /> contains an element with
/// the specified key; otherwise, false.
/// </returns>
bool Contains(
TFirst key
);
bool TryGetValue(
TSecond key,
out TFirst value
);
#pragma warning disable CA1033
ICollection<TFirst> IDictionary<TFirst, TSecond>.Keys => FirstIndex;
ICollection<TSecond> IDictionary<TFirst, TSecond>.Values => SecondIndex;
bool IDictionary<TFirst, TSecond>.ContainsKey(
TFirst key
)
{
return Contains(key);
}
#pragma warning restore CA1033
}
}
@@ -0,0 +1,193 @@
// -----------------------------------------------------------------------------
// file="LinearBag"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace StgSharp.Collections
{
public static class LinearBagBuilder
{
/// <summary>
/// Build a <see cref="LinearBag{T}" /> from a read-only span. Capacity will exactly fit
/// the input.
/// </summary>
public static LinearBag<T> Create<T>(
ReadOnlySpan<T> values
)
{
if (values.IsEmpty) {
return new LinearBag<T>(4);
}
LinearBag<T> bag = new(values.Length);
for (int i = 0; i < values.Length; i++) {
bag.Add(values[i]);
}
return bag;
}
}
/// <summary>
/// A very lightweight contiguous storage structure: unordered, not enumerable by design.
/// Provides O(1) Add and O(1) RemoveAt (by swapping with the tail). Intended for high-
/// performance scenarios where element ordering does not matter. NOT thread-safe.
/// </summary>
/// <typeparam name="T">
/// Element type.
/// </typeparam>
[CollectionBuilder(typeof(LinearBagBuilder), nameof(LinearBagBuilder.Create))]
public class LinearBag<T>(
int capacity = 4
)
{
// Invariant: valid elements occupy indexes [0, Count-1]. Count is the number of live elements.
private T[] _items = new T[Math.Max(0, capacity)];
/// <summary>
/// Access element by index (0..Count-1). Throws if out of range.
/// </summary>
public T this[
int index
]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Count);
return _items[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Count);
_items[index] = value;
}
}
/// <summary>
/// Total allocated slots.
/// </summary>
public int Capacity => _items.Length;
/// <summary>
/// Number of currently stored elements (valid index range 0..Count-1).
/// </summary>
public int Count { get; private set; }
/// <summary>
/// Adds an item (unordered). Expands internal array if needed.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(
T item
)
{
if (Count == _items.Length) {
EnsureCapacity(Count + 1);
}
_items[Count] = item;
Count++;
}
/// <summary>
/// Returns a span over the live portion of the bag (0..Count-1).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> AsSpan()
{
return new Span<T>(_items, 0, Count);
}
/// <summary>
/// Clears all elements and zeroes out references to avoid retention. Count becomes 0.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
Array.Clear(_items, 0, Count);
Count = 0;
}
/// <summary>
/// Fast clear: only resets Count (leaves underlying array unchanged). References are NOT
/// released. Use only when you are certain old references can remain.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FastClear()
{
Count = 0;
}
/// <summary>
/// Removes the first occurrence of value (unordered). Does nothing if not found.
/// </summary>
public void Remove(
T value,
EqualityComparer<T> comparer = default
)
{
comparer ??= EqualityComparer<T>.Default;
for (int i = 0; i < Count; i++)
{
if (comparer.Equals(_items[i], value))
{
RemoveAt(i);
return;
}
}
}
/// <summary>
/// Removes element at index by swapping the last element into its slot (unless already
/// last). Preserves O(1) removal at cost of ordering.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveAt(
int index
)
{
if ((uint)index >= (uint)Count) {
throw new ArgumentOutOfRangeException(nameof(index));
}
int last = Count - 1;
if (index != last) {
_items[index] = _items[last];
}
_items[last] = default!; // release reference / reset value
Count = last;
}
/// <summary>
/// Ensures capacity is at least <paramref name="min" />. Doubles size when growing.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureCapacity(
int min
)
{
if (_items.Length >= min) {
return;
}
int newCapacity = (_items.Length == 0) ? 4 : (_items.Length * 2);
if (newCapacity < min) {
newCapacity = min;
}
Array.Resize(ref _items, newCapacity);
}
}
}
@@ -0,0 +1,128 @@
// -----------------------------------------------------------------------------
// file="ListSegment"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace StgSharp.Collections
{
/// <summary>
/// Part of a collection. This contains a direct reference to _origin collection, so all
/// change to _origin collection will be reflected in this object.
/// </summary>
/// <typeparam name="T">
///
/// </typeparam>
public struct ListSegment<T> : ICollection<T>
{
private int _begin;
private int _length;
private List<T> _collection;
public ListSegment(
List<T> collection,
int begin,
int length
)
{
ArgumentNullException.ThrowIfNull(collection);
ArgumentOutOfRangeException.ThrowIfLessThan(begin, 0);
ArgumentOutOfRangeException.ThrowIfLessThan(length, 0);
ArgumentOutOfRangeException.ThrowIfLessThan(begin + length, collection.Count);
_collection = collection;
_begin = begin;
_length = length;
}
public T this[
int indexValue
]
{
get
{
ArgumentOutOfRangeException.ThrowIfLessThan(indexValue, 0);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(indexValue, _length);
return _collection[indexValue + _begin];
}
}
public T this[
Index i
]
{
get
{
int offset = i.IsFromEnd ? (_length - i.Value) : i.Value;
ArgumentOutOfRangeException.ThrowIfLessThan(offset, 0);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(offset, _length);
return _collection[offset + _begin];
}
}
public bool IsReadOnly => true;
public int Count => _length;
public bool Contains(
T item
)
{
int index = _collection.IndexOf(item);
return (index >= _begin) && (index < _length + index);
}
public void CopyTo(
T[] array,
int arrayIndex
)
{
ArgumentNullException.ThrowIfNull(array);
Span<T> span = CollectionsMarshal.AsSpan<T>(_collection).Slice(_begin, _length);
Span<T> target = new(array, arrayIndex, _length);
span.CopyTo(target);
}
public IEnumerator<T> GetEnumerator()
{
for (int i = _begin; i < _length; i++) {
yield return this[i];
}
}
void ICollection<T>.Add(
T item
)
{
throw new NotSupportedException();
}
void ICollection<T>.Clear()
{
throw new NotSupportedException();
}
bool ICollection<T>.Remove(
T item
)
{
throw new NotSupportedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,169 @@
// -----------------------------------------------------------------------------
// file="RandomAccessibleStack"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
namespace StgSharp.Collections
{
public class RandomAccessibleStack<T> : IEnumerable<T>
{
private const int DefaultCapacity = 4;
private T[] _array;
private int _size;
private int _version;
/// <summary>
/// Initializes a new instance of the RandomAccessStack class that is empty and has the
/// default initial capacity.
/// </summary>
public RandomAccessibleStack()
{
_array = new T[DefaultCapacity];
}
/// <summary>
/// Initializes a new instance of the RandomAccessStack class that is empty and has the
/// specified initial capacity.
/// </summary>
public RandomAccessibleStack(
int capacity
)
{
if (capacity < 0) {
throw new ArgumentOutOfRangeException(
nameof(capacity), "Capacity must be non-negative.");
}
_array = new T[capacity];
}
/// <summary>
/// Gets or sets the element at the specified index in the stack (0 = bottom, Count-1 =
/// top).
/// </summary>
public T this[
int index
]
{
get
{
if (index < 0 || index >= _size) {
throw new ArgumentOutOfRangeException(nameof(index));
}
return _array[index];
}
/*
set
{
if( index < 0 || index >= PrimSize ) {
throw new ArgumentOutOfRangeException( nameof( index ) );
}
_array[ index ] = value;
_version++;
}
/**/
}
/// <summary>
/// Gets the number of elements contained in the stack.
/// </summary>
public int Count => _size;
/// <summary>
/// Removes all objects from the stack.
/// </summary>
public void Clear()
{
Array.Clear(_array, 0, _size);
_size = 0;
_version++;
}
/// <summary>
/// Returns an enumerator that iterates through the stack from top to bottom. Throws if
/// the stack is modified during iteration.
/// </summary>
public IEnumerator<T> GetEnumerator()
{
int expectedVersion = _version;
for (int i = _size - 1; i >= 0; i--)
{
if (expectedVersion != _version) {
throw new InvalidOperationException(
"Collection was modified during enumeration.");
}
yield return _array[i];
}
}
/// <summary>
/// Returns the object at the top of the stack without removing it.
/// </summary>
public T Peek()
{
if (_size == 0) {
throw new InvalidOperationException("Stack is empty.");
}
return _array[_size - 1];
}
/// <summary>
/// Removes and returns the object at the top of the stack.
/// </summary>
public T Pop()
{
if (_size == 0) {
throw new InvalidOperationException("Stack is empty.");
}
T item = _array[--_size];
_array[_size] = default!;
_version++;
return item;
}
/// <summary>
/// Adds an item to the top of the stack.
/// </summary>
public void Push(
T item
)
{
if (_size == _array.Length) {
EnsureCapacity(_size + 1);
}
_array[_size++] = item;
_version++;
}
private void EnsureCapacity(
int min
)
{
int newCapacity = _array.Length == 0 ? DefaultCapacity : _array.Length * 2;
if (newCapacity < min) {
newCapacity = min;
}
Array.Resize(ref _array, newCapacity);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,130 @@
// -----------------------------------------------------------------------------
// file="SwissTable.X86"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace StgSharp.Collections
{
public partial class SwissTable
{
private unsafe class BucketScannerX86 : IBucketScanner
{
public bool TryFind(
nuint key,
uint hash,
Bucket* bucket,
out nuint value
)
{
byte ctrl = (byte)((hash >> EntryMaskWidth) & 0x7F);
Vector128<byte> ctrlVector = Vector128.Create(ctrl);
Vector128<byte> bucketCtrlVector = Sse2.LoadVector128((byte*)&bucket->Control);
Vector128<byte> cmpResult = Sse2.CompareEqual(ctrlVector, bucketCtrlVector);
int mask = Sse2.MoveMask(cmpResult);
while (mask != 0)
{
int bitPos = BitOperations.TrailingZeroCount(mask);
int index = bitPos;
if (bucket->Keys[index] == key)
{
value = bucket->Values[index];
return true;
}
mask &= mask - 1;
}
value = default;
return false;
}
public bool TryRemove(
nuint key,
uint hash,
Bucket* bucket,
out nuint value
)
{
byte ctrl = (byte)((hash >> EntryMaskWidth) & 0x7F);
Vector128<byte> ctrlVector = Vector128.Create(ctrl);
Vector128<byte> bucketCtrl = Sse2.LoadVector128((byte*)&bucket->Control);
Vector128<byte> matchMask = Sse2.CompareEqual(ctrlVector, bucketCtrl);
int matches = Sse2.MoveMask(matchMask);
while (matches != 0)
{
int bitPos = BitOperations.TrailingZeroCount(matches);
int index = bitPos;
if (bucket->Keys[index] == key)
{
value = bucket->Values[index];
bucket->Control[index] = 0xFE; // mark as DELETED
// no need to remove Key/Value
return true;
}
matches &= matches - 1;
}
value = default;
return false;
}
public bool TrySet(
nuint key,
uint hash,
Bucket* bucket,
nuint value,
out bool isNewKey
)
{
byte ctrl = (byte)((hash >> EntryMaskWidth) & 0x7F); // H2: upper 8 bits (highest bit is naturally 0)
Vector128<byte> ctrlVector = Vector128.Create(ctrl);
Vector128<byte> bucketCtrl = Sse2.LoadVector128(
(byte*)&bucket->Control);
// === Phase 1: Look up an existing key ===
Vector128<byte> matchMask = Sse2.CompareEqual(ctrlVector, bucketCtrl);
int matches = Sse2.MoveMask(matchMask);
while (matches != 0)
{
int bitPos = BitOperations.TrailingZeroCount(matches);
int index = bitPos;
if (bucket->Keys[index] == key)
{
// Key already exists -> update value
bucket->Values[index] = value;
isNewKey = false;
return true;
}
matches &= matches - 1;
}
// === Phase 2: Track an available insertion slot ===
int emptyOrDeleted = Sse2.MoveMask(bucketCtrl);
if (emptyOrDeleted == 0)
{
isNewKey = false;
return false; // bucket full
}
int slot = BitOperations.TrailingZeroCount(emptyOrDeleted);
bucket->Control[slot] = ctrl;
bucket->Keys[slot] = key;
bucket->Values[slot] = value;
isNewKey = true;
return true;
}
}
}
}
@@ -0,0 +1,224 @@
// -----------------------------------------------------------------------------
// file="SwissTable"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
namespace StgSharp.Collections
{
/// <summary>
///
/// </summary>
public sealed unsafe partial class SwissTable : IDisposable
{
private const int BucketCount = 1 << EntryMaskWidth;
private const int BucketSize = 16;
private const uint CrcMagic = 0xffff9961; // Magic number for CRC32C, used in hash computation
private const int EntryMaskWidth = 10; // 1024 entries total, 16 per bucket
public const int MaxCapacity = BucketCount * BucketSize; // 256 entries per bucket, 1024 buckets
public static readonly nuint RequestedNativeMemorySize = (nuint)Unsafe.SizeOf<Entry>();
private readonly Entry* _entries;
private readonly Action<nuint> _freeHandle;
private bool _disposed;
private SwissTable(
Entry* buffer,
Action<nuint> freeHandle
)
{
_entries = buffer;
_freeHandle = freeHandle;
}
public static SwissTable Create()
{
Entry* buffer = (Entry*)NativeMemory.AlignedAlloc((nuint)Unsafe.SizeOf<Entry>(), 16);
for (int i = 0; i < BucketCount; i++)
{
// Set all control bytes to 0xFF (empty)
Unsafe.InitBlock(&(*buffer)[i].Control, 0xFF, BucketSize);
}
return new SwissTable(buffer, handle => NativeMemory.AlignedFree((void*)handle));
}
public static SwissTable Create(
Func<nuint, nuint> allocator,
Action<nuint> freeHandle
)
{
Entry* buffer = (Entry*)allocator(RequestedNativeMemorySize);
for (int i = 0; i < BucketCount; i++)
{
// Set all control bytes to 0xFF (empty)
Unsafe.InitBlock(&(*buffer)[i].Control, 0xFF, BucketSize);
}
return new SwissTable(buffer, freeHandle);
}
public void Dispose()
{
if (_disposed) {
return;
}
_disposed = true;
_freeHandle((nuint)_entries);
GC.SuppressFinalize(this);
}
public bool TryAddOrSet(
nuint key,
nuint value,
out bool isNewKey
)
{
uint hash = BitOperations.Crc32C(CrcMagic, (ulong)key);
ushort entry = (ushort)(hash & (BucketCount - 1));
Bucket* bucket = &(*_entries)[entry];
return IBucketScanner.Current.TrySet(key, hash, bucket, value, out isNewKey);
}
public bool TryGet(
nuint key,
out nuint value
)
{
uint hash = BitOperations.Crc32C(CrcMagic, key);
ushort entry = (ushort)(hash & (BucketCount - 1));
Bucket* bucket = &(*_entries)[entry];
return IBucketScanner.Current.TryFind(key, hash, bucket, out value);
/*
* hash = CRC32 of key
* entry = low 8 of hash
* ctrl = 8-15 of hash
* index = rest of hash
* find value of hash
*/
}
public bool TryRemove(
nuint key,
out nuint value
)
{
uint hash = BitOperations.Crc32C(CrcMagic, key);
ushort entry = (ushort)(hash & (BucketCount - 1));
Bucket* bucket = &(*_entries)[entry];
return IBucketScanner.Current.TryRemove(key, hash, bucket, out value);
}
internal static SwissTable Create(
nuint bufferPtr,
Action<nuint> FreeHandle
)
{
Entry* buffer = (Entry*)bufferPtr;
for (int i = 0; i < BucketCount; i++)
{
// Set all control bytes to 0xFF (empty)
Unsafe.InitBlock(&(*buffer)[i].Control, 0xFF, BucketSize);
}
return new SwissTable(buffer, FreeHandle);
}
~SwissTable()
{
Dispose();
}
[InlineArray(BucketCount)]
private struct Entry
{
public Bucket Bucket;
}
private unsafe struct Bucket
{
public BucketControl Control;
public BucketKeyStorage Keys;
public BucketValueStorage Values;
}
private interface IBucketScanner
{
static IBucketScanner Current { get; } = Create();
bool TryFind(
nuint key,
uint Hash,
Bucket* bucket,
out nuint value
);
unsafe bool TryRemove(
nuint key,
uint hash,
Bucket* bucket,
out nuint value
);
bool TrySet(
nuint key,
uint hash,
Bucket* bucket,
nuint value,
out bool isNewKey
);
private static IBucketScanner Create()
{
if (Sse42.IsSupported)
{
return new BucketScannerX86();
} else if (ArmBase.IsSupported)
{
throw new NotSupportedException("ARM architecture is currently not supported for bucket scanning. Please use an x86 platform.");
} else
{
throw new NotSupportedException("No suitable bucket scanner implementation found for the current platform.");
}
}
}
[InlineArray(BucketSize)]
private struct BucketControl
{
public byte Control;
}
[InlineArray(BucketSize)]
private struct BucketKeyStorage
{
public nuint value;
}
[InlineArray(BucketSize)]
private struct BucketValueStorage
{
public nuint value;
}
}
}
@@ -0,0 +1,156 @@
// -----------------------------------------------------------------------------
// file="UnmanagedArray"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.Collections
{
/// <summary>
/// Zero-overhead view over a contiguous region of unmanaged memory. Does not own the memory —
/// caller is responsible for lifetime management.
/// </summary>
/// <typeparam name="T">
/// Element type. Must be unmanaged.
/// </typeparam>
public readonly unsafe struct UnmanagedArray<T> where T : unmanaged
{
private static readonly nint ElementSize = sizeof(T);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UnmanagedArray(
T* pointer,
int length
)
{
Address = pointer;
Length = length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UnmanagedArray(
nuint address,
int length
)
{
Address = (T*)address;
Length = length;
}
/// <summary>
/// Indexed access. No bounds check in Release builds.
/// </summary>
public ref T this[
int index
]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Debug.Assert((uint)index < (uint)Length,
"UnmanagedArray index out of range");
return ref Address[index];
}
}
/// <summary>
/// Raw pointer to the first element.
/// </summary>
public T* Address
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
init;
}
/// <summary>
/// Element count.
/// </summary>
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
init;
}
/// <summary>
/// Total byte size of the array (for allocation calculations).
/// </summary>
public int ByteSize => Length * sizeof(T);
/// <summary>
/// Wraps the array as a <see cref="Span{T}" />.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> AsSpan()
{
return new Span<T>(Address, Length);
}
/// <summary>
/// Wraps a sub-range as a <see cref="Span{T}" />.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> AsSpan(
int start,
int length
)
{
Debug.Assert(((uint)start) + ((uint)length) <= (uint)Length,
"UnmanagedArray.AsSpan range out of bounds");
return new Span<T>(Address + start, length);
}
/// <summary>
/// Zero-fill all elements.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
Unsafe.InitBlock(Address, 0, (uint)(Length * sizeof(T)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int IndexOf(
T* ptr
)
{
int offset = (int)((((nint)ptr) - ((nint)Address)) / ElementSize);
ArgumentOutOfRangeException.ThrowIfNegative(offset, nameof(ptr));
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(offset, Length, nameof(ptr));
return offset;
}
/// <summary>
/// Returns a pointer to element at <paramref name="index" />. Useful for feeding SIMD
/// load instructions (e.g. Sse2.LoadVector128).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T* PtrAt(
int index
)
{
Debug.Assert((uint)index < (uint)Length,
"UnmanagedArray.PtrAt index out of range");
return Address + index;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int UncheckedIndexOf(
T* ptr
)
{
return (int)((((nint)ptr) - ((nint)Address)) / ElementSize);
}
}
}
@@ -0,0 +1,85 @@
// -----------------------------------------------------------------------------
// file="UnmanagedStack"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.Collections
{
public unsafe class UnmanagedStack<T>(
T* address,
int capacity,
Action<nuint> freeHandle
) : IDisposable where T : unmanaged
{
private T* _array = address;
private readonly Action<nuint> _freeHandle = freeHandle;
private readonly int _capacity = capacity;
private int _index;
public void Clear()
{
_index = 0;
}
public void Dispose()
{
if (_array != null)
{
_freeHandle((nuint)_array);
_array = null;
}
GC.SuppressFinalize(this);
}
public T Pop()
{
if (_index == 0) {
throw new InvalidOperationException("Stack is empty.");
}
return _array[--_index];
}
public void Push(
T value
)
{
_array[_index] = value;
_index++;
}
public bool TryPop(
out T value
)
{
if (_index == 0)
{
value = default;
return false;
}
value = _array[--_index];
return true;
}
public bool TryPush(
T value
)
{
if (_index >= _capacity) {
return false;
}
_array[_index] = value;
_index++;
return true;
}
}
}