Skip to content

Commit fae1cf4

Browse files
authored
feat: implement IDisposable on ISceneManager and ISceneLoader (#27)
1 parent 3040251 commit fae1cf4

File tree

14 files changed

+679
-157
lines changed

14 files changed

+679
-157
lines changed

Packages/mygamedevtools-scene-loader/Runtime/Interfaces/ISceneLoader.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
using System;
12
using UnityEngine.SceneManagement;
23

34
namespace MyGameDevTools.SceneLoading
45
{
56
/// <summary>
67
/// Interface to standardize scene loading operations.
78
/// </summary>
8-
public interface ISceneLoader
9+
public interface ISceneLoader : IDisposable
910
{
1011
/// <summary>
1112
/// Reference to the <see cref="ISceneManager"/>, responsible for performing the scene loading operations.

Packages/mygamedevtools-scene-loader/Runtime/Interfaces/ISceneManager.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading;
23
using System.Threading.Tasks;
34
using UnityEngine.SceneManagement;
45

@@ -10,7 +11,7 @@ namespace MyGameDevTools.SceneLoading
1011
/// <br/>
1112
/// A scene manager should only keep track of scenes loaded within its own scope.
1213
/// </summary>
13-
public interface ISceneManager
14+
public interface ISceneManager : IDisposable
1415
{
1516
/// <summary>
1617
/// Reports that the active scene has changed, passing the <b>previous</b> and <b>current</b> active scene as parameters.
@@ -53,7 +54,9 @@ public interface ISceneManager
5354
/// <param name="setIndexActive">Index of the desired scene to set active, based on the <paramref name="sceneInfos"/> array.</param>
5455
/// <param name="progress">Object to report the loading operations progress to, from 0 to 1.</param>
5556
/// <returns>A <see cref="System.Threading.Tasks.ValueTask{TResult}"/> with all scenes loaded.</returns>
56-
ValueTask<Scene[]> LoadScenesAsync(ILoadSceneInfo[] sceneInfos, int setIndexActive = -1, IProgress<float> progress = null);
57+
/// <exception cref="ArgumentException">When scene info group is null, empty or the setIndexName is bigger than the scene length.</exception>
58+
/// <exception cref="InvalidOperationException">When the provided scene info group fails to produce valid load scene operations.</exception>
59+
ValueTask<Scene[]> LoadScenesAsync(ILoadSceneInfo[] sceneInfos, int setIndexActive = -1, IProgress<float> progress = null, CancellationToken token = default);
5760

5861
/// <summary>
5962
/// Loads a scene referenced by the <paramref name="sceneInfo"/>, optionally enabling it as the active scene.
@@ -63,7 +66,9 @@ public interface ISceneManager
6366
/// <param name="setActive">Should the loaded scene be enabled as the active scene?</param>
6467
/// <param name="progress">Object to report the loading operation progress to, from 0 to 1.</param>
6568
/// <returns>A <see cref="System.Threading.Tasks.ValueTask{TResult}"/> with the loaded scene as the result.</returns>
66-
ValueTask<Scene> LoadSceneAsync(ILoadSceneInfo sceneInfo, bool setActive = false, IProgress<float> progress = null);
69+
/// <exception cref="ArgumentException">When scene info is null.</exception>
70+
/// <exception cref="InvalidOperationException">When the provided scene info fails to produce a valid load scene operation.</exception>
71+
ValueTask<Scene> LoadSceneAsync(ILoadSceneInfo sceneInfo, bool setActive = false, IProgress<float> progress = null, CancellationToken token = default);
6772

6873
/// <summary>
6974
/// Unloads all scenes provided by the <paramref name="sceneInfos"/> array in parallel.
@@ -74,7 +79,9 @@ public interface ISceneManager
7479
/// <br/>
7580
/// Note that in some cases, the returned scenes might no longer have a reference to its native representation, hich means its <see cref="Scene.handle"/> will not point anywhere and you won't be able to perform equal comparisons between scenes.
7681
/// </returns>
77-
ValueTask<Scene[]> UnloadScenesAsync(ILoadSceneInfo[] sceneInfos);
82+
/// <exception cref="ArgumentException">When scene info group is null or empty.</exception>
83+
/// <exception cref="InvalidOperationException">When the provided scene info group fails to produce valid unload scene operations.</exception>
84+
ValueTask<Scene[]> UnloadScenesAsync(ILoadSceneInfo[] sceneInfos, CancellationToken token = default);
7885

7986
/// <summary>
8087
/// Unloads a scene referenced by the <paramref name="sceneInfo"/>.
@@ -85,7 +92,9 @@ public interface ISceneManager
8592
/// <br/>
8693
/// Note that in some cases, the returned scene might no longer have a reference to its native representation, which means its <see cref="Scene.handle"/> will not point anywhere and you won't be able to perform equal comparisons between scenes.
8794
/// </returns>
88-
ValueTask<Scene> UnloadSceneAsync(ILoadSceneInfo sceneInfo);
95+
/// <exception cref="ArgumentException">When scene info is null.</exception>
96+
/// <exception cref="InvalidOperationException">When the provided scene info fails to produce a valid unload scene operation.</exception>
97+
ValueTask<Scene> UnloadSceneAsync(ILoadSceneInfo sceneInfo, CancellationToken token = default);
8998

9099
/// <summary>
91100
/// Gets the current active scene in this <see cref="ISceneManager"/> instance.

Packages/mygamedevtools-scene-loader/Runtime/Managers/SceneManager.cs

Lines changed: 113 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Collections.Generic;
99
using System.Runtime.CompilerServices;
1010
using System.Text;
11+
using System.Threading;
1112
using System.Threading.Tasks;
1213
using UnityEngine;
1314
using UnityEngine.SceneManagement;
@@ -28,9 +29,19 @@ public class SceneManager : ISceneManager, ISceneManagerReporter
2829

2930
readonly List<Scene> _unloadingScenes = new List<Scene>();
3031
readonly List<Scene> _loadedScenes = new List<Scene>();
32+
readonly CancellationTokenSource _lifetimeToken = new CancellationTokenSource();
3133

3234
Scene _activeScene;
3335

36+
public void Dispose()
37+
{
38+
_lifetimeToken.Cancel();
39+
_lifetimeToken.Dispose();
40+
41+
_unloadingScenes.Clear();
42+
_loadedScenes.Clear();
43+
}
44+
3445
public void SetActiveScene(Scene scene)
3546
{
3647
var validScene = scene.IsValid();
@@ -69,7 +80,89 @@ public Scene GetLoadedSceneByName(string name)
6980
throw new ArgumentException($"[{GetType().Name}] Could not find any loaded scene with the name '{name}'.", nameof(name));
7081
}
7182

72-
public async ValueTask<Scene[]> LoadScenesAsync(ILoadSceneInfo[] sceneInfos, int setIndexActive = -1, IProgress<float> progress = null)
83+
public async ValueTask<Scene[]> LoadScenesAsync(ILoadSceneInfo[] sceneInfos, int setIndexActive = -1, IProgress<float> progress = null, CancellationToken token = default)
84+
{
85+
CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(_lifetimeToken.Token, token);
86+
try
87+
{
88+
return await LoadScenesAsync_Internal(sceneInfos, setIndexActive, progress, linkedSource.Token);
89+
}
90+
catch (OperationCanceledException cancelException)
91+
{
92+
Debug.LogWarningFormat("[{0}] LoadScenesAsync was canceled. Exception:\n{1}", GetType().Name, cancelException);
93+
throw;
94+
}
95+
finally
96+
{
97+
linkedSource.Dispose();
98+
}
99+
}
100+
101+
public async ValueTask<Scene> LoadSceneAsync(ILoadSceneInfo sceneInfo, bool setActive = false, IProgress<float> progress = null, CancellationToken token = default)
102+
{
103+
sceneInfo = sceneInfo ?? throw new NullReferenceException($"[{GetType().Name}] Provided scene info is null.");
104+
105+
CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(_lifetimeToken.Token, token);
106+
Scene[] loadedScenes = null;
107+
try
108+
{
109+
loadedScenes = await LoadScenesAsync_Internal(new ILoadSceneInfo[] { sceneInfo }, setActive ? 0 : -1, progress, linkedSource.Token);
110+
}
111+
catch (OperationCanceledException cancelException)
112+
{
113+
Debug.LogWarningFormat("[{0}] LoadSceneAsync was canceled. Exception:\n{1}", GetType().Name, cancelException);
114+
throw;
115+
}
116+
finally
117+
{
118+
linkedSource.Dispose();
119+
}
120+
121+
return loadedScenes != null && loadedScenes.Length > 0 ? loadedScenes[0] : default;
122+
}
123+
124+
public async ValueTask<Scene[]> UnloadScenesAsync(ILoadSceneInfo[] sceneInfos, CancellationToken token = default)
125+
{
126+
CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(_lifetimeToken.Token, token);
127+
try
128+
{
129+
return await UnloadScenesAsync_Internal(sceneInfos, linkedSource.Token);
130+
}
131+
catch (OperationCanceledException cancelException)
132+
{
133+
Debug.LogWarningFormat("[{0}] UnloadScenesAsync was canceled. Exception:\n{1}", GetType().Name, cancelException);
134+
throw;
135+
}
136+
finally
137+
{
138+
linkedSource.Dispose();
139+
}
140+
}
141+
142+
public async ValueTask<Scene> UnloadSceneAsync(ILoadSceneInfo sceneInfo, CancellationToken token = default)
143+
{
144+
sceneInfo = sceneInfo ?? throw new ArgumentNullException(nameof(sceneInfo), $"[{GetType().Name}] Provided scene info is null.");
145+
146+
CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(_lifetimeToken.Token, token);
147+
Scene[] unloadedScenes = null;
148+
try
149+
{
150+
unloadedScenes = await UnloadScenesAsync_Internal(new ILoadSceneInfo[] { sceneInfo }, linkedSource.Token);
151+
}
152+
catch (OperationCanceledException cancelException)
153+
{
154+
Debug.LogWarningFormat("[{0}] UnloadSceneAsync was canceled. Exception:\n{1}", GetType().Name, cancelException);
155+
throw;
156+
}
157+
finally
158+
{
159+
linkedSource.Dispose();
160+
}
161+
162+
return unloadedScenes != null && unloadedScenes.Length > 0 ? unloadedScenes[0] : default;
163+
}
164+
165+
async ValueTask<Scene[]> LoadScenesAsync_Internal(ILoadSceneInfo[] sceneInfos, int setIndexActive, IProgress<float> progress, CancellationToken token)
73166
{
74167
if (sceneInfos == null || sceneInfos.Length == 0)
75168
throw new ArgumentException(nameof(sceneInfos), $"[{GetType().Name}] Provided scene group is null or empty.");
@@ -78,18 +171,20 @@ public async ValueTask<Scene[]> LoadScenesAsync(ILoadSceneInfo[] sceneInfos, int
78171

79172
var operationGroup = GetLoadSceneOperations(sceneInfos, ref setIndexActive);
80173
if (operationGroup.Operations.Count == 0)
81-
return Array.Empty<Scene>();
174+
throw new InvalidOperationException($"[{GetType().Name} Provided scene group was not able to generate any valid load scene operations.");
82175

83-
while (!operationGroup.IsDone)
176+
while (!operationGroup.IsDone && !token.IsCancellationRequested)
84177
{
85178
#if USE_UNITASK
86-
await UniTask.Yield();
179+
await UniTask.Yield(token);
87180
#else
88181
await Task.Yield();
89182
#endif
90183
progress?.Report(operationGroup.Progress);
91184
}
92185

186+
token.ThrowIfCancellationRequested();
187+
93188
var loadedScenes = GetLastUnityLoadedScenesByInfos(sceneInfos, ref setIndexActive);
94189

95190
_loadedScenes.AddRange(loadedScenes);
@@ -102,23 +197,14 @@ public async ValueTask<Scene[]> LoadScenesAsync(ILoadSceneInfo[] sceneInfos, int
102197
return loadedScenes;
103198
}
104199

105-
public async ValueTask<Scene> LoadSceneAsync(ILoadSceneInfo sceneInfo, bool setActive = false, IProgress<float> progress = null)
106-
{
107-
sceneInfo = sceneInfo ?? throw new NullReferenceException($"[{GetType().Name}] Provided scene info is null.");
108-
var loadedScenes = await LoadScenesAsync(new ILoadSceneInfo[] { sceneInfo }, setActive ? 0 : -1, progress);
109-
if (loadedScenes.Length == 0)
110-
return default;
111-
return loadedScenes[0];
112-
}
113-
114-
public async ValueTask<Scene[]> UnloadScenesAsync(ILoadSceneInfo[] sceneInfos)
200+
async ValueTask<Scene[]> UnloadScenesAsync_Internal(ILoadSceneInfo[] sceneInfos, CancellationToken token)
115201
{
116202
if (sceneInfos == null || sceneInfos.Length == 0)
117203
throw new ArgumentException($"[{GetType().Name}] Provided scene group is null or empty.", nameof(sceneInfos));
118204

119205
var loadedScenes = GetLastLoadedScenesByInfos(sceneInfos, out var unloadingIndexes);
120206
if (loadedScenes.Count == 0)
121-
return Array.Empty<Scene>();
207+
throw new InvalidOperationException($"[{GetType().Name} Provided scene group was not able to generate any valid unload scene operations.");
122208

123209
int unloadingLength = unloadingIndexes.Length;
124210
var unloadingScenes = new Scene[unloadingLength];
@@ -137,12 +223,16 @@ public async ValueTask<Scene[]> UnloadScenesAsync(ILoadSceneInfo[] sceneInfos)
137223
_unloadingScenes.Add(scene);
138224
}
139225

140-
while (!operationGroup.IsDone)
226+
while (!operationGroup.IsDone && !token.IsCancellationRequested)
227+
{
141228
#if USE_UNITASK
142-
await UniTask.Yield();
229+
await UniTask.Yield(token);
143230
#else
144231
await Task.Yield();
145232
#endif
233+
}
234+
235+
token.ThrowIfCancellationRequested();
146236

147237
foreach (var scene in loadedScenes)
148238
{
@@ -154,7 +244,7 @@ public async ValueTask<Scene[]> UnloadScenesAsync(ILoadSceneInfo[] sceneInfos)
154244

155245
var tasks = new Task[unloadingLength];
156246
for (i = 0; i < unloadingLength; i++)
157-
tasks[i] = WaitForSceneUnload(unloadingScenes[i]).AsTask();
247+
tasks[i] = WaitForSceneUnload(unloadingScenes[i], token).AsTask();
158248

159249
await Task.WhenAll(tasks);
160250

@@ -163,23 +253,16 @@ public async ValueTask<Scene[]> UnloadScenesAsync(ILoadSceneInfo[] sceneInfos)
163253
return loadedScenes.ToArray();
164254
}
165255

166-
public async ValueTask<Scene> UnloadSceneAsync(ILoadSceneInfo sceneInfo)
167-
{
168-
sceneInfo = sceneInfo ?? throw new ArgumentNullException(nameof(sceneInfo), $"[{GetType().Name}] Provided scene info is null.");
169-
var unloadedScenes = await UnloadScenesAsync(new ILoadSceneInfo[] { sceneInfo });
170-
if (unloadedScenes.Length == 0)
171-
return default;
172-
return unloadedScenes[0];
173-
}
174-
175-
async ValueTask<Scene> WaitForSceneUnload(Scene scene)
256+
async ValueTask<Scene> WaitForSceneUnload(Scene scene, CancellationToken token)
176257
{
177258
#if USE_UNITASK
178-
await UniTask.WaitUntil(() => !_unloadingScenes.Contains(scene));
259+
await UniTask.WaitUntil(() => !_unloadingScenes.Contains(scene), cancellationToken: token);
179260
#else
180-
while (_unloadingScenes.Contains(scene))
261+
while (_unloadingScenes.Contains(scene) && !token.IsCancellationRequested)
181262
await Task.Yield();
182263
#endif
264+
token.ThrowIfCancellationRequested();
265+
183266
return scene;
184267
}
185268

0 commit comments

Comments
 (0)