| | | 1 | | #nullable enable |
| | | 2 | | |
| | | 3 | | using System; |
| | | 4 | | using System.Buffers; |
| | | 5 | | using System.Collections.Generic; |
| | | 6 | | using System.Collections.Specialized; |
| | | 7 | | using System.Runtime.InteropServices; |
| | | 8 | | |
| | | 9 | | namespace Hertzole.ScriptableValues |
| | | 10 | | { |
| | | 11 | | /// <summary> |
| | | 12 | | /// Represents the method that will handle an event when a collection changes. |
| | | 13 | | /// </summary> |
| | | 14 | | /// <typeparam name="T">The type of the items in the collection.</typeparam> |
| | | 15 | | public delegate void CollectionChangedEventHandler<T>(CollectionChangedArgs<T> e); |
| | | 16 | | |
| | | 17 | | public static class CollectionChangedArgsExtensions |
| | | 18 | | { |
| | | 19 | | /// <summary> |
| | | 20 | | /// Converts the <see cref="CollectionChangedArgs{T}" /> to a <see cref="NotifyCollectionChangedEventArgs" / |
| | | 21 | | /// </summary> |
| | | 22 | | /// <param name="args">The current args.</param> |
| | | 23 | | /// <typeparam name="T">The type of the items in the collection.</typeparam> |
| | | 24 | | /// <returns>The converted <see cref="NotifyCollectionChangedEventArgs" />.</returns> |
| | | 25 | | /// <exception cref="NotSupportedException"><c>args.Action</c> is not supported.</exception> |
| | | 26 | | public static NotifyCollectionChangedEventArgs ToNotifyCollectionChangedEventArgs<T>(this CollectionChangedArgs< |
| | | 27 | | { |
| | | 28 | | switch (args.Action) |
| | | 29 | | { |
| | | 30 | | case NotifyCollectionChangedAction.Add: |
| | | 31 | | return new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, args.NewItems.ToArray |
| | | 32 | | case NotifyCollectionChangedAction.Remove: |
| | | 33 | | return new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, args.OldItems.ToAr |
| | | 34 | | case NotifyCollectionChangedAction.Replace: |
| | | 35 | | return new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, args.NewItems.ToA |
| | | 36 | | args.NewIndex); |
| | | 37 | | case NotifyCollectionChangedAction.Reset: |
| | | 38 | | return new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); |
| | | 39 | | } |
| | | 40 | | |
| | | 41 | | throw new NotSupportedException($"Can't create NotifyCollectionChangedEventArgs from this action ({args.Acti |
| | | 42 | | } |
| | | 43 | | |
| | | 44 | | /// <summary> |
| | | 45 | | /// Helper method to get a value from the <see cref="CollectionChangedArgs{T}.NewItems" /> based on the give |
| | | 46 | | /// </summary> |
| | | 47 | | /// <param name="args">The current args.</param> |
| | | 48 | | /// <param name="key">The key to find the value for.</param> |
| | | 49 | | /// <param name="value">The found value, if any.</param> |
| | | 50 | | /// <typeparam name="TKey">The type of the key.</typeparam> |
| | | 51 | | /// <typeparam name="TValue">The type of the value.</typeparam> |
| | | 52 | | /// <returns><see langword="true" /> if a value was found; otherwise <see langword="false" />.</returns> |
| | | 53 | | public static bool TryGetNewValue<TKey, TValue>(this CollectionChangedArgs<KeyValuePair<TKey, TValue>> args, TKe |
| | | 54 | | { |
| | | 55 | | return TryGetValue(args.NewItems.Span, key, out value); |
| | | 56 | | } |
| | | 57 | | |
| | | 58 | | /// <summary> |
| | | 59 | | /// Helper method to get a value from the <see cref="CollectionChangedArgs{T}.OldItems" /> based on the give |
| | | 60 | | /// </summary> |
| | | 61 | | /// <param name="args">The current args.</param> |
| | | 62 | | /// <param name="key">The key to find the value for.</param> |
| | | 63 | | /// <param name="value">The found value, if any.</param> |
| | | 64 | | /// <typeparam name="TKey">The type of the key.</typeparam> |
| | | 65 | | /// <typeparam name="TValue">The type of the value.</typeparam> |
| | | 66 | | /// <returns><see langword="true" /> if a value was found; otherwise <see langword="false" />.</returns> |
| | | 67 | | public static bool TryGetOldValue<TKey, TValue>(this CollectionChangedArgs<KeyValuePair<TKey, TValue>> args, TKe |
| | | 68 | | { |
| | | 69 | | return TryGetValue(args.OldItems.Span, key, out value); |
| | | 70 | | } |
| | | 71 | | |
| | | 72 | | private static bool TryGetValue<TKey, TValue>(ReadOnlySpan<KeyValuePair<TKey, TValue>> items, TKey key, out TVal |
| | | 73 | | { |
| | | 74 | | if (items.Length == 0) |
| | | 75 | | { |
| | | 76 | | value = default; |
| | | 77 | | return false; |
| | | 78 | | } |
| | | 79 | | |
| | | 80 | | EqualityComparer<TKey> equalityComparer = EqualityComparer<TKey>.Default; |
| | | 81 | | |
| | | 82 | | for (int i = 0; i < items.Length; i++) |
| | | 83 | | { |
| | | 84 | | if (equalityComparer.Equals(items[i].Key, key)) |
| | | 85 | | { |
| | | 86 | | value = items[i].Value; |
| | | 87 | | return true; |
| | | 88 | | } |
| | | 89 | | } |
| | | 90 | | |
| | | 91 | | value = default; |
| | | 92 | | return false; |
| | | 93 | | } |
| | | 94 | | } |
| | | 95 | | |
| | | 96 | | /// <summary> |
| | | 97 | | /// Provides data for the <see cref="CollectionChangedEventHandler{T}" /> events. |
| | | 98 | | /// </summary> |
| | | 99 | | /// <typeparam name="T">The type of the items in the collection.</typeparam> |
| | | 100 | | [StructLayout(LayoutKind.Auto)] |
| | | 101 | | public readonly struct CollectionChangedArgs<T> : IEquatable<CollectionChangedArgs<T>> |
| | | 102 | | { |
| | | 103 | | /// <summary> |
| | | 104 | | /// Gets the action that specifies how the collection changed. |
| | | 105 | | /// </summary> |
| | 1452 | 106 | | public NotifyCollectionChangedAction Action { get; } |
| | | 107 | | /// <summary> |
| | | 108 | | /// Gets the index at which the new item was added. |
| | | 109 | | /// </summary> |
| | 744 | 110 | | public int NewIndex { get; } |
| | | 111 | | /// <summary> |
| | | 112 | | /// Gets the index at which the old item was removed. |
| | | 113 | | /// </summary> |
| | 450 | 114 | | public int OldIndex { get; } |
| | | 115 | | /// <summary> |
| | | 116 | | /// Gets the items that were added. |
| | | 117 | | /// </summary> |
| | 36942 | 118 | | public ReadOnlyMemory<T> OldItems { get; } |
| | | 119 | | /// <summary> |
| | | 120 | | /// Gets the items that were removed. |
| | | 121 | | /// </summary> |
| | 36894 | 122 | | public ReadOnlyMemory<T> NewItems { get; } |
| | | 123 | | |
| | | 124 | | internal CollectionChangedArgs(NotifyCollectionChangedAction action, |
| | | 125 | | int newIndex = -1, |
| | | 126 | | ReadOnlyMemory<T> newItems = default, |
| | | 127 | | int oldIndex = -1, |
| | | 128 | | ReadOnlyMemory<T> oldItems = default) |
| | 10590 | 129 | | { |
| | 10590 | 130 | | Action = action; |
| | 10590 | 131 | | NewIndex = newIndex; |
| | 10590 | 132 | | OldIndex = oldIndex; |
| | 10590 | 133 | | OldItems = oldItems; |
| | 10590 | 134 | | NewItems = newItems; |
| | 10590 | 135 | | } |
| | | 136 | | |
| | | 137 | | /// <summary> |
| | | 138 | | /// Creates a new instance of <see cref="CollectionChangedArgs{T}" /> with the <c>Add</c> action with only o |
| | | 139 | | /// </summary> |
| | | 140 | | /// <param name="newItem">The new item that was added.</param> |
| | | 141 | | /// <param name="startingIndex">The index at which the new item was added.</param> |
| | | 142 | | /// <returns>A new instance of <see cref="CollectionChangedArgs{T}" />.</returns> |
| | | 143 | | public static CollectionChangedArgs<T> Add(T newItem, int startingIndex) |
| | 6192 | 144 | | { |
| | 6192 | 145 | | using (IMemoryOwner<T> owner = MemoryPool<T>.Shared.Rent(1)) |
| | 6192 | 146 | | { |
| | 6192 | 147 | | Memory<T> memory = owner.Memory.Slice(0, 1); |
| | 6192 | 148 | | memory.Span[0] = newItem; |
| | 6192 | 149 | | return new CollectionChangedArgs<T>(NotifyCollectionChangedAction.Add, startingIndex, memory); |
| | | 150 | | } |
| | 6192 | 151 | | } |
| | | 152 | | |
| | | 153 | | /// <summary> |
| | | 154 | | /// Creates a new instance of <see cref="CollectionChangedArgs{T}" /> with the <c>Add</c> action with multip |
| | | 155 | | /// </summary> |
| | | 156 | | /// <param name="newItems">The new items that were added.</param> |
| | | 157 | | /// <param name="startingIndex">The index at which the new items were added.</param> |
| | | 158 | | /// <returns>A new instance of <see cref="CollectionChangedArgs{T}" />.</returns> |
| | | 159 | | public static CollectionChangedArgs<T> Add(ReadOnlySpan<T> newItems, int startingIndex) |
| | 1338 | 160 | | { |
| | 1338 | 161 | | using (IMemoryOwner<T> owner = MemoryPool<T>.Shared.Rent(newItems.Length)) |
| | 1338 | 162 | | { |
| | 1338 | 163 | | Memory<T> memory = owner.Memory.Slice(0, newItems.Length); |
| | 1338 | 164 | | newItems.CopyTo(memory.Span); |
| | 1338 | 165 | | return new CollectionChangedArgs<T>(NotifyCollectionChangedAction.Add, startingIndex, memory); |
| | | 166 | | } |
| | 1338 | 167 | | } |
| | | 168 | | |
| | | 169 | | /// <summary> |
| | | 170 | | /// Creates a new instance of <see cref="CollectionChangedArgs{T}" /> with the <c>Remove</c> action with onl |
| | | 171 | | /// </summary> |
| | | 172 | | /// <param name="oldItem">The old item that was removed.</param> |
| | | 173 | | /// <param name="removedIndex">The index at which the old item was removed.</param> |
| | | 174 | | /// <returns>A new instance of <see cref="CollectionChangedArgs{T}" />.</returns> |
| | | 175 | | public static CollectionChangedArgs<T> Remove(T oldItem, int removedIndex) |
| | 210 | 176 | | { |
| | 210 | 177 | | using (IMemoryOwner<T> owner = MemoryPool<T>.Shared.Rent(1)) |
| | 210 | 178 | | { |
| | 210 | 179 | | Memory<T> memory = owner.Memory.Slice(0, 1); |
| | 210 | 180 | | memory.Span[0] = oldItem; |
| | 210 | 181 | | return new CollectionChangedArgs<T>(NotifyCollectionChangedAction.Remove, oldIndex: removedIndex, oldIte |
| | | 182 | | } |
| | 210 | 183 | | } |
| | | 184 | | |
| | | 185 | | /// <summary> |
| | | 186 | | /// Creates a new instance of <see cref="CollectionChangedArgs{T}" /> with the <c>Remove</c> action with mul |
| | | 187 | | /// items. |
| | | 188 | | /// </summary> |
| | | 189 | | /// <param name="oldItems">The old items that were removed.</param> |
| | | 190 | | /// <param name="removedIndex">The index at which the old items were removed.</param> |
| | | 191 | | /// <returns>A new instance of <see cref="CollectionChangedArgs{T}" />.</returns> |
| | | 192 | | public static CollectionChangedArgs<T> Remove(ReadOnlySpan<T> oldItems, int removedIndex) |
| | 84 | 193 | | { |
| | 84 | 194 | | using (IMemoryOwner<T> owner = MemoryPool<T>.Shared.Rent(oldItems.Length)) |
| | 84 | 195 | | { |
| | 84 | 196 | | Memory<T> memory = owner.Memory.Slice(0, oldItems.Length); |
| | 84 | 197 | | oldItems.CopyTo(memory.Span); |
| | 84 | 198 | | return new CollectionChangedArgs<T>(NotifyCollectionChangedAction.Remove, oldIndex: removedIndex, oldIte |
| | | 199 | | } |
| | 84 | 200 | | } |
| | | 201 | | |
| | | 202 | | /// <summary> |
| | | 203 | | /// Creates a new instance of <see cref="CollectionChangedArgs{T}" /> with the <c>Replace</c> action with on |
| | | 204 | | /// item. |
| | | 205 | | /// </summary> |
| | | 206 | | /// <param name="oldItem">The old item that was replaced.</param> |
| | | 207 | | /// <param name="newItem">The new item that was added.</param> |
| | | 208 | | /// <param name="index">The index at which the item was replaced.</param> |
| | | 209 | | /// <returns>A new instance of <see cref="CollectionChangedArgs{T}" />.</returns> |
| | | 210 | | public static CollectionChangedArgs<T> Replace(T oldItem, T newItem, int index) |
| | 150 | 211 | | { |
| | 150 | 212 | | using (IMemoryOwner<T> oldOwner = MemoryPool<T>.Shared.Rent(1)) |
| | 150 | 213 | | { |
| | 150 | 214 | | Memory<T> oldMemory = oldOwner.Memory.Slice(0, 1); |
| | 150 | 215 | | oldMemory.Span[0] = oldItem; |
| | | 216 | | |
| | 150 | 217 | | using (IMemoryOwner<T> newOwner = MemoryPool<T>.Shared.Rent(1)) |
| | 150 | 218 | | { |
| | 150 | 219 | | Memory<T> newMemory = newOwner.Memory.Slice(0, 1); |
| | 150 | 220 | | newMemory.Span[0] = newItem; |
| | 150 | 221 | | return new CollectionChangedArgs<T>(NotifyCollectionChangedAction.Replace, index, newMemory, index, |
| | | 222 | | } |
| | | 223 | | } |
| | 150 | 224 | | } |
| | | 225 | | |
| | | 226 | | /// <summary> |
| | | 227 | | /// Creates a new instance of <see cref="CollectionChangedArgs{T}" /> with the <c>Replace</c> action with mu |
| | | 228 | | /// items. |
| | | 229 | | /// </summary> |
| | | 230 | | /// <param name="oldItems">The old items that were replaced.</param> |
| | | 231 | | /// <param name="newItems">The new items that were added.</param> |
| | | 232 | | /// <param name="startingIndex">The index at which the items were replaced.</param> |
| | | 233 | | /// <returns>A new instance of <see cref="CollectionChangedArgs{T}" />.</returns> |
| | | 234 | | public static CollectionChangedArgs<T> Replace(ReadOnlySpan<T> oldItems, ReadOnlySpan<T> newItems, int startingI |
| | 222 | 235 | | { |
| | 222 | 236 | | using IMemoryOwner<T> oldOwner = MemoryPool<T>.Shared.Rent(oldItems.Length); |
| | 222 | 237 | | Memory<T> oldMemory = oldOwner.Memory.Slice(0, oldItems.Length); |
| | 222 | 238 | | oldItems.CopyTo(oldMemory.Span); |
| | | 239 | | |
| | 222 | 240 | | using IMemoryOwner<T> newOwner = MemoryPool<T>.Shared.Rent(newItems.Length); |
| | 222 | 241 | | Memory<T> newMemory = newOwner.Memory.Slice(0, newItems.Length); |
| | 222 | 242 | | newItems.CopyTo(newMemory.Span); |
| | | 243 | | |
| | 222 | 244 | | return new CollectionChangedArgs<T>(NotifyCollectionChangedAction.Replace, startingIndex, newMemory, startin |
| | 222 | 245 | | } |
| | | 246 | | |
| | | 247 | | /// <summary> |
| | | 248 | | /// Creates a new instance of <see cref="CollectionChangedArgs{T}" /> with the <c>Reset</c> action. |
| | | 249 | | /// </summary> |
| | | 250 | | /// <param name="items">The items that were cleared.</param> |
| | | 251 | | /// <param name="newIndex">The index at which the items were cleared.</param> |
| | | 252 | | /// <param name="oldIndex">The index at which the items were cleared.</param> |
| | | 253 | | /// <returns>A new instance of <see cref="CollectionChangedArgs{T}" />.</returns> |
| | | 254 | | public static CollectionChangedArgs<T> Clear(ReadOnlySpan<T> items, int newIndex = 0, int oldIndex = 0) |
| | 2388 | 255 | | { |
| | 2388 | 256 | | using IMemoryOwner<T> owner = MemoryPool<T>.Shared.Rent(items.Length); |
| | 2388 | 257 | | Memory<T> memory = owner.Memory.Slice(0, items.Length); |
| | 2388 | 258 | | items.CopyTo(memory.Span); |
| | | 259 | | |
| | 2388 | 260 | | return new CollectionChangedArgs<T>(NotifyCollectionChangedAction.Reset, newIndex, oldIndex: oldIndex, oldIt |
| | 2388 | 261 | | } |
| | | 262 | | |
| | | 263 | | /// <summary> |
| | | 264 | | /// Checks if the current instance is equal to another <see cref="CollectionChangedArgs{T}" /> instance. |
| | | 265 | | /// </summary> |
| | | 266 | | /// <param name="other">The other instance to compare with.</param> |
| | | 267 | | /// <returns><c>true</c> if the instances are equal; otherwise, <c>false</c>.</returns> |
| | | 268 | | public bool Equals(CollectionChangedArgs<T> other) |
| | 396 | 269 | | { |
| | 396 | 270 | | return Action == other.Action && NewIndex == other.NewIndex && OldIndex == other.OldIndex && OldItems.Equals |
| | | 271 | | NewItems.Equals(other.NewItems); |
| | 396 | 272 | | } |
| | | 273 | | |
| | | 274 | | /// <summary> |
| | | 275 | | /// Checks if the current instance is the same type as the given object and if they are equal. |
| | | 276 | | /// </summary> |
| | | 277 | | /// <param name="obj">The object to compare with.</param> |
| | | 278 | | /// <returns> |
| | | 279 | | /// <c>true</c> if the object is a <see cref="CollectionChangedArgs{T}" /> and is equal to the current insta |
| | | 280 | | /// otherwise, <c>false</c>. |
| | | 281 | | /// </returns> |
| | | 282 | | public override bool Equals(object? obj) |
| | 0 | 283 | | { |
| | 0 | 284 | | return obj is CollectionChangedArgs<T> other && Equals(other); |
| | 0 | 285 | | } |
| | | 286 | | |
| | | 287 | | /// <summary> |
| | | 288 | | /// Generates a hash code for the current instance. |
| | | 289 | | /// </summary> |
| | | 290 | | /// <returns>A hash code for the current instance.</returns> |
| | | 291 | | public override int GetHashCode() |
| | 0 | 292 | | { |
| | | 293 | | unchecked |
| | 0 | 294 | | { |
| | 0 | 295 | | int hashCode = (int) Action; |
| | 0 | 296 | | hashCode = (hashCode * 397) ^ NewIndex; |
| | 0 | 297 | | hashCode = (hashCode * 397) ^ OldIndex; |
| | 0 | 298 | | hashCode = (hashCode * 397) ^ OldItems.GetHashCode(); |
| | 0 | 299 | | hashCode = (hashCode * 397) ^ NewItems.GetHashCode(); |
| | 0 | 300 | | return hashCode; |
| | | 301 | | } |
| | 0 | 302 | | } |
| | | 303 | | |
| | | 304 | | public static bool operator ==(CollectionChangedArgs<T> left, CollectionChangedArgs<T> right) |
| | 396 | 305 | | { |
| | 396 | 306 | | return left.Equals(right); |
| | 396 | 307 | | } |
| | | 308 | | |
| | | 309 | | public static bool operator !=(CollectionChangedArgs<T> left, CollectionChangedArgs<T> right) |
| | 0 | 310 | | { |
| | 0 | 311 | | return !left.Equals(right); |
| | 0 | 312 | | } |
| | | 313 | | } |
| | | 314 | | } |