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

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 (52) hide show
  1. package/Docs/ILIST_SORTING_PERFORMANCE.md +92 -0
  2. package/Docs/ILIST_SORTING_PERFORMANCE.md.meta +7 -0
  3. package/Docs/INDEX.md +10 -1
  4. package/Docs/Images/random_generators.svg +7 -7
  5. package/Docs/RANDOM_PERFORMANCE.md +17 -14
  6. package/Docs/SPATIAL_TREE_2D_PERFORMANCE.md +64 -64
  7. package/Docs/SPATIAL_TREE_3D_PERFORMANCE.md +64 -64
  8. package/Editor/Core/Helper/AnimationEventHelpers.cs +1 -1
  9. package/README.md +25 -15
  10. package/Runtime/Core/Extension/IListExtensions.cs +720 -12
  11. package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +2 -3
  12. package/Runtime/Core/Random/AbstractRandom.cs +52 -5
  13. package/Runtime/Core/Random/DotNetRandom.cs +3 -3
  14. package/Runtime/Core/Random/FlurryBurstRandom.cs +285 -0
  15. package/Runtime/Core/Random/FlurryBurstRandom.cs.meta +3 -0
  16. package/Runtime/Core/Random/IllusionFlow.cs +3 -3
  17. package/Runtime/Core/Random/LinearCongruentialGenerator.cs +3 -3
  18. package/Runtime/Core/Random/PcgRandom.cs +6 -6
  19. package/Runtime/Core/Random/PhotonSpinRandom.cs +387 -0
  20. package/Runtime/Core/Random/PhotonSpinRandom.cs.meta +3 -0
  21. package/Runtime/Core/Random/RomuDuo.cs +3 -3
  22. package/Runtime/Core/Random/SplitMix64.cs +3 -3
  23. package/Runtime/Core/Random/SquirrelRandom.cs +6 -4
  24. package/Runtime/Core/Random/StormDropRandom.cs +271 -0
  25. package/Runtime/Core/Random/StormDropRandom.cs.meta +3 -0
  26. package/Runtime/Core/Random/UnityRandom.cs +3 -3
  27. package/Runtime/Core/Random/WyRandom.cs +6 -4
  28. package/Runtime/Core/Random/XorShiftRandom.cs +3 -3
  29. package/Runtime/Core/Random/XoroShiroRandom.cs +3 -3
  30. package/Runtime/Tags/AttributeMetadataCache.cs +312 -3
  31. package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs +192 -0
  32. package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs.meta +11 -0
  33. package/Tests/Editor/Tags.meta +8 -0
  34. package/Tests/Runtime/Extensions/IListExtensionTests.cs +187 -1
  35. package/Tests/Runtime/Helper/ObjectsTests.cs +3 -3
  36. package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs +2 -2
  37. package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs +346 -0
  38. package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs.meta +11 -0
  39. package/Tests/Runtime/Performance/RandomPerformanceTests.cs +3 -0
  40. package/Tests/Runtime/Random/FlurryBurstRandomTests.cs +12 -0
  41. package/Tests/Runtime/Random/FlurryBurstRandomTests.cs.meta +3 -0
  42. package/Tests/Runtime/Random/PhotonSpinRandomTests.cs +12 -0
  43. package/Tests/Runtime/Random/PhotonSpinRandomTests.cs.meta +3 -0
  44. package/Tests/Runtime/Random/RandomProtoSerializationTests.cs +14 -0
  45. package/Tests/Runtime/Random/RandomTestBase.cs +39 -4
  46. package/Tests/Runtime/Random/StormDropRandomTests.cs +12 -0
  47. package/Tests/Runtime/Random/StormDropRandomTests.cs.meta +3 -0
  48. package/Tests/Runtime/Serialization/ProtoInterfaceResolutionEdgeTests.cs +2 -2
  49. package/Tests/Runtime/Serialization/ProtoRootRegistrationTests.cs +1 -1
  50. package/Tests/Runtime/Serialization/ProtoSerializeBehaviorTests.cs +1 -1
  51. package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs +2 -2
  52. package/package.json +1 -1
@@ -0,0 +1,271 @@
1
+ namespace WallstopStudios.UnityHelpers.Core.Random
2
+ {
3
+ using System;
4
+ using System.Runtime.Serialization;
5
+ using System.Text.Json.Serialization;
6
+ using ProtoBuf;
7
+ using WallstopStudios.UnityHelpers.Core.Extension;
8
+ using WallstopStudios.UnityHelpers.Core.Helper;
9
+ using WallstopStudios.UnityHelpers.Utils;
10
+
11
+ /// <summary>
12
+ /// StormDrop32: a large-state ARX generator inspired by SHISHUA-style buffer mixing, emphasizing long periods and diffusion.
13
+ /// </summary>
14
+ /// <remarks>
15
+ /// <para>
16
+ /// https://github.com/wileylooper/stormdrop
17
+ /// Ported from <c>wileylooper/stormdrop</c>. The 32-bit variant maintains a 1024-element ring buffer and two 32-bit
18
+ /// accumulators. Each step mixes the current index with the accumulators, rotates, and feeds the buffer to provide
19
+ /// high-quality sequences suitable for heavy simulation workloads.
20
+ /// </para>
21
+ /// <para>Pros:</para>
22
+ /// <list type="bullet">
23
+ /// <item><description>Large period and strong diffusion thanks to the 4 KB buffer.</description></item>
24
+ /// <item><description>Deterministic snapshots via <see cref="RandomState"/>.</description></item>
25
+ /// <item><description>Thread-local access available via <see cref="ThreadLocalRandom{T}.Instance"/>.</description></item>
26
+ /// </list>
27
+ /// <para>Cons:</para>
28
+ /// <list type="bullet">
29
+ /// <item><description>Higher per-instance memory compared to smaller generators.</description></item>
30
+ /// <item><description>Not cryptographically secure.</description></item>
31
+ /// </list>
32
+ /// <para>When to use:</para>
33
+ /// <list type="bullet">
34
+ /// <item><description>Procedural workloads needing long non-overlapping streams or large batches.</description></item>
35
+ /// </list>
36
+ /// <para>When not to use:</para>
37
+ /// <list type="bullet">
38
+ /// <item><description>Memory-constrained contexts; prefer smaller-state generators like FlurryBurst.</description></item>
39
+ /// <item><description>Security/adversarial scenarios.</description></item>
40
+ /// </list>
41
+ /// </remarks>
42
+ /// <example>
43
+ /// <code>
44
+ /// using WallstopStudios.UnityHelpers.Core.Random;
45
+ ///
46
+ /// StormDropRandom rng = new StormDropRandom(seed: 42u);
47
+ /// float noise = rng.NextFloat();
48
+ /// Vector3 point = rng.NextVector3InSphere(10f); // via RandomExtensions
49
+ /// </code>
50
+ /// </example>
51
+ [Serializable]
52
+ [DataContract]
53
+ [ProtoContract(SkipConstructor = true)]
54
+ public sealed class StormDropRandom
55
+ : AbstractRandom,
56
+ IEquatable<StormDropRandom>,
57
+ IComparable,
58
+ IComparable<StormDropRandom>
59
+ {
60
+ private const uint Increment = 1_111_111_111U;
61
+ private const int ElementCount = 1024;
62
+ private const int ElementMask = ElementCount - 1;
63
+ private const int ElementByteSize = ElementCount * sizeof(uint);
64
+ private const int WarmupRounds = 128;
65
+
66
+ public static StormDropRandom Instance => ThreadLocalRandom<StormDropRandom>.Instance;
67
+
68
+ public override RandomState InternalState
69
+ {
70
+ get
71
+ {
72
+ using PooledResource<byte[]> payloadLease = WallstopArrayPool<byte>.Get(
73
+ ElementByteSize,
74
+ out byte[] buffer
75
+ );
76
+ Buffer.BlockCopy(_elements, 0, buffer, 0, ElementByteSize);
77
+
78
+ ulong state1 = ((ulong)_a << 32) | _b;
79
+ return BuildState(
80
+ state1,
81
+ payload: new ArraySegment<byte>(buffer, 0, ElementByteSize)
82
+ );
83
+ }
84
+ }
85
+
86
+ [ProtoMember(6)]
87
+ private uint[] _elements = new uint[ElementCount];
88
+
89
+ [ProtoMember(7)]
90
+ private uint _a;
91
+
92
+ [ProtoMember(8)]
93
+ private uint _b;
94
+
95
+ public StormDropRandom()
96
+ : this(Guid.NewGuid()) { }
97
+
98
+ public StormDropRandom(Guid guid)
99
+ {
100
+ InitializeFromGuid(guid);
101
+ }
102
+
103
+ public StormDropRandom(uint seed)
104
+ {
105
+ uint seedB = seed ^ 0x9E3779B9U;
106
+ if (seedB == 0)
107
+ {
108
+ seedB = 1U;
109
+ }
110
+
111
+ InitializeFromScalars(seed, seedB);
112
+ }
113
+
114
+ public StormDropRandom(uint seedA, uint seedB)
115
+ {
116
+ InitializeFromScalars(seedA, seedB == 0 ? 1U : seedB);
117
+ }
118
+
119
+ [JsonConstructor]
120
+ public StormDropRandom(RandomState internalState)
121
+ {
122
+ _a = (uint)(internalState.State1 >> 32);
123
+ _b = (uint)internalState.State1;
124
+ LoadSerializedElements(internalState._payload);
125
+ RestoreCommonState(internalState);
126
+ }
127
+
128
+ public override uint NextUint()
129
+ {
130
+ unchecked
131
+ {
132
+ uint index = _b & ElementMask;
133
+ uint mix = (_elements[index] ^ _a) + _b;
134
+
135
+ _a = RotateLeft(_a, 17) ^ _b;
136
+ _b += Increment;
137
+
138
+ _elements[_b & ElementMask] += RotateLeft(mix, 13);
139
+
140
+ return mix;
141
+ }
142
+ }
143
+
144
+ public override IRandom Copy()
145
+ {
146
+ return new StormDropRandom(InternalState);
147
+ }
148
+
149
+ public bool Equals(StormDropRandom other)
150
+ {
151
+ if (other == null)
152
+ {
153
+ return false;
154
+ }
155
+
156
+ if (_a != other._a || _b != other._b)
157
+ {
158
+ return false;
159
+ }
160
+
161
+ if (!_elements.AsSpan().SequenceEqual(other._elements))
162
+ {
163
+ return false;
164
+ }
165
+
166
+ return _cachedGaussian == other._cachedGaussian;
167
+ }
168
+
169
+ public override bool Equals(object obj)
170
+ {
171
+ return Equals(obj as StormDropRandom);
172
+ }
173
+
174
+ public int CompareTo(object obj)
175
+ {
176
+ return CompareTo(obj as StormDropRandom);
177
+ }
178
+
179
+ public int CompareTo(StormDropRandom other)
180
+ {
181
+ if (other == null)
182
+ {
183
+ return -1;
184
+ }
185
+
186
+ int comparison = _a.CompareTo(other._a);
187
+ if (comparison != 0)
188
+ {
189
+ return comparison;
190
+ }
191
+
192
+ comparison = _b.CompareTo(other._b);
193
+ if (comparison != 0)
194
+ {
195
+ return comparison;
196
+ }
197
+
198
+ for (int i = 0; i < ElementCount; ++i)
199
+ {
200
+ comparison = _elements[i].CompareTo(other._elements[i]);
201
+ if (comparison != 0)
202
+ {
203
+ return comparison;
204
+ }
205
+ }
206
+
207
+ return 0;
208
+ }
209
+
210
+ public override int GetHashCode()
211
+ {
212
+ return Objects.HashCode(_a, _b);
213
+ }
214
+
215
+ public override string ToString()
216
+ {
217
+ return this.ToJson();
218
+ }
219
+
220
+ private void InitializeFromGuid(Guid guid)
221
+ {
222
+ (ulong seed0, ulong seed1) = RandomUtilities.GuidToUInt64Pair(guid);
223
+ ulong mixer = seed0 ^ (seed1 << 1) ^ 0x9E3779B97F4A7C15UL;
224
+ InitializeFromMixer(ref mixer);
225
+ }
226
+
227
+ private void InitializeFromScalars(uint seedA, uint seedB)
228
+ {
229
+ ulong mixer = ((ulong)seedA << 32) | seedB;
230
+ mixer ^= 0xD2B74407B1CE6E93UL;
231
+ InitializeFromMixer(ref mixer);
232
+ }
233
+
234
+ private void InitializeFromMixer(ref ulong mixer)
235
+ {
236
+ if (_elements == null || _elements.Length != ElementCount)
237
+ {
238
+ _elements = new uint[ElementCount];
239
+ }
240
+
241
+ for (int i = 0; i < ElementCount; ++i)
242
+ {
243
+ _elements[i] = Mix32(ref mixer);
244
+ }
245
+
246
+ _a = Mix32(ref mixer);
247
+ _b = Mix32(ref mixer) | 1U;
248
+
249
+ for (int i = 0; i < WarmupRounds; ++i)
250
+ {
251
+ _ = NextUint();
252
+ }
253
+ }
254
+
255
+ private void LoadSerializedElements(byte[] payload)
256
+ {
257
+ if (_elements == null || _elements.Length != ElementCount)
258
+ {
259
+ _elements = new uint[ElementCount];
260
+ }
261
+
262
+ if (payload != null && payload.Length >= ElementByteSize)
263
+ {
264
+ Buffer.BlockCopy(payload, 0, _elements, 0, ElementByteSize);
265
+ return;
266
+ }
267
+
268
+ Array.Clear(_elements, 0, _elements.Length);
269
+ }
270
+ }
271
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: c0ca8e2683c74d1fb77e8f1f5a54b490
3
+ timeCreated: 1761441026
@@ -5,9 +5,6 @@ namespace WallstopStudios.UnityHelpers.Core.Random
5
5
  using System.Text.Json.Serialization;
6
6
  using ProtoBuf;
7
7
 
8
- [Serializable]
9
- [DataContract]
10
- [ProtoContract]
11
8
  /// <summary>
12
9
  /// An adapter over <c>UnityEngine.Random</c> exposing the <see cref="IRandom"/> interface.
13
10
  /// </summary>
@@ -47,6 +44,9 @@ namespace WallstopStudios.UnityHelpers.Core.Random
47
44
  /// // Note: calling UnityEngine.Random elsewhere will affect this sequence.
48
45
  /// </code>
49
46
  /// </example>
47
+ [Serializable]
48
+ [DataContract]
49
+ [ProtoContract]
50
50
  public sealed class UnityRandom : AbstractRandom
51
51
  {
52
52
  public static readonly UnityRandom Instance = new();
@@ -7,15 +7,14 @@ namespace WallstopStudios.UnityHelpers.Core.Random
7
7
  using System.Text.Json.Serialization;
8
8
  using ProtoBuf;
9
9
 
10
- // https://github.com/cocowalla/wyhash-dotnet/blob/master/src/WyHash/WyRng.cs
11
- [Serializable]
12
- [DataContract]
13
- [ProtoContract(SkipConstructor = true)]
14
10
  /// <summary>
15
11
  /// A wyhash-inspired PRNG variant (WyRandom) leveraging multiply-mix operations for speed and good distribution.
16
12
  /// </summary>
17
13
  /// <remarks>
18
14
  /// <para>
15
+ /// Reference implementation: https://github.com/cocowalla/wyhash-dotnet/blob/master/src/WyHash/WyRng.cs
16
+ /// </para>
17
+ /// <para>
19
18
  /// Designed around 64-bit multiply-and-mix steps, this generator is fast and suitable for general-purpose
20
19
  /// randomness and hashing-like use cases. It is not a cryptographic hash nor a CSPRNG.
21
20
  /// </para>
@@ -45,6 +44,9 @@ namespace WallstopStudios.UnityHelpers.Core.Random
45
44
  /// var color = rng.NextColor(); // via RandomExtensions
46
45
  /// </code>
47
46
  /// </example>
47
+ [Serializable]
48
+ [DataContract]
49
+ [ProtoContract(SkipConstructor = true)]
48
50
  public sealed class WyRandom : AbstractRandom
49
51
  {
50
52
  private const ulong Prime0 = 0xa0761d6478bd642f;
@@ -5,9 +5,6 @@ namespace WallstopStudios.UnityHelpers.Core.Random
5
5
  using System.Text.Json.Serialization;
6
6
  using ProtoBuf;
7
7
 
8
- [Serializable]
9
- [DataContract]
10
- [ProtoContract]
11
8
  /// <summary>
12
9
  /// A classic, extremely fast XorShift PRNG with small state and modest quality.
13
10
  /// </summary>
@@ -53,6 +50,9 @@ namespace WallstopStudios.UnityHelpers.Core.Random
53
50
  /// var fast = XorShiftRandom.Instance; // per-thread instance
54
51
  /// </code>
55
52
  /// </example>
53
+ [Serializable]
54
+ [DataContract]
55
+ [ProtoContract]
56
56
  public sealed class XorShiftRandom : AbstractRandom
57
57
  {
58
58
  public static XorShiftRandom Instance => ThreadLocalRandom<XorShiftRandom>.Instance;
@@ -8,9 +8,6 @@ namespace WallstopStudios.UnityHelpers.Core.Random
8
8
  using Helper;
9
9
  using ProtoBuf;
10
10
 
11
- [Serializable]
12
- [DataContract]
13
- [ProtoContract]
14
11
  /// <summary>
15
12
  /// A fast 128-bit state Xoroshiro-based PRNG with good quality and tiny footprint.
16
13
  /// </summary>
@@ -55,6 +52,9 @@ namespace WallstopStudios.UnityHelpers.Core.Random
55
52
  /// var replay = new XoroShiroRandom(state);
56
53
  /// </code>
57
54
  /// </example>
55
+ [Serializable]
56
+ [DataContract]
57
+ [ProtoContract]
58
58
  public sealed class XoroShiroRandom
59
59
  : AbstractRandom,
60
60
  IEquatable<XoroShiroRandom>,
@@ -484,9 +484,15 @@ namespace WallstopStudios.UnityHelpers.Tags
484
484
  RelationalTypeMetadata[] relationalTypeMetadata
485
485
  )
486
486
  {
487
- _allAttributeNames = allAttributeNames;
488
- _typeMetadata = typeMetadata;
489
- _relationalTypeMetadata = relationalTypeMetadata;
487
+ string[] normalizedAttributeNames = SortAttributeNames(allAttributeNames);
488
+ TypeFieldMetadata[] normalizedTypeMetadata = SortTypeMetadata(typeMetadata);
489
+ RelationalTypeMetadata[] normalizedRelationalMetadata = SortRelationalTypeMetadata(
490
+ relationalTypeMetadata
491
+ );
492
+
493
+ _allAttributeNames = normalizedAttributeNames;
494
+ _typeMetadata = normalizedTypeMetadata;
495
+ _relationalTypeMetadata = normalizedRelationalMetadata;
490
496
  _computedAllAttributeNames = null;
491
497
  _computedAllAttributeNamesIncludesTests = false;
492
498
  _typeFieldsLookup = null;
@@ -495,6 +501,309 @@ namespace WallstopStudios.UnityHelpers.Tags
495
501
  _elementTypeLookup = null;
496
502
  UnityEditor.EditorUtility.SetDirty(this);
497
503
  }
504
+
505
+ private static string[] SortAttributeNames(string[] attributeNames)
506
+ {
507
+ if (attributeNames == null || attributeNames.Length == 0)
508
+ {
509
+ return Array.Empty<string>();
510
+ }
511
+
512
+ string[] result = new string[attributeNames.Length];
513
+ Array.Copy(attributeNames, result, attributeNames.Length);
514
+ Array.Sort(result, StringComparer.Ordinal);
515
+ return result;
516
+ }
517
+
518
+ private static TypeFieldMetadata[] SortTypeMetadata(TypeFieldMetadata[] typeMetadata)
519
+ {
520
+ if (typeMetadata == null || typeMetadata.Length == 0)
521
+ {
522
+ return Array.Empty<TypeFieldMetadata>();
523
+ }
524
+
525
+ int nonNullCount = 0;
526
+ for (int i = 0; i < typeMetadata.Length; i++)
527
+ {
528
+ if (typeMetadata[i] != null)
529
+ {
530
+ nonNullCount++;
531
+ }
532
+ }
533
+
534
+ if (nonNullCount == 0)
535
+ {
536
+ return Array.Empty<TypeFieldMetadata>();
537
+ }
538
+
539
+ TypeFieldMetadata[] result = new TypeFieldMetadata[nonNullCount];
540
+ int resultIndex = 0;
541
+ for (int i = 0; i < typeMetadata.Length; i++)
542
+ {
543
+ TypeFieldMetadata metadata = typeMetadata[i];
544
+ if (metadata == null)
545
+ {
546
+ continue;
547
+ }
548
+
549
+ string typeName = metadata.typeName ?? string.Empty;
550
+ string[] fieldNames = metadata.fieldNames ?? Array.Empty<string>();
551
+ string[] sortedFieldNames =
552
+ fieldNames.Length == 0 ? Array.Empty<string>() : CopyAndSort(fieldNames);
553
+
554
+ result[resultIndex] = new TypeFieldMetadata(typeName, sortedFieldNames);
555
+ resultIndex++;
556
+ }
557
+
558
+ Array.Sort(result, CompareTypeFieldMetadata);
559
+ return result;
560
+ }
561
+
562
+ private static RelationalTypeMetadata[] SortRelationalTypeMetadata(
563
+ RelationalTypeMetadata[] relationalTypeMetadata
564
+ )
565
+ {
566
+ if (relationalTypeMetadata == null || relationalTypeMetadata.Length == 0)
567
+ {
568
+ return Array.Empty<RelationalTypeMetadata>();
569
+ }
570
+
571
+ int nonNullCount = 0;
572
+ for (int i = 0; i < relationalTypeMetadata.Length; i++)
573
+ {
574
+ if (relationalTypeMetadata[i] != null)
575
+ {
576
+ nonNullCount++;
577
+ }
578
+ }
579
+
580
+ if (nonNullCount == 0)
581
+ {
582
+ return Array.Empty<RelationalTypeMetadata>();
583
+ }
584
+
585
+ RelationalTypeMetadata[] result = new RelationalTypeMetadata[nonNullCount];
586
+ int resultIndex = 0;
587
+ for (int i = 0; i < relationalTypeMetadata.Length; i++)
588
+ {
589
+ RelationalTypeMetadata metadata = relationalTypeMetadata[i];
590
+ if (metadata == null)
591
+ {
592
+ continue;
593
+ }
594
+
595
+ string typeName = metadata.typeName ?? string.Empty;
596
+ RelationalFieldMetadata[] sortedFields = SortRelationalFields(metadata.fields);
597
+ result[resultIndex] = new RelationalTypeMetadata(typeName, sortedFields);
598
+ resultIndex++;
599
+ }
600
+
601
+ Array.Sort(result, CompareRelationalTypeMetadata);
602
+ return result;
603
+ }
604
+
605
+ private static RelationalFieldMetadata[] SortRelationalFields(
606
+ RelationalFieldMetadata[] relationalFields
607
+ )
608
+ {
609
+ if (relationalFields == null || relationalFields.Length == 0)
610
+ {
611
+ return Array.Empty<RelationalFieldMetadata>();
612
+ }
613
+
614
+ int nonNullCount = 0;
615
+ for (int i = 0; i < relationalFields.Length; i++)
616
+ {
617
+ if (relationalFields[i] != null)
618
+ {
619
+ nonNullCount++;
620
+ }
621
+ }
622
+
623
+ if (nonNullCount == 0)
624
+ {
625
+ return Array.Empty<RelationalFieldMetadata>();
626
+ }
627
+
628
+ RelationalFieldMetadata[] result = new RelationalFieldMetadata[nonNullCount];
629
+ int resultIndex = 0;
630
+ for (int i = 0; i < relationalFields.Length; i++)
631
+ {
632
+ RelationalFieldMetadata field = relationalFields[i];
633
+ if (field == null)
634
+ {
635
+ continue;
636
+ }
637
+
638
+ result[resultIndex] = new RelationalFieldMetadata(
639
+ field.fieldName,
640
+ field.attributeKind,
641
+ field.fieldKind,
642
+ field.elementTypeName,
643
+ field.isInterface
644
+ );
645
+ resultIndex++;
646
+ }
647
+
648
+ Array.Sort(result, CompareRelationalFieldMetadata);
649
+ return result;
650
+ }
651
+
652
+ private static string[] CopyAndSort(string[] values)
653
+ {
654
+ string[] result = new string[values.Length];
655
+ Array.Copy(values, result, values.Length);
656
+ Array.Sort(result, StringComparer.Ordinal);
657
+ return result;
658
+ }
659
+
660
+ private static int CompareTypeFieldMetadata(TypeFieldMetadata left, TypeFieldMetadata right)
661
+ {
662
+ if (ReferenceEquals(left, right))
663
+ {
664
+ return 0;
665
+ }
666
+
667
+ if (left == null)
668
+ {
669
+ return -1;
670
+ }
671
+
672
+ if (right == null)
673
+ {
674
+ return 1;
675
+ }
676
+
677
+ int typeNameComparison = string.CompareOrdinal(left.typeName, right.typeName);
678
+ if (typeNameComparison != 0)
679
+ {
680
+ return typeNameComparison;
681
+ }
682
+
683
+ string[] leftFields = left.fieldNames ?? Array.Empty<string>();
684
+ string[] rightFields = right.fieldNames ?? Array.Empty<string>();
685
+
686
+ int lengthComparison = leftFields.Length.CompareTo(rightFields.Length);
687
+ if (lengthComparison != 0)
688
+ {
689
+ return lengthComparison;
690
+ }
691
+
692
+ for (int i = 0; i < leftFields.Length; i++)
693
+ {
694
+ int fieldComparison = string.CompareOrdinal(leftFields[i], rightFields[i]);
695
+ if (fieldComparison != 0)
696
+ {
697
+ return fieldComparison;
698
+ }
699
+ }
700
+
701
+ return 0;
702
+ }
703
+
704
+ private static int CompareRelationalTypeMetadata(
705
+ RelationalTypeMetadata left,
706
+ RelationalTypeMetadata right
707
+ )
708
+ {
709
+ if (ReferenceEquals(left, right))
710
+ {
711
+ return 0;
712
+ }
713
+
714
+ if (left == null)
715
+ {
716
+ return -1;
717
+ }
718
+
719
+ if (right == null)
720
+ {
721
+ return 1;
722
+ }
723
+
724
+ int typeNameComparison = string.CompareOrdinal(left.typeName, right.typeName);
725
+ if (typeNameComparison != 0)
726
+ {
727
+ return typeNameComparison;
728
+ }
729
+
730
+ RelationalFieldMetadata[] leftFields =
731
+ left.fields ?? Array.Empty<RelationalFieldMetadata>();
732
+ RelationalFieldMetadata[] rightFields =
733
+ right.fields ?? Array.Empty<RelationalFieldMetadata>();
734
+
735
+ int lengthComparison = leftFields.Length.CompareTo(rightFields.Length);
736
+ if (lengthComparison != 0)
737
+ {
738
+ return lengthComparison;
739
+ }
740
+
741
+ for (int i = 0; i < leftFields.Length; i++)
742
+ {
743
+ int fieldComparison = CompareRelationalFieldMetadata(leftFields[i], rightFields[i]);
744
+ if (fieldComparison != 0)
745
+ {
746
+ return fieldComparison;
747
+ }
748
+ }
749
+
750
+ return 0;
751
+ }
752
+
753
+ private static int CompareRelationalFieldMetadata(
754
+ RelationalFieldMetadata left,
755
+ RelationalFieldMetadata right
756
+ )
757
+ {
758
+ if (ReferenceEquals(left, right))
759
+ {
760
+ return 0;
761
+ }
762
+
763
+ if (left == null)
764
+ {
765
+ return -1;
766
+ }
767
+
768
+ if (right == null)
769
+ {
770
+ return 1;
771
+ }
772
+
773
+ int fieldNameComparison = string.CompareOrdinal(left.fieldName, right.fieldName);
774
+ if (fieldNameComparison != 0)
775
+ {
776
+ return fieldNameComparison;
777
+ }
778
+
779
+ int attributeComparison = left.attributeKind.CompareTo(right.attributeKind);
780
+ if (attributeComparison != 0)
781
+ {
782
+ return attributeComparison;
783
+ }
784
+
785
+ int fieldKindComparison = left.fieldKind.CompareTo(right.fieldKind);
786
+ if (fieldKindComparison != 0)
787
+ {
788
+ return fieldKindComparison;
789
+ }
790
+
791
+ int elementTypeComparison = string.CompareOrdinal(
792
+ left.elementTypeName,
793
+ right.elementTypeName
794
+ );
795
+ if (elementTypeComparison != 0)
796
+ {
797
+ return elementTypeComparison;
798
+ }
799
+
800
+ if (left.isInterface == right.isInterface)
801
+ {
802
+ return 0;
803
+ }
804
+
805
+ return left.isInterface ? -1 : 1;
806
+ }
498
807
  #endif
499
808
  }
500
809
  }