com.wallstop-studios.unity-helpers 2.0.0-rc79.3 → 2.0.0-rc79.5

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 (48) hide show
  1. package/.github/dependabot.yml +5 -1
  2. package/.github/workflows/npm-publish.yml +2 -2
  3. package/Runtime/Core/Extension/IReadonlyListExtensions.cs +1 -1
  4. package/Runtime/Core/Extension/IReadonlyListExtensions.cs.meta +1 -1
  5. package/Runtime/Core/Extension/StringExtensions.cs +1 -1
  6. package/Runtime/Core/Helper/StringInList.cs +3 -21
  7. package/Runtime/Core/Threading/SingleThreadedThreadPool.cs +157 -49
  8. package/Runtime/Utils/Buffers.cs +118 -2
  9. package/Runtime/Utils/SevenZip/Common/CRC.cs +9 -0
  10. package/Runtime/Utils/SevenZip/Common/InBuffer.cs +15 -2
  11. package/Runtime/Utils/SevenZip/Common/OutBuffer.cs +7 -2
  12. package/Runtime/Utils/SevenZip/Compress/LZ/LzBinTree.cs +50 -0
  13. package/Runtime/Utils/SevenZip/Compress/LZ/LzInWindow.cs +26 -0
  14. package/Runtime/Utils/SevenZip/Compress/LZ/LzOutWindow.cs +27 -0
  15. package/Runtime/Utils/SevenZip/Compress/LZMA/LzmaBase.cs +9 -0
  16. package/Runtime/Utils/SevenZip/Compress/LZMA/LzmaDecoder.cs +80 -17
  17. package/Runtime/Utils/SevenZip/Compress/LZMA/LzmaEncoder.cs +265 -30
  18. package/Runtime/Utils/SevenZip/Compress/RangeCoder/RangeCoder.cs +9 -0
  19. package/Runtime/Utils/SevenZip/Compress/RangeCoder/RangeCoderBit.cs +13 -1
  20. package/Runtime/Utils/SevenZip/Compress/RangeCoder/RangeCoderBitTree.cs +11 -4
  21. package/Tests/Runtime/Core/Threading/SingleThreadedThreadPoolTests.cs +54 -0
  22. package/Tests/Runtime/Core/Threading/SingleThreadedThreadPoolTests.cs.meta +3 -0
  23. package/Tests/Runtime/Core/Threading.meta +3 -0
  24. package/Tests/Runtime/Core.meta +3 -0
  25. package/Tests/Runtime/DataStructures/BalancedKDTreeTests.cs +1 -1
  26. package/Tests/Runtime/DataStructures/CyclicBufferTests.cs +1 -1
  27. package/Tests/Runtime/DataStructures/QuadTreeTests.cs +1 -1
  28. package/Tests/Runtime/DataStructures/SpatialTreeTests.cs +1 -1
  29. package/Tests/Runtime/DataStructures/UnbalancedKDTreeTests.cs +1 -1
  30. package/Tests/Runtime/Extensions/DictionaryExtensionTests.cs +1 -1
  31. package/Tests/Runtime/Extensions/EnumExtensionTests.cs +1 -1
  32. package/Tests/Runtime/Extensions/IListExtensionTests.cs +1 -1
  33. package/Tests/Runtime/Extensions/IReadonlyListExtensionTests.cs +1 -1
  34. package/Tests/Runtime/Extensions/IReadonlyListExtensionTests.cs.meta +1 -1
  35. package/Tests/Runtime/Extensions/LoggingExtensionTests.cs +1 -1
  36. package/Tests/Runtime/Extensions/RandomExtensionTests.cs +1 -1
  37. package/Tests/Runtime/Extensions/StringExtensionTests.cs +1 -1
  38. package/Tests/Runtime/Helper/ObjectHelperTests.cs +1 -1
  39. package/Tests/Runtime/Helper/WallMathTests.cs +1 -1
  40. package/Tests/Runtime/Performance/KDTreePerformanceTests.cs +1 -1
  41. package/Tests/Runtime/Performance/QuadTreePerformanceTests.cs +1 -1
  42. package/Tests/Runtime/Performance/SpatialTreePerformanceTest.cs +1 -2
  43. package/Tests/Runtime/Performance/UnbalancedKDTreeTests.cs +1 -1
  44. package/Tests/Runtime/Random/RandomTestBase.cs +2 -2
  45. package/Tests/Runtime/Serialization/JsonSerializationTest.cs +1 -1
  46. package/Tests/Runtime/Utils/BuffersTests.cs +55 -0
  47. package/Tests/Runtime/Utils/BuffersTests.cs.meta +3 -0
  48. package/package.json +3 -1
@@ -3,4 +3,8 @@ updates:
3
3
  - package-ecosystem: "github-actions"
4
4
  directory: "/"
5
5
  schedule:
6
- interval: "weekly"
6
+ interval: "weekly"
7
+ assignees:
8
+ - wallstop
9
+ reviewers:
10
+ - wallstop
@@ -14,12 +14,12 @@ jobs:
14
14
 
15
15
  steps:
16
16
  - name: Checkout Repository
17
- uses: actions/checkout@v4
17
+ uses: actions/checkout@v5
18
18
  with:
19
19
  fetch-depth: 0 # Ensure full commit history for version comparison
20
20
 
21
21
  - name: Set up Node.js
22
- uses: actions/setup-node@v4
22
+ uses: actions/setup-node@v5
23
23
  with:
24
24
  node-version: 18
25
25
  registry-url: 'https://registry.npmjs.org/'
@@ -1,4 +1,4 @@
1
- namespace WallstopStudios.UnityHelpers.Core.Extension
1
+ namespace WallstopStudios.UnityHelpers.Core.Extension
2
2
  {
3
3
  using System;
4
4
  using System.Collections.Generic;
@@ -1,3 +1,3 @@
1
- fileFormatVersion: 2
1
+ fileFormatVersion: 2
2
2
  guid: 8c6bf3a13af8429cb8e738bd09541780
3
3
  timeCreated: 1757613609
@@ -8,7 +8,7 @@ namespace WallstopStudios.UnityHelpers.Core.Extension
8
8
 
9
9
  public static class StringExtensions
10
10
  {
11
- private static ThreadLocal<StringBuilder> StringBuilderCache = new(() =>
11
+ private static readonly ThreadLocal<StringBuilder> StringBuilderCache = new(() =>
12
12
  new StringBuilder()
13
13
  );
14
14
 
@@ -6,21 +6,15 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
6
6
 
7
7
  public sealed class StringInList : PropertyAttribute
8
8
  {
9
- public delegate string[] GetStringList();
10
-
11
- private bool _shouldRefresh;
12
- private string[] _list;
13
- private Func<string[]> _getStringList;
9
+ private readonly Func<string[]> _getStringList;
14
10
 
15
11
  public StringInList(params string[] list)
16
12
  {
17
- _shouldRefresh = false;
18
- _list = list;
13
+ _getStringList = () => list;
19
14
  }
20
15
 
21
16
  public StringInList(Type type, string methodName)
22
17
  {
23
- _shouldRefresh = true;
24
18
  MethodInfo method = type.GetMethod(
25
19
  methodName,
26
20
  BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic
@@ -36,18 +30,6 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
36
30
  }
37
31
  }
38
32
 
39
- public string[] List
40
- {
41
- get
42
- {
43
- if (_shouldRefresh)
44
- {
45
- return _getStringList();
46
- }
47
-
48
- return _list;
49
- }
50
- private set { _list = value; }
51
- }
33
+ public string[] List => _getStringList();
52
34
  }
53
35
  }
@@ -3,116 +3,224 @@ namespace WallstopStudios.UnityHelpers.Core.Threading
3
3
  using System;
4
4
  using System.Collections.Concurrent;
5
5
  using System.Threading;
6
+ using System.Threading.Tasks;
6
7
 
7
8
  public sealed class SingleThreadedThreadPool : IDisposable
8
9
  {
9
10
  public ConcurrentQueue<Exception> Exceptions => _exceptions;
10
- public int Count => _work.Count + Interlocked.CompareExchange(ref _working, 0, 0);
11
-
12
- private int _active;
13
- private int _working;
14
- private Thread _worker;
15
- private readonly ConcurrentQueue<Action> _work;
16
- private AutoResetEvent _waitHandle;
17
- private bool _disposed;
11
+ public int Count => _work.Count + (_isWorking ? 1 : 0);
12
+
13
+ private volatile bool _active = true;
14
+ private volatile bool _isWorking;
15
+ private volatile bool _disposed;
16
+
17
+ private readonly Task _workerTask;
18
+ private readonly ConcurrentQueue<WorkItem> _work;
19
+ private readonly SemaphoreSlim _workAvailable;
18
20
  private readonly ConcurrentQueue<Exception> _exceptions;
19
21
  private readonly TimeSpan _noWorkWaitTime;
22
+ private readonly CancellationTokenSource _cancellationTokenSource;
20
23
 
21
24
  public SingleThreadedThreadPool(
22
- bool runInBackground = false,
25
+ bool runInBackground = true,
23
26
  TimeSpan? noWorkWaitTime = null
24
27
  )
25
28
  {
26
- _active = 1;
27
- _working = 1;
28
- _work = new ConcurrentQueue<Action>();
29
+ _work = new ConcurrentQueue<WorkItem>();
29
30
  _exceptions = new ConcurrentQueue<Exception>();
30
- _waitHandle = new AutoResetEvent(false);
31
+ _workAvailable = new SemaphoreSlim(0);
31
32
  _noWorkWaitTime = noWorkWaitTime ?? TimeSpan.FromSeconds(1);
32
- _worker = new Thread(DoWork) { IsBackground = runInBackground };
33
- _worker.Start();
34
- }
33
+ _cancellationTokenSource = new CancellationTokenSource();
35
34
 
36
- ~SingleThreadedThreadPool()
37
- {
38
- Dispose(false);
35
+ _workerTask = runInBackground
36
+ ? Task.Run(DoWorkAsync)
37
+ : Task.Factory.StartNew(DoWorkAsync, TaskCreationOptions.LongRunning).Unwrap();
39
38
  }
40
39
 
41
40
  public void Enqueue(Action work)
42
41
  {
43
- _work.Enqueue(work);
44
- _ = _waitHandle.Set();
42
+ if (_disposed || !_active)
43
+ {
44
+ return;
45
+ }
46
+
47
+ _work.Enqueue(WorkItem.FromAction(work));
48
+ Signal();
45
49
  }
46
50
 
47
- public void Dispose()
51
+ public void Enqueue(Func<Task> work)
48
52
  {
49
- Dispose(true);
50
- GC.SuppressFinalize(this);
53
+ if (_disposed || !_active)
54
+ {
55
+ return;
56
+ }
57
+
58
+ _work.Enqueue(WorkItem.FromTask(work));
59
+ Signal();
51
60
  }
52
61
 
53
- public void Dispose(bool disposing)
62
+ public void Enqueue(Func<ValueTask> work)
54
63
  {
55
- if (_disposed)
64
+ if (_disposed || !_active)
56
65
  {
57
66
  return;
58
67
  }
59
68
 
60
- Interlocked.Exchange(ref _active, 0);
69
+ _work.Enqueue(WorkItem.FromValueTask(work));
70
+ Signal();
71
+ }
61
72
 
62
- if (disposing)
73
+ private void Signal()
74
+ {
75
+ try
63
76
  {
64
- try
65
- {
66
- _worker?.Join(TimeSpan.FromSeconds(30));
67
- _waitHandle?.Dispose();
68
- }
69
- catch
70
- {
71
- // Swallow
72
- }
77
+ _workAvailable.Release();
78
+ }
79
+ catch
80
+ {
81
+ // Swallow
82
+ }
83
+ }
73
84
 
74
- _waitHandle = null;
75
- _worker = null;
85
+ public async ValueTask DisposeAsync()
86
+ {
87
+ if (_disposed)
88
+ {
89
+ return;
76
90
  }
77
91
 
92
+ _active = false;
78
93
  _disposed = true;
94
+
95
+ _cancellationTokenSource.Cancel();
96
+ Signal();
97
+ try
98
+ {
99
+ await _workerTask.ConfigureAwait(false);
100
+ }
101
+ catch (OperationCanceledException)
102
+ {
103
+ // Expected during shutdown
104
+ }
105
+ catch
106
+ {
107
+ // Swallow other exceptions during disposal
108
+ }
109
+
110
+ _cancellationTokenSource?.Dispose();
111
+ _workAvailable?.Dispose();
112
+
113
+ GC.SuppressFinalize(this);
114
+ }
115
+
116
+ public void Dispose()
117
+ {
118
+ DisposeAsync().AsTask().GetAwaiter().GetResult();
79
119
  }
80
120
 
81
- private void DoWork()
121
+ private async Task DoWorkAsync()
82
122
  {
83
- while (Interlocked.CompareExchange(ref _active, 0, 0) != 0)
123
+ CancellationToken cancellationToken = _cancellationTokenSource.Token;
124
+
125
+ while (_active && !cancellationToken.IsCancellationRequested)
84
126
  {
85
127
  try
86
128
  {
87
- if (_work.TryDequeue(out Action workItem))
129
+ if (_work.TryDequeue(out WorkItem workItem))
88
130
  {
89
- _ = Interlocked.Exchange(ref _working, 1);
131
+ _isWorking = true;
90
132
  try
91
133
  {
92
- workItem();
134
+ await workItem.ExecuteAsync().ConfigureAwait(false);
93
135
  }
94
136
  catch (Exception e)
95
137
  {
96
138
  _exceptions.Enqueue(e);
97
139
  }
140
+ finally
141
+ {
142
+ _isWorking = false;
143
+ }
98
144
  }
99
145
  else
100
146
  {
101
147
  try
102
148
  {
103
- _ = _waitHandle?.WaitOne(_noWorkWaitTime);
149
+ await _workAvailable
150
+ .WaitAsync(_noWorkWaitTime, cancellationToken)
151
+ .ConfigureAwait(false);
104
152
  }
105
- catch (ObjectDisposedException)
153
+ catch (OperationCanceledException)
106
154
  {
107
- return;
155
+ break;
108
156
  }
109
157
  }
110
158
  }
111
- finally
159
+ catch (ObjectDisposedException)
112
160
  {
113
- _ = Interlocked.Exchange(ref _working, 0);
161
+ break;
114
162
  }
115
163
  }
116
164
  }
165
+
166
+ private enum WorkItemType
167
+ {
168
+ Unknown = 0,
169
+ Action = 1,
170
+ Task = 2,
171
+ ValueTask = 3,
172
+ }
173
+
174
+ private readonly struct WorkItem
175
+ {
176
+ private readonly WorkItemType _type;
177
+ private readonly Action _action;
178
+ private readonly Func<Task> _taskFunc;
179
+ private readonly Func<ValueTask> _valueTaskFunc;
180
+
181
+ private WorkItem(
182
+ WorkItemType type,
183
+ Action action = null,
184
+ Func<Task> taskFunc = null,
185
+ Func<ValueTask> valueTaskFunc = null
186
+ )
187
+ {
188
+ _type = type;
189
+ _action = action;
190
+ _taskFunc = taskFunc;
191
+ _valueTaskFunc = valueTaskFunc;
192
+ }
193
+
194
+ public static WorkItem FromAction(Action action) =>
195
+ new(WorkItemType.Action, action: action);
196
+
197
+ public static WorkItem FromTask(Func<Task> taskFunc) =>
198
+ new(WorkItemType.Task, taskFunc: taskFunc);
199
+
200
+ public static WorkItem FromValueTask(Func<ValueTask> valueTaskFunc) =>
201
+ new(WorkItemType.ValueTask, valueTaskFunc: valueTaskFunc);
202
+
203
+ public ValueTask ExecuteAsync()
204
+ {
205
+ return _type switch
206
+ {
207
+ WorkItemType.Action => ExecuteAction(),
208
+ WorkItemType.Task => ExecuteTask(),
209
+ WorkItemType.ValueTask => _valueTaskFunc(),
210
+ _ => default,
211
+ };
212
+ }
213
+
214
+ private ValueTask ExecuteAction()
215
+ {
216
+ _action();
217
+ return default;
218
+ }
219
+
220
+ private async ValueTask ExecuteTask()
221
+ {
222
+ await _taskFunc().ConfigureAwait(false);
223
+ }
224
+ }
117
225
  }
118
226
  }
@@ -1,6 +1,8 @@
1
1
  namespace WallstopStudios.UnityHelpers.Utils
2
2
  {
3
+ using System;
3
4
  using System.Collections.Generic;
5
+ using System.Reflection;
4
6
  using System.Text;
5
7
  using UnityEngine;
6
8
 
@@ -27,11 +29,125 @@ namespace WallstopStudios.UnityHelpers.Utils
27
29
 
28
30
  public static class Buffers<T>
29
31
  {
30
- public static readonly T[] Array = new T[Buffers.BufferSize];
31
32
  public static readonly List<T> List = new();
32
33
  public static readonly HashSet<T> HashSet = new();
33
34
  public static readonly Queue<T> Queue = new();
34
35
  public static readonly Stack<T> Stack = new();
35
- public static readonly LinkedList<T> LinkedList = new();
36
+ }
37
+
38
+ public static class WallstopGenericPool<T>
39
+ where T : new()
40
+ {
41
+ private static readonly List<T> _pool = new();
42
+ private static readonly Action<T> _clearAction = GetClearAction();
43
+ private static readonly Action<T> _onDispose = Release;
44
+
45
+ public static PooledResource<T> Get()
46
+ {
47
+ if (_pool.Count == 0)
48
+ {
49
+ return new PooledResource<T>(new T(), _onDispose);
50
+ }
51
+
52
+ int lastIndex = _pool.Count - 1;
53
+ T instance = _pool[lastIndex];
54
+ _pool.RemoveAt(lastIndex);
55
+ return new PooledResource<T>(instance, _onDispose);
56
+ }
57
+
58
+ private static Action<T> GetClearAction()
59
+ {
60
+ Type type = typeof(T);
61
+ foreach (
62
+ MethodInfo method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public)
63
+ )
64
+ {
65
+ if (
66
+ string.Equals(method.Name, "Clear", StringComparison.Ordinal)
67
+ && method.GetParameters().Length == 0
68
+ )
69
+ {
70
+ return (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), method);
71
+ }
72
+ }
73
+
74
+ return _ => { };
75
+ }
76
+
77
+ private static void Release(T resource)
78
+ {
79
+ _clearAction.Invoke(resource);
80
+ _pool.Add(resource);
81
+ }
82
+ }
83
+
84
+ public static class WallstopArrayPool<T>
85
+ {
86
+ private static readonly Dictionary<int, List<T[]>> _pool = new();
87
+ private static readonly Action<T[]> _onDispose = Release;
88
+
89
+ public static PooledResource<T[]> Get(int size)
90
+ {
91
+ switch (size)
92
+ {
93
+ case < 0:
94
+ {
95
+ throw new ArgumentOutOfRangeException(
96
+ nameof(size),
97
+ size,
98
+ "Must be non-negative."
99
+ );
100
+ }
101
+ case 0:
102
+ {
103
+ return new PooledResource<T[]>(Array.Empty<T>(), _ => { });
104
+ }
105
+ }
106
+
107
+ if (!_pool.TryGetValue(size, out List<T[]> pool))
108
+ {
109
+ pool = new List<T[]>();
110
+ _pool[size] = pool;
111
+ }
112
+
113
+ if (pool.Count == 0)
114
+ {
115
+ return new PooledResource<T[]>(new T[size], _onDispose);
116
+ }
117
+
118
+ int lastIndex = pool.Count - 1;
119
+ T[] instance = pool[lastIndex];
120
+ pool.RemoveAt(lastIndex);
121
+ return new PooledResource<T[]>(instance, _onDispose);
122
+ }
123
+
124
+ private static void Release(T[] resource)
125
+ {
126
+ int length = resource.Length;
127
+ Array.Clear(resource, 0, length);
128
+ if (!_pool.TryGetValue(length, out List<T[]> pool))
129
+ {
130
+ pool = new List<T[]>();
131
+ _pool[resource.Length] = pool;
132
+ }
133
+ pool.Add(resource);
134
+ }
135
+ }
136
+
137
+ public readonly struct PooledResource<T> : IDisposable
138
+ {
139
+ public readonly T resource;
140
+ private readonly Action<T> _onDispose;
141
+
142
+ internal PooledResource(T resource, Action<T> onDispose)
143
+ {
144
+ this.resource = resource;
145
+ _onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
146
+ }
147
+
148
+ public void Dispose()
149
+ {
150
+ _onDispose(resource);
151
+ }
36
152
  }
37
153
  }
@@ -14,10 +14,17 @@ namespace SevenZip
14
14
  {
15
15
  uint r = i;
16
16
  for (int j = 0; j < 8; j++)
17
+ {
17
18
  if ((r & 1) != 0)
19
+ {
18
20
  r = (r >> 1) ^ kPoly;
21
+ }
19
22
  else
23
+ {
20
24
  r >>= 1;
25
+ }
26
+ }
27
+
21
28
  Table[i] = r;
22
29
  }
23
30
  }
@@ -37,7 +44,9 @@ namespace SevenZip
37
44
  public void Update(byte[] data, uint offset, uint size)
38
45
  {
39
46
  for (uint i = 0; i < size; i++)
47
+ {
40
48
  _value = Table[(((byte)(_value)) ^ data[offset + i])] ^ (_value >> 8);
49
+ }
41
50
  }
42
51
 
43
52
  public uint GetDigest()
@@ -4,10 +4,10 @@ namespace SevenZip.Buffer
4
4
  {
5
5
  public class InBuffer
6
6
  {
7
- byte[] m_Buffer;
7
+ readonly byte[] m_Buffer;
8
8
  uint m_Pos;
9
9
  uint m_Limit;
10
- uint m_BufferSize;
10
+ readonly uint m_BufferSize;
11
11
  System.IO.Stream m_Stream;
12
12
  bool m_StreamWasExhausted;
13
13
  ulong m_ProcessedSize;
@@ -30,7 +30,10 @@ namespace SevenZip.Buffer
30
30
  public bool ReadBlock()
31
31
  {
32
32
  if (m_StreamWasExhausted)
33
+ {
33
34
  return false;
35
+ }
36
+
34
37
  m_ProcessedSize += m_Pos;
35
38
  int aNumProcessedBytes = m_Stream.Read(m_Buffer, 0, (int)m_BufferSize);
36
39
  m_Pos = 0;
@@ -48,8 +51,13 @@ namespace SevenZip.Buffer
48
51
  public bool ReadByte(byte b) // check it
49
52
  {
50
53
  if (m_Pos >= m_Limit)
54
+ {
51
55
  if (!ReadBlock())
56
+ {
52
57
  return false;
58
+ }
59
+ }
60
+
53
61
  b = m_Buffer[m_Pos++];
54
62
  return true;
55
63
  }
@@ -58,8 +66,13 @@ namespace SevenZip.Buffer
58
66
  {
59
67
  // return (byte)m_Stream.ReadByte();
60
68
  if (m_Pos >= m_Limit)
69
+ {
61
70
  if (!ReadBlock())
71
+ {
62
72
  return 0xFF;
73
+ }
74
+ }
75
+
63
76
  return m_Buffer[m_Pos++];
64
77
  }
65
78
 
@@ -4,9 +4,9 @@ namespace SevenZip.Buffer
4
4
  {
5
5
  public class OutBuffer
6
6
  {
7
- byte[] m_Buffer;
7
+ readonly byte[] m_Buffer;
8
8
  uint m_Pos;
9
- uint m_BufferSize;
9
+ readonly uint m_BufferSize;
10
10
  System.IO.Stream m_Stream;
11
11
  ulong m_ProcessedSize;
12
12
 
@@ -46,13 +46,18 @@ namespace SevenZip.Buffer
46
46
  {
47
47
  m_Buffer[m_Pos++] = b;
48
48
  if (m_Pos >= m_BufferSize)
49
+ {
49
50
  FlushData();
51
+ }
50
52
  }
51
53
 
52
54
  public void FlushData()
53
55
  {
54
56
  if (m_Pos == 0)
57
+ {
55
58
  return;
59
+ }
60
+
56
61
  m_Stream.Write(m_Buffer, 0, (int)m_Pos);
57
62
  m_Pos = 0;
58
63
  }