com.wallstop-studios.unity-helpers 2.0.0-rc25 → 2.0.0-rc26
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/README.md +88 -0
- package/Runtime/Core/DataStructure/CyclicBuffer.cs +8 -1
- package/Runtime/Core/Extension/IListExtensions.cs +10 -0
- package/Tests/Runtime/DataStructures/CyclicBufferTests.cs +36 -0
- package/Tests/Runtime/Extensions/IListExtensionTests.cs +76 -0
- package/Tests/Runtime/Extensions/IListExtensionTests.cs.meta +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,3 +10,91 @@ Various Unity Helpers. Includes many deterministic, seedable random number gener
|
|
|
10
10
|
- URL: `https://registry.npmjs.org`
|
|
11
11
|
- Scope(s): `com.wallstop-studios.unity-helpers`
|
|
12
12
|
5. Resolve the latest `com.wallstop-studios.unity-helpers`
|
|
13
|
+
|
|
14
|
+
# Package Contents
|
|
15
|
+
- Random Number Generators
|
|
16
|
+
- Spatial Trees
|
|
17
|
+
- Protobuf, Binary, and JSON formatting
|
|
18
|
+
- A resizable CyclicBuffer
|
|
19
|
+
- Simple single-threaded thread pool
|
|
20
|
+
- A LayeredImage for use with Unity's UI Toolkit
|
|
21
|
+
- Geometry Helpers
|
|
22
|
+
- Child/Parent/Sibling Attributes to automatically get components
|
|
23
|
+
- ReadOnly attribute to disable editing of serialized properties in the inspector
|
|
24
|
+
- An extensive collection of helpers
|
|
25
|
+
- Simple math functions including a generic Range
|
|
26
|
+
- Common buffers to reduce allocations
|
|
27
|
+
- A RuntimeSingleton implementation for automatic creation/accessing of singletons
|
|
28
|
+
- String helpers, like converting to PascalCase, like Unity does for variable names in the inspector
|
|
29
|
+
- A randomizable PerlinNoise implementation
|
|
30
|
+
- And more!
|
|
31
|
+
|
|
32
|
+
# Random Number Generators
|
|
33
|
+
This package implements several high quality, seedable, and serializable random number generators. The best one (currently) is the PCG Random. This has been hidden behind the `PRNG.Instance` class, which is thread-safe. As the package evolves, the exact implementation of this may change.
|
|
34
|
+
|
|
35
|
+
All of these implement a custom [IRandom](./Runtime/Core/Random/IRandom.cs) interface with a significantly richer suite of methods than the standard .Net and Unity randoms offer.
|
|
36
|
+
|
|
37
|
+
To use:
|
|
38
|
+
|
|
39
|
+
```csharp
|
|
40
|
+
IRandom random = PRNG.Instance;
|
|
41
|
+
|
|
42
|
+
random.NextFloat(); // Something between 0.0f and 1.0f
|
|
43
|
+
random.NextDouble(); // Something between 0.0d and 1.0d
|
|
44
|
+
random.Next(); // Something between 0 and int.MaxValue
|
|
45
|
+
random.NextUint(); // Something between 0 and uint.MaxValue
|
|
46
|
+
|
|
47
|
+
int [] values = {1, 2, 3};
|
|
48
|
+
random.NextOf(values); // 1, 2, or 3
|
|
49
|
+
random.NextOf(Enumerable.Range(0, 3)); // 1, 2, or 3
|
|
50
|
+
HashSet<int> setValues = new() {1, 2, 3};
|
|
51
|
+
random.NextOf(setValues); // 1, 2, or 3
|
|
52
|
+
|
|
53
|
+
random.NextGuid(); // A valid UUIDv4
|
|
54
|
+
random.NextGaussian(); // A value sampled from a gaussian curve around mean=0, stdDev=1 (configurable via parameters)
|
|
55
|
+
random.NextEnum<T>(); // A randomly selected enum of type T
|
|
56
|
+
|
|
57
|
+
int width = 100;
|
|
58
|
+
int height = 100;
|
|
59
|
+
random.NextNoiseMap(width, height); // A configurable noise map generated using random octave offsets
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Implemented Random Number Generators
|
|
63
|
+
- PCG
|
|
64
|
+
- DotNet (uses the currently implemented .Net Random)
|
|
65
|
+
- RomoDuo
|
|
66
|
+
- SplitMix64
|
|
67
|
+
- Squirrel
|
|
68
|
+
- System (uses a port of the Windows .Net Random)
|
|
69
|
+
- Unity (uses Unity's random under the hood)
|
|
70
|
+
- Wy
|
|
71
|
+
- XorShift
|
|
72
|
+
- XorShiro
|
|
73
|
+
|
|
74
|
+
# Spatial Trees
|
|
75
|
+
There are three implemented 2D immutable spatial trees that can store generic objects, as long as there is some resolution function that can convert them into Vector2 spatial positions.
|
|
76
|
+
|
|
77
|
+
- QuadTree (easiest to use)
|
|
78
|
+
- KDTree
|
|
79
|
+
- RTree
|
|
80
|
+
|
|
81
|
+
Spatial trees, after construction, allow for O(log(n)) spatial query time instead of O(n). They are extremely useful if you need repeated spatial queries, or if you have relatively static spatial data.
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
```csharp
|
|
86
|
+
GameObject [] spatialStorage = { myCoolGameObject };
|
|
87
|
+
QuadTree<GameObject> quadTree = new(spatialStorage, go => go.transform.position);
|
|
88
|
+
|
|
89
|
+
// Might return your object, might not
|
|
90
|
+
GameObject [] inBounds = quadTree.GetElementsInBounds(new Bounds(0, 0, 100, 100));
|
|
91
|
+
|
|
92
|
+
// Uses a "good-enough" nearest-neighbor approximately for cheap neighbors
|
|
93
|
+
List<GameObject> nearestNeighbors = new();
|
|
94
|
+
quadTree.GetApproximateNearestNeighbors(myCoolGameObject.transform.position, 1, nearestNeighbors);
|
|
95
|
+
Assert.AreEqual(1, nearestNeighbors.Count);
|
|
96
|
+
Assert.AreEqual(myCoolGameObject, nearestNeighbors[0]);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Note
|
|
100
|
+
All spatial trees expect the positional data to be *immutable*. It is very important that the positions do not change. If they do, you will need to reconstruct the tree.
|
|
@@ -95,19 +95,26 @@
|
|
|
95
95
|
|
|
96
96
|
public void Resize(int newCapacity)
|
|
97
97
|
{
|
|
98
|
+
if (newCapacity == Capacity)
|
|
99
|
+
{
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
98
103
|
if (newCapacity < 0)
|
|
99
104
|
{
|
|
100
105
|
throw new ArgumentException(nameof(newCapacity));
|
|
101
106
|
}
|
|
102
107
|
|
|
108
|
+
int oldCapacity = Capacity;
|
|
103
109
|
Capacity = newCapacity;
|
|
104
110
|
_buffer.Shift(-_position);
|
|
105
111
|
if (newCapacity < _buffer.Count)
|
|
106
112
|
{
|
|
107
113
|
_buffer.RemoveRange(newCapacity, _buffer.Count - newCapacity);
|
|
108
|
-
_position = 0;
|
|
109
114
|
}
|
|
110
115
|
|
|
116
|
+
_position =
|
|
117
|
+
newCapacity < oldCapacity && newCapacity <= _buffer.Count ? 0 : _buffer.Count;
|
|
111
118
|
Count = Math.Min(newCapacity, Count);
|
|
112
119
|
}
|
|
113
120
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
namespace UnityHelpers.Core.Extension
|
|
2
2
|
{
|
|
3
|
+
using System;
|
|
3
4
|
using System.Collections.Generic;
|
|
4
5
|
using Helper;
|
|
5
6
|
using Random;
|
|
@@ -48,6 +49,15 @@
|
|
|
48
49
|
|
|
49
50
|
public static void Reverse<T>(this IList<T> list, int start, int end)
|
|
50
51
|
{
|
|
52
|
+
if (start < 0 || list.Count <= start)
|
|
53
|
+
{
|
|
54
|
+
throw new ArgumentException(nameof(start));
|
|
55
|
+
}
|
|
56
|
+
if (end < 0 || list.Count <= end)
|
|
57
|
+
{
|
|
58
|
+
throw new ArgumentException(nameof(end));
|
|
59
|
+
}
|
|
60
|
+
|
|
51
61
|
while (start < end)
|
|
52
62
|
{
|
|
53
63
|
(list[start], list[end]) = (list[end], list[start]);
|
|
@@ -244,6 +244,11 @@
|
|
|
244
244
|
Assert.AreEqual(newCapacity, newValues.Length);
|
|
245
245
|
Assert.That(values.Take(newCapacity), Is.EqualTo(newValues));
|
|
246
246
|
|
|
247
|
+
buffer.Add(1);
|
|
248
|
+
buffer.Add(2);
|
|
249
|
+
int[] afterAddition = buffer.ToArray();
|
|
250
|
+
Assert.That(afterAddition, Is.EqualTo(newValues.Skip(2).Concat(new[] { 1, 2 })));
|
|
251
|
+
|
|
247
252
|
newCapacity = 0;
|
|
248
253
|
buffer.Resize(newCapacity);
|
|
249
254
|
newValues = buffer.ToArray();
|
|
@@ -277,6 +282,37 @@
|
|
|
277
282
|
Assert.AreEqual(Math.Min(filled, newCapacity), newValues.Length);
|
|
278
283
|
Assert.That(values.Take(newCapacity), Is.EqualTo(newValues));
|
|
279
284
|
|
|
285
|
+
buffer.Add(1);
|
|
286
|
+
buffer.Add(2);
|
|
287
|
+
int[] afterAddition = buffer.ToArray();
|
|
288
|
+
if (newCapacity <= filled)
|
|
289
|
+
{
|
|
290
|
+
Assert.That(
|
|
291
|
+
afterAddition,
|
|
292
|
+
Is.EqualTo(newValues.Skip(2).Concat(new[] { 1, 2 })),
|
|
293
|
+
$"Resize failed for iteration {i}, fillPercent {fillPercent:0.00}, capacityPercent: {capacityPercent:0.00}. "
|
|
294
|
+
+ $"Capacity: {capacity}, newCapacity: {newCapacity}, filled: {filled}."
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
else if (newCapacity == filled + 1)
|
|
298
|
+
{
|
|
299
|
+
Assert.That(
|
|
300
|
+
afterAddition,
|
|
301
|
+
Is.EqualTo(newValues.Skip(1).Concat(new[] { 1, 2 })),
|
|
302
|
+
$"Resize failed for iteration {i}, fillPercent {fillPercent:0.00}, capacityPercent: {capacityPercent:0.00}. "
|
|
303
|
+
+ $"Capacity: {capacity}, newCapacity: {newCapacity}, filled: {filled}."
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
else
|
|
307
|
+
{
|
|
308
|
+
Assert.That(
|
|
309
|
+
afterAddition,
|
|
310
|
+
Is.EqualTo(newValues.Concat(new[] { 1, 2 })),
|
|
311
|
+
$"Resize failed for iteration {i}, fillPercent {fillPercent:0.00}, capacityPercent: {capacityPercent:0.00}. "
|
|
312
|
+
+ $"Capacity: {capacity}, newCapacity: {newCapacity}, filled: {filled}."
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
280
316
|
newCapacity = 0;
|
|
281
317
|
buffer.Resize(newCapacity);
|
|
282
318
|
newValues = buffer.ToArray();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
namespace UnityHelpers.Tests.Extensions
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Linq;
|
|
5
|
+
using Core.Extension;
|
|
6
|
+
using NUnit.Framework;
|
|
7
|
+
|
|
8
|
+
public sealed class IListExtensionTests
|
|
9
|
+
{
|
|
10
|
+
[Test]
|
|
11
|
+
public void ShiftLeft()
|
|
12
|
+
{
|
|
13
|
+
int[] input = Enumerable.Range(0, 10).ToArray();
|
|
14
|
+
for (int i = 0; i < input.Length * 2; ++i)
|
|
15
|
+
{
|
|
16
|
+
int[] shifted = input.ToArray();
|
|
17
|
+
shifted.Shift(-1 * i);
|
|
18
|
+
Assert.That(
|
|
19
|
+
input.Skip(i % input.Length).Concat(input.Take(i % input.Length)),
|
|
20
|
+
Is.EqualTo(shifted)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
[Test]
|
|
26
|
+
public void ShiftRight()
|
|
27
|
+
{
|
|
28
|
+
int[] input = Enumerable.Range(0, 10).ToArray();
|
|
29
|
+
for (int i = 0; i < input.Length * 2; ++i)
|
|
30
|
+
{
|
|
31
|
+
int[] shifted = input.ToArray();
|
|
32
|
+
shifted.Shift(i);
|
|
33
|
+
Assert.That(
|
|
34
|
+
input
|
|
35
|
+
.Skip((input.Length * 3 - i) % input.Length)
|
|
36
|
+
.Concat(input.Take((input.Length * 3 - i) % input.Length)),
|
|
37
|
+
Is.EqualTo(shifted),
|
|
38
|
+
$"Shift failed for amount {i}."
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
[Test]
|
|
44
|
+
public void Reverse()
|
|
45
|
+
{
|
|
46
|
+
int[] input = Enumerable.Range(0, 10).ToArray();
|
|
47
|
+
for (int i = 0; i < input.Length; ++i)
|
|
48
|
+
{
|
|
49
|
+
int[] shifted = input.ToArray();
|
|
50
|
+
shifted.Reverse(0, i);
|
|
51
|
+
Assert.That(
|
|
52
|
+
input.Take(i + 1).Reverse().Concat(input.Skip(i + 1)),
|
|
53
|
+
Is.EqualTo(shifted),
|
|
54
|
+
$"Reverse failed for reversal from [0, {i}]."
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// TODO
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[Test]
|
|
62
|
+
public void ReverseInvalidArguments()
|
|
63
|
+
{
|
|
64
|
+
int[] input = Enumerable.Range(0, 10).ToArray();
|
|
65
|
+
Assert.Throws<ArgumentException>(() => input.Reverse(-1, 1));
|
|
66
|
+
Assert.Throws<ArgumentException>(() => input.Reverse(input.Length, 1));
|
|
67
|
+
Assert.Throws<ArgumentException>(() => input.Reverse(int.MaxValue, 1));
|
|
68
|
+
Assert.Throws<ArgumentException>(() => input.Reverse(int.MinValue, 1));
|
|
69
|
+
|
|
70
|
+
Assert.Throws<ArgumentException>(() => input.Reverse(1, -1));
|
|
71
|
+
Assert.Throws<ArgumentException>(() => input.Reverse(1, input.Length));
|
|
72
|
+
Assert.Throws<ArgumentException>(() => input.Reverse(1, int.MaxValue));
|
|
73
|
+
Assert.Throws<ArgumentException>(() => input.Reverse(1, int.MinValue));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|