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
@@ -0,0 +1,102 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using ProtoBuf;
6
+ using UnityEngine;
7
+
8
+ /// <summary>
9
+ /// Authoring data for a periodic modifier bundle that executes on a cadence while an effect is active.
10
+ /// </summary>
11
+ /// <remarks>
12
+ /// <para>
13
+ /// The owning <see cref="EffectHandler"/> evaluates each periodic definition after <see cref="initialDelay"/>, applies the configured
14
+ /// <see cref="modifications"/>, and repeats every <see cref="interval"/> seconds until <see cref="maxTicks"/> is reached or the effect ends.
15
+ /// </para>
16
+ /// <para>
17
+ /// Definitions are processed in list order and maintain independent runtime state per <see cref="EffectHandle"/>, enabling designers to mix
18
+ /// damage-over-time, heal-over-time, or custom triggers alongside bespoke <see cref="EffectBehavior"/> implementations.
19
+ /// </para>
20
+ /// </remarks>
21
+ /// <example>
22
+ /// <code language="csharp">
23
+ /// using System.Collections.Generic;
24
+ /// using UnityEngine;
25
+ /// using WallstopStudios.UnityHelpers.Tags;
26
+ ///
27
+ /// public sealed class BurnEffectAuthoring : MonoBehaviour
28
+ /// {
29
+ /// [SerializeField]
30
+ /// private AttributeEffect burnEffect;
31
+ ///
32
+ /// [SerializeField]
33
+ /// private EffectHandler effectHandler;
34
+ ///
35
+ /// public void ApplyBurn(GameObject target)
36
+ /// {
37
+ /// PeriodicEffectDefinition burnTick = new PeriodicEffectDefinition
38
+ /// {
39
+ /// name = "Burn Damage",
40
+ /// initialDelay = 0.5f,
41
+ /// interval = 1.0f,
42
+ /// maxTicks = 5,
43
+ /// modifications = new List<AttributeModification>
44
+ /// {
45
+ /// new AttributeModification("Health", ModificationAction.Addition, -5f),
46
+ /// },
47
+ /// };
48
+ ///
49
+ /// burnEffect.periodicEffects.Add(burnTick);
50
+ ///
51
+ /// if (effectHandler == null)
52
+ /// {
53
+ /// effectHandler = target.GetComponent<EffectHandler>();
54
+ /// }
55
+ ///
56
+ /// EffectHandle? handle = effectHandler.ApplyEffect(burnEffect);
57
+ /// }
58
+ /// }
59
+ /// </code>
60
+ /// <para>
61
+ /// In the example above the handler waits for <c>initialDelay</c>, applies the <see cref="modifications"/> every <c>interval</c> seconds,
62
+ /// and stops after <see cref="maxTicks"/> executions or as soon as the effect is removed.
63
+ /// </para>
64
+ /// </example>
65
+ [Serializable]
66
+ [ProtoContract]
67
+ public sealed class PeriodicEffectDefinition
68
+ {
69
+ /// <summary>
70
+ /// Optional label shown in tooling to help distinguish multiple periodic definitions.
71
+ /// </summary>
72
+ [ProtoMember(1)]
73
+ public string name;
74
+
75
+ /// <summary>
76
+ /// Time (seconds) before the first tick fires after the effect is applied.
77
+ /// </summary>
78
+ [Min(0f)]
79
+ [ProtoMember(2)]
80
+ public float initialDelay;
81
+
82
+ /// <summary>
83
+ /// Interval (seconds) between ticks once the first tick has executed.
84
+ /// </summary>
85
+ [Min(0.01f)]
86
+ [ProtoMember(3)]
87
+ public float interval = 1f;
88
+
89
+ /// <summary>
90
+ /// Maximum number of ticks to execute. Zero or negative means unlimited ticks.
91
+ /// </summary>
92
+ [Min(0)]
93
+ [ProtoMember(4)]
94
+ public int maxTicks;
95
+
96
+ /// <summary>
97
+ /// Attribute modifications applied each time the tick fires.
98
+ /// </summary>
99
+ [ProtoMember(5)]
100
+ public List<AttributeModification> modifications = new();
101
+ }
102
+ }
@@ -0,0 +1,4 @@
1
+ fileFormatVersion: 2
2
+ guid: 8d763ed3f32f4fbf9bcaca8f24ef1a31
3
+ timeCreated: 1759598400
4
+
@@ -0,0 +1,40 @@
1
+ namespace WallstopStudios.UnityHelpers.Tags
2
+ {
3
+ using UnityEngine;
4
+
5
+ /// <summary>
6
+ /// Runtime tracking for a periodic effect definition.
7
+ /// </summary>
8
+ internal sealed class PeriodicEffectRuntimeState
9
+ {
10
+ internal bool IsComplete => definition.maxTicks > 0 && ExecutedTicks >= definition.maxTicks;
11
+
12
+ internal float NextTickTime { get; private set; }
13
+
14
+ internal int ExecutedTicks { get; private set; }
15
+
16
+ internal readonly PeriodicEffectDefinition definition;
17
+ internal readonly float interval;
18
+
19
+ internal PeriodicEffectRuntimeState(PeriodicEffectDefinition definition, float startTime)
20
+ {
21
+ this.definition = definition;
22
+ ExecutedTicks = 0;
23
+ float clampedInterval = Mathf.Max(0.01f, definition.interval);
24
+ interval = clampedInterval;
25
+ NextTickTime = startTime + Mathf.Max(0f, definition.initialDelay);
26
+ }
27
+
28
+ internal bool TryConsumeTick(float currentTime)
29
+ {
30
+ if (currentTime < NextTickTime || IsComplete)
31
+ {
32
+ return false;
33
+ }
34
+
35
+ ++ExecutedTicks;
36
+ NextTickTime = currentTime + interval;
37
+ return true;
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,4 @@
1
+ fileFormatVersion: 2
2
+ guid: 5af38ac9186645fabd9858dfead3c001
3
+ timeCreated: 1759598400
4
+
@@ -71,8 +71,6 @@ public class Enemy : MonoBehaviour
71
71
  2. Add the `RelationalComponentsInstaller` component to the same GameObject
72
72
  3. Enable **"Assign Scene On Initialize"** to automatically wire all scene components after the container builds (recommended)
73
73
 
74
- ![SceneContext Setup](../../Docs/Images/zenject_setup.png)
75
-
76
74
  > 💡 **Beginner tip:** Enable both checkboxes in the inspector:
77
75
  >
78
76
  > - ✅ **Assign Scene On Initialize** → Auto-wires all scene objects (saves you from calling it manually)
@@ -0,0 +1,285 @@
1
+ namespace WallstopStudios.UnityHelpers.Tests.Editor.Attributes
2
+ {
3
+ using System.Reflection;
4
+ using NUnit.Framework;
5
+ using UnityEditor;
6
+ using UnityEngine;
7
+ using WallstopStudios.UnityHelpers.Core.Attributes;
8
+ using WallstopStudios.UnityHelpers.Editor.CustomDrawers;
9
+ using WallstopStudios.UnityHelpers.Tags;
10
+ using WallstopStudios.UnityHelpers.Tests.Editor.Utils;
11
+
12
+ [TestFixture]
13
+ public sealed class WShowIfPropertyDrawerTests : CommonTestBase
14
+ {
15
+ private static readonly FieldInfo AttributeField = typeof(PropertyDrawer).GetField(
16
+ "m_Attribute",
17
+ BindingFlags.Instance | BindingFlags.NonPublic
18
+ );
19
+
20
+ [Test]
21
+ public void BoolConditionHidesFieldWhenFalse()
22
+ {
23
+ TestContainer container = CreateScriptableObject<TestContainer>();
24
+ SerializedObject serializedObject = new(container);
25
+ serializedObject.Update();
26
+
27
+ SerializedProperty dependentProperty = serializedObject.FindProperty(
28
+ nameof(TestContainer.boolDependent)
29
+ );
30
+ Assert.NotNull(dependentProperty);
31
+
32
+ WShowIfPropertyDrawer drawer = CreateDrawer(
33
+ new WShowIfAttribute(nameof(TestContainer.boolCondition))
34
+ );
35
+
36
+ container.boolCondition = false;
37
+ serializedObject.Update();
38
+ float hiddenHeight = drawer.GetPropertyHeight(
39
+ dependentProperty,
40
+ new GUIContent("boolDependent")
41
+ );
42
+ Assert.That(hiddenHeight, Is.Zero);
43
+
44
+ container.boolCondition = true;
45
+ serializedObject.Update();
46
+ float shownHeight = drawer.GetPropertyHeight(
47
+ dependentProperty,
48
+ new GUIContent("boolDependent")
49
+ );
50
+ Assert.That(shownHeight, Is.GreaterThan(0f));
51
+ }
52
+
53
+ [Test]
54
+ public void EnumConditionMatchesExpectedValue()
55
+ {
56
+ TestContainer container = CreateScriptableObject<TestContainer>();
57
+ SerializedObject serializedObject = new(container);
58
+ serializedObject.Update();
59
+
60
+ SerializedProperty dependentProperty = serializedObject.FindProperty(
61
+ nameof(TestContainer.durationDependent)
62
+ );
63
+ Assert.NotNull(dependentProperty);
64
+
65
+ WShowIfPropertyDrawer drawer = CreateDrawer(
66
+ new WShowIfAttribute(
67
+ nameof(TestContainer.durationType),
68
+ expectedValues: new object[] { ModifierDurationType.Duration }
69
+ )
70
+ );
71
+
72
+ container.durationType = ModifierDurationType.Instant;
73
+ serializedObject.Update();
74
+ float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
75
+ Assert.That(hiddenHeight, Is.Zero);
76
+
77
+ container.durationType = ModifierDurationType.Duration;
78
+ serializedObject.Update();
79
+ float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
80
+ Assert.That(shownHeight, Is.GreaterThan(0f));
81
+ }
82
+
83
+ [Test]
84
+ public void EnumConditionHonorsInverseFlag()
85
+ {
86
+ TestContainer container = CreateScriptableObject<TestContainer>();
87
+ SerializedObject serializedObject = new(container);
88
+ serializedObject.Update();
89
+
90
+ SerializedProperty dependentProperty = serializedObject.FindProperty(
91
+ nameof(TestContainer.inverseDependent)
92
+ );
93
+ Assert.NotNull(dependentProperty);
94
+
95
+ WShowIfPropertyDrawer drawer = CreateDrawer(
96
+ new WShowIfAttribute(
97
+ nameof(TestContainer.durationType),
98
+ inverse: true,
99
+ expectedValues: new object[] { ModifierDurationType.Instant }
100
+ )
101
+ );
102
+
103
+ container.durationType = ModifierDurationType.Instant;
104
+ serializedObject.Update();
105
+ float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
106
+ Assert.That(hiddenHeight, Is.Zero);
107
+
108
+ container.durationType = ModifierDurationType.Infinite;
109
+ serializedObject.Update();
110
+ float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
111
+ Assert.That(shownHeight, Is.GreaterThan(0f));
112
+ }
113
+
114
+ [Test]
115
+ public void FloatConditionMatchesExpectedValue()
116
+ {
117
+ TestContainer container = CreateScriptableObject<TestContainer>();
118
+ SerializedObject serializedObject = new(container);
119
+ serializedObject.Update();
120
+
121
+ SerializedProperty dependentProperty = serializedObject.FindProperty(
122
+ nameof(TestContainer.floatDependent)
123
+ );
124
+ Assert.NotNull(dependentProperty);
125
+
126
+ WShowIfPropertyDrawer drawer = CreateDrawer(
127
+ new WShowIfAttribute(
128
+ nameof(TestContainer.floatCondition),
129
+ expectedValues: new object[] { 3.5f }
130
+ )
131
+ );
132
+
133
+ container.floatCondition = 0f;
134
+ serializedObject.Update();
135
+ float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
136
+ Assert.That(hiddenHeight, Is.Zero);
137
+
138
+ container.floatCondition = 3.5f;
139
+ serializedObject.Update();
140
+ float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
141
+ Assert.That(shownHeight, Is.GreaterThan(0f));
142
+ }
143
+
144
+ [Test]
145
+ public void DoubleConditionMatchesEquivalentIntExpectedValue()
146
+ {
147
+ TestContainer container = CreateScriptableObject<TestContainer>();
148
+ SerializedObject serializedObject = new(container);
149
+ serializedObject.Update();
150
+
151
+ SerializedProperty dependentProperty = serializedObject.FindProperty(
152
+ nameof(TestContainer.doubleDependent)
153
+ );
154
+ Assert.NotNull(dependentProperty);
155
+
156
+ WShowIfPropertyDrawer drawer = CreateDrawer(
157
+ new WShowIfAttribute(
158
+ nameof(TestContainer.doubleCondition),
159
+ expectedValues: new object[] { 7 }
160
+ )
161
+ );
162
+
163
+ container.doubleCondition = 2.5d;
164
+ serializedObject.Update();
165
+ float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
166
+ Assert.That(hiddenHeight, Is.Zero);
167
+
168
+ container.doubleCondition = 7d;
169
+ serializedObject.Update();
170
+ float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
171
+ Assert.That(shownHeight, Is.GreaterThan(0f));
172
+ }
173
+
174
+ [Test]
175
+ public void IntConditionMatchesExpectedValue()
176
+ {
177
+ TestContainer container = CreateScriptableObject<TestContainer>();
178
+ SerializedObject serializedObject = new(container);
179
+ serializedObject.Update();
180
+
181
+ SerializedProperty dependentProperty = serializedObject.FindProperty(
182
+ nameof(TestContainer.intDependent)
183
+ );
184
+ Assert.NotNull(dependentProperty);
185
+
186
+ WShowIfPropertyDrawer drawer = CreateDrawer(
187
+ new WShowIfAttribute(
188
+ nameof(TestContainer.intCondition),
189
+ expectedValues: new object[] { 42 }
190
+ )
191
+ );
192
+
193
+ container.intCondition = 7;
194
+ serializedObject.Update();
195
+ float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
196
+ Assert.That(hiddenHeight, Is.Zero);
197
+
198
+ container.intCondition = 42;
199
+ serializedObject.Update();
200
+ float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
201
+ Assert.That(shownHeight, Is.GreaterThan(0f));
202
+ }
203
+
204
+ [Test]
205
+ public void StringConditionMatchesExpectedValues()
206
+ {
207
+ TestContainer container = CreateScriptableObject<TestContainer>();
208
+ SerializedObject serializedObject = new(container);
209
+ serializedObject.Update();
210
+
211
+ SerializedProperty dependentProperty = serializedObject.FindProperty(
212
+ nameof(TestContainer.stringDependent)
213
+ );
214
+ Assert.NotNull(dependentProperty);
215
+
216
+ WShowIfPropertyDrawer drawer = CreateDrawer(
217
+ new WShowIfAttribute(
218
+ nameof(TestContainer.stringCondition),
219
+ expectedValues: new object[] { "alpha", "beta" }
220
+ )
221
+ );
222
+
223
+ container.stringCondition = "gamma";
224
+ serializedObject.Update();
225
+ float hiddenHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
226
+ Assert.That(hiddenHeight, Is.Zero);
227
+
228
+ container.stringCondition = "alpha";
229
+ serializedObject.Update();
230
+ float shownHeight = drawer.GetPropertyHeight(dependentProperty, GUIContent.none);
231
+ Assert.That(shownHeight, Is.GreaterThan(0f));
232
+ }
233
+
234
+ private static WShowIfPropertyDrawer CreateDrawer(WShowIfAttribute attribute)
235
+ {
236
+ WShowIfPropertyDrawer drawer = new();
237
+ Assert.NotNull(AttributeField);
238
+ AttributeField.SetValue(drawer, attribute);
239
+ return drawer;
240
+ }
241
+
242
+ private sealed class TestContainer : ScriptableObject
243
+ {
244
+ public bool boolCondition;
245
+
246
+ [WShowIf(nameof(boolCondition))]
247
+ public int boolDependent;
248
+
249
+ public ModifierDurationType durationType = ModifierDurationType.Instant;
250
+
251
+ [WShowIf(
252
+ nameof(durationType),
253
+ expectedValues: new object[] { ModifierDurationType.Duration }
254
+ )]
255
+ public int durationDependent;
256
+
257
+ [WShowIf(
258
+ nameof(durationType),
259
+ inverse: true,
260
+ expectedValues: new object[] { ModifierDurationType.Instant }
261
+ )]
262
+ public int inverseDependent;
263
+
264
+ public float floatCondition;
265
+
266
+ [WShowIf(nameof(floatCondition), expectedValues: new object[] { 3.5f })]
267
+ public int floatDependent;
268
+
269
+ public double doubleCondition;
270
+
271
+ [WShowIf(nameof(doubleCondition), expectedValues: new object[] { 7 })]
272
+ public int doubleDependent;
273
+
274
+ public int intCondition;
275
+
276
+ [WShowIf(nameof(intCondition), expectedValues: new object[] { 42 })]
277
+ public int intDependent;
278
+
279
+ public string stringCondition;
280
+
281
+ [WShowIf(nameof(stringCondition), expectedValues: new object[] { "alpha", "beta" })]
282
+ public int stringDependent;
283
+ }
284
+ }
285
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 1e5fb5f2a5e7464493dca6801e4eb1a6
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -24,7 +24,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Core.Attributes
24
24
  }
25
25
 
26
26
  [Test]
27
- public void HasRelationalAssignments_RespectsMetadata()
27
+ public void HasRelationalAssignmentsRespectsMetadata()
28
28
  {
29
29
  AttributeMetadataCache cache = CreateScriptableObject<AttributeMetadataCache>();
30
30
 
@@ -62,7 +62,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Core.Attributes
62
62
  }
63
63
 
64
64
  [Test]
65
- public void Assign_IEnumerable_AssignsOnlyRelationalTypes_AndSkipsNulls()
65
+ public void AssignIEnumerableAssignsOnlyRelationalTypesAndSkipsNulls()
66
66
  {
67
67
  AttributeMetadataCache cache = CreateScriptableObject<AttributeMetadataCache>();
68
68
 
@@ -0,0 +1,192 @@
1
+ namespace WallstopStudios.UnityHelpers.Tests.Editor.Tags
2
+ {
3
+ using System;
4
+ using System.Reflection;
5
+ using NUnit.Framework;
6
+ using UnityEngine;
7
+ using UnityHelpers.Tags;
8
+
9
+ [TestFixture]
10
+ public sealed class AttributeMetadataCacheTests
11
+ {
12
+ [Test]
13
+ public void SetMetadataSortsSerializedContent()
14
+ {
15
+ AttributeMetadataCache cache =
16
+ ScriptableObject.CreateInstance<AttributeMetadataCache>();
17
+
18
+ try
19
+ {
20
+ string[] attributeNames = new string[] { "Gamma", "Alpha", "Beta" };
21
+
22
+ string alphaAttributesTypeName =
23
+ typeof(AlphaAttributesComponent).AssemblyQualifiedName ?? string.Empty;
24
+ string bravoAttributesTypeName =
25
+ typeof(BravoAttributesComponent).AssemblyQualifiedName ?? string.Empty;
26
+
27
+ AttributeMetadataCache.TypeFieldMetadata alphaTypeMetadata = new(
28
+ alphaAttributesTypeName,
29
+ new string[] { "gammaField", "betaField" }
30
+ );
31
+
32
+ AttributeMetadataCache.TypeFieldMetadata bravoTypeMetadata = new(
33
+ bravoAttributesTypeName,
34
+ new string[] { "zetaField", "alphaField" }
35
+ );
36
+
37
+ string alphaRelationalTypeName =
38
+ typeof(AlphaRelationalComponent).AssemblyQualifiedName ?? string.Empty;
39
+ string bravoRelationalTypeName =
40
+ typeof(BravoRelationalComponent).AssemblyQualifiedName ?? string.Empty;
41
+
42
+ AttributeMetadataCache.RelationalFieldMetadata[] alphaRelationalFields =
43
+ new AttributeMetadataCache.RelationalFieldMetadata[]
44
+ {
45
+ new(
46
+ "betaRelation",
47
+ AttributeMetadataCache.RelationalAttributeKind.Parent,
48
+ AttributeMetadataCache.FieldKind.HashSet,
49
+ typeof(Light).AssemblyQualifiedName ?? string.Empty,
50
+ true
51
+ ),
52
+ new(
53
+ "alphaRelation",
54
+ AttributeMetadataCache.RelationalAttributeKind.Child,
55
+ AttributeMetadataCache.FieldKind.Single,
56
+ typeof(Transform).AssemblyQualifiedName ?? string.Empty,
57
+ false
58
+ ),
59
+ };
60
+
61
+ AttributeMetadataCache.RelationalFieldMetadata[] bravoRelationalFields =
62
+ new AttributeMetadataCache.RelationalFieldMetadata[]
63
+ {
64
+ new(
65
+ "zetaRelation",
66
+ AttributeMetadataCache.RelationalAttributeKind.Sibling,
67
+ AttributeMetadataCache.FieldKind.List,
68
+ typeof(Camera).AssemblyQualifiedName ?? string.Empty,
69
+ true
70
+ ),
71
+ new(
72
+ "alphaRelation",
73
+ AttributeMetadataCache.RelationalAttributeKind.Child,
74
+ AttributeMetadataCache.FieldKind.Single,
75
+ typeof(Transform).AssemblyQualifiedName ?? string.Empty,
76
+ false
77
+ ),
78
+ };
79
+
80
+ AttributeMetadataCache.RelationalTypeMetadata[] relationalMetadata =
81
+ new AttributeMetadataCache.RelationalTypeMetadata[]
82
+ {
83
+ new(
84
+ bravoRelationalTypeName,
85
+ new AttributeMetadataCache.RelationalFieldMetadata[]
86
+ {
87
+ bravoRelationalFields[0],
88
+ null,
89
+ bravoRelationalFields[1],
90
+ }
91
+ ),
92
+ null,
93
+ new(alphaRelationalTypeName, alphaRelationalFields),
94
+ };
95
+
96
+ AttributeMetadataCache.TypeFieldMetadata[] typeMetadata =
97
+ new AttributeMetadataCache.TypeFieldMetadata[]
98
+ {
99
+ bravoTypeMetadata,
100
+ null,
101
+ alphaTypeMetadata,
102
+ };
103
+
104
+ cache.SetMetadata(attributeNames, typeMetadata, relationalMetadata);
105
+
106
+ string[] storedAttributeNames = GetPrivateField<string[]>(
107
+ cache,
108
+ "_allAttributeNames"
109
+ );
110
+ Assert.That(
111
+ storedAttributeNames,
112
+ Is.EqualTo(new string[] { "Alpha", "Beta", "Gamma" })
113
+ );
114
+
115
+ AttributeMetadataCache.TypeFieldMetadata[] storedTypeMetadata =
116
+ GetPrivateField<AttributeMetadataCache.TypeFieldMetadata[]>(
117
+ cache,
118
+ "_typeMetadata"
119
+ );
120
+ Assert.That(storedTypeMetadata.Length, Is.EqualTo(2));
121
+ Assert.That(storedTypeMetadata[0].typeName, Is.EqualTo(alphaAttributesTypeName));
122
+ Assert.That(
123
+ storedTypeMetadata[0].fieldNames,
124
+ Is.EqualTo(new string[] { "betaField", "gammaField" })
125
+ );
126
+ Assert.That(storedTypeMetadata[1].typeName, Is.EqualTo(bravoAttributesTypeName));
127
+ Assert.That(
128
+ storedTypeMetadata[1].fieldNames,
129
+ Is.EqualTo(new string[] { "alphaField", "zetaField" })
130
+ );
131
+
132
+ AttributeMetadataCache.RelationalTypeMetadata[] storedRelationalMetadata =
133
+ GetPrivateField<AttributeMetadataCache.RelationalTypeMetadata[]>(
134
+ cache,
135
+ "_relationalTypeMetadata"
136
+ );
137
+ Assert.That(storedRelationalMetadata.Length, Is.EqualTo(2));
138
+ Assert.That(
139
+ storedRelationalMetadata[0].typeName,
140
+ Is.EqualTo(alphaRelationalTypeName)
141
+ );
142
+ Assert.That(storedRelationalMetadata[0].fields.Length, Is.EqualTo(2));
143
+ Assert.That(
144
+ storedRelationalMetadata[0].fields[0].fieldName,
145
+ Is.EqualTo("alphaRelation")
146
+ );
147
+ Assert.That(
148
+ storedRelationalMetadata[0].fields[1].fieldName,
149
+ Is.EqualTo("betaRelation")
150
+ );
151
+ Assert.That(
152
+ storedRelationalMetadata[1].typeName,
153
+ Is.EqualTo(bravoRelationalTypeName)
154
+ );
155
+ Assert.That(storedRelationalMetadata[1].fields.Length, Is.EqualTo(2));
156
+ Assert.That(
157
+ storedRelationalMetadata[1].fields[0].fieldName,
158
+ Is.EqualTo("alphaRelation")
159
+ );
160
+ Assert.That(
161
+ storedRelationalMetadata[1].fields[1].fieldName,
162
+ Is.EqualTo("zetaRelation")
163
+ );
164
+ }
165
+ finally
166
+ {
167
+ UnityEngine.Object.DestroyImmediate(cache);
168
+ }
169
+ }
170
+
171
+ private static T GetPrivateField<T>(AttributeMetadataCache cache, string fieldName)
172
+ {
173
+ BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
174
+ FieldInfo field = typeof(AttributeMetadataCache).GetField(fieldName, bindingFlags);
175
+ if (field == null)
176
+ {
177
+ throw new InvalidOperationException($"Field '{fieldName}' was not found.");
178
+ }
179
+
180
+ object value = field.GetValue(cache);
181
+ return value is T castValue ? castValue : default;
182
+ }
183
+
184
+ private sealed class AlphaAttributesComponent : AttributesComponent { }
185
+
186
+ private sealed class BravoAttributesComponent : AttributesComponent { }
187
+
188
+ private sealed class AlphaRelationalComponent : MonoBehaviour { }
189
+
190
+ private sealed class BravoRelationalComponent : MonoBehaviour { }
191
+ }
192
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: e6e89a6fc9964f9f92a7bb5cbb4f793d
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant: