From 4ea1d79cadb1146862621513f8772c17ac124d52 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Fri, 3 May 2024 18:03:26 -0700 Subject: [PATCH] fix: Removes broken OrderedHashSet and adds a simple OrderedSet (#1756) > [!CAUTION] > **BREAKING CHANGE** > Removed `OrderdHashSet` and `PooledOrderedHashSet` due to bugs. > [!NOTE] > **Developer Note** > The OrderedSet is not a full data structure. It is not particularly efficient. Pull Requests are welcome for a better implementation, especially if it ends up supporting `ISet` and `IReadOnlySet` ## Summary The ordered hash set was buggy. It's kind of painful to implement, so for now, I added a simple `OrderedSet` to suffice for gumps. Please reach out if this causes disruption! --- .../Tests/Collections/OrderedHashSetTests.cs | 46 -- .../Tests/Collections/OrderedSetTests.cs | 29 + Projects/Server/Collections/OrderedHashSet.cs | 578 ----------------- Projects/Server/Collections/OrderedSet.cs | 62 ++ .../Collections/PooledOrderedHashSet.cs | 602 ------------------ .../Server/Gumps/Legacy/GumpAlphaRegion.cs | 2 +- .../Server/Gumps/Legacy/GumpBackground.cs | 2 +- Projects/Server/Gumps/Legacy/GumpButton.cs | 2 +- Projects/Server/Gumps/Legacy/GumpCheck.cs | 2 +- .../Server/Gumps/Legacy/GumpECHandleInput.cs | 2 +- Projects/Server/Gumps/Legacy/GumpEntry.cs | 4 +- Projects/Server/Gumps/Legacy/GumpGroup.cs | 2 +- Projects/Server/Gumps/Legacy/GumpHtml.cs | 4 +- .../Server/Gumps/Legacy/GumpHtmlLocalized.cs | 2 +- Projects/Server/Gumps/Legacy/GumpImage.cs | 2 +- .../Gumps/Legacy/GumpImageTileButton.cs | 2 +- .../Server/Gumps/Legacy/GumpImageTiled.cs | 2 +- Projects/Server/Gumps/Legacy/GumpItem.cs | 2 +- .../Server/Gumps/Legacy/GumpItemProperty.cs | 2 +- Projects/Server/Gumps/Legacy/GumpLabel.cs | 4 +- .../Server/Gumps/Legacy/GumpLabelCropped.cs | 4 +- .../Server/Gumps/Legacy/GumpMasterGump.cs | 2 +- Projects/Server/Gumps/Legacy/GumpPage.cs | 2 +- Projects/Server/Gumps/Legacy/GumpRadio.cs | 2 +- .../Server/Gumps/Legacy/GumpSpriteImage.cs | 2 +- Projects/Server/Gumps/Legacy/GumpTextEntry.cs | 4 +- .../Gumps/Legacy/GumpTextEntryLimited.cs | 4 +- Projects/Server/Gumps/Legacy/GumpTooltip.cs | 2 +- .../Network/Packets/OutgoingGumpPackets.cs | 2 +- version.json | 2 +- 30 files changed, 121 insertions(+), 1258 deletions(-) delete mode 100644 Projects/Server.Tests/Tests/Collections/OrderedHashSetTests.cs create mode 100644 Projects/Server.Tests/Tests/Collections/OrderedSetTests.cs delete mode 100644 Projects/Server/Collections/OrderedHashSet.cs create mode 100644 Projects/Server/Collections/OrderedSet.cs delete mode 100644 Projects/Server/Collections/PooledOrderedHashSet.cs diff --git a/Projects/Server.Tests/Tests/Collections/OrderedHashSetTests.cs b/Projects/Server.Tests/Tests/Collections/OrderedHashSetTests.cs deleted file mode 100644 index 916a5c389..000000000 --- a/Projects/Server.Tests/Tests/Collections/OrderedHashSetTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Linq; -using Server.Collections; -using Xunit; - -namespace Server.Tests -{ - public class OrderedHashSetTests - { - [Fact] - public void TestOrderedHashSet() - { - var set = new OrderedHashSet(1) - { - "random string1", "another random string1", "another random string2", "another random string3" - }; - set.Remove("another random string1"); - var list = set.ToList(); - - string[] arr = { "random string1", "another random string2", "another random string3" }; - int i = 0; - foreach (var entry in list) - { - Assert.Equal(entry, arr[i++]); - } - } - - [Fact] - public void TestOrderedHashWithNull() - { - var set = new OrderedHashSet(1) - { - "random string1", null, "another random string2", "another random string3" - }; - - set.Remove("another random string1"); - var list = set.ToList(); - - string[] arr = { "random string1", null, "another random string2", "another random string3" }; - int i = 0; - foreach (var entry in list) - { - Assert.Equal(entry, arr[i++]); - } - } - } -} diff --git a/Projects/Server.Tests/Tests/Collections/OrderedSetTests.cs b/Projects/Server.Tests/Tests/Collections/OrderedSetTests.cs new file mode 100644 index 000000000..4d81d5598 --- /dev/null +++ b/Projects/Server.Tests/Tests/Collections/OrderedSetTests.cs @@ -0,0 +1,29 @@ +using Server.Collections; +using Xunit; + +namespace Server.Tests; + +public class OrderedSetTests +{ + [Fact] + public void TestOrderedSet() + { + int[] expected = [6, 2, 4, 23, 0]; + var set = new OrderedSet(); + for (var i = 0; i < expected.Length; i++) + { + set.Add(expected[i]); + } + + Assert.Equal(expected.Length, set.Count); + + Assert.True(set.Contains(4)); + Assert.False(set.Contains(8)); + + int index = 0; + foreach (var value in set) + { + Assert.Equal(expected[index++], value); + } + } +} diff --git a/Projects/Server/Collections/OrderedHashSet.cs b/Projects/Server/Collections/OrderedHashSet.cs deleted file mode 100644 index 47a173aef..000000000 --- a/Projects/Server/Collections/OrderedHashSet.cs +++ /dev/null @@ -1,578 +0,0 @@ -/************************************************************************* - * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: OrderedHashSet.cs * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using Microsoft.Collections.Extensions; - -namespace Server.Collections; - -[DebuggerDisplay("Count = {Count}")] -public class OrderedHashSet : IList -{ - private struct Entry - { - public uint HashCode; - public TValue Value; - public int Next; // the index of the next item in the same bucket, -1 if last - } - - private static readonly Entry[] InitialEntries = new Entry[1]; - private int[] _buckets = HashHelpers.SizeOneIntArray; - private Entry[] _entries = InitialEntries; - private ulong _fastModMultiplier; - private int _count; - private int _version; -#nullable enable - private readonly IEqualityComparer? _comparer; -#nullable disable - - public int Count => _count; -#nullable enable - public IEqualityComparer? Comparer => _comparer; -#nullable disable - - public OrderedHashSet() - : this(0) - { - } - - public OrderedHashSet(IEqualityComparer comparer) - : this(0, comparer) - { - } - - public OrderedHashSet(int capacity, IEqualityComparer comparer = null) - { - if (capacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity)); - } - - if (capacity > 0) - { - int newSize = HashHelpers.GetPrime(capacity); - _buckets = new int[newSize]; - _entries = new Entry[newSize]; - _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); - } - - if (comparer != EqualityComparer.Default) - { - _comparer = comparer; - } - } - - public OrderedHashSet(IEnumerable collection, IEqualityComparer comparer = null) - : this((collection as ICollection)?.Count ?? 0, comparer) - { - if (collection == null) - { - throw new ArgumentNullException(nameof(collection)); - } - - foreach (TValue value in collection) - { - Add(value); - } - } - - public bool Contains(TValue item) => TryGetValue(item, out var value) && EqualityComparer.Default.Equals(value); - - public void Clear() - { - if (_count > 0) - { - Array.Clear(_buckets, 0, _buckets.Length); - Array.Clear(_entries, 0, _count); - _count = 0; - ++_version; - } - } - - public int EnsureCapacity(int capacity) - { - if (capacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity)); - } - - if (_entries.Length >= capacity) - { - return _entries.Length; - } - int newSize = HashHelpers.GetPrime(capacity); - Resize(newSize); - ++_version; - return newSize; - } - - public Enumerator GetEnumerator() => new(this); - - void ICollection.Add(TValue item) => TryAdd(item); - - public bool Add(TValue item) => TryAdd(item); - - public int GetOrAdd(TValue value) => TryInsert(null, value); - - public int IndexOf(TValue value) => IndexOf(value, out _); - - public void Insert(int index, TValue value) - { - if ((uint)index > (uint)Count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - TryInsert(index, value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ref int GetBucketRef(uint hashCode) - { - int[] buckets = _buckets!; - return ref buckets[HashHelpers.FastMod(hashCode, (uint)buckets.Length, _fastModMultiplier)]; - } - - public bool Remove(TValue value) - { - int index = IndexOf(value); - if (index >= 0) - { - RemoveAt(index); - return true; - } - - return false; - } - - public void RemoveAt(int index) - { - int count = Count; - if ((uint)index >= (uint)count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - // Remove the entry from the bucket - RemoveEntryFromBucket(index); - - // Decrement the indices > index - Entry[] entries = _entries; - for (int i = index + 1; i < count; ++i) - { - entries[i - 1] = entries[i]; - UpdateBucketIndex(i, incrementAmount: -1); - } - --_count; - entries[_count] = default; - ++_version; - } - - public void TrimExcess(int capacity) - { - if (capacity < Count) - { - throw new ArgumentOutOfRangeException(nameof(capacity)); - } - - int newSize = HashHelpers.GetPrime(capacity); - if (newSize < _entries.Length) - { - Resize(newSize); - ++_version; - } - } - - public bool TryAdd(TValue value) => TryInsert(null, value) != _count - 1; - - public bool TryGetValue(TValue value, out TValue actualValue) - { - int index = IndexOf(value); - if (index >= 0) - { - actualValue = _entries[index].Value; - return true; - } - - actualValue = default; - return false; - } - - public TValue this[int index] - { - get - { - if ((uint)index >= (uint)Count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - return _entries[index].Value; - } - set - { - if ((uint)index >= (uint)Count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - TValue v = value; - int foundIndex = IndexOf(v, out uint hashCode); - if (foundIndex < 0) - { - RemoveEntryFromBucket(index); - Entry entry = new Entry { HashCode = hashCode, Value = value }; - AddEntryToBucket(ref entry, index, _buckets); - _entries[index] = entry; - ++_version; - } - else if (foundIndex == index) - { - ref Entry entry = ref _entries[index]; - entry.Value = value; - } - else - { - throw new ArgumentException(string.Format(CollectionThrowStrings.Argument_AddingDuplicate, v.ToString())); - } - } - } - - public bool IsReadOnly => false; - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public void CopyTo(TValue[] array, int arrayIndex) - { - if (array == null) - { - throw new ArgumentNullException(nameof(array)); - } - - if ((uint)arrayIndex > (uint)array.Length) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex), CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum); - } - - int count = Count; - if (array.Length - arrayIndex < count) - { - throw new ArgumentException(CollectionThrowStrings.Arg_ArrayPlusOffTooSmall); - } - - Entry[] entries = _entries; - for (int i = 0; i < count; ++i) - { - Entry entry = entries[i]; - array[i + arrayIndex] = entry.Value; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Entry[] Resize(int newSize) - { - int[] newBuckets = new int[newSize]; - Entry[] newEntries = new Entry[newSize]; - - int count = Count; - Array.Copy(_entries, newEntries, count); - - _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); - - for (int i = 0; i < count; ++i) - { - AddEntryToBucket(ref newEntries[i], i, newBuckets); - } - - _buckets = newBuckets; - _entries = newEntries; - return newEntries; - } - -#nullable enable - private int IndexOf(TValue value, out uint hashCode) - { - ref int bucket = ref Unsafe.NullRef(); - int i; - - IEqualityComparer? comparer = _comparer; - if (comparer == null) - { - hashCode = (uint)(value?.GetHashCode() ?? 0); - bucket = ref GetBucketRef(hashCode); - i = bucket - 1; - - if (i >= 0) - { - if (typeof(TValue).IsValueType) - { - // ValueType: Devirtualize with EqualityComparer.Default intrinsic - Entry[] entries = _entries; - int collisionCount = 0; - do - { - Entry entry = entries[i]; - if (entry.HashCode == hashCode && EqualityComparer.Default.Equals(entry.Value, value)) - { - break; - } - - i = entry.Next; - if (collisionCount >= entries.Length) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException( - CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported - ); - } - - ++collisionCount; - } while (i >= 0); - } - else - { - // Object type: Shared Generic, EqualityComparer.Default won't devirtualize (https://github.com/dotnet/runtime/issues/10050), - // so cache in a local rather than get EqualityComparer per loop iteration. - var defaultComparer = EqualityComparer.Default; - Entry[] entries = _entries; - int collisionCount = 0; - do - { - Entry entry = entries[i]; - if (entry.HashCode == hashCode && defaultComparer.Equals(entry.Value, value)) - { - break; - } - - i = entry.Next; - if (collisionCount >= entries.Length) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException( - CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported - ); - } - - ++collisionCount; - } while (i >= 0); - } - } - } - else - { - hashCode = (uint)comparer.GetHashCode(value); - bucket = ref GetBucketRef(hashCode); - i = bucket - 1; - if (i >= 0) - { - Entry[] entries = _entries; - int collisionCount = 0; - do - { - Entry entry = entries[i]; - if (entry.HashCode == hashCode && comparer.Equals(entry.Value, value)) - { - break; - } - i = entry.Next; - if (collisionCount >= entries.Length) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); - } - ++collisionCount; - } while (i >= 0); - } - } - - return i; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int TryInsert(int? index, TValue value) - { - int i = IndexOf(value, out uint hashCode); - return i >= 0 ? i : AddInternal(index, value, hashCode); - } - - private int AddInternal(int? index, TValue value, uint hashCode) - { - Entry[] entries = _entries; - // Check if resize is needed - int count = Count; - if (entries.Length == count || entries.Length == 1) - { - entries = Resize(HashHelpers.ExpandPrime(entries.Length)); - } - - // Increment indices >= index; - int actualIndex = index ?? count; - for (int i = count - 1; i >= actualIndex; --i) - { - entries[i + 1] = entries[i]; - UpdateBucketIndex(i, incrementAmount: 1); - } - - ref Entry entry = ref entries[actualIndex]; - entry.HashCode = hashCode; - entry.Value = value; - AddEntryToBucket(ref entry, actualIndex, _buckets); - ++_count; - ++_version; - return actualIndex; - } -#nullable restore - - // Returns the index of the next entry in the bucket - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AddEntryToBucket(ref Entry entry, int entryIndex, int[] buckets) - { - ref int b = ref buckets[(int)(entry.HashCode % (uint)buckets.Length)]; - entry.Next = b - 1; - b = entryIndex + 1; - } - - private void RemoveEntryFromBucket(int entryIndex) - { - Entry[] entries = _entries; - Entry entry = entries[entryIndex]; - ref int bucket = ref GetBucketRef(entry.HashCode); - // Bucket was pointing to removed entry. Update it to point to the next in the chain - if (bucket == entryIndex + 1) - { - bucket = entry.Next + 1; - } - else - { - // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to remove, then fix the chain - int i = bucket - 1; - int collisionCount = 0; - while (true) - { - ref Entry e = ref entries[i]; - if (e.Next == entryIndex) - { - e.Next = entry.Next; - return; - } - i = e.Next; - if (collisionCount >= entries.Length) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); - } - ++collisionCount; - } - } - } - - private void UpdateBucketIndex(int entryIndex, int incrementAmount) - { - Entry[] entries = _entries; - Entry entry = entries[entryIndex]; - ref int bucket = ref GetBucketRef(entry.HashCode); - // Bucket was pointing to entry. Increment the index by incrementAmount. - if (bucket == entryIndex + 1) - { - bucket += incrementAmount; - } - else - { - // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to increment. - int i = bucket - 1; - int collisionCount = 0; - while (true) - { - ref Entry e = ref entries[i]; - if (e.Next == entryIndex) - { - e.Next += incrementAmount; - return; - } - i = e.Next; - if (collisionCount >= entries.Length) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); - } - ++collisionCount; - } - } - } - - public struct Enumerator : IEnumerator - { - private readonly OrderedHashSet _orderedHashSet; - private readonly int _version; - private int _index; - private TValue _current; - - public TValue Current => _current; - - object IEnumerator.Current => _current; - - internal Enumerator(OrderedHashSet orderedHashSet) - { - _orderedHashSet = orderedHashSet; - _version = orderedHashSet._version; - _index = 0; - _current = default; - } - - public void Dispose() - { - } - - public bool MoveNext() - { - if (_version != _orderedHashSet._version) - { - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); - } - - if (_index < _orderedHashSet.Count) - { - Entry entry = _orderedHashSet._entries[_index]; - _current = entry.Value; - ++_index; - return true; - } - _current = default; - return false; - } - - void IEnumerator.Reset() - { - if (_version != _orderedHashSet._version) - { - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); - } - - _index = 0; - _current = default; - } - } -} diff --git a/Projects/Server/Collections/OrderedSet.cs b/Projects/Server/Collections/OrderedSet.cs new file mode 100644 index 000000000..7e55db30b --- /dev/null +++ b/Projects/Server/Collections/OrderedSet.cs @@ -0,0 +1,62 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2024 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: OrderedSet.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Server.Collections; + +/// +/// A data structure designed for scenarios where the order of insertion is important. +/// Note: This is not particularly effecient. +/// +/// The type of elements in the set. +public class OrderedSet +{ + private readonly Dictionary _dictionary; + private readonly List _list; + + public OrderedSet(IEqualityComparer comparer = null) + { + _dictionary = new Dictionary(comparer); + _list = []; + } + + public int Count => _dictionary.Count; + + public int Add(T value) + { + ref var order = ref CollectionsMarshal.GetValueRefOrAddDefault(_dictionary, value, out var exists); + if (exists) + { + return order; + } + + _list.Add(value); + return order = _list.Count - 1; + } + + public bool Contains(T value) => _dictionary.ContainsKey(value); + + public void Clear() + { + _dictionary.Clear(); + _list.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public List.Enumerator GetEnumerator() => _list.GetEnumerator(); +} diff --git a/Projects/Server/Collections/PooledOrderedHashSet.cs b/Projects/Server/Collections/PooledOrderedHashSet.cs deleted file mode 100644 index d60f4c4d7..000000000 --- a/Projects/Server/Collections/PooledOrderedHashSet.cs +++ /dev/null @@ -1,602 +0,0 @@ -/************************************************************************* - * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: PooledOrderedHashSet.cs * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using Microsoft.Collections.Extensions; -using Server.Buffers; - -namespace Server.Collections; - -[DebuggerDisplay("Count = {Count}")] -public class PooledOrderedHashSet : IList, IDisposable -{ - private struct Entry - { - public uint HashCode; - public TValue Value; - public int Next; // the index of the next item in the same bucket, -1 if last - } - - private static readonly Entry[] InitialEntries = new Entry[1]; - private int[] _buckets = HashHelpers.SizeOneIntArray; - private int _bucketsLength = 1; - private Entry[] _entries = InitialEntries; - private int _entriesLength = 1; - private ulong _fastModMultiplier; - private int _count; - private int _version; -#nullable enable - private readonly IEqualityComparer? _comparer; -#nullable disable - - public int Count => _count; -#nullable enable - public IEqualityComparer? Comparer => _comparer; -#nullable disable - - public PooledOrderedHashSet() - : this(0) - { - } - - public PooledOrderedHashSet(IEqualityComparer comparer) - : this(0, comparer) - { - } - - public PooledOrderedHashSet(int capacity, IEqualityComparer comparer = null) - { - if (capacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity)); - } - - if (capacity > 0) - { - int newSize = HashHelpers.GetPrime(capacity); - _buckets = STArrayPool.Shared.Rent(newSize); - _bucketsLength = newSize; - _entries = STArrayPool.Shared.Rent(newSize); - _entriesLength = newSize; - _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); - } - - if (comparer != EqualityComparer.Default) - { - _comparer = comparer; - } - } - - public PooledOrderedHashSet(IEnumerable collection, IEqualityComparer comparer = null) - : this((collection as ICollection)?.Count ?? 0, comparer) - { - if (collection == null) - { - throw new ArgumentNullException(nameof(collection)); - } - - foreach (TValue value in collection) - { - Add(value); - } - } - - public bool Contains(TValue item) => TryGetValue(item, out var value) && EqualityComparer.Default.Equals(value); - - public void Clear() - { - if (_count > 0) - { - Array.Clear(_buckets, 0, _bucketsLength); - Array.Clear(_entries, 0, _count); - _count = 0; - ++_version; - } - } - - public Enumerator GetEnumerator() => new(this); - - void ICollection.Add(TValue item) => TryAdd(item); - - public bool Add(TValue item) => TryAdd(item); - - public int GetOrAdd(TValue value) => TryInsert(null, value); - - public int IndexOf(TValue value) => IndexOf(value, out _); - - public void Insert(int index, TValue value) - { - if ((uint)index > (uint)Count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - TryInsert(index, value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ref int GetBucketRef(uint hashCode) - { - int[] buckets = _buckets!; - return ref buckets[HashHelpers.FastMod(hashCode, (uint)_bucketsLength, _fastModMultiplier)]; - } - - public bool Remove(TValue value) - { - int index = IndexOf(value); - if (index >= 0) - { - RemoveAt(index); - return true; - } - - return false; - } - - public void RemoveAt(int index) - { - int count = Count; - if ((uint)index >= (uint)count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - // Remove the entry from the bucket - RemoveEntryFromBucket(index); - - // Decrement the indices > index - Entry[] entries = _entries; - for (int i = index + 1; i < count; ++i) - { - entries[i - 1] = entries[i]; - UpdateBucketIndex(i, incrementAmount: -1); - } - --_count; - entries[_count] = default; - ++_version; - } - - public bool TryAdd(TValue value) => TryInsert(null, value) != _count - 1; - - public bool TryGetValue(TValue value, out TValue actualValue) - { - int index = IndexOf(value); - if (index >= 0) - { - actualValue = _entries[index].Value; - return true; - } - - actualValue = default; - return false; - } - - public TValue this[int index] - { - get - { - if ((uint)index >= (uint)Count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - return _entries[index].Value; - } - set - { - if ((uint)index >= (uint)Count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - TValue v = value; - int foundIndex = IndexOf(v, out uint hashCode); - if (foundIndex < 0) - { - RemoveEntryFromBucket(index); - Entry entry = new Entry { HashCode = hashCode, Value = value }; - AddEntryToBucket(ref entry, index, _buckets, _bucketsLength); - _entries[index] = entry; - ++_version; - } - else if (foundIndex == index) - { - ref Entry entry = ref _entries[index]; - entry.Value = value; - } - else - { - throw new ArgumentException(string.Format(CollectionThrowStrings.Argument_AddingDuplicate, v.ToString())); - } - } - } - - public bool IsReadOnly => false; - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public void CopyTo(TValue[] array, int arrayIndex) - { - if (array == null) - { - throw new ArgumentNullException(nameof(array)); - } - - if ((uint)arrayIndex > (uint)array.Length) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex), CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum); - } - - int count = Count; - if (array.Length - arrayIndex < count) - { - throw new ArgumentException(CollectionThrowStrings.Arg_ArrayPlusOffTooSmall); - } - - Entry[] entries = _entries; - for (int i = 0; i < count; ++i) - { - Entry entry = entries[i]; - array[i + arrayIndex] = entry.Value; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Entry[] Resize(int newSize) - { - int[] newBuckets = _buckets.Length < newSize ? STArrayPool.Shared.Rent(newSize) : _buckets; - Entry[] newEntries = _entries.Length < newSize ? STArrayPool.Shared.Rent(newSize) : _entries; - - int count = Count; - Array.Copy(_entries, newEntries, count); - - _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); - - for (int i = 0; i < count; ++i) - { - AddEntryToBucket(ref newEntries[i], i, newBuckets, newSize); - } - - var oldBuckets = _buckets; - var oldEntries = _entries; - - if (oldBuckets.Length > 1 && oldBuckets != newBuckets) - { - STArrayPool.Shared.Return(oldBuckets, true); - } - - if (oldEntries.Length > 1 && oldEntries != newEntries) - { - STArrayPool.Shared.Return(oldEntries, true); - } - - _buckets = newBuckets; - _bucketsLength = newSize; - _entries = newEntries; - _entriesLength = newSize; - return newEntries; - } - -#nullable enable - private int IndexOf(TValue value, out uint hashCode) - { - ref int bucket = ref Unsafe.NullRef(); - int i; - - IEqualityComparer? comparer = _comparer; - if (comparer == null) - { - hashCode = (uint)value.GetHashCode(); - bucket = ref GetBucketRef(hashCode); - i = bucket - 1; - - if (i >= 0) - { - if (typeof(TValue).IsValueType) - { - // ValueType: Devirtualize with EqualityComparer.Default intrinsic - Entry[] entries = _entries; - int collisionCount = 0; - do - { - Entry entry = entries[i]; - if (entry.HashCode == hashCode && EqualityComparer.Default.Equals(entry.Value, value)) - { - break; - } - - i = entry.Next; - if (collisionCount >= _entriesLength) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException( - CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported - ); - } - - ++collisionCount; - } while (i >= 0); - } - else - { - // Object type: Shared Generic, EqualityComparer.Default won't devirtualize (https://github.com/dotnet/runtime/issues/10050), - // so cache in a local rather than get EqualityComparer per loop iteration. - var defaultComparer = EqualityComparer.Default; - Entry[] entries = _entries; - int collisionCount = 0; - do - { - Entry entry = entries[i]; - if (entry.HashCode == hashCode && defaultComparer.Equals(entry.Value, value)) - { - break; - } - - i = entry.Next; - if (collisionCount >= _entriesLength) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException( - CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported - ); - } - - ++collisionCount; - } while (i >= 0); - } - } - } - else - { - hashCode = (uint)comparer.GetHashCode(value); - bucket = ref GetBucketRef(hashCode); - i = bucket - 1; - if (i >= 0) - { - Entry[] entries = _entries; - int collisionCount = 0; - do - { - Entry entry = entries[i]; - if (entry.HashCode == hashCode && comparer.Equals(entry.Value, value)) - { - break; - } - i = entry.Next; - if (collisionCount >= _entriesLength) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); - } - ++collisionCount; - } while (i >= 0); - } - } - - return i; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int TryInsert(int? index, TValue value) - { - int i = IndexOf(value, out uint hashCode); - return i >= 0 ? i : AddInternal(index, value, hashCode); - } - - private int AddInternal(int? index, TValue value, uint hashCode) - { - Entry[] entries = _entries; - // Check if resize is needed - int count = Count; - if (_entriesLength == count || entries.Length == 1) - { - entries = Resize(HashHelpers.ExpandPrime(_entriesLength)); - } - - // Increment indices >= index; - int actualIndex = index ?? count; - for (int i = count - 1; i >= actualIndex; --i) - { - entries[i + 1] = entries[i]; - UpdateBucketIndex(i, incrementAmount: 1); - } - - ref Entry entry = ref entries[actualIndex]; - entry.HashCode = hashCode; - entry.Value = value; - AddEntryToBucket(ref entry, actualIndex, _buckets, _bucketsLength); - ++_count; - ++_version; - return actualIndex; - } -#nullable restore - - // Returns the index of the next entry in the bucket - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AddEntryToBucket(ref Entry entry, int entryIndex, int[] buckets, int bucketsLength) - { - ref int b = ref buckets[(int)(entry.HashCode % (uint)bucketsLength)]; - entry.Next = b - 1; - b = entryIndex + 1; - } - - private void RemoveEntryFromBucket(int entryIndex) - { - Entry[] entries = _entries; - Entry entry = entries[entryIndex]; - ref int bucket = ref GetBucketRef(entry.HashCode); - // Bucket was pointing to removed entry. Update it to point to the next in the chain - if (bucket == entryIndex + 1) - { - bucket = entry.Next + 1; - } - else - { - // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to remove, then fix the chain - int i = bucket - 1; - int collisionCount = 0; - while (true) - { - ref Entry e = ref entries[i]; - if (e.Next == entryIndex) - { - e.Next = entry.Next; - return; - } - i = e.Next; - if (collisionCount >= _entriesLength) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); - } - ++collisionCount; - } - } - } - - private void UpdateBucketIndex(int entryIndex, int incrementAmount) - { - Entry[] entries = _entries; - Entry entry = entries[entryIndex]; - ref int bucket = ref GetBucketRef(entry.HashCode); - // Bucket was pointing to entry. Increment the index by incrementAmount. - if (bucket == entryIndex + 1) - { - bucket += incrementAmount; - } - else - { - // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to increment. - int i = bucket - 1; - int collisionCount = 0; - while (true) - { - ref Entry e = ref entries[i]; - if (e.Next == entryIndex) - { - e.Next += incrementAmount; - return; - } - i = e.Next; - if (collisionCount >= _entriesLength) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); - } - ++collisionCount; - } - } - } - - public struct Enumerator : IEnumerator - { - private readonly PooledOrderedHashSet _PooledOrderedHashSet; - private readonly int _version; - private int _index; - private TValue _current; - - public TValue Current => _current; - - object IEnumerator.Current => _current; - - internal Enumerator(PooledOrderedHashSet PooledOrderedHashSet) - { - _PooledOrderedHashSet = PooledOrderedHashSet; - _version = PooledOrderedHashSet._version; - _index = 0; - _current = default; - } - - public void Dispose() - { - } - - public bool MoveNext() - { - if (_version != _PooledOrderedHashSet._version) - { - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); - } - - if (_index < _PooledOrderedHashSet.Count) - { - Entry entry = _PooledOrderedHashSet._entries[_index]; - _current = entry.Value; - ++_index; - return true; - } - _current = default; - return false; - } - - void IEnumerator.Reset() - { - if (_version != _PooledOrderedHashSet._version) - { - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); - } - - _index = 0; - _current = default; - } - } - - public void Dispose() - { - if (_buckets.Length > 1) - { - STArrayPool.Shared.Return(_buckets, true); - } - - if (_entries.Length > 1) - { - STArrayPool.Shared.Return(_entries, true); - } - - _buckets = HashHelpers.SizeOneIntArray; - _entries = InitialEntries; - _count = 0; - - GC.SuppressFinalize(this); - } - - ~PooledOrderedHashSet() - { - if (_buckets.Length > 1) - { - STArrayPool.Shared.Return(_buckets, true); - } - - if (_entries.Length > 1) - { - STArrayPool.Shared.Return(_entries, true); - } - - _buckets = HashHelpers.SizeOneIntArray; - _entries = InitialEntries; - _count = 0; - } -} diff --git a/Projects/Server/Gumps/Legacy/GumpAlphaRegion.cs b/Projects/Server/Gumps/Legacy/GumpAlphaRegion.cs index b6bef4f90..f3e1c1aad 100644 --- a/Projects/Server/Gumps/Legacy/GumpAlphaRegion.cs +++ b/Projects/Server/Gumps/Legacy/GumpAlphaRegion.cs @@ -36,7 +36,7 @@ public class GumpAlphaRegion : GumpEntry public int Height { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { writer.WriteAscii($"{{ checkertrans {X} {Y} {Width} {Height} }}"); } diff --git a/Projects/Server/Gumps/Legacy/GumpBackground.cs b/Projects/Server/Gumps/Legacy/GumpBackground.cs index eba28c240..e303cf949 100644 --- a/Projects/Server/Gumps/Legacy/GumpBackground.cs +++ b/Projects/Server/Gumps/Legacy/GumpBackground.cs @@ -39,7 +39,7 @@ public class GumpBackground : GumpEntry public int GumpID { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { writer.WriteAscii($"{{ resizepic {X} {Y} {GumpID} {Width} {Height} }}"); } diff --git a/Projects/Server/Gumps/Legacy/GumpButton.cs b/Projects/Server/Gumps/Legacy/GumpButton.cs index dc873a676..c2418ea00 100644 --- a/Projects/Server/Gumps/Legacy/GumpButton.cs +++ b/Projects/Server/Gumps/Legacy/GumpButton.cs @@ -54,7 +54,7 @@ public class GumpButton : GumpEntry public int Param { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { writer.WriteAscii($"{{ button {X} {Y} {NormalID} {PressedID} {(int)Type} {Param} {ButtonID} }}"); } diff --git a/Projects/Server/Gumps/Legacy/GumpCheck.cs b/Projects/Server/Gumps/Legacy/GumpCheck.cs index 3664ee325..b2b71a3d5 100644 --- a/Projects/Server/Gumps/Legacy/GumpCheck.cs +++ b/Projects/Server/Gumps/Legacy/GumpCheck.cs @@ -42,7 +42,7 @@ public class GumpCheck : GumpEntry public int SwitchID { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { var initialState = InitialState ? "1" : "0"; writer.WriteAscii($"{{ checkbox {X} {Y} {InactiveID} {ActiveID} {initialState} {SwitchID} }}"); diff --git a/Projects/Server/Gumps/Legacy/GumpECHandleInput.cs b/Projects/Server/Gumps/Legacy/GumpECHandleInput.cs index 680af6b41..57d701687 100644 --- a/Projects/Server/Gumps/Legacy/GumpECHandleInput.cs +++ b/Projects/Server/Gumps/Legacy/GumpECHandleInput.cs @@ -20,7 +20,7 @@ namespace Server.Gumps; public class GumpECHandleInput : GumpEntry { - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { writer.Write("{ echandleinput }"u8); } diff --git a/Projects/Server/Gumps/Legacy/GumpEntry.cs b/Projects/Server/Gumps/Legacy/GumpEntry.cs index 175f6f813..171d1f1e4 100644 --- a/Projects/Server/Gumps/Legacy/GumpEntry.cs +++ b/Projects/Server/Gumps/Legacy/GumpEntry.cs @@ -23,7 +23,5 @@ public abstract class GumpEntry } } - // TODO: Replace OrderedHashSet with InsertOnlyHashSet, a copy of HashSet that is ReadOnly compatible, but includes - // a public AddIfNotPresent function that returns the index of the element - public abstract void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches); + public abstract void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches); } diff --git a/Projects/Server/Gumps/Legacy/GumpGroup.cs b/Projects/Server/Gumps/Legacy/GumpGroup.cs index e34d4c8ae..2f9460db2 100644 --- a/Projects/Server/Gumps/Legacy/GumpGroup.cs +++ b/Projects/Server/Gumps/Legacy/GumpGroup.cs @@ -24,7 +24,7 @@ public class GumpGroup : GumpEntry public int Group { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { if (Group == 1) { diff --git a/Projects/Server/Gumps/Legacy/GumpHtml.cs b/Projects/Server/Gumps/Legacy/GumpHtml.cs index d9790c5cf..250b9f99a 100644 --- a/Projects/Server/Gumps/Legacy/GumpHtml.cs +++ b/Projects/Server/Gumps/Legacy/GumpHtml.cs @@ -45,9 +45,9 @@ public class GumpHtml : GumpEntry public bool Scrollbar { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { - var textIndex = strings.GetOrAdd(Text ?? ""); + var textIndex = strings.Add(Text ?? ""); var background = Background ? "1" : "0"; var scrollbar = Scrollbar ? "1" : "0"; writer.WriteAscii($"{{ htmlgump {X} {Y} {Width} {Height} {textIndex} {background} {scrollbar} }}"); diff --git a/Projects/Server/Gumps/Legacy/GumpHtmlLocalized.cs b/Projects/Server/Gumps/Legacy/GumpHtmlLocalized.cs index dba9394e3..2a9482d81 100644 --- a/Projects/Server/Gumps/Legacy/GumpHtmlLocalized.cs +++ b/Projects/Server/Gumps/Legacy/GumpHtmlLocalized.cs @@ -101,7 +101,7 @@ public class GumpHtmlLocalized : GumpEntry public GumpHtmlLocalizedType Type { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { var background = Background ? "1" : "0"; var scrollbar = Scrollbar ? "1" : "0"; diff --git a/Projects/Server/Gumps/Legacy/GumpImage.cs b/Projects/Server/Gumps/Legacy/GumpImage.cs index 3e4bc59ad..c17ebe501 100644 --- a/Projects/Server/Gumps/Legacy/GumpImage.cs +++ b/Projects/Server/Gumps/Legacy/GumpImage.cs @@ -39,7 +39,7 @@ public class GumpImage : GumpEntry public string Class { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { var hasHue = Hue != 0; var hasClass = !string.IsNullOrEmpty(Class); diff --git a/Projects/Server/Gumps/Legacy/GumpImageTileButton.cs b/Projects/Server/Gumps/Legacy/GumpImageTileButton.cs index 36f50d985..453795d3d 100644 --- a/Projects/Server/Gumps/Legacy/GumpImageTileButton.cs +++ b/Projects/Server/Gumps/Legacy/GumpImageTileButton.cs @@ -61,7 +61,7 @@ public class GumpImageTileButton : GumpEntry public int Height { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { writer.WriteAscii( $"{{ buttontileart {X} {Y} {NormalID} {PressedID} {(int)Type} {Param} {ButtonID} {ItemID} {Hue} {Width} {Height} }}" diff --git a/Projects/Server/Gumps/Legacy/GumpImageTiled.cs b/Projects/Server/Gumps/Legacy/GumpImageTiled.cs index 25e3390a2..03f685045 100644 --- a/Projects/Server/Gumps/Legacy/GumpImageTiled.cs +++ b/Projects/Server/Gumps/Legacy/GumpImageTiled.cs @@ -39,7 +39,7 @@ public class GumpImageTiled : GumpEntry public int GumpID { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { writer.WriteAscii($"{{ gumppictiled {X} {Y} {Width} {Height} {GumpID} }}"); } diff --git a/Projects/Server/Gumps/Legacy/GumpItem.cs b/Projects/Server/Gumps/Legacy/GumpItem.cs index cfeeb6dac..f3b8ae57b 100644 --- a/Projects/Server/Gumps/Legacy/GumpItem.cs +++ b/Projects/Server/Gumps/Legacy/GumpItem.cs @@ -36,7 +36,7 @@ public class GumpItem : GumpEntry public int Hue { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { writer.WriteAscii(Hue == 0 ? $"{{ tilepic {X} {Y} {ItemID} }}" : $"{{ tilepichue {X} {Y} {ItemID} {Hue} }}"); } diff --git a/Projects/Server/Gumps/Legacy/GumpItemProperty.cs b/Projects/Server/Gumps/Legacy/GumpItemProperty.cs index b6d465908..4d52fac6a 100644 --- a/Projects/Server/Gumps/Legacy/GumpItemProperty.cs +++ b/Projects/Server/Gumps/Legacy/GumpItemProperty.cs @@ -24,7 +24,7 @@ public class GumpItemProperty : GumpEntry public Serial Serial { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { writer.WriteAscii($"{{ itemproperty {Serial.Value} }}"); } diff --git a/Projects/Server/Gumps/Legacy/GumpLabel.cs b/Projects/Server/Gumps/Legacy/GumpLabel.cs index 06abd0137..5c3e7a81b 100644 --- a/Projects/Server/Gumps/Legacy/GumpLabel.cs +++ b/Projects/Server/Gumps/Legacy/GumpLabel.cs @@ -36,9 +36,9 @@ public class GumpLabel : GumpEntry public string Text { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { - var textIndex = strings.GetOrAdd(Text ?? ""); + var textIndex = strings.Add(Text ?? ""); writer.WriteAscii($"{{ text {X} {Y} {Hue} {textIndex} }}"); } } diff --git a/Projects/Server/Gumps/Legacy/GumpLabelCropped.cs b/Projects/Server/Gumps/Legacy/GumpLabelCropped.cs index f9a9ff187..2188ae9cd 100644 --- a/Projects/Server/Gumps/Legacy/GumpLabelCropped.cs +++ b/Projects/Server/Gumps/Legacy/GumpLabelCropped.cs @@ -42,9 +42,9 @@ public class GumpLabelCropped : GumpEntry public string Text { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { - var textIndex = strings.GetOrAdd(Text ?? ""); + var textIndex = strings.Add(Text ?? ""); writer.WriteAscii($"{{ croppedtext {X} {Y} {Width} {Height} {Hue} {textIndex} }}"); } } diff --git a/Projects/Server/Gumps/Legacy/GumpMasterGump.cs b/Projects/Server/Gumps/Legacy/GumpMasterGump.cs index d754ae4b8..71578ade8 100644 --- a/Projects/Server/Gumps/Legacy/GumpMasterGump.cs +++ b/Projects/Server/Gumps/Legacy/GumpMasterGump.cs @@ -24,7 +24,7 @@ public class GumpMasterGump : GumpEntry public int GumpID { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { writer.WriteAscii($"{{ mastergump {GumpID} }}"); } diff --git a/Projects/Server/Gumps/Legacy/GumpPage.cs b/Projects/Server/Gumps/Legacy/GumpPage.cs index a561125b8..726c767f6 100644 --- a/Projects/Server/Gumps/Legacy/GumpPage.cs +++ b/Projects/Server/Gumps/Legacy/GumpPage.cs @@ -24,7 +24,7 @@ public class GumpPage : GumpEntry public int Page { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { if (Page == 0) { diff --git a/Projects/Server/Gumps/Legacy/GumpRadio.cs b/Projects/Server/Gumps/Legacy/GumpRadio.cs index 73e314358..516c58622 100644 --- a/Projects/Server/Gumps/Legacy/GumpRadio.cs +++ b/Projects/Server/Gumps/Legacy/GumpRadio.cs @@ -42,7 +42,7 @@ public class GumpRadio : GumpEntry public int SwitchID { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { var initialState = InitialState ? "1" : "0"; writer.WriteAscii($"{{ radio {X} {Y} {InactiveID} {ActiveID} {initialState} {SwitchID} }}"); diff --git a/Projects/Server/Gumps/Legacy/GumpSpriteImage.cs b/Projects/Server/Gumps/Legacy/GumpSpriteImage.cs index 89f390b39..9881b301e 100644 --- a/Projects/Server/Gumps/Legacy/GumpSpriteImage.cs +++ b/Projects/Server/Gumps/Legacy/GumpSpriteImage.cs @@ -45,7 +45,7 @@ public class GumpSpriteImage : GumpEntry public int SY { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { writer.WriteAscii($"{{ picinpic {X} {Y} {GumpID} {Width} {Height} {SX} {SY} }}"); } diff --git a/Projects/Server/Gumps/Legacy/GumpTextEntry.cs b/Projects/Server/Gumps/Legacy/GumpTextEntry.cs index 0bf60ab5b..4889dcab6 100644 --- a/Projects/Server/Gumps/Legacy/GumpTextEntry.cs +++ b/Projects/Server/Gumps/Legacy/GumpTextEntry.cs @@ -45,9 +45,9 @@ public class GumpTextEntry : GumpEntry public string InitialText { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { - var textIndex = strings.GetOrAdd(InitialText ?? ""); + var textIndex = strings.Add(InitialText ?? ""); writer.WriteAscii($"{{ textentry {X} {Y} {Width} {Height} {Hue} {EntryID} {textIndex} }}"); entries++; } diff --git a/Projects/Server/Gumps/Legacy/GumpTextEntryLimited.cs b/Projects/Server/Gumps/Legacy/GumpTextEntryLimited.cs index 7c15274c9..f23d72ed9 100644 --- a/Projects/Server/Gumps/Legacy/GumpTextEntryLimited.cs +++ b/Projects/Server/Gumps/Legacy/GumpTextEntryLimited.cs @@ -50,9 +50,9 @@ public class GumpTextEntryLimited : GumpEntry public int Size { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { - var textIndex = strings.GetOrAdd(InitialText ?? ""); + var textIndex = strings.Add(InitialText ?? ""); writer.WriteAscii($"{{ textentrylimited {X} {Y} {Width} {Height} {Hue} {EntryID} {textIndex} {Size} }}"); entries++; } diff --git a/Projects/Server/Gumps/Legacy/GumpTooltip.cs b/Projects/Server/Gumps/Legacy/GumpTooltip.cs index ffe8ccb12..2d584eb8b 100644 --- a/Projects/Server/Gumps/Legacy/GumpTooltip.cs +++ b/Projects/Server/Gumps/Legacy/GumpTooltip.cs @@ -32,7 +32,7 @@ public class GumpTooltip : GumpEntry public string Args { get; set; } - public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + public override void AppendTo(ref SpanWriter writer, OrderedSet strings, ref int entries, ref int switches) { writer.WriteAscii(string.IsNullOrEmpty(Args) ? $"{{ tooltip {Number} }}" : $"{{ tooltip {Number} @{Args}@ }}"); } diff --git a/Projects/Server/Network/Packets/OutgoingGumpPackets.cs b/Projects/Server/Network/Packets/OutgoingGumpPackets.cs index 11d106dc2..8fe6fc066 100644 --- a/Projects/Server/Network/Packets/OutgoingGumpPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingGumpPackets.cs @@ -50,7 +50,7 @@ public static class OutgoingGumpPackets private static readonly byte[] _layoutBuffer = GC.AllocateUninitializedArray(0x20000); private static readonly byte[] _stringsBuffer = GC.AllocateUninitializedArray(0x20000); private static readonly byte[] _packBuffer = GC.AllocateUninitializedArray(0x20000); - private static readonly OrderedHashSet _stringsList = new(32); + private static readonly OrderedSet _stringsList = new(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WritePacked(ReadOnlySpan span, ref SpanWriter writer) diff --git a/version.json b/version.json index 80683645f..49a4bfc02 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.13.1" + "version": "0.13.2" }