| | | 1 | | using MemoryPack; |
| | | 2 | | using System; |
| | | 3 | | using System.Collections; |
| | | 4 | | using System.Collections.Generic; |
| | | 5 | | using System.Runtime.CompilerServices; |
| | | 6 | | using System.Text.Json.Serialization; |
| | | 7 | | |
| | | 8 | | namespace SwiftCollections; |
| | | 9 | | |
| | | 10 | | /// <summary> |
| | | 11 | | /// Represents a high-performance sparse map that stores values indexed by externally supplied integer keys. |
| | | 12 | | /// Provides O(1) Add, Remove, Contains, and lookup operations while maintaining densely packed storage |
| | | 13 | | /// for cache-friendly iteration. |
| | | 14 | | /// </summary> |
| | | 15 | | /// <remarks> |
| | | 16 | | /// Unlike <see cref="SwiftBucket{T}"/>, which internally assigns and manages item indices, |
| | | 17 | | /// <see cref="SwiftSparseMap{T}"/> is externally keyed. The caller supplies the integer key |
| | | 18 | | /// (for example, an entity ID or handle) used to index the value. |
| | | 19 | | /// |
| | | 20 | | /// Internally, the container maintains: |
| | | 21 | | /// <list type="bullet"> |
| | | 22 | | /// <item> |
| | | 23 | | /// <description>A sparse lookup table mapping keys to dense indices.</description> |
| | | 24 | | /// </item> |
| | | 25 | | /// <item> |
| | | 26 | | /// <description>A dense array of keys.</description> |
| | | 27 | | /// </item> |
| | | 28 | | /// <item> |
| | | 29 | | /// <description>A dense array of values.</description> |
| | | 30 | | /// </item> |
| | | 31 | | /// </list> |
| | | 32 | | /// |
| | | 33 | | /// Removal uses a swap-back strategy to keep dense storage contiguous. As a result, |
| | | 34 | | /// iteration order is not guaranteed to remain stable. |
| | | 35 | | /// |
| | | 36 | | /// Keys are used as direct indices into the sparse lookup table, so memory usage scales |
| | | 37 | | /// with the highest stored key rather than the number of stored values. This container is |
| | | 38 | | /// intended for compact, non-negative IDs such as entity handles or slot indices. It is |
| | | 39 | | /// not a good fit for arbitrary hashes or widely spaced keys; for those workloads prefer |
| | | 40 | | /// <c>SwiftDictionary<TKey, TValue></c>. |
| | | 41 | | /// </remarks> |
| | | 42 | | /// <typeparam name="T">Value type stored by key.</typeparam> |
| | | 43 | | [Serializable] |
| | | 44 | | [JsonConverter(typeof(SwiftStateJsonConverterFactory))] |
| | | 45 | | [MemoryPackable] |
| | | 46 | | public sealed partial class SwiftSparseMap<T> : ISwiftCloneable<T>, IEnumerable<KeyValuePair<int, T>>, IEnumerable |
| | | 47 | | { |
| | | 48 | | #region Constants |
| | | 49 | | |
| | | 50 | | public const int DefaultDenseCapacity = 8; |
| | | 51 | | public const int DefaultSparseCapacity = 8; |
| | | 52 | | |
| | | 53 | | // sparse[key] stores (denseIndex + 1). 0 means "not present". |
| | | 54 | | private const int NotPresent = 0; |
| | | 55 | | |
| | | 56 | | #endregion |
| | | 57 | | |
| | | 58 | | #region Fields |
| | | 59 | | |
| | | 60 | | private int[] _sparse; // key -> denseIndex+1 |
| | | 61 | | private int[] _denseKeys; // denseIndex -> key |
| | | 62 | | private T[] _denseValues; // denseIndex -> value |
| | | 63 | | private int _count; |
| | | 64 | | |
| | | 65 | | [NonSerialized] |
| | | 66 | | private uint _version; |
| | | 67 | | |
| | | 68 | | [NonSerialized] |
| | | 69 | | private object _syncRoot; |
| | | 70 | | |
| | | 71 | | #endregion |
| | | 72 | | |
| | | 73 | | #region Constructors |
| | | 74 | | |
| | 93 | 75 | | public SwiftSparseMap() : this(DefaultSparseCapacity, DefaultDenseCapacity) { } |
| | | 76 | | |
| | | 77 | | /// <summary> |
| | | 78 | | /// Initializes a new sparse map with the specified sparse and dense capacities. |
| | | 79 | | /// </summary> |
| | | 80 | | /// <param name="sparseCapacity"> |
| | | 81 | | /// Initial sparse lookup capacity. This should track the highest expected key plus one, |
| | | 82 | | /// not the number of stored values. |
| | | 83 | | /// </param> |
| | | 84 | | /// <param name="denseCapacity">Initial dense storage capacity for values.</param> |
| | 32 | 85 | | public SwiftSparseMap(int sparseCapacity, int denseCapacity) |
| | 32 | 86 | | { |
| | 32 | 87 | | SwiftThrowHelper.ThrowIfNegative(sparseCapacity, nameof(sparseCapacity)); |
| | 32 | 88 | | SwiftThrowHelper.ThrowIfNegative(denseCapacity, nameof(denseCapacity)); |
| | | 89 | | |
| | 32 | 90 | | int sparseSize = sparseCapacity == 0 ? 0 : SwiftHashTools.NextPowerOfTwo(sparseCapacity); |
| | 32 | 91 | | _sparse = sparseCapacity == 0 |
| | 32 | 92 | | ? Array.Empty<int>() |
| | 32 | 93 | | : new int[sparseSize]; |
| | 32 | 94 | | int denseSize = denseCapacity < DefaultDenseCapacity |
| | 32 | 95 | | ? DefaultDenseCapacity |
| | 32 | 96 | | : SwiftHashTools.NextPowerOfTwo(denseCapacity); |
| | 32 | 97 | | _denseKeys = denseCapacity == 0 |
| | 32 | 98 | | ? Array.Empty<int>() |
| | 32 | 99 | | : new int[denseSize]; |
| | 32 | 100 | | _denseValues = _denseKeys.Length == 0 ? Array.Empty<T>() : new T[_denseKeys.Length]; |
| | | 101 | | |
| | 32 | 102 | | _count = 0; |
| | 32 | 103 | | } |
| | | 104 | | |
| | | 105 | | [MemoryPackConstructor] |
| | 6 | 106 | | public SwiftSparseMap(SwiftSparseSetState<T> state) |
| | 6 | 107 | | { |
| | 6 | 108 | | State = state; |
| | 4 | 109 | | } |
| | | 110 | | |
| | | 111 | | #endregion |
| | | 112 | | |
| | | 113 | | #region Properties |
| | | 114 | | |
| | | 115 | | [JsonIgnore] |
| | | 116 | | [MemoryPackIgnore] |
| | 14 | 117 | | public int Count => _count; |
| | | 118 | | |
| | | 119 | | /// <summary> |
| | | 120 | | /// Capacity of the dense arrays (Keys/Values storage). |
| | | 121 | | /// </summary> |
| | | 122 | | [JsonIgnore] |
| | | 123 | | [MemoryPackIgnore] |
| | 2 | 124 | | public int DenseCapacity => _denseKeys.Length; |
| | | 125 | | |
| | | 126 | | /// <summary> |
| | | 127 | | /// Capacity of the sparse array (max key+1 that can be mapped without resizing). |
| | | 128 | | /// Memory usage grows with this capacity. |
| | | 129 | | /// </summary> |
| | | 130 | | [JsonIgnore] |
| | | 131 | | [MemoryPackIgnore] |
| | 3 | 132 | | public int SparseCapacity => _sparse.Length; |
| | | 133 | | |
| | | 134 | | [JsonIgnore] |
| | | 135 | | [MemoryPackIgnore] |
| | 1 | 136 | | public bool IsSynchronized => false; |
| | | 137 | | |
| | | 138 | | [JsonIgnore] |
| | | 139 | | [MemoryPackIgnore] |
| | 1 | 140 | | public object SyncRoot => _syncRoot ??= new object(); |
| | | 141 | | |
| | | 142 | | /// <summary> |
| | | 143 | | /// Returns the dense keys array (valid range: [0..Count)). |
| | | 144 | | /// </summary> |
| | | 145 | | [JsonIgnore] |
| | | 146 | | [MemoryPackIgnore] |
| | 5 | 147 | | public int[] DenseKeys => _denseKeys; |
| | | 148 | | |
| | | 149 | | [JsonIgnore] |
| | | 150 | | [MemoryPackIgnore] |
| | 1 | 151 | | public Span<int> Keys => _denseKeys.AsSpan(0, _count); |
| | | 152 | | |
| | | 153 | | /// <summary> |
| | | 154 | | /// Returns the dense values array (valid range: [0..Count)). |
| | | 155 | | /// </summary> |
| | | 156 | | [JsonIgnore] |
| | | 157 | | [MemoryPackIgnore] |
| | 4 | 158 | | public T[] DenseValues => _denseValues; |
| | | 159 | | |
| | | 160 | | [JsonIgnore] |
| | | 161 | | [MemoryPackIgnore] |
| | 1 | 162 | | public Span<T> Values => _denseValues.AsSpan(0, _count); |
| | | 163 | | |
| | | 164 | | /// <summary> |
| | | 165 | | /// Gets/sets the value for a key. Setting: |
| | | 166 | | /// - overwrites if present |
| | | 167 | | /// - inserts if not present |
| | | 168 | | /// </summary> |
| | | 169 | | [JsonIgnore] |
| | | 170 | | [MemoryPackIgnore] |
| | | 171 | | public T this[int key] |
| | | 172 | | { |
| | | 173 | | get |
| | 19 | 174 | | { |
| | 19 | 175 | | int denseIndex = GetDenseIndexOrThrow(key); |
| | 17 | 176 | | return _denseValues[denseIndex]; |
| | 17 | 177 | | } |
| | | 178 | | set |
| | 40 | 179 | | { |
| | 40 | 180 | | EnsureSparseCapacity(GetRequiredSparseCapacity(key)); |
| | | 181 | | |
| | 38 | 182 | | int slot = _sparse[key]; |
| | 38 | 183 | | if (slot != NotPresent) |
| | 1 | 184 | | { |
| | | 185 | | // present -> overwrite |
| | 1 | 186 | | int denseIndex = slot - 1; |
| | 1 | 187 | | _denseValues[denseIndex] = value; |
| | 1 | 188 | | _version++; |
| | 1 | 189 | | return; |
| | | 190 | | } |
| | | 191 | | |
| | | 192 | | // not present -> insert |
| | 37 | 193 | | EnsureDenseCapacity(_count + 1); |
| | | 194 | | |
| | 37 | 195 | | int newIndex = _count++; |
| | 37 | 196 | | _denseKeys[newIndex] = key; |
| | 37 | 197 | | _denseValues[newIndex] = value; |
| | 37 | 198 | | _sparse[key] = newIndex + 1; |
| | | 199 | | |
| | 37 | 200 | | _version++; |
| | 38 | 201 | | } |
| | | 202 | | } |
| | | 203 | | |
| | | 204 | | [JsonInclude] |
| | | 205 | | [MemoryPackInclude] |
| | | 206 | | public SwiftSparseSetState<T> State |
| | | 207 | | { |
| | | 208 | | get |
| | 4 | 209 | | { |
| | | 210 | | // Serialize only the used portions of dense arrays |
| | 4 | 211 | | var denseKeys = new int[_count]; |
| | 4 | 212 | | Array.Copy(_denseKeys, denseKeys, _count); |
| | | 213 | | |
| | 4 | 214 | | var denseValues = new T[_count]; |
| | 4 | 215 | | Array.Copy(_denseValues, denseValues, _count); |
| | | 216 | | |
| | 4 | 217 | | return new SwiftSparseSetState<T>(denseKeys, denseValues); |
| | 4 | 218 | | } |
| | | 219 | | internal set |
| | 6 | 220 | | { |
| | 6 | 221 | | int n = value.DenseKeys?.Length ?? 0; |
| | | 222 | | |
| | 6 | 223 | | if (n != (value.DenseValues?.Length ?? 0)) |
| | 1 | 224 | | throw new ArgumentException("DenseKeys and DenseValues length mismatch."); |
| | | 225 | | |
| | | 226 | | // Allocate dense storage |
| | 5 | 227 | | _denseKeys = n == 0 ? Array.Empty<int>() : new int[Math.Max(DefaultDenseCapacity, n)]; |
| | 5 | 228 | | _denseValues = n == 0 ? Array.Empty<T>() : new T[_denseKeys.Length]; |
| | | 229 | | |
| | 5 | 230 | | if (n > 0) |
| | 5 | 231 | | { |
| | 5 | 232 | | Array.Copy(value.DenseKeys, _denseKeys, n); |
| | 5 | 233 | | Array.Copy(value.DenseValues, _denseValues, n); |
| | 5 | 234 | | } |
| | | 235 | | |
| | 5 | 236 | | _count = n; |
| | | 237 | | |
| | | 238 | | // Compute maxKey from dense keys |
| | 5 | 239 | | int maxKey = -1; |
| | 28 | 240 | | for (int i = 0; i < n; i++) |
| | 9 | 241 | | { |
| | 9 | 242 | | int key = _denseKeys[i]; |
| | 9 | 243 | | if (key < 0) |
| | 0 | 244 | | throw new ArgumentException("Key cannot be negative."); |
| | | 245 | | |
| | 9 | 246 | | if (key > maxKey) |
| | 8 | 247 | | maxKey = key; |
| | 9 | 248 | | } |
| | | 249 | | |
| | | 250 | | // Allocate sparse map |
| | 5 | 251 | | int sparseSize = maxKey < 0 |
| | 5 | 252 | | ? DefaultSparseCapacity |
| | 5 | 253 | | : Math.Max(DefaultSparseCapacity, GetRequiredSparseCapacity(maxKey)); |
| | 5 | 254 | | _sparse = new int[sparseSize]; |
| | | 255 | | |
| | | 256 | | // Rebuild sparse lookup |
| | 26 | 257 | | for (int i = 0; i < n; i++) |
| | 9 | 258 | | { |
| | 9 | 259 | | int key = _denseKeys[i]; |
| | | 260 | | |
| | 9 | 261 | | if (_sparse[key] != NotPresent) |
| | 1 | 262 | | throw new ArgumentException("Duplicate key in DenseKeys."); |
| | | 263 | | |
| | 8 | 264 | | _sparse[key] = i + 1; |
| | 8 | 265 | | } |
| | | 266 | | |
| | 4 | 267 | | _version++; |
| | 4 | 268 | | } |
| | | 269 | | } |
| | | 270 | | |
| | | 271 | | #endregion |
| | | 272 | | |
| | | 273 | | #region Core Operations |
| | | 274 | | |
| | | 275 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 276 | | public bool ContainsKey(int key) |
| | 11 | 277 | | { |
| | 12 | 278 | | if ((uint)key >= (uint)_sparse.Length) return false; |
| | 10 | 279 | | return _sparse[key] != NotPresent; |
| | 11 | 280 | | } |
| | | 281 | | |
| | | 282 | | /// <summary> |
| | | 283 | | /// Adds a key/value only if the key is not present. |
| | | 284 | | /// Returns false if already present. |
| | | 285 | | /// </summary> |
| | | 286 | | public bool TryAdd(int key, T value) |
| | 2 | 287 | | { |
| | 2 | 288 | | EnsureSparseCapacity(GetRequiredSparseCapacity(key)); |
| | 2 | 289 | | if (_sparse[key] != NotPresent) |
| | 1 | 290 | | return false; |
| | | 291 | | |
| | 1 | 292 | | EnsureDenseCapacity(_count + 1); |
| | | 293 | | |
| | 1 | 294 | | int newIndex = _count++; |
| | 1 | 295 | | _denseKeys[newIndex] = key; |
| | 1 | 296 | | _denseValues[newIndex] = value; |
| | 1 | 297 | | _sparse[key] = newIndex + 1; |
| | | 298 | | |
| | 1 | 299 | | _version++; |
| | 1 | 300 | | return true; |
| | 2 | 301 | | } |
| | | 302 | | |
| | | 303 | | /// <summary> |
| | | 304 | | /// Adds or overwrites (same behavior as indexer set). |
| | | 305 | | /// </summary> |
| | 39 | 306 | | public void Add(int key, T value) => this[key] = value; |
| | | 307 | | |
| | | 308 | | public bool TryGetValue(int key, out T value) |
| | 2 | 309 | | { |
| | 2 | 310 | | if ((uint)key < (uint)_sparse.Length) |
| | 1 | 311 | | { |
| | 1 | 312 | | int slot = _sparse[key]; |
| | 1 | 313 | | if (slot != NotPresent) |
| | 1 | 314 | | { |
| | 1 | 315 | | value = _denseValues[slot - 1]; |
| | 1 | 316 | | return true; |
| | | 317 | | } |
| | 0 | 318 | | } |
| | | 319 | | |
| | 1 | 320 | | value = default; |
| | 1 | 321 | | return false; |
| | 2 | 322 | | } |
| | | 323 | | |
| | | 324 | | public bool Remove(int key) |
| | 4 | 325 | | { |
| | 5 | 326 | | if ((uint)key >= (uint)_sparse.Length) return false; |
| | | 327 | | |
| | 3 | 328 | | int slot = _sparse[key]; |
| | 3 | 329 | | if (slot == NotPresent) return false; |
| | | 330 | | |
| | 3 | 331 | | int index = slot - 1; |
| | 3 | 332 | | int last = --_count; |
| | | 333 | | |
| | 3 | 334 | | _sparse[key] = NotPresent; |
| | | 335 | | |
| | 3 | 336 | | if (index != last) |
| | 1 | 337 | | { |
| | 1 | 338 | | int movedKey = _denseKeys[last]; |
| | | 339 | | |
| | 1 | 340 | | _denseKeys[index] = movedKey; |
| | 1 | 341 | | _denseValues[index] = _denseValues[last]; |
| | | 342 | | |
| | 1 | 343 | | _sparse[movedKey] = index + 1; |
| | 1 | 344 | | } |
| | | 345 | | |
| | 3 | 346 | | _denseKeys[last] = default; |
| | 3 | 347 | | _denseValues[last] = default; |
| | | 348 | | |
| | 3 | 349 | | _version++; |
| | 3 | 350 | | return true; |
| | 4 | 351 | | } |
| | | 352 | | |
| | | 353 | | public void Clear() |
| | 5 | 354 | | { |
| | 7 | 355 | | if (_count == 0) return; |
| | | 356 | | |
| | | 357 | | // reset sparse for keys that were present |
| | 14 | 358 | | for (int i = 0; i < _count; i++) |
| | 4 | 359 | | { |
| | 4 | 360 | | int key = _denseKeys[i]; |
| | 4 | 361 | | if ((uint)key < (uint)_sparse.Length) |
| | 4 | 362 | | _sparse[key] = NotPresent; |
| | 4 | 363 | | } |
| | | 364 | | |
| | 3 | 365 | | Array.Clear(_denseKeys, 0, _count); |
| | 3 | 366 | | Array.Clear(_denseValues, 0, _count); |
| | | 367 | | |
| | 3 | 368 | | _count = 0; |
| | 3 | 369 | | _version++; |
| | 5 | 370 | | } |
| | | 371 | | |
| | | 372 | | #endregion |
| | | 373 | | |
| | | 374 | | #region Capacity Management |
| | | 375 | | |
| | | 376 | | public void EnsureDenseCapacity(int capacity) |
| | 39 | 377 | | { |
| | 77 | 378 | | if (capacity <= _denseKeys.Length) return; |
| | | 379 | | |
| | 1 | 380 | | int newCap = _denseKeys.Length == 0 ? DefaultDenseCapacity : _denseKeys.Length * 2; |
| | 2 | 381 | | if (newCap < capacity) newCap = capacity; |
| | | 382 | | |
| | 1 | 383 | | newCap = SwiftHashTools.NextPowerOfTwo(newCap); |
| | | 384 | | |
| | 1 | 385 | | var newKeys = new int[newCap]; |
| | 1 | 386 | | var newVals = new T[newCap]; |
| | | 387 | | |
| | 1 | 388 | | if (_count > 0) |
| | 0 | 389 | | { |
| | 0 | 390 | | Array.Copy(_denseKeys, newKeys, _count); |
| | 0 | 391 | | Array.Copy(_denseValues, newVals, _count); |
| | 0 | 392 | | } |
| | | 393 | | |
| | 1 | 394 | | _denseKeys = newKeys; |
| | 1 | 395 | | _denseValues = newVals; |
| | | 396 | | |
| | 1 | 397 | | _version++; |
| | 39 | 398 | | } |
| | | 399 | | |
| | | 400 | | public void EnsureSparseCapacity(int capacity) |
| | 41 | 401 | | { |
| | 78 | 402 | | if (capacity <= _sparse.Length) return; |
| | | 403 | | |
| | 4 | 404 | | int newCap = _sparse.Length == 0 |
| | 4 | 405 | | ? DefaultSparseCapacity |
| | 4 | 406 | | : _sparse.Length * 2; |
| | 7 | 407 | | if (newCap < capacity) newCap = capacity; |
| | | 408 | | |
| | 4 | 409 | | newCap = SwiftHashTools.NextPowerOfTwo(newCap); |
| | | 410 | | |
| | 4 | 411 | | var newSparse = new int[newCap]; |
| | 4 | 412 | | if (_sparse.Length > 0) |
| | 4 | 413 | | Array.Copy(_sparse, newSparse, _sparse.Length); |
| | | 414 | | |
| | 4 | 415 | | _sparse = newSparse; |
| | 4 | 416 | | _version++; |
| | 41 | 417 | | } |
| | | 418 | | |
| | | 419 | | public void TrimExcess() |
| | 1 | 420 | | { |
| | | 421 | | // Dense: shrink to Count (with a minimum) |
| | 1 | 422 | | int newDense = Math.Max(DefaultDenseCapacity, _count); |
| | 1 | 423 | | if (newDense < _denseKeys.Length) |
| | 1 | 424 | | { |
| | 1 | 425 | | var newKeys = new int[newDense]; |
| | 1 | 426 | | var newVals = new T[newDense]; |
| | 1 | 427 | | if (_count > 0) |
| | 1 | 428 | | { |
| | 1 | 429 | | Array.Copy(_denseKeys, newKeys, _count); |
| | 1 | 430 | | Array.Copy(_denseValues, newVals, _count); |
| | 1 | 431 | | } |
| | 1 | 432 | | _denseKeys = newKeys; |
| | 1 | 433 | | _denseValues = newVals; |
| | 1 | 434 | | } |
| | | 435 | | |
| | | 436 | | // Sparse: shrink to (maxKey+1) based on dense keys |
| | 1 | 437 | | int maxKey = -1; |
| | 4 | 438 | | for (int i = 0; i < _count; i++) |
| | 2 | 439 | | if (_denseKeys[i] > maxKey) maxKey = _denseKeys[i]; |
| | | 440 | | |
| | 1 | 441 | | int newSparse = maxKey < 0 |
| | 1 | 442 | | ? DefaultSparseCapacity |
| | 1 | 443 | | : Math.Max(DefaultSparseCapacity, GetRequiredSparseCapacity(maxKey)); |
| | 1 | 444 | | if (newSparse < _sparse.Length) |
| | 1 | 445 | | { |
| | 1 | 446 | | var newMap = new int[newSparse]; |
| | | 447 | | // rebuild from dense |
| | 4 | 448 | | for (int i = 0; i < _count; i++) |
| | 1 | 449 | | newMap[_denseKeys[i]] = i + 1; |
| | 1 | 450 | | _sparse = newMap; |
| | 1 | 451 | | } |
| | | 452 | | |
| | 1 | 453 | | _version++; |
| | 1 | 454 | | } |
| | | 455 | | |
| | | 456 | | #endregion |
| | | 457 | | |
| | | 458 | | #region Enumeration |
| | | 459 | | |
| | 8 | 460 | | public Enumerator GetEnumerator() => new Enumerator(this); |
| | 5 | 461 | | IEnumerator<KeyValuePair<int, T>> IEnumerable<KeyValuePair<int, T>>.GetEnumerator() => GetEnumerator(); |
| | 1 | 462 | | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
| | | 463 | | |
| | | 464 | | public struct Enumerator : IEnumerator<KeyValuePair<int, T>> |
| | | 465 | | { |
| | | 466 | | private readonly SwiftSparseMap<T> _set; |
| | | 467 | | private readonly int[] _keys; |
| | | 468 | | private readonly T[] _values; |
| | | 469 | | private readonly int _count; |
| | | 470 | | private readonly uint _version; |
| | | 471 | | private int _index; |
| | | 472 | | |
| | | 473 | | internal Enumerator(SwiftSparseMap<T> set) |
| | 8 | 474 | | { |
| | 8 | 475 | | _set = set; |
| | 8 | 476 | | _keys = set._denseKeys; |
| | 8 | 477 | | _values = set._denseValues; |
| | 8 | 478 | | _count = set._count; |
| | 8 | 479 | | _version = set._version; |
| | 8 | 480 | | _index = -1; |
| | 8 | 481 | | Current = default; |
| | 8 | 482 | | } |
| | | 483 | | |
| | 24 | 484 | | public KeyValuePair<int, T> Current { get; private set; } |
| | 1 | 485 | | object IEnumerator.Current => Current; |
| | | 486 | | |
| | | 487 | | public bool MoveNext() |
| | 11 | 488 | | { |
| | 11 | 489 | | if (_version != _set._version) |
| | 1 | 490 | | throw new InvalidOperationException("Collection was modified during enumeration."); |
| | | 491 | | |
| | 10 | 492 | | int next = _index + 1; |
| | 10 | 493 | | if (next >= _count) |
| | 5 | 494 | | { |
| | 5 | 495 | | Current = default; |
| | 5 | 496 | | return false; |
| | | 497 | | } |
| | | 498 | | |
| | 5 | 499 | | _index = next; |
| | 5 | 500 | | Current = new KeyValuePair<int, T>(_keys[_index], _values[_index]); |
| | 5 | 501 | | return true; |
| | 10 | 502 | | } |
| | | 503 | | |
| | | 504 | | public void Reset() |
| | 1 | 505 | | { |
| | 1 | 506 | | if (_version != _set._version) |
| | 0 | 507 | | throw new InvalidOperationException("Collection was modified during enumeration."); |
| | 1 | 508 | | _index = -1; |
| | 1 | 509 | | Current = default; |
| | 1 | 510 | | } |
| | | 511 | | |
| | 10 | 512 | | public void Dispose() { } |
| | | 513 | | } |
| | | 514 | | |
| | | 515 | | #endregion |
| | | 516 | | |
| | | 517 | | #region Helpers |
| | | 518 | | |
| | | 519 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 520 | | public void GetDense(out int[] keys, out T[] values, out int count) |
| | 1 | 521 | | { |
| | 1 | 522 | | keys = _denseKeys; |
| | 1 | 523 | | values = _denseValues; |
| | 1 | 524 | | count = _count; |
| | 1 | 525 | | } |
| | | 526 | | |
| | | 527 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 528 | | private static int GetRequiredSparseCapacity(int key) |
| | 48 | 529 | | { |
| | 48 | 530 | | if (key < 0) |
| | 1 | 531 | | throw new ArgumentOutOfRangeException(nameof(key), "Key must be non-negative."); |
| | | 532 | | |
| | 47 | 533 | | if (key == int.MaxValue) |
| | 1 | 534 | | throw new ArgumentOutOfRangeException(nameof(key), "Key is too large for direct sparse indexing."); |
| | | 535 | | |
| | 46 | 536 | | return key + 1; |
| | 46 | 537 | | } |
| | | 538 | | |
| | | 539 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 540 | | private int GetDenseIndexOrThrow(int key) |
| | 19 | 541 | | { |
| | 19 | 542 | | if ((uint)key >= (uint)_sparse.Length) |
| | 1 | 543 | | throw new KeyNotFoundException($"Key not found: {key}"); |
| | | 544 | | |
| | 18 | 545 | | int slot = _sparse[key]; |
| | 18 | 546 | | if (slot == NotPresent) |
| | 1 | 547 | | throw new KeyNotFoundException($"Key not found: {key}"); |
| | | 548 | | |
| | 17 | 549 | | return slot - 1; |
| | 17 | 550 | | } |
| | | 551 | | |
| | | 552 | | public void CloneTo(ICollection<T> output) |
| | 1 | 553 | | { |
| | 1 | 554 | | SwiftThrowHelper.ThrowIfNull(output, nameof(output)); |
| | | 555 | | |
| | 1 | 556 | | output.Clear(); |
| | | 557 | | |
| | 6 | 558 | | for (int i = 0; i < _count; i++) |
| | 2 | 559 | | output.Add(_denseValues[i]); |
| | 1 | 560 | | } |
| | | 561 | | |
| | | 562 | | #endregion |
| | | 563 | | } |