com.wallstop-studios.unity-helpers 2.0.0-rc42 → 2.0.0-rc44

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 (38) hide show
  1. package/Editor/AnimationCopier.cs +1 -1
  2. package/Editor/AnimationCreator.cs +1 -1
  3. package/Editor/{EnsureTextureSizeWizard.cs → FitTextureSizeWizard.cs} +47 -15
  4. package/Editor/PrefabCheckWizard.cs +30 -25
  5. package/Editor/SpriteSettingsApplier.cs +130 -26
  6. package/Editor/TextureSettingsApplier.cs +7 -0
  7. package/Editor/WShowIfPropertyDrawer.cs +63 -0
  8. package/Editor/WShowIfPropertyDrawer.cs.meta +3 -0
  9. package/README.md +16 -0
  10. package/Runtime/Core/Attributes/WShowIfAttribute.cs +16 -0
  11. package/Runtime/Core/Attributes/WShowIfAttribute.cs.meta +3 -0
  12. package/Runtime/Core/Extension/ColorExtensions.cs +378 -87
  13. package/Runtime/Core/Extension/SerializedPropertyExtensions.cs +157 -0
  14. package/Runtime/Core/Extension/SerializedPropertyExtensions.cs.meta +3 -0
  15. package/Runtime/Core/Helper/FormattingHelpers.cs +32 -0
  16. package/Runtime/Core/Helper/FormattingHelpers.cs.meta +3 -0
  17. package/Runtime/Core/Helper/Objects.cs +4 -2
  18. package/Runtime/Core/Helper/Partials/TransformHelpers.cs +0 -1
  19. package/Runtime/Core/Random/AbstractRandom.cs +6 -5
  20. package/Runtime/Core/Random/DotNetRandom.cs +2 -0
  21. package/Runtime/Core/Random/IRandom.cs +1 -0
  22. package/Runtime/Core/Random/LinearCongruentialGenerator.cs +49 -0
  23. package/Runtime/Core/Random/LinearCongruentialGenerator.cs.meta +3 -0
  24. package/Runtime/Core/Random/PcgRandom.cs +1 -1
  25. package/Runtime/Core/Random/RomuDuo.cs +1 -1
  26. package/Runtime/Core/Random/SplitMix64.cs +1 -1
  27. package/Runtime/Core/Random/SystemRandom.cs +1 -1
  28. package/Runtime/Core/Random/WyRandom.cs +1 -1
  29. package/Runtime/Core/Random/XorShiftRandom.cs +13 -8
  30. package/Runtime/Core/Random/XorShiroRandom.cs +2 -0
  31. package/Runtime/Core/Threading/SingleThreadedThreadPool.cs +44 -35
  32. package/Runtime/UI/LayeredImage.cs +2 -2
  33. package/Runtime/Utils/SpriteRendererMetadata.cs +104 -42
  34. package/Tests/Runtime/Performance/RandomPerformanceTests.cs +21 -3
  35. package/Tests/Runtime/Random/LinearCongruentialGeneratorTests.cs +12 -0
  36. package/Tests/Runtime/Random/LinearCongruentialGeneratorTests.cs.meta +3 -0
  37. package/package.json +1 -1
  38. /package/Editor/{EnsureTextureSizeWizard.cs.meta → FitTextureSizeWizard.cs.meta} +0 -0
@@ -0,0 +1,157 @@
1
+ namespace UnityHelpers.Core.Extension
2
+ {
3
+ #if UNITY_EDITOR
4
+ using UnityEditor;
5
+ using System;
6
+ using System.Reflection;
7
+
8
+ public static class SerializedPropertyExtensions
9
+ {
10
+ /// <summary>
11
+ /// Gets the instance object that contains the given SerializedProperty.
12
+ /// </summary>
13
+ /// <param name="property">The SerializedProperty.</param>
14
+ /// <param name="fieldInfo">Outputs the FieldInfo of the referenced field.</param>
15
+ /// <returns>The instance object that owns the field.</returns>
16
+ public static object GetEnclosingObject(
17
+ this SerializedProperty property,
18
+ out FieldInfo fieldInfo
19
+ )
20
+ {
21
+ fieldInfo = null;
22
+ object obj = property.serializedObject.targetObject;
23
+ if (obj == null)
24
+ {
25
+ return null;
26
+ }
27
+ Type type = obj.GetType();
28
+ string[] pathParts = property.propertyPath.Split('.');
29
+
30
+ // Traverse the path but stop at the second-to-last field
31
+ for (int i = 0; i < pathParts.Length - 1; ++i)
32
+ {
33
+ string fieldName = pathParts[i];
34
+
35
+ if (string.Equals(fieldName, "Array", StringComparison.Ordinal))
36
+ {
37
+ // Move to "data[i]", no need to length-check, we're guarded above
38
+
39
+ ++i;
40
+ if (
41
+ !int.TryParse(
42
+ pathParts[i]
43
+ .Replace("data[", string.Empty, StringComparison.Ordinal)
44
+ .Replace("]", string.Empty, StringComparison.Ordinal),
45
+ out int index
46
+ )
47
+ )
48
+ {
49
+ // Unexpected, die
50
+ fieldInfo = null;
51
+ return null;
52
+ }
53
+ obj = GetElementAtIndex(obj, index);
54
+ type = obj?.GetType();
55
+ continue;
56
+ }
57
+
58
+ fieldInfo = type?.GetField(
59
+ fieldName,
60
+ BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance
61
+ );
62
+ if (fieldInfo == null)
63
+ {
64
+ return null;
65
+ }
66
+
67
+ // Move deeper but stop before the last property in the path
68
+ if (i < pathParts.Length - 2)
69
+ {
70
+ obj = fieldInfo.GetValue(obj);
71
+ type = fieldInfo.FieldType;
72
+ }
73
+ }
74
+
75
+ return obj;
76
+ }
77
+
78
+ /// <summary>
79
+ /// Gets the FieldInfo and the instance object that owns the field for a given SerializedProperty.
80
+ /// </summary>
81
+ /// <param name="property">The SerializedProperty to reflect upon.</param>
82
+ /// <param name="fieldInfo">Outputs the FieldInfo of the referenced field.</param>
83
+ /// <returns>The instance object that owns the field.</returns>
84
+ public static object GetTargetObjectWithField(
85
+ this SerializedProperty property,
86
+ out FieldInfo fieldInfo
87
+ )
88
+ {
89
+ fieldInfo = null;
90
+ object obj = property.serializedObject.targetObject;
91
+ if (obj == null)
92
+ {
93
+ return null;
94
+ }
95
+
96
+ Type type = obj.GetType();
97
+ string[] pathParts = property.propertyPath.Split('.');
98
+
99
+ for (int i = 0; i < pathParts.Length; ++i)
100
+ {
101
+ string fieldName = pathParts[i];
102
+
103
+ if (string.Equals(fieldName, "Array", StringComparison.Ordinal))
104
+ {
105
+ // Move to "data[i]"
106
+ ++i;
107
+ if (pathParts.Length <= i)
108
+ {
109
+ break;
110
+ }
111
+
112
+ if (
113
+ !int.TryParse(
114
+ pathParts[i]
115
+ .Replace("data[", string.Empty, StringComparison.Ordinal)
116
+ .Replace("]", string.Empty, StringComparison.Ordinal),
117
+ out int index
118
+ )
119
+ )
120
+ {
121
+ // Unexpected, die
122
+ fieldInfo = null;
123
+ return null;
124
+ }
125
+ obj = GetElementAtIndex(obj, index);
126
+ type = obj?.GetType();
127
+ continue;
128
+ }
129
+
130
+ fieldInfo = type?.GetField(
131
+ fieldName,
132
+ BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance
133
+ );
134
+ if (fieldInfo == null)
135
+ {
136
+ return null;
137
+ }
138
+
139
+ // Move deeper into the object tree
140
+ obj = fieldInfo.GetValue(obj);
141
+ type = fieldInfo.FieldType;
142
+ }
143
+
144
+ return obj;
145
+ }
146
+
147
+ private static object GetElementAtIndex(object obj, int index)
148
+ {
149
+ if (obj is System.Collections.IList list && index >= 0 && index < list.Count)
150
+ {
151
+ return list[index];
152
+ }
153
+ return null;
154
+ }
155
+ }
156
+ #endif
157
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: f58cbae584d8414a89cc3996d16fc5b0
3
+ timeCreated: 1742507900
@@ -0,0 +1,32 @@
1
+ namespace UnityHelpers.Core.Helper
2
+ {
3
+ using System;
4
+
5
+ public static class FormattingHelpers
6
+ {
7
+ public static string FormatBytes(long bytes)
8
+ {
9
+ string[] sizes = { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
10
+ double len = bytes;
11
+ int order = 0;
12
+
13
+ bytes = Math.Max(0, bytes);
14
+
15
+ const int byteInChunk = 1024;
16
+ while (byteInChunk <= len)
17
+ {
18
+ len /= byteInChunk;
19
+ if (order < sizes.Length - 1)
20
+ {
21
+ ++order;
22
+ }
23
+ else
24
+ {
25
+ throw new ArgumentException($"Too many bytes! Cannot parse {bytes}");
26
+ }
27
+ }
28
+
29
+ return $"{len:0.##} {sizes[order]}";
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: a4bf649a0aef41b48c8afb261c4bd1d1
3
+ timeCreated: 1742614257
@@ -18,7 +18,8 @@
18
18
  }
19
19
 
20
20
  [MethodImpl(MethodImplOptions.AggressiveInlining)]
21
- public static bool Null(UnityEngine.Object instance)
21
+ public static bool Null<T>(T instance)
22
+ where T : UnityEngine.Object
22
23
  {
23
24
  return instance == null;
24
25
  }
@@ -29,7 +30,8 @@
29
30
  }
30
31
 
31
32
  [MethodImpl(MethodImplOptions.AggressiveInlining)]
32
- public static bool NotNull(UnityEngine.Object instance)
33
+ public static bool NotNull<T>(T instance)
34
+ where T : UnityEngine.Object
33
35
  {
34
36
  return instance != null;
35
37
  }
@@ -1,7 +1,6 @@
1
1
  namespace UnityHelpers.Core.Helper
2
2
  {
3
3
  using System.Collections.Generic;
4
- using System.Linq;
5
4
  using UnityEngine;
6
5
 
7
6
  public static partial class Helpers
@@ -21,6 +21,8 @@
21
21
 
22
22
  public abstract RandomState InternalState { get; }
23
23
 
24
+ private readonly byte[] _guidBytes = new byte[16];
25
+
24
26
  public virtual int Next()
25
27
  {
26
28
  // Mask out the MSB to ensure the value is within [0, int.MaxValue]
@@ -410,24 +412,23 @@
410
412
  where T : struct, Enum
411
413
  {
412
414
  Type enumType = typeof(T);
413
- T[] enumValues = (T[])EnumTypeCache.GetOrAdd(enumType, Enum.GetValues);
415
+ T[] enumValues = (T[])EnumTypeCache.GetOrAdd(enumType, type => Enum.GetValues(type));
414
416
 
415
417
  return RandomOf(enumValues);
416
418
  }
417
419
 
418
420
  public Guid NextGuid()
419
421
  {
420
- return new Guid(GenerateGuidBytes());
422
+ return new Guid(GenerateGuidBytes(_guidBytes));
421
423
  }
422
424
 
423
425
  public KGuid NextKGuid()
424
426
  {
425
- return new KGuid(GenerateGuidBytes());
427
+ return new KGuid(GenerateGuidBytes(_guidBytes));
426
428
  }
427
429
 
428
- private byte[] GenerateGuidBytes()
430
+ private byte[] GenerateGuidBytes(byte[] guidBytes)
429
431
  {
430
- byte[] guidBytes = new byte[16];
431
432
  NextBytes(guidBytes);
432
433
  SetUuidV4Bits(guidBytes);
433
434
  return guidBytes;
@@ -8,6 +8,8 @@
8
8
  [DataContract]
9
9
  public sealed class DotNetRandom : AbstractRandom
10
10
  {
11
+ public static DotNetRandom Instance => ThreadLocalRandom<DotNetRandom>.Instance;
12
+
11
13
  public override RandomState InternalState =>
12
14
  new RandomState(unchecked((ulong)_seed), state2: _numberGenerated);
13
15
 
@@ -138,6 +138,7 @@
138
138
  double NextGaussian(double mean = 0, double stdDev = 1);
139
139
 
140
140
  Guid NextGuid();
141
+
141
142
  KGuid NextKGuid();
142
143
 
143
144
  T NextOf<T>(IEnumerable<T> enumerable);
@@ -0,0 +1,49 @@
1
+ namespace UnityHelpers.Core.Random
2
+ {
3
+ using System;
4
+ using System.Text.Json.Serialization;
5
+
6
+ public sealed class LinearCongruentialGenerator : AbstractRandom
7
+ {
8
+ public static LinearCongruentialGenerator Instance =>
9
+ ThreadLocalRandom<LinearCongruentialGenerator>.Instance;
10
+
11
+ public override RandomState InternalState => new(_state, 0, _cachedGaussian);
12
+
13
+ private uint _state;
14
+
15
+ public LinearCongruentialGenerator()
16
+ : this(Guid.NewGuid()) { }
17
+
18
+ public LinearCongruentialGenerator(int seed)
19
+ {
20
+ _state = unchecked((uint)seed);
21
+ }
22
+
23
+ public LinearCongruentialGenerator(Guid seed)
24
+ {
25
+ _state = unchecked((uint)seed.GetHashCode());
26
+ }
27
+
28
+ [JsonConstructor]
29
+ public LinearCongruentialGenerator(RandomState internalState)
30
+ {
31
+ _state = unchecked((uint)internalState.State1);
32
+ _cachedGaussian = internalState.Gaussian;
33
+ }
34
+
35
+ public override uint NextUint()
36
+ {
37
+ unchecked
38
+ {
39
+ _state = _state * 1664525U + 1013904223U;
40
+ }
41
+ return _state;
42
+ }
43
+
44
+ public override IRandom Copy()
45
+ {
46
+ return new LinearCongruentialGenerator(InternalState);
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 23eaec3a914247e59d9bd39e199b718e
3
+ timeCreated: 1742702544
@@ -15,7 +15,7 @@
15
15
  IComparable,
16
16
  IComparable<PcgRandom>
17
17
  {
18
- public static IRandom Instance => ThreadLocalRandom<PcgRandom>.Instance;
18
+ public static PcgRandom Instance => ThreadLocalRandom<PcgRandom>.Instance;
19
19
 
20
20
  public override RandomState InternalState => new(_state, _increment, _cachedGaussian);
21
21
 
@@ -15,7 +15,7 @@
15
15
  IComparable,
16
16
  IComparable<RomuDuo>
17
17
  {
18
- public static IRandom Instance => ThreadLocalRandom<RomuDuo>.Instance;
18
+ public static RomuDuo Instance => ThreadLocalRandom<RomuDuo>.Instance;
19
19
  public override RandomState InternalState => new(_x, _y, _cachedGaussian);
20
20
 
21
21
  internal ulong _x;
@@ -12,7 +12,7 @@
12
12
  IComparable,
13
13
  IComparable<SplitMix64>
14
14
  {
15
- public static IRandom Instance => ThreadLocalRandom<SplitMix64>.Instance;
15
+ public static SplitMix64 Instance => ThreadLocalRandom<SplitMix64>.Instance;
16
16
 
17
17
  public override RandomState InternalState => new(_state, 0, _cachedGaussian);
18
18
 
@@ -16,7 +16,7 @@
16
16
  private const int SeedArraySize = 56;
17
17
  private const int LastSeedIndex = SeedArraySize - 1;
18
18
 
19
- public static IRandom Instance => ThreadLocalRandom<SystemRandom>.Instance;
19
+ public static SystemRandom Instance => ThreadLocalRandom<SystemRandom>.Instance;
20
20
 
21
21
  public override RandomState InternalState =>
22
22
  new(
@@ -14,7 +14,7 @@
14
14
  private const ulong Prime0 = 0xa0761d6478bd642f;
15
15
  private const ulong Prime1 = 0xe7037ed1a0b428db;
16
16
 
17
- public static IRandom Instance => ThreadLocalRandom<WyRandom>.Instance;
17
+ public static WyRandom Instance => ThreadLocalRandom<WyRandom>.Instance;
18
18
 
19
19
  public override RandomState InternalState => new RandomState(_state);
20
20
 
@@ -8,18 +8,25 @@
8
8
  [DataContract]
9
9
  public sealed class XorShiftRandom : AbstractRandom
10
10
  {
11
- public static IRandom Instance => ThreadLocalRandom<XorShiftRandom>.Instance;
11
+ public static XorShiftRandom Instance => ThreadLocalRandom<XorShiftRandom>.Instance;
12
12
 
13
13
  public override RandomState InternalState => new(_state, 0, _cachedGaussian);
14
14
 
15
15
  private uint _state;
16
16
 
17
17
  public XorShiftRandom()
18
- : this(Guid.NewGuid().GetHashCode()) { }
18
+ : this(Guid.NewGuid()) { }
19
19
 
20
20
  public XorShiftRandom(int state)
21
21
  {
22
22
  _state = unchecked((uint)state);
23
+ _state = _state != 0 ? _state : 2463534242U;
24
+ }
25
+
26
+ public XorShiftRandom(Guid seed)
27
+ {
28
+ _state = unchecked((uint)seed.GetHashCode());
29
+ _state = _state != 0 ? _state : 2463534242U;
23
30
  }
24
31
 
25
32
  [JsonConstructor]
@@ -31,12 +38,10 @@
31
38
 
32
39
  public override uint NextUint()
33
40
  {
34
- uint state = _state;
35
- state ^= state << 13;
36
- state ^= state >> 17;
37
- state ^= state << 5;
38
- _state = state;
39
- return state;
41
+ _state ^= _state << 13;
42
+ _state ^= _state >> 17;
43
+ _state ^= _state << 5;
44
+ return _state;
40
45
  }
41
46
 
42
47
  public override IRandom Copy()
@@ -12,6 +12,8 @@
12
12
  IComparable,
13
13
  IComparable<XorShiroRandom>
14
14
  {
15
+ public static XorShiroRandom Instance => ThreadLocalRandom<XorShiroRandom>.Instance;
16
+
15
17
  public override RandomState InternalState => new(_s0, _s1, _cachedGaussian);
16
18
 
17
19
  internal ulong _s0;
@@ -7,6 +7,7 @@
7
7
  public sealed class SingleThreadedThreadPool : IDisposable
8
8
  {
9
9
  public ConcurrentQueue<Exception> Exceptions => _exceptions;
10
+ public int Count => _work.Count + Interlocked.CompareExchange(ref _working, 0, 0);
10
11
 
11
12
  private int _active;
12
13
  private int _working;
@@ -16,14 +17,14 @@
16
17
  private bool _disposed;
17
18
  private readonly ConcurrentQueue<Exception> _exceptions;
18
19
 
19
- public SingleThreadedThreadPool()
20
+ public SingleThreadedThreadPool(bool runInBackground = false)
20
21
  {
21
22
  _active = 1;
22
23
  _working = 1;
23
24
  _work = new ConcurrentQueue<Action>();
24
25
  _exceptions = new ConcurrentQueue<Exception>();
25
26
  _waitHandle = new AutoResetEvent(false);
26
- _worker = new Thread(DoWork);
27
+ _worker = new Thread(DoWork) { IsBackground = runInBackground };
27
28
  _worker.Start();
28
29
  }
29
30
 
@@ -32,40 +33,12 @@
32
33
  Dispose(false);
33
34
  }
34
35
 
35
- private void DoWork()
36
- {
37
- while (Interlocked.CompareExchange(ref _active, 0, 0) != 0)
38
- {
39
- _ = Interlocked.Exchange(ref _working, 0);
40
- if (_work.TryDequeue(out Action workItem))
41
- {
42
- _ = Interlocked.Exchange(ref _working, 1);
43
- try
44
- {
45
- workItem();
46
- }
47
- catch (Exception e)
48
- {
49
- _exceptions.Enqueue(e);
50
- }
51
- }
52
- else
53
- {
54
- _ = _waitHandle.WaitOne(TimeSpan.FromSeconds(1));
55
- }
56
-
57
- _ = Interlocked.Exchange(ref _working, 0);
58
- }
59
- }
60
-
61
36
  public void Enqueue(Action work)
62
37
  {
63
38
  _work.Enqueue(work);
64
39
  _ = _waitHandle.Set();
65
40
  }
66
41
 
67
- public int Count => _work.Count + Interlocked.CompareExchange(ref _working, 0, 0);
68
-
69
42
  public void Dispose()
70
43
  {
71
44
  Dispose(true);
@@ -79,17 +52,16 @@
79
52
  return;
80
53
  }
81
54
 
82
- int active;
83
- do
55
+ while (Interlocked.CompareExchange(ref _active, 0, _active) != 0)
84
56
  {
85
- active = Interlocked.CompareExchange(ref _active, 0, _active);
86
- } while (active != 0);
57
+ // Spin
58
+ }
87
59
 
88
60
  if (disposing)
89
61
  {
90
62
  try
91
63
  {
92
- _worker?.Join();
64
+ _worker?.Join(TimeSpan.FromSeconds(30));
93
65
  _waitHandle?.Dispose();
94
66
  }
95
67
  catch
@@ -103,5 +75,42 @@
103
75
 
104
76
  _disposed = true;
105
77
  }
78
+
79
+ private void DoWork()
80
+ {
81
+ while (Interlocked.CompareExchange(ref _active, 0, 0) != 0)
82
+ {
83
+ try
84
+ {
85
+ if (_work.TryDequeue(out Action workItem))
86
+ {
87
+ _ = Interlocked.Exchange(ref _working, 1);
88
+ try
89
+ {
90
+ workItem();
91
+ }
92
+ catch (Exception e)
93
+ {
94
+ _exceptions.Enqueue(e);
95
+ }
96
+ }
97
+ else
98
+ {
99
+ try
100
+ {
101
+ _ = _waitHandle?.WaitOne(TimeSpan.FromSeconds(1));
102
+ }
103
+ catch (ObjectDisposedException)
104
+ {
105
+ return;
106
+ }
107
+ }
108
+ }
109
+ finally
110
+ {
111
+ _ = Interlocked.Exchange(ref _working, 0);
112
+ }
113
+ }
114
+ }
106
115
  }
107
116
  }
@@ -176,7 +176,7 @@
176
176
  int maxY = int.MinValue;
177
177
  foreach (AnimatedSpriteLayer layer in _layers)
178
178
  {
179
- if (!layer.frames.Any())
179
+ if (layer.frames.Length <= 0)
180
180
  {
181
181
  continue;
182
182
  }
@@ -210,7 +210,7 @@
210
210
 
211
211
  foreach (AnimatedSpriteLayer layer in _layers)
212
212
  {
213
- if (!layer.frames.Any())
213
+ if (layer.frames.Length <= 0)
214
214
  {
215
215
  continue;
216
216
  }