com.wallstop-studios.unity-helpers 2.0.4 β†’ 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 (99) hide show
  1. package/Docs/DATA_STRUCTURES.md +7 -7
  2. package/Docs/EFFECTS_SYSTEM.md +836 -8
  3. package/Docs/EFFECTS_SYSTEM_TUTORIAL.md +77 -18
  4. package/Docs/HULLS.md +2 -2
  5. package/Docs/ILIST_SORTING_PERFORMANCE.md +92 -0
  6. package/Docs/ILIST_SORTING_PERFORMANCE.md.meta +7 -0
  7. package/Docs/INDEX.md +10 -1
  8. package/Docs/Images/random_generators.svg +7 -7
  9. package/Docs/RANDOM_PERFORMANCE.md +18 -15
  10. package/Docs/REFLECTION_HELPERS.md +1 -1
  11. package/Docs/RELATIONAL_COMPONENTS.md +51 -6
  12. package/Docs/SERIALIZATION.md +1 -1
  13. package/Docs/SINGLETONS.md +2 -2
  14. package/Docs/SPATIAL_TREES_2D_GUIDE.md +3 -3
  15. package/Docs/SPATIAL_TREES_3D_GUIDE.md +3 -3
  16. package/Docs/SPATIAL_TREE_2D_PERFORMANCE.md +64 -64
  17. package/Docs/SPATIAL_TREE_3D_PERFORMANCE.md +64 -64
  18. package/Docs/SPATIAL_TREE_SEMANTICS.md +7 -7
  19. package/Editor/Core/Helper/AnimationEventHelpers.cs +1 -1
  20. package/Editor/CustomDrawers/WShowIfPropertyDrawer.cs +131 -41
  21. package/Editor/Utils/ScriptableObjectSingletonCreator.cs +175 -18
  22. package/README.md +42 -18
  23. package/Runtime/Core/Extension/IListExtensions.cs +720 -12
  24. package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +2 -3
  25. package/Runtime/Core/Random/AbstractRandom.cs +52 -5
  26. package/Runtime/Core/Random/DotNetRandom.cs +3 -3
  27. package/Runtime/Core/Random/FlurryBurstRandom.cs +285 -0
  28. package/Runtime/Core/Random/FlurryBurstRandom.cs.meta +3 -0
  29. package/Runtime/Core/Random/IllusionFlow.cs +3 -3
  30. package/Runtime/Core/Random/LinearCongruentialGenerator.cs +3 -3
  31. package/Runtime/Core/Random/PcgRandom.cs +6 -6
  32. package/Runtime/Core/Random/PhotonSpinRandom.cs +387 -0
  33. package/Runtime/Core/Random/PhotonSpinRandom.cs.meta +3 -0
  34. package/Runtime/Core/Random/RomuDuo.cs +3 -3
  35. package/Runtime/Core/Random/SplitMix64.cs +3 -3
  36. package/Runtime/Core/Random/SquirrelRandom.cs +6 -4
  37. package/Runtime/Core/Random/StormDropRandom.cs +271 -0
  38. package/Runtime/Core/Random/StormDropRandom.cs.meta +3 -0
  39. package/Runtime/Core/Random/UnityRandom.cs +3 -3
  40. package/Runtime/Core/Random/WyRandom.cs +6 -4
  41. package/Runtime/Core/Random/XorShiftRandom.cs +3 -3
  42. package/Runtime/Core/Random/XoroShiroRandom.cs +3 -3
  43. package/Runtime/Tags/Attribute.cs +144 -24
  44. package/Runtime/Tags/AttributeEffect.cs +119 -16
  45. package/Runtime/Tags/AttributeMetadataCache.cs +312 -3
  46. package/Runtime/Tags/AttributeModification.cs +59 -29
  47. package/Runtime/Tags/AttributesComponent.cs +20 -0
  48. package/Runtime/Tags/EffectBehavior.cs +171 -0
  49. package/Runtime/Tags/EffectBehavior.cs.meta +4 -0
  50. package/Runtime/Tags/EffectHandle.cs +5 -0
  51. package/Runtime/Tags/EffectHandler.cs +385 -39
  52. package/Runtime/Tags/EffectStackKey.cs +79 -0
  53. package/Runtime/Tags/EffectStackKey.cs.meta +4 -0
  54. package/Runtime/Tags/PeriodicEffectDefinition.cs +102 -0
  55. package/Runtime/Tags/PeriodicEffectDefinition.cs.meta +4 -0
  56. package/Runtime/Tags/PeriodicEffectRuntimeState.cs +40 -0
  57. package/Runtime/Tags/PeriodicEffectRuntimeState.cs.meta +4 -0
  58. package/Samples~/DI - Zenject/README.md +0 -2
  59. package/Tests/Editor/Attributes/WShowIfPropertyDrawerTests.cs +285 -0
  60. package/Tests/Editor/Attributes/WShowIfPropertyDrawerTests.cs.meta +11 -0
  61. package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +2 -2
  62. package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs +192 -0
  63. package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs.meta +11 -0
  64. package/{node_modules.meta β†’ Tests/Editor/Tags.meta} +1 -1
  65. package/Tests/Editor/Utils/ScriptableObjectSingletonTests.cs +41 -0
  66. package/Tests/Runtime/Extensions/IListExtensionTests.cs +187 -1
  67. package/Tests/Runtime/Helper/ObjectsTests.cs +3 -3
  68. package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs +2 -2
  69. package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs +346 -0
  70. package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs.meta +11 -0
  71. package/Tests/Runtime/Performance/RandomPerformanceTests.cs +3 -0
  72. package/Tests/Runtime/Random/FlurryBurstRandomTests.cs +12 -0
  73. package/Tests/Runtime/Random/FlurryBurstRandomTests.cs.meta +3 -0
  74. package/Tests/Runtime/Random/PhotonSpinRandomTests.cs +12 -0
  75. package/Tests/Runtime/Random/PhotonSpinRandomTests.cs.meta +3 -0
  76. package/Tests/Runtime/Random/RandomProtoSerializationTests.cs +14 -0
  77. package/Tests/Runtime/Random/RandomTestBase.cs +39 -4
  78. package/Tests/Runtime/Random/StormDropRandomTests.cs +12 -0
  79. package/Tests/Runtime/Random/StormDropRandomTests.cs.meta +3 -0
  80. package/Tests/Runtime/Serialization/JsonSerializationTest.cs +4 -3
  81. package/Tests/Runtime/Serialization/ProtoInterfaceResolutionEdgeTests.cs +2 -2
  82. package/Tests/Runtime/Serialization/ProtoRootRegistrationTests.cs +1 -1
  83. package/Tests/Runtime/Serialization/ProtoSerializeBehaviorTests.cs +1 -1
  84. package/Tests/Runtime/Tags/AttributeEffectTests.cs +135 -0
  85. package/Tests/Runtime/Tags/AttributeEffectTests.cs.meta +3 -0
  86. package/Tests/Runtime/Tags/AttributeModificationTests.cs +137 -0
  87. package/Tests/Runtime/Tags/AttributeTests.cs +192 -0
  88. package/Tests/Runtime/Tags/AttributeTests.cs.meta +3 -0
  89. package/Tests/Runtime/Tags/EffectBehaviorTests.cs +184 -0
  90. package/Tests/Runtime/Tags/EffectBehaviorTests.cs.meta +3 -0
  91. package/Tests/Runtime/Tags/EffectHandlerTests.cs +618 -0
  92. package/Tests/Runtime/Tags/Helpers/RecordingEffectBehavior.cs +89 -0
  93. package/Tests/Runtime/Tags/Helpers/RecordingEffectBehavior.cs.meta +4 -0
  94. package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs +92 -0
  95. package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs.meta +3 -0
  96. package/package.json +1 -1
  97. package/scripts/lint-doc-links.ps1 +156 -11
  98. package/Tests/Runtime/Tags/AttributeDataTests.cs +0 -312
  99. /package/Tests/Runtime/Tags/{AttributeDataTests.cs.meta β†’ AttributeModificationTests.cs.meta} +0 -0
@@ -42,63 +42,153 @@ namespace WallstopStudios.UnityHelpers.Editor.CustomDrawers
42
42
  SerializedProperty conditionProperty = property.serializedObject.FindProperty(
43
43
  showIf.conditionField
44
44
  );
45
- if (conditionProperty is not { propertyType: SerializedPropertyType.Boolean })
45
+ if (conditionProperty != null)
46
46
  {
47
- if (conditionProperty != null)
47
+ if (TryEvaluateCondition(conditionProperty, showIf, out bool serializedResult))
48
48
  {
49
- return true;
49
+ return serializedResult;
50
50
  }
51
+ return true;
52
+ }
51
53
 
52
- // This might not be a unity object, so fall back to reflection
53
- object enclosingObject = property.GetEnclosingObject(out _);
54
- if (enclosingObject == null)
55
- {
56
- return true;
57
- }
54
+ object enclosingObject = property.GetEnclosingObject(out _);
55
+ if (enclosingObject == null)
56
+ {
57
+ return true;
58
+ }
58
59
 
59
- Type type = enclosingObject.GetType();
60
- Dictionary<string, Func<object, object>> cachedFields = CachedFields.GetOrAdd(type);
61
- if (
62
- !cachedFields.TryGetValue(
63
- showIf.conditionField,
64
- out Func<object, object> accessor
65
- )
66
- )
60
+ Type type = enclosingObject.GetType();
61
+ Dictionary<string, Func<object, object>> cachedFields = CachedFields.GetOrAdd(type);
62
+ if (!cachedFields.TryGetValue(showIf.conditionField, out Func<object, object> accessor))
63
+ {
64
+ FieldInfo field = type.GetField(
65
+ showIf.conditionField,
66
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
67
+ );
68
+ if (field == null)
67
69
  {
68
- FieldInfo field = type.GetField(
69
- showIf.conditionField,
70
- BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
70
+ Debug.LogError(
71
+ $"Failed to find conditional field {showIf.conditionField} on {type.Name}!"
71
72
  );
72
- if (field == null)
73
- {
74
- Debug.LogError(
75
- $"Failed to find conditional field {showIf.conditionField} on {type.Name}!"
76
- );
77
- accessor = _ => null;
78
- }
79
- else
80
- {
81
- accessor = ReflectionHelpers.GetFieldGetter(field);
82
- }
83
- cachedFields[showIf.conditionField] = accessor;
73
+ accessor = _ => null;
84
74
  }
85
- object fieldValue = accessor(enclosingObject);
86
- if (fieldValue is bool maybeCondition)
75
+ else
87
76
  {
88
- return showIf.inverse ? !maybeCondition : maybeCondition;
77
+ accessor = ReflectionHelpers.GetFieldGetter(field);
89
78
  }
79
+ cachedFields[showIf.conditionField] = accessor;
80
+ }
81
+ object fieldValue = accessor(enclosingObject);
82
+ return !TryEvaluateCondition(fieldValue, showIf, out bool reflectedResult)
83
+ ? true
84
+ : reflectedResult;
85
+ }
90
86
 
91
- int index = Array.IndexOf(showIf.expectedValues, fieldValue);
92
- if (showIf.inverse)
87
+ private static bool TryEvaluateCondition(
88
+ SerializedProperty conditionProperty,
89
+ WShowIfAttribute showIf,
90
+ out bool shouldShow
91
+ )
92
+ {
93
+ if (conditionProperty == null)
94
+ {
95
+ shouldShow = true;
96
+ return false;
97
+ }
98
+
99
+ if (conditionProperty.propertyType == SerializedPropertyType.Boolean)
100
+ {
101
+ bool condition = conditionProperty.boolValue;
102
+ shouldShow = showIf.inverse ? !condition : condition;
103
+ return true;
104
+ }
105
+
106
+ object conditionValue = conditionProperty.GetTargetObjectWithField(out _);
107
+ return TryEvaluateCondition(conditionValue, showIf, out shouldShow);
108
+ }
109
+
110
+ private static bool TryEvaluateCondition(
111
+ object conditionValue,
112
+ WShowIfAttribute showIf,
113
+ out bool shouldShow
114
+ )
115
+ {
116
+ if (conditionValue is bool boolean)
117
+ {
118
+ shouldShow = showIf.inverse ? !boolean : boolean;
119
+ return true;
120
+ }
121
+
122
+ object[] expectedValues = showIf.expectedValues;
123
+ if (expectedValues == null || expectedValues.Length == 0)
124
+ {
125
+ shouldShow = true;
126
+ return false;
127
+ }
128
+
129
+ bool match = false;
130
+ for (int i = 0; i < expectedValues.Length; ++i)
131
+ {
132
+ if (ValuesEqual(conditionValue, expectedValues[i]))
93
133
  {
94
- return index < 0;
134
+ match = true;
135
+ break;
95
136
  }
137
+ }
138
+
139
+ shouldShow = showIf.inverse ? !match : match;
140
+ return true;
141
+ }
142
+
143
+ private static bool ValuesEqual(object actual, object expected)
144
+ {
145
+ if (ReferenceEquals(actual, expected))
146
+ {
147
+ return true;
148
+ }
149
+
150
+ if (actual == null || expected == null)
151
+ {
152
+ return false;
153
+ }
154
+
155
+ if (actual.Equals(expected))
156
+ {
157
+ return true;
158
+ }
159
+
160
+ Type actualType = actual.GetType();
161
+ Type expectedType = expected.GetType();
162
+
163
+ try
164
+ {
165
+ if (actualType.IsEnum || expectedType.IsEnum)
166
+ {
167
+ long actualValue = Convert.ToInt64(actual);
168
+ long expectedValue = Convert.ToInt64(expected);
169
+ return actualValue == expectedValue;
170
+ }
171
+ }
172
+ catch
173
+ {
174
+ return false;
175
+ }
96
176
 
97
- return 0 <= index;
177
+ if (actual is IConvertible && expected is IConvertible)
178
+ {
179
+ try
180
+ {
181
+ double actualValue = Convert.ToDouble(actual);
182
+ double expectedValue = Convert.ToDouble(expected);
183
+ return Math.Abs(actualValue - expectedValue) < double.Epsilon;
184
+ }
185
+ catch
186
+ {
187
+ return false;
188
+ }
98
189
  }
99
190
 
100
- bool condition = conditionProperty.boolValue;
101
- return showIf.inverse ? !condition : condition;
191
+ return false;
102
192
  }
103
193
  }
104
194
  #endif
@@ -18,6 +18,7 @@ namespace WallstopStudios.UnityHelpers.Editor.Utils
18
18
 
19
19
  // Prevents re-entrant execution during domain reloads/asset refreshes
20
20
  private static bool _isEnsuring;
21
+ private static int _assetEditingScopeDepth;
21
22
 
22
23
  // Controls whether informational logs are emitted. Warnings still always log.
23
24
  internal static bool VerboseLogging { get; set; }
@@ -41,6 +42,7 @@ namespace WallstopStudios.UnityHelpers.Editor.Utils
41
42
 
42
43
  _isEnsuring = true;
43
44
  AssetDatabase.StartAssetEditing();
45
+ _assetEditingScopeDepth++;
44
46
  bool anyChanges = false;
45
47
  try
46
48
  {
@@ -94,10 +96,24 @@ namespace WallstopStudios.UnityHelpers.Editor.Utils
94
96
  }
95
97
 
96
98
  string resolvedResourcesRoot = EnsureAndResolveFolderPath(ResourcesRoot);
99
+ if (string.IsNullOrWhiteSpace(resolvedResourcesRoot))
100
+ {
101
+ Debug.LogError(
102
+ "ScriptableObjectSingletonCreator: Unable to resolve required Resources root folder. Aborting singleton auto-creation."
103
+ );
104
+ break;
105
+ }
97
106
 
98
107
  string resourcesSubFolder = GetResourcesSubFolder(derivedType);
99
108
  string targetFolderRequested = CombinePaths(ResourcesRoot, resourcesSubFolder);
100
109
  string targetFolder = EnsureAndResolveFolderPath(targetFolderRequested);
110
+ if (string.IsNullOrWhiteSpace(targetFolder))
111
+ {
112
+ Debug.LogError(
113
+ $"ScriptableObjectSingletonCreator: Unable to ensure folder '{targetFolderRequested}' for singleton {derivedType.FullName}. Skipping asset creation."
114
+ );
115
+ continue;
116
+ }
101
117
 
102
118
  string targetAssetPath = CombinePaths(
103
119
  targetFolder,
@@ -156,6 +172,10 @@ namespace WallstopStudios.UnityHelpers.Editor.Utils
156
172
  finally
157
173
  {
158
174
  AssetDatabase.StopAssetEditing();
175
+ if (_assetEditingScopeDepth > 0)
176
+ {
177
+ _assetEditingScopeDepth--;
178
+ }
159
179
  _isEnsuring = false;
160
180
 
161
181
  if (anyChanges)
@@ -448,31 +468,119 @@ namespace WallstopStudios.UnityHelpers.Editor.Utils
448
468
  {
449
469
  string desiredName = parts[i];
450
470
 
451
- // Find an existing subfolder that matches the desired segment, ignoring case
452
- string[] subFolders = AssetDatabase.GetSubFolders(current);
453
- string matchedExisting = null;
454
- if (subFolders != null)
471
+ string matchedExisting = FindMatchingSubfolder(current, desiredName);
472
+
473
+ if (string.IsNullOrEmpty(matchedExisting))
455
474
  {
456
- foreach (string sub in subFolders)
475
+ string intendedPath = current + "/" + desiredName;
476
+ string createdGuid = AssetDatabase.CreateFolder(current, desiredName);
477
+ string createdPath = string.Empty;
478
+ if (!string.IsNullOrEmpty(createdGuid))
479
+ {
480
+ createdPath = NormalizePath(AssetDatabase.GUIDToAssetPath(createdGuid));
481
+ }
482
+
483
+ string actualPath = FindMatchingSubfolder(current, desiredName);
484
+ if (string.IsNullOrEmpty(actualPath))
485
+ {
486
+ actualPath = createdPath;
487
+ }
488
+
489
+ bool intendedValid = AssetDatabase.IsValidFolder(intendedPath);
490
+ bool actualValid =
491
+ !string.IsNullOrEmpty(actualPath)
492
+ && AssetDatabase.IsValidFolder(actualPath);
493
+
494
+ if (!intendedValid && !actualValid)
495
+ {
496
+ bool directoryExists =
497
+ Directory.Exists(intendedPath)
498
+ || (!string.IsNullOrEmpty(actualPath) && Directory.Exists(actualPath));
499
+ if (directoryExists || !string.IsNullOrEmpty(createdGuid))
500
+ {
501
+ ForceAssetDatabaseSync();
502
+ }
503
+
504
+ intendedValid = AssetDatabase.IsValidFolder(intendedPath);
505
+ if (!intendedValid)
506
+ {
507
+ actualPath = FindMatchingSubfolder(current, desiredName);
508
+ if (
509
+ string.IsNullOrEmpty(actualPath)
510
+ && !string.IsNullOrEmpty(createdGuid)
511
+ )
512
+ {
513
+ actualPath = NormalizePath(
514
+ AssetDatabase.GUIDToAssetPath(createdGuid)
515
+ );
516
+ }
517
+
518
+ actualValid =
519
+ !string.IsNullOrEmpty(actualPath)
520
+ && AssetDatabase.IsValidFolder(actualPath);
521
+ }
522
+ else
523
+ {
524
+ actualPath = intendedPath;
525
+ actualValid = true;
526
+ }
527
+ }
528
+
529
+ if (intendedValid)
457
530
  {
458
- // sub is like "Assets/Resources" β€” compare only the terminal name
459
- int lastSlash = sub.LastIndexOf('/', sub.Length - 1);
460
- string terminal = lastSlash >= 0 ? sub.Substring(lastSlash + 1) : sub;
461
- if (
462
- string.Equals(terminal, desiredName, StringComparison.OrdinalIgnoreCase)
531
+ current = ResolveExistingFolderPath(intendedPath);
532
+ LogVerbose(
533
+ $"ScriptableObjectSingletonCreator: Created folder '{current}'."
534
+ );
535
+ continue;
536
+ }
537
+
538
+ if (
539
+ actualValid
540
+ && string.Equals(
541
+ actualPath,
542
+ intendedPath,
543
+ StringComparison.OrdinalIgnoreCase
463
544
  )
545
+ )
546
+ {
547
+ string renameError = AssetDatabase.MoveAsset(actualPath, intendedPath);
548
+ if (string.IsNullOrEmpty(renameError))
549
+ {
550
+ LogVerbose(
551
+ $"ScriptableObjectSingletonCreator: Renamed folder '{actualPath}' to '{intendedPath}' to correct casing."
552
+ );
553
+ current = ResolveExistingFolderPath(intendedPath);
554
+ continue;
555
+ }
556
+
557
+ LogVerbose(
558
+ $"ScriptableObjectSingletonCreator: Reusing newly created folder '{actualPath}' when casing correction to '{intendedPath}' failed: {renameError}."
559
+ );
560
+ current = ResolveExistingFolderPath(actualPath);
561
+ continue;
562
+ }
563
+
564
+ if (actualValid && AssetDatabase.IsValidFolder(actualPath))
565
+ {
566
+ bool deleted = AssetDatabase.DeleteAsset(actualPath);
567
+ if (!deleted)
464
568
  {
465
- matchedExisting = sub;
466
- break;
569
+ Debug.LogWarning(
570
+ $"ScriptableObjectSingletonCreator: Unexpected folder '{actualPath}' was created while attempting to create '{intendedPath}', but it could not be removed."
571
+ );
467
572
  }
573
+
574
+ Debug.LogError(
575
+ $"ScriptableObjectSingletonCreator: Expected to create folder '{intendedPath}', but Unity created '{actualPath}'. Aborting to avoid duplicate folders."
576
+ );
577
+ return string.Empty;
468
578
  }
469
- }
470
579
 
471
- if (string.IsNullOrEmpty(matchedExisting))
472
- {
473
- AssetDatabase.CreateFolder(current, desiredName);
474
- current = current + "/" + desiredName;
475
- LogVerbose($"ScriptableObjectSingletonCreator: Created folder '{current}'.");
580
+ Debug.LogError(
581
+ $"ScriptableObjectSingletonCreator: Failed to create folder '{intendedPath}'."
582
+ );
583
+ return string.Empty;
476
584
  }
477
585
  else
478
586
  {
@@ -561,6 +669,33 @@ namespace WallstopStudios.UnityHelpers.Editor.Utils
561
669
  return current;
562
670
  }
563
671
 
672
+ private static string FindMatchingSubfolder(string parent, string desiredName)
673
+ {
674
+ if (string.IsNullOrWhiteSpace(parent) || string.IsNullOrWhiteSpace(desiredName))
675
+ {
676
+ return null;
677
+ }
678
+
679
+ string[] subFolders = AssetDatabase.GetSubFolders(parent);
680
+ if (subFolders == null || subFolders.Length == 0)
681
+ {
682
+ return null;
683
+ }
684
+
685
+ for (int i = 0; i < subFolders.Length; i++)
686
+ {
687
+ string sub = subFolders[i];
688
+ int lastSlash = sub.LastIndexOf('/', sub.Length - 1);
689
+ string terminal = lastSlash >= 0 ? sub.Substring(lastSlash + 1) : sub;
690
+ if (string.Equals(terminal, desiredName, StringComparison.OrdinalIgnoreCase))
691
+ {
692
+ return sub;
693
+ }
694
+ }
695
+
696
+ return null;
697
+ }
698
+
564
699
  private static string ResolveExistingFolderPath(string intended)
565
700
  {
566
701
  if (string.IsNullOrWhiteSpace(intended))
@@ -621,6 +756,28 @@ namespace WallstopStudios.UnityHelpers.Editor.Utils
621
756
  return current;
622
757
  }
623
758
 
759
+ private static void ForceAssetDatabaseSync()
760
+ {
761
+ if (_assetEditingScopeDepth > 0)
762
+ {
763
+ AssetDatabase.StopAssetEditing();
764
+ try
765
+ {
766
+ AssetDatabase.SaveAssets();
767
+ AssetDatabase.Refresh();
768
+ }
769
+ finally
770
+ {
771
+ AssetDatabase.StartAssetEditing();
772
+ }
773
+ }
774
+ else
775
+ {
776
+ AssetDatabase.SaveAssets();
777
+ AssetDatabase.Refresh();
778
+ }
779
+ }
780
+
624
781
  private static void LogVerbose(string message)
625
782
  {
626
783
  if (VerboseLogging)
package/README.md CHANGED
@@ -77,7 +77,7 @@ void Awake() {
77
77
  void Awake() => this.AssignRelationalComponents();
78
78
  ```
79
79
 
80
- **Bonus:** Works with VContainer/Zenject for automatic DI + relational wiring!
80
+ **Bonus:** Works with VContainer/Zenject/Reflex for automatic DI + relational wiring!
81
81
 
82
82
  [πŸ“– Learn More](Docs/RELATIONAL_COMPONENTS.md) | [🎯 DI – VContainer](Samples~/DI%20-%20VContainer/README.md) | [🎯 DI – Zenject](Samples~/DI%20-%20Zenject/README.md) | [🎯 DI – Reflex](Samples~/DI%20-%20Reflex/README.md)
83
83
 
@@ -454,24 +454,27 @@ Already read the [Top 5 Time-Savers](#-top-5-time-savers)? Jump directly to the
454
454
 
455
455
  ### Random Number Generators
456
456
 
457
- Unity Helpers includes **12 high-quality random number generators**, all implementing a rich `IRandom` interface:
457
+ Unity Helpers includes **15 high-quality random number generators**, all implementing a rich `IRandom` interface:
458
458
 
459
459
  #### Available Generators
460
460
 
461
- | Generator | Speed | Quality | Use Case |
462
- | ------------------------------- | --------- | --------- | ---------------------------------------- |
463
- | **IllusionFlow** ⭐ | Fast | Good | Default choice (via PRNG.Instance) |
464
- | **PcgRandom** | Very Fast | Excellent | Deterministic gameplay; explicit seeding |
465
- | **RomuDuo** | Fastest | Good | Maximum performance needed |
466
- | **LinearCongruentialGenerator** | Fastest | Fair | Simple, fast generation |
467
- | **XorShiftRandom** | Very Fast | Good | General purpose |
468
- | **XoroShiroRandom** | Very Fast | Good | General purpose |
469
- | **SplitMix64** | Very Fast | Good | Initialization, hashing |
470
- | **SquirrelRandom** | Moderate | Good | Hash-based generation |
471
- | **WyRandom** | Moderate | Good | Hashing applications |
472
- | **DotNetRandom** | Moderate | Good | .NET compatibility |
473
- | **SystemRandom** | Slow | Good | Backward compatibility |
474
- | **UnityRandom** | Very Slow | Good | Unity compatibility |
461
+ | Generator | Speed | Quality | Use Case |
462
+ | ------------------------------- | --------- | --------- | ------------------------------------------ |
463
+ | **IllusionFlow** ⭐ | Very Fast | Excellent | Default choice (via PRNG.Instance) |
464
+ | **PcgRandom** ⭐ | Very Fast | Excellent | Deterministic gameplay; explicit seeding |
465
+ | **FlurryBurstRandom** | Very Fast | Excellent | High-quality PCG/Xoshiro alternative |
466
+ | **RomuDuo** | Very Fast | Good | Maximum performance needed |
467
+ | **LinearCongruentialGenerator** | Fastest | Fair | Simple, fast generation |
468
+ | **XorShiftRandom** | Very Fast | Good | General purpose |
469
+ | **XoroShiroRandom** | Very Fast | Good | General purpose |
470
+ | **SplitMix64** | Very Fast | Good | Initialization, hashing |
471
+ | **StormDropRandom** | Fast | Excellent | Large-buffer streams for heavy simulations |
472
+ | **PhotonSpinRandom** | Moderate | Excellent | Bulk generation; long non-overlapping runs |
473
+ | **SquirrelRandom** | Moderate | Good | Hash-based generation |
474
+ | **WyRandom** | Moderate | Good | Hashing applications |
475
+ | **DotNetRandom** | Moderate | Good | .NET compatibility |
476
+ | **SystemRandom** | Slow | Good | Backward compatibility |
477
+ | **UnityRandom** | Very Slow | Good | Unity compatibility |
475
478
 
476
479
  ⭐ **Recommended**: Use `PRNG.Instance` (currently IllusionFlow)
477
480
 
@@ -1018,7 +1021,7 @@ void ProcessLargeDataset(int size)
1018
1021
 
1019
1022
  - When the define is present, optional assemblies under `Runtime/Integrations/*` compile automatically and expose helpers like `RelationalComponentsInstaller` (Zenject/Reflex) and `RegisterRelationalComponents()` (VContainer).
1020
1023
  - If you use UPM, no manual defines are required β€” the package IDs above trigger symbols via `versionDefines` in the asmdefs.
1021
- - For test scenarios without LifetimeScope (VContainer) or SceneContext (Zenject), see [DI Integrations: Testing and Edge Cases](Docs/RELATIONAL_COMPONENTS.md#di-integrations-testing-and-edge-cases) for step‑by‑step patterns.
1024
+ - For test scenarios without LifetimeScope (VContainer), SceneContext (Zenject), or SceneScope (Reflex), see [DI Integrations: Testing and Edge Cases](Docs/RELATIONAL_COMPONENTS.md#di-integrations-testing-and-edge-cases) for step‑by‑step patterns.
1022
1025
 
1023
1026
  **Quick start:**
1024
1027
 
@@ -1046,6 +1049,12 @@ using Zenject;
1046
1049
  using WallstopStudios.UnityHelpers.Integrations.Zenject;
1047
1050
 
1048
1051
  var enemy = Container.InstantiateComponentWithRelations(enemyPrefab, parent);
1052
+
1053
+ // Reflex β€” prefab instantiation with DI + relations
1054
+ using Reflex.Core;
1055
+ using WallstopStudios.UnityHelpers.Integrations.Reflex;
1056
+
1057
+ var enemy = container.InstantiateComponentWithRelations(enemyPrefab, parent);
1049
1058
  ```
1050
1059
 
1051
1060
  See the full guide with scenarios, troubleshooting, and testing patterns: [Relational Components Guide](Docs/RELATIONAL_COMPONENTS.md)
@@ -1064,10 +1073,17 @@ See the full guide with scenarios, troubleshooting, and testing patterns: [Relat
1064
1073
  - `container.InjectGameObjectWithRelations(root, includeInactiveChildren)` β€” inject hierarchy + assign
1065
1074
  - `container.InstantiateGameObjectWithRelations(prefab, parent)` β€” instantiate GO + inject + assign
1066
1075
 
1076
+ - Reflex:
1077
+ - `container.InjectWithRelations(component)` β€” inject + assign a single instance
1078
+ - `container.InstantiateComponentWithRelations(prefab, parent)` β€” instantiate + inject + assign
1079
+ - `container.InjectGameObjectWithRelations(root, includeInactiveChildren)` β€” inject hierarchy + assign
1080
+ - `container.InstantiateGameObjectWithRelations(prefab, parent)` β€” instantiate GO + inject + assign
1081
+
1067
1082
  ### Additive Scene Loads
1068
1083
 
1069
1084
  - VContainer: `RegisterRelationalComponents(..., enableAdditiveSceneListener: true)` registers a listener that hydrates components in newly loaded scenes.
1070
- - Zenject: `RelationalComponentsInstaller` exposes a toggle β€œListen For Additive Scenes” to register the same behavior.
1085
+ - Zenject: `RelationalComponentsInstaller` exposes a toggle "Listen For Additive Scenes" to register the same behavior.
1086
+ - Reflex: `RelationalComponentsInstaller` exposes a toggle "Listen For Additive Scenes" to register the same behavior.
1071
1087
  - Only the newly loaded scene is processed; other loaded scenes are not re‑scanned.
1072
1088
 
1073
1089
  ### Performance Options
@@ -1076,6 +1092,7 @@ See the full guide with scenarios, troubleshooting, and testing patterns: [Relat
1076
1092
  - Single-pass scan (default) reduces `FindObjectsOfType` calls by scanning once and checking type ancestry.
1077
1093
  - VContainer: `new RelationalSceneAssignmentOptions(includeInactive: true, useSinglePassScan: true)`
1078
1094
  - Zenject: `new RelationalSceneAssignmentOptions(includeInactive: true, useSinglePassScan: true)`
1095
+ - Reflex: `new RelationalSceneAssignmentOptions(includeInactive: true, useSinglePassScan: true)`
1079
1096
  - Per-object paths (instantiate/inject helpers, pools) avoid global scans entirely for objects created via DI.
1080
1097
 
1081
1098
  ---
@@ -1110,6 +1127,12 @@ Unity Helpers is built with performance as a top priority:
1110
1127
  - Safe for IL2CPP and AOT platforms
1111
1128
  - [πŸ“Š Reflection Performance](Docs/REFLECTION_HELPERS.md)
1112
1129
 
1130
+ **List Sorting:**
1131
+
1132
+ - Multiple adaptive algorithms (`Ghost`, `Meteor`, `Power`, `Grail`, `Pattern-Defeating QuickSort`, `Insertion`) tuned for `IList<T>`
1133
+ - Deterministic datasets (sorted, nearly sorted, shuffled) across sizes from 100 to 1,000,000
1134
+ - [πŸ“Š IList Sorting Performance Benchmarks](Docs/ILIST_SORTING_PERFORMANCE.md)
1135
+
1113
1136
  ---
1114
1137
 
1115
1138
  ## Documentation Index
@@ -1143,6 +1166,7 @@ Unity Helpers is built with performance as a top priority:
1143
1166
 
1144
1167
  - Random Performance β€” [Random Performance](Docs/RANDOM_PERFORMANCE.md)
1145
1168
  - Reflection Helpers β€” [Reflection Helpers](Docs/REFLECTION_HELPERS.md)
1169
+ - IList Sorting Performance β€” [IList Sorting Performance](Docs/ILIST_SORTING_PERFORMANCE.md)
1146
1170
 
1147
1171
  **Project Info:**
1148
1172