com.wallstop-studios.unity-helpers 2.1.1 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/AGENTS.md +1 -0
  2. package/Docs/ILIST_SORTING_PERFORMANCE.md +16 -16
  3. package/Docs/INDEX.md +1 -0
  4. package/Docs/RANDOM_PERFORMANCE.md +15 -15
  5. package/Docs/REFLECTION_HELPERS.md +84 -1
  6. package/Docs/REFLECTION_PERFORMANCE.md +169 -0
  7. package/{package-lock.json.meta → Docs/REFLECTION_PERFORMANCE.md.meta} +1 -1
  8. package/Docs/RELATIONAL_COMPONENTS.md +6 -0
  9. package/Docs/RELATIONAL_COMPONENT_PERFORMANCE.md +63 -0
  10. package/Docs/RELATIONAL_COMPONENT_PERFORMANCE.md.meta +7 -0
  11. package/Docs/SPATIAL_TREE_2D_PERFORMANCE.md +64 -64
  12. package/Docs/SPATIAL_TREE_3D_PERFORMANCE.md +64 -64
  13. package/Editor/Sprites/AnimationCopier.cs +1 -1
  14. package/Editor/Sprites/AnimationViewerWindow.cs +4 -4
  15. package/Editor/Sprites/SpriteSettingsApplierAPI.cs +2 -1
  16. package/Editor/Sprites/TextureResizerWizard.cs +4 -3
  17. package/Editor/Utils/ScriptableObjectSingletonCreator.cs +3 -3
  18. package/README.md +8 -3
  19. package/Runtime/Core/Attributes/BaseRelationalComponentAttribute.cs +147 -20
  20. package/Runtime/Core/Attributes/ChildComponentAttribute.cs +630 -117
  21. package/Runtime/Core/Attributes/NotNullAttribute.cs +5 -2
  22. package/Runtime/Core/Attributes/ParentComponentAttribute.cs +477 -103
  23. package/Runtime/Core/Attributes/RelationalComponentAssigner.cs +26 -3
  24. package/Runtime/Core/Attributes/RelationalComponentExtensions.cs +19 -3
  25. package/Runtime/Core/Attributes/SiblingComponentAttribute.cs +265 -92
  26. package/Runtime/Core/CodeGen.meta +8 -0
  27. package/Runtime/Core/DataStructure/ImmutableBitSet.cs +5 -20
  28. package/Runtime/Core/Helper/Logging/UnityLogTagFormatter.cs +11 -7
  29. package/Runtime/Core/Helper/Objects.cs +1 -1
  30. package/Runtime/Core/Helper/ReflectionHelpers.Factory.cs +5142 -0
  31. package/Runtime/Core/Helper/ReflectionHelpers.Factory.cs.meta +11 -0
  32. package/Runtime/Core/Helper/ReflectionHelpers.cs +1812 -1518
  33. package/Runtime/Core/Math/Line2D.cs +2 -4
  34. package/Runtime/Core/Math/Line3D.cs +2 -4
  35. package/Runtime/Core/Random/FlurryBurstRandom.cs +0 -6
  36. package/Runtime/Tags/AttributeMetadataCache.cs +4 -6
  37. package/Runtime/Tags/CosmeticEffectData.cs +1 -1
  38. package/Runtime/Visuals/UIToolkit/MultiFileSelectorElement.cs +3 -3
  39. package/Tests/Editor/Helper/HelpersTests.cs +2 -2
  40. package/Tests/Editor/Helper/ReflectionHelpersTypedEditorTests.cs +87 -0
  41. package/Tests/Editor/Helper/ReflectionHelpersTypedEditorTests.cs.meta +11 -0
  42. package/Tests/Editor/Helper/SpriteHelpersTests.cs +1 -1
  43. package/Tests/Editor/PrefabCheckerReportTests.cs +3 -3
  44. package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs +18 -12
  45. package/Tests/Editor/Sprites/AnimationCopierWindowTests.cs +8 -7
  46. package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs +2 -1
  47. package/Tests/Editor/Sprites/ScriptableSpriteAtlasEditorTests.cs +6 -5
  48. package/Tests/Editor/Sprites/SpriteCropperAdditionalTests.cs +2 -1
  49. package/Tests/Editor/Sprites/SpriteCropperTests.cs +7 -6
  50. package/Tests/Editor/Sprites/SpritePivotAdjusterAdditionalTests.cs +2 -1
  51. package/Tests/Editor/Sprites/SpritePivotAdjusterTests.cs +4 -3
  52. package/Tests/Editor/Sprites/TextureResizerWizardTests.cs +10 -9
  53. package/Tests/Editor/Sprites/TextureSettingsApplierAPITests.cs +2 -1
  54. package/Tests/Runtime/Helper/ObjectsTests.cs +1 -1
  55. package/Tests/Runtime/Helper/ReflectionHelperCapabilityMatrixTests.cs +2923 -0
  56. package/Tests/Runtime/Helper/ReflectionHelperCapabilityMatrixTests.cs.meta +11 -0
  57. package/Tests/Runtime/Helper/ReflectionHelperTests.cs +660 -0
  58. package/Tests/Runtime/Performance/ReflectionPerformanceTests.cs +1238 -0
  59. package/Tests/Runtime/Performance/ReflectionPerformanceTests.cs.meta +11 -0
  60. package/Tests/Runtime/Performance/RelationalComponentBenchmarkTests.cs +832 -0
  61. package/Tests/Runtime/Performance/RelationalComponentBenchmarkTests.cs.meta +11 -0
  62. package/package.json +1 -1
  63. package/Tests/Runtime/Performance/RelationComponentPerformanceTests.cs +0 -60
  64. package/Tests/Runtime/Performance/RelationComponentPerformanceTests.cs.meta +0 -3
@@ -14,6 +14,8 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
14
14
  public sealed class RelationalComponentAssigner : IRelationalComponentAssigner
15
15
  {
16
16
  private readonly AttributeMetadataCache _metadataCache;
17
+ private readonly Dictionary<Type, bool> _hasAssignmentsCache;
18
+ private readonly object _cacheLock = new();
17
19
 
18
20
  /// <summary>
19
21
  /// Creates a new assigner using the active <see cref="AttributeMetadataCache.Instance"/>.
@@ -27,6 +29,7 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
27
29
  public RelationalComponentAssigner(AttributeMetadataCache metadataCache)
28
30
  {
29
31
  _metadataCache = metadataCache;
32
+ _hasAssignmentsCache = new Dictionary<Type, bool>();
30
33
  }
31
34
 
32
35
  /// <inheritdoc />
@@ -37,11 +40,20 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
37
40
  return false;
38
41
  }
39
42
 
43
+ lock (_cacheLock)
44
+ {
45
+ if (_hasAssignmentsCache.TryGetValue(componentType, out bool cachedResult))
46
+ {
47
+ return cachedResult;
48
+ }
49
+ }
50
+
40
51
  AttributeMetadataCache cache = _metadataCache ?? AttributeMetadataCache.Instance;
41
52
  if (cache == null)
42
53
  {
43
- // Fallback to reflection-based discovery when no cache is available
44
- return HasRelationalAttributesViaReflection(componentType);
54
+ bool reflectionResult = HasRelationalAttributesViaReflection(componentType);
55
+ StoreCacheResult(componentType, reflectionResult);
56
+ return reflectionResult;
45
57
  }
46
58
 
47
59
  Type current = componentType;
@@ -55,13 +67,24 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
55
67
  && fields.Length > 0
56
68
  )
57
69
  {
70
+ StoreCacheResult(componentType, true);
58
71
  return true;
59
72
  }
60
73
  current = current.BaseType;
61
74
  }
62
75
 
63
76
  // Fallback: inspect fields via reflection to detect relational attributes
64
- return HasRelationalAttributesViaReflection(componentType);
77
+ bool result = HasRelationalAttributesViaReflection(componentType);
78
+ StoreCacheResult(componentType, result);
79
+ return result;
80
+ }
81
+
82
+ private void StoreCacheResult(Type componentType, bool result)
83
+ {
84
+ lock (_cacheLock)
85
+ {
86
+ _hasAssignmentsCache[componentType] = result;
87
+ }
65
88
  }
66
89
 
67
90
  private static bool HasRelationalAttributesViaReflection(Type componentType)
@@ -1,5 +1,6 @@
1
1
  namespace WallstopStudios.UnityHelpers.Core.Attributes
2
2
  {
3
+ using System;
3
4
  using UnityEngine;
4
5
 
5
6
  /// <summary>
@@ -53,9 +54,24 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
53
54
  /// </example>
54
55
  public static void AssignRelationalComponents(this Component component)
55
56
  {
56
- component.AssignParentComponents();
57
- component.AssignSiblingComponents();
58
- component.AssignChildComponents();
57
+ if (component == null)
58
+ {
59
+ return;
60
+ }
61
+
62
+ Type componentType = component.GetType();
63
+ ParentComponentExtensions.AssignParentComponents(
64
+ component,
65
+ ParentComponentExtensions.GetOrCreateFields(componentType)
66
+ );
67
+ SiblingComponentExtensions.AssignSiblingComponents(
68
+ component,
69
+ SiblingComponentExtensions.GetOrCreateFields(componentType)
70
+ );
71
+ ChildComponentExtensions.AssignChildComponents(
72
+ component,
73
+ ChildComponentExtensions.GetOrCreateFields(componentType)
74
+ );
59
75
  }
60
76
  }
61
77
  }
@@ -3,6 +3,9 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
3
3
  using System;
4
4
  using System.Collections;
5
5
  using System.Collections.Generic;
6
+ using System.Linq;
7
+ using System.Linq.Expressions;
8
+ using System.Reflection;
6
9
  using UnityEngine;
7
10
  using WallstopStudios.UnityHelpers.Core.Extension;
8
11
  using WallstopStudios.UnityHelpers.Utils;
@@ -86,11 +89,22 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
86
89
  /// </example>
87
90
  public static void AssignSiblingComponents(this Component component)
88
91
  {
89
- Type componentType = component.GetType();
90
92
  FieldMetadata<SiblingComponentAttribute>[] fields = FieldsByType.GetOrAdd(
91
- componentType,
93
+ component.GetType(),
92
94
  type => GetFieldMetadata<SiblingComponentAttribute>(type)
93
95
  );
96
+ AssignSiblingComponents(component, fields);
97
+ }
98
+
99
+ internal static void AssignSiblingComponents(
100
+ Component component,
101
+ FieldMetadata<SiblingComponentAttribute>[] fields
102
+ )
103
+ {
104
+ if (component == null || fields == null || fields.Length == 0)
105
+ {
106
+ return;
107
+ }
94
108
 
95
109
  foreach (FieldMetadata<SiblingComponentAttribute> metadata in fields)
96
110
  {
@@ -107,103 +121,142 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
107
121
  }
108
122
  else
109
123
  {
110
- FilterParameters filters = new(metadata.attribute);
111
- switch (metadata.kind)
124
+ FilterParameters filters = metadata.Filters;
125
+ if (
126
+ !metadata.isInterface
127
+ && !filters.RequiresPostProcessing
128
+ && metadata.attribute.MaxCount <= 0
129
+ )
130
+ {
131
+ foundSibling = TryAssignSiblingCollectionFast(component, metadata);
132
+ }
133
+ else
112
134
  {
113
- case FieldKind.Array:
135
+ switch (metadata.kind)
114
136
  {
115
- using PooledResource<List<Component>> componentBuffer =
116
- Buffers<Component>.List.Get(out List<Component> components);
117
- GetComponentsOfType(
118
- component,
119
- metadata.elementType,
120
- metadata.isInterface,
121
- metadata.attribute.AllowInterfaces,
122
- components
123
- );
124
-
125
- int filteredCount = FilterComponentsInPlace(
126
- components,
127
- filters,
128
- metadata.attribute,
129
- metadata.elementType,
130
- metadata.isInterface
131
- );
132
-
133
- Array correctTypedArray = metadata.arrayCreator(filteredCount);
134
- for (int i = 0; i < filteredCount; ++i)
137
+ case FieldKind.Array:
135
138
  {
136
- correctTypedArray.SetValue(components[i], i);
137
- }
139
+ using PooledResource<List<Component>> componentBuffer =
140
+ Buffers<Component>.List.Get(out List<Component> components);
141
+ GetComponentsOfType(
142
+ component,
143
+ metadata.elementType,
144
+ metadata.isInterface,
145
+ metadata.attribute.AllowInterfaces,
146
+ components
147
+ );
138
148
 
139
- metadata.setter(component, correctTypedArray);
140
- foundSibling = filteredCount > 0;
141
- break;
142
- }
143
- case FieldKind.List:
144
- {
145
- using PooledResource<List<Component>> componentBuffer =
146
- Buffers<Component>.List.Get(out List<Component> components);
147
- GetComponentsOfType(
148
- component,
149
- metadata.elementType,
150
- metadata.isInterface,
151
- metadata.attribute.AllowInterfaces,
152
- components
153
- );
154
-
155
- int filteredCount = FilterComponentsInPlace(
156
- components,
157
- filters,
158
- metadata.attribute,
159
- metadata.elementType,
160
- metadata.isInterface
161
- );
162
-
163
- IList instance = metadata.listCreator(filteredCount);
164
- for (int i = 0; i < filteredCount; ++i)
149
+ int filteredCount =
150
+ !filters.RequiresPostProcessing
151
+ && metadata.attribute.MaxCount <= 0
152
+ ? components.Count
153
+ : FilterComponentsInPlace(
154
+ components,
155
+ filters,
156
+ metadata.attribute,
157
+ metadata.elementType,
158
+ metadata.isInterface
159
+ );
160
+
161
+ Array correctTypedArray = metadata.arrayCreator(filteredCount);
162
+ for (int i = 0; i < filteredCount; ++i)
163
+ {
164
+ correctTypedArray.SetValue(components[i], i);
165
+ }
166
+
167
+ metadata.SetValue(component, correctTypedArray);
168
+ foundSibling = filteredCount > 0;
169
+ break;
170
+ }
171
+ case FieldKind.List:
165
172
  {
166
- instance.Add(components[i]);
173
+ using PooledResource<List<Component>> componentBuffer =
174
+ Buffers<Component>.List.Get(out List<Component> components);
175
+ GetComponentsOfType(
176
+ component,
177
+ metadata.elementType,
178
+ metadata.isInterface,
179
+ metadata.attribute.AllowInterfaces,
180
+ components
181
+ );
182
+
183
+ int filteredCount =
184
+ !filters.RequiresPostProcessing
185
+ && metadata.attribute.MaxCount <= 0
186
+ ? components.Count
187
+ : FilterComponentsInPlace(
188
+ components,
189
+ filters,
190
+ metadata.attribute,
191
+ metadata.elementType,
192
+ metadata.isInterface
193
+ );
194
+
195
+ object existing = metadata.GetValue(component);
196
+ if (existing is IList instance)
197
+ {
198
+ instance.Clear();
199
+ }
200
+ else
201
+ {
202
+ instance = metadata.listCreator(filteredCount);
203
+ metadata.SetValue(component, instance);
204
+ }
205
+ for (int i = 0; i < filteredCount; ++i)
206
+ {
207
+ instance.Add(components[i]);
208
+ }
209
+
210
+ foundSibling = filteredCount > 0;
211
+ break;
167
212
  }
213
+ case FieldKind.HashSet:
214
+ {
215
+ using PooledResource<List<Component>> componentBuffer =
216
+ Buffers<Component>.List.Get(out List<Component> components);
217
+ GetComponentsOfType(
218
+ component,
219
+ metadata.elementType,
220
+ metadata.isInterface,
221
+ metadata.attribute.AllowInterfaces,
222
+ components
223
+ );
168
224
 
169
- metadata.setter(component, instance);
170
- foundSibling = filteredCount > 0;
171
- break;
172
- }
173
- case FieldKind.HashSet:
174
- {
175
- using PooledResource<List<Component>> componentBuffer =
176
- Buffers<Component>.List.Get(out List<Component> components);
177
- GetComponentsOfType(
178
- component,
179
- metadata.elementType,
180
- metadata.isInterface,
181
- metadata.attribute.AllowInterfaces,
182
- components
183
- );
184
-
185
- int filteredCount = FilterComponentsInPlace(
186
- components,
187
- filters,
188
- metadata.attribute,
189
- metadata.elementType,
190
- metadata.isInterface
191
- );
192
-
193
- object instance = metadata.hashSetCreator(filteredCount);
194
- for (int i = 0; i < filteredCount; ++i)
225
+ int filteredCount =
226
+ !filters.RequiresPostProcessing
227
+ && metadata.attribute.MaxCount <= 0
228
+ ? components.Count
229
+ : FilterComponentsInPlace(
230
+ components,
231
+ filters,
232
+ metadata.attribute,
233
+ metadata.elementType,
234
+ metadata.isInterface
235
+ );
236
+
237
+ object instance = metadata.GetValue(component);
238
+ if (instance != null && metadata.hashSetClearer != null)
239
+ {
240
+ metadata.hashSetClearer(instance);
241
+ }
242
+ else
243
+ {
244
+ instance = metadata.hashSetCreator(filteredCount);
245
+ metadata.SetValue(component, instance);
246
+ }
247
+ for (int i = 0; i < filteredCount; ++i)
248
+ {
249
+ metadata.hashSetAdder(instance, components[i]);
250
+ }
251
+
252
+ foundSibling = filteredCount > 0;
253
+ break;
254
+ }
255
+ default:
195
256
  {
196
- metadata.hashSetAdder(instance, components[i]);
257
+ foundSibling = TryAssignSingleSibling(component, metadata);
258
+ break;
197
259
  }
198
-
199
- metadata.setter(component, instance);
200
- foundSibling = filteredCount > 0;
201
- break;
202
- }
203
- default:
204
- {
205
- foundSibling = TryAssignSingleSibling(component, metadata);
206
- break;
207
260
  }
208
261
  }
209
262
  }
@@ -215,6 +268,11 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
215
268
  }
216
269
  }
217
270
 
271
+ internal static FieldMetadata<SiblingComponentAttribute>[] GetOrCreateFields(Type type)
272
+ {
273
+ return FieldsByType.GetOrAdd(type, t => GetFieldMetadata<SiblingComponentAttribute>(t));
274
+ }
275
+
218
276
  private static bool TryAssignSingleSibling(
219
277
  Component component,
220
278
  FieldMetadata<SiblingComponentAttribute> metadata
@@ -236,7 +294,7 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
236
294
  {
237
295
  if (component.TryGetComponent(metadata.elementType, out Component sibling))
238
296
  {
239
- metadata.setter(component, sibling);
297
+ metadata.SetValue(component, sibling);
240
298
  return true;
241
299
  }
242
300
  return false;
@@ -255,10 +313,125 @@ namespace WallstopStudios.UnityHelpers.Core.Attributes
255
313
  )
256
314
  )
257
315
  {
258
- metadata.setter(component, resolved);
316
+ metadata.SetValue(component, resolved);
259
317
  return true;
260
318
  }
261
319
  return false;
262
320
  }
321
+
322
+ private static bool TryAssignSiblingCollectionFast(
323
+ Component component,
324
+ FieldMetadata<SiblingComponentAttribute> metadata
325
+ )
326
+ {
327
+ Array componentsArray = SiblingComponentFastInvoker.GetArray(
328
+ component,
329
+ metadata.elementType
330
+ );
331
+
332
+ return AssignComponentsFromArray(component, metadata, componentsArray);
333
+ }
334
+
335
+ private static bool AssignComponentsFromArray(
336
+ Component component,
337
+ FieldMetadata<SiblingComponentAttribute> metadata,
338
+ Array componentsArray
339
+ )
340
+ {
341
+ if (componentsArray == null)
342
+ {
343
+ componentsArray = Array.CreateInstance(metadata.elementType, 0);
344
+ }
345
+
346
+ int count = componentsArray.Length;
347
+
348
+ switch (metadata.kind)
349
+ {
350
+ case FieldKind.Array:
351
+ {
352
+ metadata.SetValue(component, componentsArray);
353
+ return count > 0;
354
+ }
355
+ case FieldKind.List:
356
+ {
357
+ if (metadata.GetValue(component) is IList instance)
358
+ {
359
+ instance.Clear();
360
+ }
361
+ else
362
+ {
363
+ instance = metadata.listCreator(count);
364
+ metadata.SetValue(component, instance);
365
+ }
366
+
367
+ for (int i = 0; i < count; ++i)
368
+ {
369
+ instance.Add(componentsArray.GetValue(i));
370
+ }
371
+
372
+ return count > 0;
373
+ }
374
+ case FieldKind.HashSet:
375
+ {
376
+ object hashSet = metadata.GetValue(component);
377
+ if (hashSet != null && metadata.hashSetClearer != null)
378
+ {
379
+ metadata.hashSetClearer(hashSet);
380
+ }
381
+ else
382
+ {
383
+ hashSet = metadata.hashSetCreator(count);
384
+ metadata.SetValue(component, hashSet);
385
+ }
386
+
387
+ for (int i = 0; i < count; ++i)
388
+ {
389
+ metadata.hashSetAdder(hashSet, componentsArray.GetValue(i));
390
+ }
391
+
392
+ return count > 0;
393
+ }
394
+ default:
395
+ {
396
+ return TryAssignSingleSibling(component, metadata);
397
+ }
398
+ }
399
+ }
400
+ }
401
+
402
+ internal static class SiblingComponentFastInvoker
403
+ {
404
+ private static readonly Dictionary<Type, Func<Component, Array>> ArrayGetters = new();
405
+
406
+ private static readonly MethodInfo GetComponentsGenericDefinition = typeof(Component)
407
+ .GetMethods(BindingFlags.Instance | BindingFlags.Public)
408
+ .First(method =>
409
+ method.Name == nameof(Component.GetComponents)
410
+ && method.IsGenericMethodDefinition
411
+ && method.GetParameters().Length == 0
412
+ );
413
+
414
+ internal static Array GetArray(Component component, Type elementType)
415
+ {
416
+ if (!ArrayGetters.TryGetValue(elementType, out Func<Component, Array> getter))
417
+ {
418
+ getter = CreateArrayGetter(elementType);
419
+ ArrayGetters[elementType] = getter;
420
+ }
421
+
422
+ return getter(component);
423
+ }
424
+
425
+ private static Func<Component, Array> CreateArrayGetter(Type elementType)
426
+ {
427
+ MethodInfo closedMethod = GetComponentsGenericDefinition.MakeGenericMethod(elementType);
428
+ ParameterExpression componentParameter = Expression.Parameter(
429
+ typeof(Component),
430
+ "component"
431
+ );
432
+ MethodCallExpression invoke = Expression.Call(componentParameter, closedMethod);
433
+ UnaryExpression convert = Expression.Convert(invoke, typeof(Array));
434
+ return Expression.Lambda<Func<Component, Array>>(convert, componentParameter).Compile();
435
+ }
263
436
  }
264
437
  }
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: 364c9d10ad9c44edae26897ee1a02506
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant:
@@ -5,6 +5,7 @@ namespace WallstopStudios.UnityHelpers.Core.DataStructure
5
5
  using System.Collections.Generic;
6
6
  using System.Runtime.CompilerServices;
7
7
  using ProtoBuf;
8
+ using WallstopStudios.UnityHelpers.Core.Helper;
8
9
 
9
10
  /// <summary>
10
11
  /// An immutable value-type variant of BitSet that provides read-only access to bit data.
@@ -318,14 +319,8 @@ namespace WallstopStudios.UnityHelpers.Core.DataStructure
318
319
  {
319
320
  return false;
320
321
  }
321
- for (int i = 0; i < _bits.Length; i++)
322
- {
323
- if (_bits[i] != other._bits[i])
324
- {
325
- return false;
326
- }
327
- }
328
- return true;
322
+
323
+ return _bits.AsSpan().SequenceEqual(other._bits);
329
324
  }
330
325
 
331
326
  public override bool Equals(object obj)
@@ -335,18 +330,8 @@ namespace WallstopStudios.UnityHelpers.Core.DataStructure
335
330
 
336
331
  public override int GetHashCode()
337
332
  {
338
- unchecked
339
- {
340
- int hash = _capacity.GetHashCode();
341
- if (_bits != null)
342
- {
343
- foreach (ulong bit in _bits)
344
- {
345
- hash = (hash * 397) ^ bit.GetHashCode();
346
- }
347
- }
348
- return hash;
349
- }
333
+ int hash = Objects.SpanHashCode<ulong>(_bits.AsSpan());
334
+ return Objects.HashCode(_capacity, hash);
350
335
  }
351
336
 
352
337
  public static bool operator ==(ImmutableBitSet left, ImmutableBitSet right)
@@ -406,13 +406,17 @@ namespace WallstopStudios.UnityHelpers.Core.Helper.Logging
406
406
  RemoveDecorationInternal(existing.priority, existing.index);
407
407
  }
408
408
 
409
- List<(
410
- string tag,
411
- bool editorOnly,
412
- Func<string, bool> predicate,
413
- Func<string, object, string> formatter
414
- )> matchingDecorations;
415
- if (!_matchingDecorations.TryGetValue(priority, out matchingDecorations))
409
+ if (
410
+ !_matchingDecorations.TryGetValue(
411
+ priority,
412
+ out List<(
413
+ string tag,
414
+ bool editorOnly,
415
+ Func<string, bool> predicate,
416
+ Func<string, object, string> formatter
417
+ )> matchingDecorations
418
+ )
419
+ )
416
420
  {
417
421
  matchingDecorations = new List<(
418
422
  string tag,
@@ -58,7 +58,7 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
58
58
  /// <summary>
59
59
  /// Combines hash codes for a span of values into a deterministic composite hash.
60
60
  /// </summary>
61
- public static int HashCode<T>(ReadOnlySpan<T> values)
61
+ public static int SpanHashCode<T>(ReadOnlySpan<T> values)
62
62
  {
63
63
  if (values.IsEmpty)
64
64
  {