com.wallstop-studios.unity-helpers 2.0.0-rc50 → 2.0.0-rc51

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.
@@ -3,6 +3,7 @@
3
3
  using System;
4
4
  using System.Collections.Generic;
5
5
  using System.Linq;
6
+ using System.Threading.Tasks;
6
7
  using DataStructure;
7
8
  using DataStructure.Adapters;
8
9
  using Helper;
@@ -1577,6 +1578,13 @@
1577
1578
  return false;
1578
1579
  }
1579
1580
 
1581
+ public static Task AsTask(this AsyncOperation asyncOp)
1582
+ {
1583
+ TaskCompletionSource<bool> taskCompletionSource = new();
1584
+ asyncOp.completed += _ => taskCompletionSource.SetResult(true);
1585
+ return taskCompletionSource.Task;
1586
+ }
1587
+
1580
1588
  #if UNITY_EDITOR
1581
1589
  public static IEnumerable<Sprite> GetSpritesFromClip(this AnimationClip clip)
1582
1590
  {
@@ -0,0 +1,216 @@
1
+ namespace UnityHelpers.Core.Helper
2
+ {
3
+ using System;
4
+ using System.Linq;
5
+ using System.Threading.Tasks;
6
+ using Extension;
7
+ using UnityEngine;
8
+ using UnityEngine.Events;
9
+ using UnityEngine.SceneManagement;
10
+ using Utils;
11
+ using Object = UnityEngine.Object;
12
+ #if UNITY_EDITOR
13
+ using UnityEditor;
14
+ using UnityEditor.SceneManagement;
15
+ #endif
16
+
17
+ public static class SceneHelper
18
+ {
19
+ public static string[] GetAllScenePaths(string[] searchFolders = null)
20
+ {
21
+ #if UNITY_EDITOR
22
+ searchFolders ??= Array.Empty<string>();
23
+ return AssetDatabase
24
+ .FindAssets("t:Scene", searchFolders)
25
+ .Select(AssetDatabase.GUIDToAssetPath)
26
+ .ToArray();
27
+ #else
28
+ return Array.Empty<string>();
29
+ #endif
30
+ }
31
+
32
+ public static string[] GetScenesInBuild()
33
+ {
34
+ #if UNITY_EDITOR
35
+ return EditorBuildSettings
36
+ .scenes.Where(scene => scene.enabled)
37
+ .Select(scene => scene.path)
38
+ .ToArray();
39
+ #else
40
+ return Array.Empty<string>();
41
+ #endif
42
+ }
43
+
44
+ public static async Task<DeferredDisposalResult<T>> GetObjectOfTypeInScene<T>(
45
+ string scenePath
46
+ )
47
+ where T : Object
48
+ {
49
+ DeferredDisposalResult<T[]> result = await GetAllObjectsOfTypeInScene<T>(scenePath);
50
+ return new DeferredDisposalResult<T>(
51
+ result.Result.FirstOrDefault(),
52
+ result.DisposeAsync
53
+ );
54
+ }
55
+
56
+ public static async Task<DeferredDisposalResult<T[]>> GetAllObjectsOfTypeInScene<T>(
57
+ string scenePath
58
+ )
59
+ where T : Object
60
+ {
61
+ // Ensure singleton is created
62
+ _ = UnityMainThreadDispatcher.Instance;
63
+ TaskCompletionSource<T[]> taskCompletionSource = new();
64
+
65
+ SceneLoadScope sceneScope = new(scenePath, OnSceneLoaded);
66
+ return await taskCompletionSource.Task.ContinueWith(result =>
67
+ {
68
+ return new DeferredDisposalResult<T[]>(
69
+ result.Result,
70
+ async () =>
71
+ {
72
+ TaskCompletionSource<bool> disposalComplete = new();
73
+ UnityMainThreadDispatcher.Instance.RunOnMainThread(
74
+ () =>
75
+ sceneScope
76
+ .DisposeAsync()
77
+ .ContinueWith(_ => disposalComplete.SetResult(true))
78
+ );
79
+
80
+ await disposalComplete.Task;
81
+ }
82
+ );
83
+ });
84
+
85
+ void OnSceneLoaded(Scene scene, LoadSceneMode mode)
86
+ {
87
+ if (!string.Equals(scene.path, scenePath, StringComparison.Ordinal))
88
+ {
89
+ return;
90
+ }
91
+
92
+ T[] foundObjects = Object
93
+ .FindObjectsByType<T>(FindObjectsInactive.Include, FindObjectsSortMode.None)
94
+ .Where(obj =>
95
+ {
96
+ GameObject go = obj.GetGameObject();
97
+ if (go == null)
98
+ {
99
+ return false;
100
+ }
101
+
102
+ return go.scene == scene;
103
+ })
104
+ .ToArray();
105
+ taskCompletionSource.TrySetResult(foundObjects);
106
+ }
107
+ }
108
+
109
+ public sealed class SceneLoadScope
110
+ {
111
+ private Scene? _openedScene;
112
+ private readonly UnityAction<Scene, LoadSceneMode> _onSceneLoaded;
113
+ private readonly bool _eventAdded;
114
+
115
+ public SceneLoadScope(string scenePath, UnityAction<Scene, LoadSceneMode> onSceneLoaded)
116
+ {
117
+ _onSceneLoaded = onSceneLoaded;
118
+ _eventAdded = false;
119
+ Scene activeScene = SceneManager.GetActiveScene();
120
+ if (
121
+ !activeScene.IsValid()
122
+ || !activeScene.isLoaded
123
+ || !string.Equals(activeScene.path, scenePath, StringComparison.Ordinal)
124
+ )
125
+ {
126
+ #if UNITY_EDITOR
127
+ if (Application.isPlaying)
128
+ {
129
+ SceneManager.sceneLoaded += onSceneLoaded;
130
+ _eventAdded = true;
131
+ _openedScene = EditorSceneManager.LoadSceneInPlayMode(
132
+ scenePath,
133
+ new LoadSceneParameters(LoadSceneMode.Additive, LocalPhysicsMode.None)
134
+ );
135
+ }
136
+ else
137
+ {
138
+ _openedScene = EditorSceneManager.OpenScene(
139
+ scenePath,
140
+ OpenSceneMode.Additive
141
+ );
142
+ onSceneLoaded?.Invoke(_openedScene.Value, LoadSceneMode.Additive);
143
+ }
144
+ #else
145
+ SceneManager.sceneLoaded += onSceneLoaded;
146
+ _eventAdded = true;
147
+ SceneManager.sceneLoaded += LocalSceneLoaded;
148
+ SceneManager.LoadScene(scenePath, LoadSceneMode.Additive);
149
+ _openedScene = SceneManager.GetSceneByPath(scenePath);
150
+
151
+ void LocalSceneLoaded(Scene scene, LoadSceneMode mode)
152
+ {
153
+ if (!string.Equals(scene.path, scenePath, StringComparison.Ordinal))
154
+ {
155
+ return;
156
+ }
157
+
158
+ _openedScene = scene;
159
+ SceneManager.sceneLoaded -= LocalSceneLoaded;
160
+ }
161
+ #endif
162
+ }
163
+ else
164
+ {
165
+ onSceneLoaded?.Invoke(activeScene, LoadSceneMode.Single);
166
+ _openedScene = null;
167
+ }
168
+ }
169
+
170
+ public async Task DisposeAsync()
171
+ {
172
+ if (_eventAdded)
173
+ {
174
+ SceneManager.sceneLoaded -= _onSceneLoaded;
175
+ }
176
+
177
+ if (_openedScene == null)
178
+ {
179
+ return;
180
+ }
181
+
182
+ Scene openedScene = _openedScene.Value;
183
+ if (!openedScene.IsValid())
184
+ {
185
+ return;
186
+ }
187
+
188
+ if (!openedScene.isLoaded)
189
+ {
190
+ return;
191
+ }
192
+
193
+ #if UNITY_EDITOR
194
+ if (Application.isPlaying)
195
+ {
196
+ AsyncOperation asyncOperation = SceneManager.UnloadSceneAsync(
197
+ openedScene,
198
+ UnloadSceneOptions.None
199
+ );
200
+ await asyncOperation.AsTask();
201
+ }
202
+ else
203
+ {
204
+ EditorSceneManager.CloseScene(openedScene, true);
205
+ }
206
+ #else
207
+ AsyncOperation asyncOperation = SceneManager.UnloadSceneAsync(
208
+ openedScene,
209
+ UnloadSceneOptions.None
210
+ );
211
+ await asyncOperation.AsTask();
212
+ #endif
213
+ }
214
+ }
215
+ }
216
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 366d25a4bde54caa9dbb17d039c63e6f
3
+ timeCreated: 1743955669
@@ -0,0 +1,32 @@
1
+ namespace UnityHelpers.Core.Helper
2
+ {
3
+ using System;
4
+ using System.Collections.Concurrent;
5
+ using UnityEngine;
6
+ using Utils;
7
+
8
+ public sealed class UnityMainThreadDispatcher : RuntimeSingleton<UnityMainThreadDispatcher>
9
+ {
10
+ private readonly ConcurrentQueue<Action> _actions = new();
11
+
12
+ public void RunOnMainThread(Action action)
13
+ {
14
+ _actions.Enqueue(action);
15
+ }
16
+
17
+ private void Update()
18
+ {
19
+ while (_actions.TryDequeue(out Action action))
20
+ {
21
+ try
22
+ {
23
+ action();
24
+ }
25
+ catch (Exception e)
26
+ {
27
+ Debug.LogException(e, this);
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 6d44672eb7364a0995fa1055d4187970
3
+ timeCreated: 1743968724
@@ -67,7 +67,13 @@
67
67
  throw new ArgumentException("Max cannot be zero");
68
68
  }
69
69
 
70
- return (uint)(NextDouble() * max);
70
+ uint result = (uint)(NextDouble() * max);
71
+ if (result == max)
72
+ {
73
+ return result - 1;
74
+ }
75
+
76
+ return result;
71
77
  }
72
78
 
73
79
  public uint NextUint(uint min, uint max)
@@ -129,7 +135,13 @@
129
135
  throw new ArgumentException($"Max {max} cannot be less-than or equal-to 0");
130
136
  }
131
137
 
132
- return (long)(NextDouble() * max);
138
+ long result = (long)(NextDouble() * max);
139
+ if (result == max)
140
+ {
141
+ return result - 1;
142
+ }
143
+
144
+ return result;
133
145
  }
134
146
 
135
147
  public long NextLong(long min, long max)
@@ -147,7 +159,13 @@
147
159
  return unchecked((long)NextUlong());
148
160
  }
149
161
 
150
- return unchecked((long)(NextDouble() * range + min));
162
+ long result = unchecked((long)(NextDouble() * range + min));
163
+ if (result == max)
164
+ {
165
+ return result - 1;
166
+ }
167
+
168
+ return result;
151
169
  }
152
170
 
153
171
  public ulong NextUlong()
@@ -159,7 +177,12 @@
159
177
 
160
178
  public ulong NextUlong(ulong max)
161
179
  {
162
- return (ulong)(NextDouble() * max);
180
+ ulong result = (ulong)(NextDouble() * max);
181
+ if (result == max)
182
+ {
183
+ return result - 1;
184
+ }
185
+ return result;
163
186
  }
164
187
 
165
188
  public ulong NextUlong(ulong min, ulong max)
@@ -0,0 +1,19 @@
1
+ namespace UnityHelpers.Utils
2
+ {
3
+ using System;
4
+ using System.Threading.Tasks;
5
+
6
+ public sealed class DeferredDisposalResult<T>
7
+ {
8
+ public T Result { get; }
9
+ private readonly Func<Task> _disposeAsync;
10
+
11
+ public DeferredDisposalResult(T result, Func<Task> disposeAsync)
12
+ {
13
+ Result = result;
14
+ _disposeAsync = disposeAsync;
15
+ }
16
+
17
+ public Task DisposeAsync() => _disposeAsync();
18
+ }
19
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 00dfb26d7b8d468fb709192ddf56e4bf
3
+ timeCreated: 1743970028
@@ -0,0 +1,92 @@
1
+ namespace UnityHelpers.Tests.Helper
2
+ {
3
+ using System;
4
+ using System.Collections;
5
+ using System.Collections.Generic;
6
+ using System.Linq;
7
+ using System.Threading.Tasks;
8
+ using Core.Helper;
9
+ using NUnit.Framework;
10
+ using UnityEngine;
11
+ using UnityEngine.TestTools;
12
+ using UnityHelpers.Utils;
13
+
14
+ public sealed class SceneHelperTests
15
+ {
16
+ private readonly List<Func<Task>> _disposalTasks = new();
17
+
18
+ [UnityTearDown]
19
+ public IEnumerator TearDown()
20
+ {
21
+ foreach (Task disposal in _disposalTasks.Select(disposalProducer => disposalProducer()))
22
+ {
23
+ while (!disposal.IsCompleted)
24
+ {
25
+ yield return null;
26
+ }
27
+ }
28
+
29
+ _disposalTasks.Clear();
30
+ }
31
+
32
+ [Test]
33
+ public void GetScenesInBuild()
34
+ {
35
+ // This will only pass if you have scenes in your build path
36
+ string[] scenes = SceneHelper.GetScenesInBuild();
37
+ Assert.That(scenes, Is.Not.Empty);
38
+ }
39
+
40
+ [Test]
41
+ public void GetAllScenePaths()
42
+ {
43
+ string[] scenePaths = SceneHelper.GetAllScenePaths();
44
+ Assert.That(scenePaths, Is.Not.Empty);
45
+ Assert.IsTrue(
46
+ scenePaths.Any(path => path.Contains("Test1")),
47
+ string.Join(",", scenePaths)
48
+ );
49
+ Assert.IsTrue(
50
+ scenePaths.Any(path => path.Contains("Test2")),
51
+ string.Join(",", scenePaths)
52
+ );
53
+ }
54
+
55
+ [UnityTest]
56
+ public IEnumerator GetObjectOfTypeInScene()
57
+ {
58
+ Task<DeferredDisposalResult<SpriteRenderer>> task =
59
+ SceneHelper.GetObjectOfTypeInScene<SpriteRenderer>(
60
+ @"Packages\com.wallstop-studios.unity-helpers\Tests\Runtime\Scenes\Test1.unity"
61
+ );
62
+ while (!task.IsCompleted)
63
+ {
64
+ yield return null;
65
+ }
66
+ Assert.IsTrue(task.IsCompletedSuccessfully, task.Exception?.ToString() ?? string.Empty);
67
+
68
+ _disposalTasks.Add(task.Result.DisposeAsync);
69
+ SpriteRenderer found = task.Result.Result;
70
+ Assert.IsTrue(found != null);
71
+ }
72
+
73
+ [UnityTest]
74
+ public IEnumerator GetAllObjectOfTypeInScene()
75
+ {
76
+ Task<DeferredDisposalResult<SpriteRenderer[]>> task =
77
+ SceneHelper.GetAllObjectsOfTypeInScene<SpriteRenderer>(
78
+ @"Packages\com.wallstop-studios.unity-helpers\Tests\Runtime\Scenes\Test1.unity"
79
+ );
80
+
81
+ while (!task.IsCompleted)
82
+ {
83
+ yield return null;
84
+ }
85
+
86
+ Assert.IsTrue(task.IsCompletedSuccessfully, task.Exception?.ToString() ?? string.Empty);
87
+ _disposalTasks.Add(task.Result.DisposeAsync);
88
+ SpriteRenderer[] found = task.Result.Result;
89
+ Assert.That(found, Has.Length.EqualTo(7));
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 342b5f1eb6ff4986934cf2fa225f34a3
3
+ timeCreated: 1743957199