com.wallstop-studios.unity-helpers 2.0.0-rc25 → 2.0.0-rc27

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 CHANGED
@@ -1,7 +1,19 @@
1
1
  # A Grab-Bag
2
2
  Various Unity Helpers. Includes many deterministic, seedable random number generators.
3
3
 
4
- # To Install as Unity Package
4
+ # Compatibility
5
+ | Platform | Compatible |
6
+ | --- | --- |
7
+ | Unity 2021 | Likely, but untested |
8
+ | Unity 2022 | ✓ |
9
+ | Unity 2023 | ✓ |
10
+ | Unity 6 | ✓ |
11
+ | URP | ✓ |
12
+ | HDRP | ✓ |
13
+
14
+ # Installation
15
+
16
+ ## To Install as Unity Package
5
17
  1. Open Unity Package Manager
6
18
  2. (Optional) Enable Pre-release packages to get the latest, cutting-edge builds
7
19
  3. Open the Advanced Package Settings
@@ -10,3 +22,97 @@ Various Unity Helpers. Includes many deterministic, seedable random number gener
10
22
  - URL: `https://registry.npmjs.org`
11
23
  - Scope(s): `com.wallstop-studios.unity-helpers`
12
24
  5. Resolve the latest `com.wallstop-studios.unity-helpers`
25
+
26
+ ## From Source
27
+ Grab a copy of this repo (either `git clone` or [download a zip of the source](https://github.com/wallstop/unity-helpers/archive/refs/heads/main.zip)) and copy the contents to your project's `Assets` folder.
28
+
29
+ ## From Releases
30
+ Check out the latest [Releases](https://github.com/wallstop/unity-helpers/releases) to grab the Unity Package and import to your project.
31
+
32
+ # Package Contents
33
+ - Random Number Generators
34
+ - Spatial Trees
35
+ - Protobuf, Binary, and JSON formatting
36
+ - A resizable CyclicBuffer
37
+ - Simple single-threaded thread pool
38
+ - A LayeredImage for use with Unity's UI Toolkit
39
+ - Geometry Helpers
40
+ - Child/Parent/Sibling Attributes to automatically get components
41
+ - ReadOnly attribute to disable editing of serialized properties in the inspector
42
+ - An extensive collection of helpers
43
+ - Simple math functions including a generic Range
44
+ - Common buffers to reduce allocations
45
+ - A RuntimeSingleton implementation for automatic creation/accessing of singletons
46
+ - String helpers, like converting to PascalCase, like Unity does for variable names in the inspector
47
+ - A randomizable PerlinNoise implementation
48
+ - And more!
49
+
50
+ # Random Number Generators
51
+ 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.
52
+
53
+ 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.
54
+
55
+ To use:
56
+
57
+ ```csharp
58
+ IRandom random = PRNG.Instance;
59
+
60
+ random.NextFloat(); // Something between 0.0f and 1.0f
61
+ random.NextDouble(); // Something between 0.0d and 1.0d
62
+ random.Next(); // Something between 0 and int.MaxValue
63
+ random.NextUint(); // Something between 0 and uint.MaxValue
64
+
65
+ int [] values = {1, 2, 3};
66
+ random.NextOf(values); // 1, 2, or 3
67
+ random.NextOf(Enumerable.Range(0, 3)); // 1, 2, or 3
68
+ HashSet<int> setValues = new() {1, 2, 3};
69
+ random.NextOf(setValues); // 1, 2, or 3
70
+
71
+ random.NextGuid(); // A valid UUIDv4
72
+ random.NextGaussian(); // A value sampled from a gaussian curve around mean=0, stdDev=1 (configurable via parameters)
73
+ random.NextEnum<T>(); // A randomly selected enum of type T
74
+
75
+ int width = 100;
76
+ int height = 100;
77
+ random.NextNoiseMap(width, height); // A configurable noise map generated using random octave offsets
78
+ ```
79
+
80
+ ## Implemented Random Number Generators
81
+ - PCG
82
+ - DotNet (uses the currently implemented .Net Random)
83
+ - RomoDuo
84
+ - SplitMix64
85
+ - Squirrel
86
+ - System (uses a port of the Windows .Net Random)
87
+ - Unity (uses Unity's random under the hood)
88
+ - Wy
89
+ - XorShift
90
+ - XorShiro
91
+
92
+ # Spatial Trees
93
+ 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.
94
+
95
+ - QuadTree (easiest to use)
96
+ - KDTree
97
+ - RTree
98
+
99
+ 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.
100
+
101
+ ## Usage
102
+
103
+ ```csharp
104
+ GameObject [] spatialStorage = { myCoolGameObject };
105
+ QuadTree<GameObject> quadTree = new(spatialStorage, go => go.transform.position);
106
+
107
+ // Might return your object, might not
108
+ GameObject [] inBounds = quadTree.GetElementsInBounds(new Bounds(0, 0, 100, 100));
109
+
110
+ // Uses a "good-enough" nearest-neighbor approximately for cheap neighbors
111
+ List<GameObject> nearestNeighbors = new();
112
+ quadTree.GetApproximateNearestNeighbors(myCoolGameObject.transform.position, 1, nearestNeighbors);
113
+ Assert.AreEqual(1, nearestNeighbors.Count);
114
+ Assert.AreEqual(myCoolGameObject, nearestNeighbors[0]);
115
+ ```
116
+
117
+ ## Note
118
+ 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.
@@ -20,12 +20,15 @@
20
20
 
21
21
  public static class ChildComponentExtensions
22
22
  {
23
- private static readonly Dictionary<Type, FieldInfo[]> FieldsByType = new();
23
+ private static readonly Dictionary<
24
+ Type,
25
+ (FieldInfo field, Action<object, object> setter)[]
26
+ > FieldsByType = new();
24
27
 
25
28
  public static void AssignChildComponents(this Component component)
26
29
  {
27
30
  Type componentType = component.GetType();
28
- FieldInfo[] fields = FieldsByType.GetOrAdd(
31
+ (FieldInfo field, Action<object, object> setter)[] fields = FieldsByType.GetOrAdd(
29
32
  componentType,
30
33
  type =>
31
34
  {
@@ -34,11 +37,12 @@
34
37
  );
35
38
  return fields
36
39
  .Where(field => Attribute.IsDefined(field, typeof(ChildComponentAttribute)))
40
+ .Select(field => (field, ReflectionHelpers.CreateFieldSetter(type, field)))
37
41
  .ToArray();
38
42
  }
39
43
  );
40
44
 
41
- foreach (FieldInfo field in fields)
45
+ foreach ((FieldInfo field, Action<object, object> setter) in fields)
42
46
  {
43
47
  Type fieldType = field.FieldType;
44
48
  bool isArray = fieldType.IsArray;
@@ -59,12 +63,12 @@
59
63
 
60
64
  foundChild = 0 < children.Count;
61
65
 
62
- Array correctTypedArray = Array.CreateInstance(
66
+ Array correctTypedArray = ReflectionHelpers.CreateArray(
63
67
  childComponentType,
64
68
  children.Count
65
69
  );
66
70
  Array.Copy(children.ToArray(), correctTypedArray, children.Count);
67
- field.SetValue(component, correctTypedArray);
71
+ setter(component, correctTypedArray);
68
72
  }
69
73
  else if (
70
74
  fieldType.IsGenericType
@@ -72,11 +76,8 @@
72
76
  )
73
77
  {
74
78
  childComponentType = fieldType.GenericTypeArguments[0];
75
- Type constructedListType = typeof(List<>).MakeGenericType(
76
- childComponentType
77
- );
78
- IList instance = (IList)Activator.CreateInstance(constructedListType);
79
79
 
80
+ IList instance = ReflectionHelpers.CreateList(childComponentType);
80
81
  foundChild = false;
81
82
  foreach (
82
83
  Component childComponent in component
@@ -90,7 +91,7 @@
90
91
  foundChild = true;
91
92
  }
92
93
 
93
- field.SetValue(component, instance);
94
+ setter(component, instance);
94
95
  }
95
96
  else
96
97
  {
@@ -107,7 +108,7 @@
107
108
  }
108
109
  if (foundChild)
109
110
  {
110
- field.SetValue(component, childComponent);
111
+ setter(component, childComponent);
111
112
  }
112
113
  }
113
114
  }
@@ -121,12 +122,12 @@
121
122
  );
122
123
  foundChild = 0 < childComponents.Length;
123
124
 
124
- Array correctTypedArray = Array.CreateInstance(
125
+ Array correctTypedArray = ReflectionHelpers.CreateArray(
125
126
  childComponentType,
126
127
  childComponents.Length
127
128
  );
128
129
  Array.Copy(childComponents, correctTypedArray, childComponents.Length);
129
- field.SetValue(component, correctTypedArray);
130
+ setter(component, correctTypedArray);
130
131
  }
131
132
  else if (
132
133
  fieldType.IsGenericType
@@ -134,11 +135,16 @@
134
135
  )
135
136
  {
136
137
  childComponentType = fieldType.GenericTypeArguments[0];
137
- Type constructedListType = typeof(List<>).MakeGenericType(
138
- childComponentType
138
+
139
+ Component[] children = component.GetComponentsInChildren(
140
+ childComponentType,
141
+ true
139
142
  );
140
- IList instance = (IList)Activator.CreateInstance(constructedListType);
141
143
 
144
+ IList instance = ReflectionHelpers.CreateList(
145
+ childComponentType,
146
+ children.Length
147
+ );
142
148
  foundChild = false;
143
149
  foreach (
144
150
  Component childComponent in component.GetComponentsInChildren(
@@ -151,7 +157,7 @@
151
157
  foundChild = true;
152
158
  }
153
159
 
154
- field.SetValue(component, instance);
160
+ setter(component, instance);
155
161
  }
156
162
  else
157
163
  {
@@ -162,7 +168,7 @@
162
168
  foundChild = childComponent != null;
163
169
  if (foundChild)
164
170
  {
165
- field.SetValue(component, childComponent);
171
+ setter(component, childComponent);
166
172
  }
167
173
  }
168
174
  }
@@ -6,6 +6,7 @@
6
6
  using System.Linq;
7
7
  using System.Reflection;
8
8
  using Extension;
9
+ using Helper;
9
10
  using JetBrains.Annotations;
10
11
  using UnityEngine;
11
12
 
@@ -19,12 +20,15 @@
19
20
 
20
21
  public static class ParentComponentExtensions
21
22
  {
22
- private static readonly Dictionary<Type, FieldInfo[]> FieldsByType = new();
23
+ private static readonly Dictionary<
24
+ Type,
25
+ (FieldInfo field, Action<object, object> setter)[]
26
+ > FieldsByType = new();
23
27
 
24
28
  public static void AssignParentComponents(this Component component)
25
29
  {
26
30
  Type componentType = component.GetType();
27
- FieldInfo[] fields = FieldsByType.GetOrAdd(
31
+ (FieldInfo field, Action<object, object> setter)[] fields = FieldsByType.GetOrAdd(
28
32
  componentType,
29
33
  type =>
30
34
  {
@@ -35,11 +39,12 @@
35
39
  .Where(field =>
36
40
  Attribute.IsDefined(field, typeof(ParentComponentAttribute))
37
41
  )
42
+ .Select(field => (field, ReflectionHelpers.CreateFieldSetter(type, field)))
38
43
  .ToArray();
39
44
  }
40
45
  );
41
46
 
42
- foreach (FieldInfo field in fields)
47
+ foreach ((FieldInfo field, Action<object, object> setter) in fields)
43
48
  {
44
49
  Type fieldType = field.FieldType;
45
50
  bool isArray = fieldType.IsArray;
@@ -61,12 +66,12 @@
61
66
  );
62
67
  foundParent = 0 < parentComponents.Length;
63
68
 
64
- Array correctTypedArray = Array.CreateInstance(
69
+ Array correctTypedArray = ReflectionHelpers.CreateArray(
65
70
  parentComponentType,
66
71
  parentComponents.Length
67
72
  );
68
73
  Array.Copy(parentComponents, correctTypedArray, parentComponents.Length);
69
- field.SetValue(component, correctTypedArray);
74
+ setter(component, correctTypedArray);
70
75
  }
71
76
  else if (
72
77
  fieldType.IsGenericType
@@ -74,24 +79,25 @@
74
79
  )
75
80
  {
76
81
  parentComponentType = fieldType.GenericTypeArguments[0];
77
- Type constructedListType = typeof(List<>).MakeGenericType(
78
- parentComponentType
82
+
83
+ Component[] parents = parent.GetComponentsInParent(
84
+ parentComponentType,
85
+ true
86
+ );
87
+
88
+ IList instance = ReflectionHelpers.CreateList(
89
+ parentComponentType,
90
+ parents.Length
79
91
  );
80
- IList instance = (IList)Activator.CreateInstance(constructedListType);
81
92
 
82
93
  foundParent = false;
83
- foreach (
84
- Component parentComponent in parent.GetComponentsInParent(
85
- parentComponentType,
86
- true
87
- )
88
- )
94
+ foreach (Component parentComponent in parents)
89
95
  {
90
96
  instance.Add(parentComponent);
91
97
  foundParent = true;
92
98
  }
93
99
 
94
- field.SetValue(component, instance);
100
+ setter(component, instance);
95
101
  }
96
102
  else
97
103
  {
@@ -102,7 +108,7 @@
102
108
  foundParent = childComponent != null;
103
109
  if (foundParent)
104
110
  {
105
- field.SetValue(component, childComponent);
111
+ setter(component, childComponent);
106
112
  }
107
113
  }
108
114
  }
@@ -116,12 +122,12 @@
116
122
  );
117
123
  foundParent = 0 < parentComponents.Length;
118
124
 
119
- Array correctTypedArray = Array.CreateInstance(
125
+ Array correctTypedArray = ReflectionHelpers.CreateArray(
120
126
  parentComponentType,
121
127
  parentComponents.Length
122
128
  );
123
129
  Array.Copy(parentComponents, correctTypedArray, parentComponents.Length);
124
- field.SetValue(component, correctTypedArray);
130
+ setter(component, correctTypedArray);
125
131
  }
126
132
  else if (
127
133
  fieldType.IsGenericType
@@ -129,24 +135,24 @@
129
135
  )
130
136
  {
131
137
  parentComponentType = fieldType.GenericTypeArguments[0];
132
- Type constructedListType = typeof(List<>).MakeGenericType(
133
- parentComponentType
138
+
139
+ Component[] parents = component.GetComponentsInParent(
140
+ parentComponentType,
141
+ true
142
+ );
143
+
144
+ IList instance = ReflectionHelpers.CreateList(
145
+ parentComponentType,
146
+ parents.Length
134
147
  );
135
- IList instance = (IList)Activator.CreateInstance(constructedListType);
136
148
 
137
149
  foundParent = false;
138
- foreach (
139
- Component parentComponent in component.GetComponentsInParent(
140
- parentComponentType,
141
- true
142
- )
143
- )
150
+ foreach (Component parentComponent in parents)
144
151
  {
145
152
  instance.Add(parentComponent);
146
153
  foundParent = true;
147
154
  }
148
-
149
- field.SetValue(component, instance);
155
+ setter(component, instance);
150
156
  }
151
157
  else
152
158
  {
@@ -157,7 +163,7 @@
157
163
  foundParent = childComponent != null;
158
164
  if (foundParent)
159
165
  {
160
- field.SetValue(component, childComponent);
166
+ setter(component, childComponent);
161
167
  }
162
168
  }
163
169
  }
@@ -6,6 +6,7 @@
6
6
  using System.Linq;
7
7
  using System.Reflection;
8
8
  using Extension;
9
+ using Helper;
9
10
  using JetBrains.Annotations;
10
11
  using UnityEngine;
11
12
 
@@ -18,12 +19,15 @@
18
19
 
19
20
  public static class SiblingComponentExtensions
20
21
  {
21
- private static readonly Dictionary<Type, FieldInfo[]> FieldsByType = new();
22
+ private static readonly Dictionary<
23
+ Type,
24
+ (FieldInfo field, Action<object, object> setter)[]
25
+ > FieldsByType = new();
22
26
 
23
27
  public static void AssignSiblingComponents(this Component component)
24
28
  {
25
29
  Type componentType = component.GetType();
26
- FieldInfo[] fields = FieldsByType.GetOrAdd(
30
+ (FieldInfo field, Action<object, object> setter)[] fields = FieldsByType.GetOrAdd(
27
31
  componentType,
28
32
  type =>
29
33
  {
@@ -34,11 +38,12 @@
34
38
  .Where(field =>
35
39
  Attribute.IsDefined(field, typeof(SiblingComponentAttribute))
36
40
  )
41
+ .Select(field => (field, ReflectionHelpers.CreateFieldSetter(type, field)))
37
42
  .ToArray();
38
43
  }
39
44
  );
40
45
 
41
- foreach (FieldInfo field in fields)
46
+ foreach ((FieldInfo field, Action<object, object> setter) in fields)
42
47
  {
43
48
  Type fieldType = field.FieldType;
44
49
  bool isArray = fieldType.IsArray;
@@ -50,12 +55,12 @@
50
55
  Component[] siblingComponents = component.GetComponents(siblingComponentType);
51
56
  foundSibling = 0 < siblingComponents.Length;
52
57
 
53
- Array correctTypedArray = Array.CreateInstance(
58
+ Array correctTypedArray = ReflectionHelpers.CreateArray(
54
59
  siblingComponentType,
55
60
  siblingComponents.Length
56
61
  );
57
62
  Array.Copy(siblingComponents, correctTypedArray, siblingComponents.Length);
58
- field.SetValue(component, correctTypedArray);
63
+ setter(component, correctTypedArray);
59
64
  }
60
65
  else if (
61
66
  fieldType.IsGenericType
@@ -63,19 +68,22 @@
63
68
  )
64
69
  {
65
70
  siblingComponentType = fieldType.GenericTypeArguments[0];
66
- Type constructedListType = typeof(List<>).MakeGenericType(siblingComponentType);
67
- IList instance = (IList)Activator.CreateInstance(constructedListType);
71
+
72
+ Component[] siblings = component.GetComponents(siblingComponentType);
73
+
74
+ IList instance = ReflectionHelpers.CreateList(
75
+ siblingComponentType,
76
+ siblings.Length
77
+ );
68
78
 
69
79
  foundSibling = false;
70
- foreach (
71
- Component siblingComponent in component.GetComponents(siblingComponentType)
72
- )
80
+ foreach (Component siblingComponent in siblings)
73
81
  {
74
82
  instance.Add(siblingComponent);
75
83
  foundSibling = true;
76
84
  }
77
85
 
78
- field.SetValue(component, instance);
86
+ setter(component, instance);
79
87
  }
80
88
  else
81
89
  {
@@ -87,7 +95,7 @@
87
95
  )
88
96
  {
89
97
  foundSibling = true;
90
- field.SetValue(component, siblingComponent);
98
+ setter(component, siblingComponent);
91
99
  }
92
100
  else
93
101
  {
@@ -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]);
@@ -0,0 +1,152 @@
1
+ namespace UnityHelpers.Core.Helper
2
+ {
3
+ using System;
4
+ using System.Collections;
5
+ using System.Collections.Generic;
6
+ using System.Reflection;
7
+ using System.Reflection.Emit;
8
+ using System.Runtime.CompilerServices;
9
+ using Extension;
10
+
11
+ public static class ReflectionHelpers
12
+ {
13
+ private static readonly Dictionary<Type, Func<int, Array>> ArrayCreators = new();
14
+ private static readonly Dictionary<Type, Func<IList>> ListCreators = new();
15
+ private static readonly Dictionary<Type, Func<int, IList>> ListWithCapacityCreators = new();
16
+
17
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
18
+ public static Func<int, Array> GetOrCreateArrayCreator(Type type)
19
+ {
20
+ return ArrayCreators.GetOrAdd(type, elementType => GetArrayCreator(elementType));
21
+ }
22
+
23
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
24
+ public static Array CreateArray(Type type, int length)
25
+ {
26
+ return GetOrCreateArrayCreator(type).Invoke(length);
27
+ }
28
+
29
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
30
+ public static IList CreateList(Type elementType, int length)
31
+ {
32
+ return ListWithCapacityCreators
33
+ .GetOrAdd(elementType, type => GetListWithCapacityCreator(type))
34
+ .Invoke(length);
35
+ }
36
+
37
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
38
+ public static IList CreateList(Type elementType)
39
+ {
40
+ return ListCreators.GetOrAdd(elementType, type => GetListCreator(type)).Invoke();
41
+ }
42
+
43
+ public static Action<object, object> CreateFieldSetter(Type type, FieldInfo field)
44
+ {
45
+ #if WEB_GL
46
+ return field.SetValue;
47
+ #else
48
+ DynamicMethod dynamicMethod = new(
49
+ $"SetField{field.Name}",
50
+ null,
51
+ new[] { typeof(object), typeof(object) },
52
+ type.Module,
53
+ true
54
+ );
55
+
56
+ ILGenerator il = dynamicMethod.GetILGenerator();
57
+
58
+ il.Emit(OpCodes.Ldarg_0); // Load the object (arg0)
59
+ il.Emit(OpCodes.Castclass, type); // Cast to the actual type
60
+
61
+ il.Emit(OpCodes.Ldarg_1); // Load the value (arg1)
62
+ il.Emit(
63
+ field.FieldType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass,
64
+ field.FieldType
65
+ ); // Cast for reference types
66
+ // Unbox if it's a value type
67
+ il.Emit(OpCodes.Stfld, field); // Set the field
68
+ il.Emit(OpCodes.Ret); // Return
69
+ return (Action<object, object>)
70
+ dynamicMethod.CreateDelegate(typeof(Action<object, object>));
71
+ #endif
72
+ }
73
+
74
+ public static Func<int, Array> GetArrayCreator(Type elementType)
75
+ {
76
+ #if WEB_GL
77
+ return size => Array.CreateInstance(elementType, size);
78
+ #else
79
+
80
+ DynamicMethod dynamicMethod = new DynamicMethod(
81
+ $"CreateArray{elementType.Namespace}",
82
+ typeof(Array), // Return type: Array
83
+ new[] { typeof(int) }, // Parameter: int (size)
84
+ true
85
+ );
86
+
87
+ ILGenerator il = dynamicMethod.GetILGenerator();
88
+ il.Emit(OpCodes.Ldarg_0); // Load the array size
89
+ il.Emit(OpCodes.Newarr, elementType); // Create a new array of 'type'
90
+ il.Emit(OpCodes.Ret); // Return the array
91
+ return (Func<int, Array>)dynamicMethod.CreateDelegate(typeof(Func<int, Array>));
92
+ #endif
93
+ }
94
+
95
+ public static Func<IList> GetListCreator(Type elementType)
96
+ {
97
+ Type listType = typeof(List<>).MakeGenericType(elementType);
98
+ #if WEB_GL
99
+ return () => (IList)Activator.CreateInstance(listType);
100
+ #else
101
+ DynamicMethod dynamicMethod = new DynamicMethod(
102
+ $"CreateList{listType.Name}",
103
+ typeof(IList), // Return type: IList
104
+ Type.EmptyTypes, // No parameters
105
+ true
106
+ );
107
+
108
+ ILGenerator il = dynamicMethod.GetILGenerator();
109
+ ConstructorInfo constructor = listType.GetConstructor(Type.EmptyTypes);
110
+ if (constructor == null)
111
+ {
112
+ throw new ArgumentException(
113
+ $"Type {listType} does not have a parameterless constructor."
114
+ );
115
+ }
116
+
117
+ il.Emit(OpCodes.Newobj, constructor); // Call List<T> constructor
118
+ il.Emit(OpCodes.Ret); // Return the instance
119
+ return (Func<IList>)dynamicMethod.CreateDelegate(typeof(Func<IList>));
120
+ #endif
121
+ }
122
+
123
+ public static Func<int, IList> GetListWithCapacityCreator(Type elementType)
124
+ {
125
+ Type listType = typeof(List<>).MakeGenericType(elementType);
126
+ #if WEB_GL
127
+ return _ => (IList)Activator.CreateInstance(listType);
128
+ #else
129
+ DynamicMethod dynamicMethod = new DynamicMethod(
130
+ $"CreateListWithCapacity{listType.Name}",
131
+ typeof(IList), // Return type: IList
132
+ new[] { typeof(int) }, // Parameter: int (size)
133
+ true
134
+ );
135
+
136
+ ILGenerator il = dynamicMethod.GetILGenerator();
137
+ ConstructorInfo constructor = listType.GetConstructor(new[] { typeof(int) });
138
+ if (constructor == null)
139
+ {
140
+ throw new ArgumentException(
141
+ $"Type {listType} does not have a constructor accepting an int."
142
+ );
143
+ }
144
+
145
+ il.Emit(OpCodes.Ldarg_0); // Load capacity argument
146
+ il.Emit(OpCodes.Newobj, constructor); // Call List<T>(int capacity) constructor
147
+ il.Emit(OpCodes.Ret); // Return the instance
148
+ return (Func<int, IList>)dynamicMethod.CreateDelegate(typeof(Func<int, IList>));
149
+ #endif
150
+ }
151
+ }
152
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 7d4e9b4eb4764c7881cc89a54582ec35
3
+ timeCreated: 1742066830
@@ -0,0 +1,34 @@
1
+ namespace UnityHelpers.Tests.Components
2
+ {
3
+ using Core.Attributes;
4
+ using UnityEngine;
5
+
6
+ [DisallowMultipleComponent]
7
+ [RequireComponent(typeof(SpriteRenderer))]
8
+ [RequireComponent(typeof(BoxCollider2D))]
9
+ [RequireComponent(typeof(BoxCollider2D))]
10
+ [RequireComponent(typeof(PolygonCollider2D))]
11
+ public sealed class RelationalComponentTester : MonoBehaviour
12
+ {
13
+ [SiblingComponent]
14
+ internal SpriteRenderer _spriteRenderer;
15
+
16
+ [SiblingComponent]
17
+ internal Transform _transform;
18
+
19
+ [SiblingComponent]
20
+ internal PolygonCollider2D _polygonCollider;
21
+
22
+ [SiblingComponent]
23
+ internal BoxCollider2D _boxCollider;
24
+
25
+ [ChildComponent]
26
+ internal Collider2D[] _childColliders;
27
+
28
+ [ParentComponent]
29
+ internal Collider2D[] _parentColliders;
30
+
31
+ [SiblingComponent]
32
+ internal Collider2D[] _siblingColliders;
33
+ }
34
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: aaa52c6bc8884eae8f45a9bb306d2841
3
+ timeCreated: 1742066326
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: f1cf7d23925e4ee2a7482f99e4153b53
3
+ timeCreated: 1742066312
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 53f10c10d1a14859a9a8616477fd318a
3
+ timeCreated: 1741982863
@@ -0,0 +1,37 @@
1
+ namespace UnityHelpers.Tests.Performance
2
+ {
3
+ using System;
4
+ using System.Diagnostics;
5
+ using Components;
6
+ using Core.Attributes;
7
+ using NUnit.Framework;
8
+ using UnityEngine;
9
+
10
+ public sealed class RelationComponentPerformanceTests
11
+ {
12
+ [Test]
13
+ public void RelationalPerformanceTest()
14
+ {
15
+ int count = 0;
16
+
17
+ GameObject go = new("Test", typeof(RelationalComponentTester));
18
+ RelationalComponentTester tester = go.GetComponent<RelationalComponentTester>();
19
+ // Pre-warm
20
+ tester.AssignRelationalComponents();
21
+
22
+ TimeSpan timeout = TimeSpan.FromSeconds(10);
23
+ Stopwatch timer = Stopwatch.StartNew();
24
+ do
25
+ {
26
+ tester.AssignRelationalComponents();
27
+ ++count;
28
+ } while (timer.Elapsed < timeout);
29
+
30
+ UnityEngine.Debug.Log($"Averaged {count / timeout.TotalSeconds} operations / second.");
31
+
32
+ Assert.AreNotEqual(0, tester._childColliders.Length);
33
+ Assert.AreNotEqual(0, tester._parentColliders.Length);
34
+ Assert.AreNotEqual(0, tester._siblingColliders.Length);
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 08f1585008ba43b0b666e27eb45f0768
3
+ timeCreated: 1742065801
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.wallstop-studios.unity-helpers",
3
- "version": "2.0.0-rc25",
3
+ "version": "2.0.0-rc27",
4
4
  "displayName": "Unity Helpers",
5
5
  "description": "Various Unity Helper Library",
6
6
  "dependencies": {},