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.
- package/Docs/DATA_STRUCTURES.md +7 -7
- package/Docs/EFFECTS_SYSTEM.md +836 -8
- package/Docs/EFFECTS_SYSTEM_TUTORIAL.md +77 -18
- package/Docs/HULLS.md +2 -2
- package/Docs/ILIST_SORTING_PERFORMANCE.md +92 -0
- package/Docs/ILIST_SORTING_PERFORMANCE.md.meta +7 -0
- package/Docs/INDEX.md +10 -1
- package/Docs/Images/random_generators.svg +7 -7
- package/Docs/RANDOM_PERFORMANCE.md +18 -15
- package/Docs/REFLECTION_HELPERS.md +1 -1
- package/Docs/RELATIONAL_COMPONENTS.md +51 -6
- package/Docs/SERIALIZATION.md +1 -1
- package/Docs/SINGLETONS.md +2 -2
- package/Docs/SPATIAL_TREES_2D_GUIDE.md +3 -3
- package/Docs/SPATIAL_TREES_3D_GUIDE.md +3 -3
- package/Docs/SPATIAL_TREE_2D_PERFORMANCE.md +64 -64
- package/Docs/SPATIAL_TREE_3D_PERFORMANCE.md +64 -64
- package/Docs/SPATIAL_TREE_SEMANTICS.md +7 -7
- package/Editor/Core/Helper/AnimationEventHelpers.cs +1 -1
- package/Editor/CustomDrawers/WShowIfPropertyDrawer.cs +131 -41
- package/Editor/Utils/ScriptableObjectSingletonCreator.cs +175 -18
- package/README.md +42 -18
- package/Runtime/Core/Extension/IListExtensions.cs +720 -12
- package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +2 -3
- package/Runtime/Core/Random/AbstractRandom.cs +52 -5
- package/Runtime/Core/Random/DotNetRandom.cs +3 -3
- package/Runtime/Core/Random/FlurryBurstRandom.cs +285 -0
- package/Runtime/Core/Random/FlurryBurstRandom.cs.meta +3 -0
- package/Runtime/Core/Random/IllusionFlow.cs +3 -3
- package/Runtime/Core/Random/LinearCongruentialGenerator.cs +3 -3
- package/Runtime/Core/Random/PcgRandom.cs +6 -6
- package/Runtime/Core/Random/PhotonSpinRandom.cs +387 -0
- package/Runtime/Core/Random/PhotonSpinRandom.cs.meta +3 -0
- package/Runtime/Core/Random/RomuDuo.cs +3 -3
- package/Runtime/Core/Random/SplitMix64.cs +3 -3
- package/Runtime/Core/Random/SquirrelRandom.cs +6 -4
- package/Runtime/Core/Random/StormDropRandom.cs +271 -0
- package/Runtime/Core/Random/StormDropRandom.cs.meta +3 -0
- package/Runtime/Core/Random/UnityRandom.cs +3 -3
- package/Runtime/Core/Random/WyRandom.cs +6 -4
- package/Runtime/Core/Random/XorShiftRandom.cs +3 -3
- package/Runtime/Core/Random/XoroShiroRandom.cs +3 -3
- package/Runtime/Tags/Attribute.cs +144 -24
- package/Runtime/Tags/AttributeEffect.cs +119 -16
- package/Runtime/Tags/AttributeMetadataCache.cs +312 -3
- package/Runtime/Tags/AttributeModification.cs +59 -29
- package/Runtime/Tags/AttributesComponent.cs +20 -0
- package/Runtime/Tags/EffectBehavior.cs +171 -0
- package/Runtime/Tags/EffectBehavior.cs.meta +4 -0
- package/Runtime/Tags/EffectHandle.cs +5 -0
- package/Runtime/Tags/EffectHandler.cs +385 -39
- package/Runtime/Tags/EffectStackKey.cs +79 -0
- package/Runtime/Tags/EffectStackKey.cs.meta +4 -0
- package/Runtime/Tags/PeriodicEffectDefinition.cs +102 -0
- package/Runtime/Tags/PeriodicEffectDefinition.cs.meta +4 -0
- package/Runtime/Tags/PeriodicEffectRuntimeState.cs +40 -0
- package/Runtime/Tags/PeriodicEffectRuntimeState.cs.meta +4 -0
- package/Samples~/DI - Zenject/README.md +0 -2
- package/Tests/Editor/Attributes/WShowIfPropertyDrawerTests.cs +285 -0
- package/Tests/Editor/Attributes/WShowIfPropertyDrawerTests.cs.meta +11 -0
- package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +2 -2
- package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs +192 -0
- package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs.meta +11 -0
- package/{node_modules.meta → Tests/Editor/Tags.meta} +1 -1
- package/Tests/Editor/Utils/ScriptableObjectSingletonTests.cs +41 -0
- package/Tests/Runtime/Extensions/IListExtensionTests.cs +187 -1
- package/Tests/Runtime/Helper/ObjectsTests.cs +3 -3
- package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs +2 -2
- package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs +346 -0
- package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs.meta +11 -0
- package/Tests/Runtime/Performance/RandomPerformanceTests.cs +3 -0
- package/Tests/Runtime/Random/FlurryBurstRandomTests.cs +12 -0
- package/Tests/Runtime/Random/FlurryBurstRandomTests.cs.meta +3 -0
- package/Tests/Runtime/Random/PhotonSpinRandomTests.cs +12 -0
- package/Tests/Runtime/Random/PhotonSpinRandomTests.cs.meta +3 -0
- package/Tests/Runtime/Random/RandomProtoSerializationTests.cs +14 -0
- package/Tests/Runtime/Random/RandomTestBase.cs +39 -4
- package/Tests/Runtime/Random/StormDropRandomTests.cs +12 -0
- package/Tests/Runtime/Random/StormDropRandomTests.cs.meta +3 -0
- package/Tests/Runtime/Serialization/JsonSerializationTest.cs +4 -3
- package/Tests/Runtime/Serialization/ProtoInterfaceResolutionEdgeTests.cs +2 -2
- package/Tests/Runtime/Serialization/ProtoRootRegistrationTests.cs +1 -1
- package/Tests/Runtime/Serialization/ProtoSerializeBehaviorTests.cs +1 -1
- package/Tests/Runtime/Tags/AttributeEffectTests.cs +135 -0
- package/Tests/Runtime/Tags/AttributeEffectTests.cs.meta +3 -0
- package/Tests/Runtime/Tags/AttributeModificationTests.cs +137 -0
- package/Tests/Runtime/Tags/AttributeTests.cs +192 -0
- package/Tests/Runtime/Tags/AttributeTests.cs.meta +3 -0
- package/Tests/Runtime/Tags/EffectBehaviorTests.cs +184 -0
- package/Tests/Runtime/Tags/EffectBehaviorTests.cs.meta +3 -0
- package/Tests/Runtime/Tags/EffectHandlerTests.cs +618 -0
- package/Tests/Runtime/Tags/Helpers/RecordingEffectBehavior.cs +89 -0
- package/Tests/Runtime/Tags/Helpers/RecordingEffectBehavior.cs.meta +4 -0
- package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs +92 -0
- package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs.meta +3 -0
- package/package.json +1 -1
- package/scripts/lint-doc-links.ps1 +156 -11
- package/Tests/Runtime/Tags/AttributeDataTests.cs +0 -312
- /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,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
|
+
}
|
|
@@ -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
|
-

|
|
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
|
+
}
|
|
@@ -24,7 +24,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Core.Attributes
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
[Test]
|
|
27
|
-
public void
|
|
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
|
|
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
|
+
}
|