Skip to content

Commit e423a47

Browse files
joaoborkssemantic-release-bot
authored andcommitted
feat: consolidate simplification (#50)
BREAKING CHANGE: The 4.x update consolidates the changes in the 3.x and simplifies a few more steps of the usage experience. It completely removes the ISceneLoader implementations and adds a static MySceneManager class so you don't have to manually control its lifecycle. Refer to the upgrade guide at https://scene-loader.mygamedevtools.com/docs/upgrades/from-3-to-4 for more details.
1 parent 9a2195a commit e423a47

File tree

129 files changed

+13325
-2139
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

129 files changed

+13325
-2139
lines changed

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 MyGameDevTools
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

LICENSE.txt.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/AdvancedSceneManager.cs renamed to Runtime/CoreSceneManager.cs

Lines changed: 114 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
#if ENABLE_UNITASK && !OVERRIDE_DISABLE_UNITASK
2-
#define USE_UNITASK
3-
#endif
4-
#if USE_UNITASK
5-
using Cysharp.Threading.Tasks;
6-
#endif
71
using System;
82
using System.Collections.Generic;
3+
using System.Linq;
94
using System.Threading;
105
using System.Threading.Tasks;
116
using UnityEngine;
@@ -14,9 +9,9 @@
149
namespace MyGameDevTools.SceneLoading
1510
{
1611
/// <summary>
17-
/// The <see cref="AdvancedSceneManager"/> is capable of managing both addressable and non-addressable scene operations.
12+
/// The <see cref="CoreSceneManager"/> is capable of managing both addressable and non-addressable scene operations.
1813
/// </summary>
19-
public class AdvancedSceneManager : ISceneManager
14+
public class CoreSceneManager : ISceneManager
2015
{
2116
public event Action<Scene, Scene> ActiveSceneChanged;
2217
public event Action<Scene> SceneUnloaded;
@@ -32,15 +27,15 @@ public class AdvancedSceneManager : ISceneManager
3227
ISceneData _activeScene;
3328

3429
/// <summary>
35-
/// Creates an <see cref="AdvancedSceneManager"/> with no initial scene references.
30+
/// Creates a <see cref="CoreSceneManager"/> with no initial scene references.
3631
/// </summary>
37-
public AdvancedSceneManager() : this(false) { }
32+
public CoreSceneManager() : this(false) { }
3833
/// <summary>
39-
/// Creates a new <see cref="AdvancedSceneManager"/> with the option to add all loaded scenes to its management.
34+
/// Creates a new <see cref="CoreSceneManager"/> with the option to add all loaded scenes to its management.
4035
/// The advantage is that you can manage those scenes through this <see cref="ISceneManager"/> instead of having to
4136
/// use the Unity <see cref="SceneManager"/>.
4237
/// </summary>
43-
public AdvancedSceneManager(bool addLoadedScenes)
38+
public CoreSceneManager(bool addLoadedScenes)
4439
{
4540
if (!addLoadedScenes)
4641
{
@@ -63,19 +58,19 @@ public AdvancedSceneManager(bool addLoadedScenes)
6358
}
6459
else if (loadedSceneCount == 0)
6560
{
66-
Debug.LogWarning("Tried to create an `AdvancedSceneManager` with all loaded scenes, but encoutered none. Did you create the scene manager on `Awake()`? If so, try moving the call to `Start()` instead.");
61+
Debug.LogWarning("Tried to create a Scene Manager with all loaded scenes, but encoutered none. Did you create the Scene Manager on `Awake()`? If so, try moving the call to `Start()` instead.");
6762
}
6863
}
6964
/// <summary>
70-
/// Creates a new <see cref="AdvancedSceneManager"/> with the option to add a list of loaded scenes to its management.
65+
/// Creates a new <see cref="CoreSceneManager"/> with the option to add a list of loaded scenes to its management.
7166
/// The advantage is that you can manage those scenes through this <see cref="ISceneManager"/> instead of having to
7267
/// use the Unity <see cref="SceneManager"/>.
7368
/// </summary>
74-
public AdvancedSceneManager(Scene[] initializationScenes)
69+
public CoreSceneManager(Scene[] initializationScenes)
7570
{
7671
if (initializationScenes == null || initializationScenes.Length == 0)
7772
{
78-
throw new ArgumentException($"Trying to create an {nameof(AdvancedSceneManager)} with a null or empty array of initialization scenes. If you want to create it without any scenes, use the empty constructor instead.", nameof(initializationScenes));
73+
throw new ArgumentException($"Trying to create an {nameof(CoreSceneManager)} with a null or empty array of initialization scenes. If you want to create it without any scenes, use the empty constructor instead.", nameof(initializationScenes));
7974
}
8075

8176
int loadedSceneCount = initializationScenes.Length;
@@ -141,45 +136,34 @@ public Scene GetLoadedSceneByName(string name)
141136
throw new ArgumentException($"[{GetType().Name}] Could not find any loaded scene with the name '{name}'.", nameof(name));
142137
}
143138

144-
public async ValueTask<Scene[]> LoadScenesAsync(ILoadSceneInfo[] sceneInfos, int setIndexActive = -1, IProgress<float> progress = null, CancellationToken token = default)
139+
public Task<SceneResult> TransitionAsync(SceneParameters sceneParameters, ILoadSceneInfo intermediateSceneReference = null, CancellationToken token = default)
145140
{
146-
CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(_lifetimeTokenSource.Token, token);
147-
return await LoadScenesAsync_Internal(sceneInfos, setIndexActive, progress, linkedSource.Token).RunAndDisposeToken(linkedSource);
148-
}
149-
150-
public async ValueTask<Scene> LoadSceneAsync(ILoadSceneInfo sceneInfo, bool setActive = false, IProgress<float> progress = null, CancellationToken token = default)
151-
{
152-
sceneInfo = sceneInfo ?? throw new NullReferenceException($"[{GetType().Name}] Provided scene info is null.");
141+
if (!sceneParameters.ShouldSetActive())
142+
throw new ArgumentException($"[{GetType().Name}] You need to provide a SceneParameters object with a valid 'setIndexActive' value to perform scene transitions.", nameof(sceneParameters));
153143

154144
CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(_lifetimeTokenSource.Token, token);
155-
Scene[] loadedScenes = await LoadScenesAsync_Internal(new ILoadSceneInfo[] { sceneInfo }, setActive ? 0 : -1, progress, linkedSource.Token).RunAndDisposeToken(linkedSource);
156-
157-
return loadedScenes != null && loadedScenes.Length > 0 ? loadedScenes[0] : default;
145+
return intermediateSceneReference == null
146+
? TransitionDirectlyAsync(sceneParameters, linkedSource.Token).RunAndDisposeToken(linkedSource)
147+
: TransitionWithIntermediateAsync(sceneParameters, intermediateSceneReference, linkedSource.Token).RunAndDisposeToken(linkedSource);
158148
}
159149

160-
public async ValueTask<Scene[]> UnloadScenesAsync(ILoadSceneInfo[] sceneInfos, CancellationToken token = default)
150+
public Task<SceneResult> LoadAsync(SceneParameters sceneParameters, IProgress<float> progress = null, CancellationToken token = default)
161151
{
162152
CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(_lifetimeTokenSource.Token, token);
163-
return await UnloadScenesAsync_Internal(sceneInfos, linkedSource.Token).RunAndDisposeToken(linkedSource);
153+
return LoadScenesAsync_Internal(sceneParameters, progress, linkedSource.Token).RunAndDisposeToken(linkedSource);
164154
}
165155

166-
public async ValueTask<Scene> UnloadSceneAsync(ILoadSceneInfo sceneInfo, CancellationToken token = default)
156+
public Task<SceneResult> UnloadAsync(SceneParameters sceneParameters, CancellationToken token = default)
167157
{
168-
sceneInfo = sceneInfo ?? throw new ArgumentNullException(nameof(sceneInfo), $"[{GetType().Name}] Provided scene info is null.");
169-
170158
CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(_lifetimeTokenSource.Token, token);
171-
Scene[] unloadedScenes = await UnloadScenesAsync_Internal(new ILoadSceneInfo[] { sceneInfo }, linkedSource.Token).RunAndDisposeToken(linkedSource);
172-
173-
return unloadedScenes != null && unloadedScenes.Length > 0 ? unloadedScenes[0] : default;
159+
return UnloadScenesAsync_Internal(sceneParameters.GetLoadSceneInfos(), linkedSource.Token).RunAndDisposeToken(linkedSource);
174160
}
175161

176-
async ValueTask<Scene[]> LoadScenesAsync_Internal(ILoadSceneInfo[] sceneInfos, int setIndexActive, IProgress<float> progress, CancellationToken token)
162+
async Task<SceneResult> LoadScenesAsync_Internal(SceneParameters sceneParameters, IProgress<float> progress, CancellationToken token)
177163
{
164+
ILoadSceneInfo[] sceneInfos = sceneParameters.GetLoadSceneInfos();
165+
int setIndexActive = sceneParameters.GetIndexToActivate();
178166
int scenesToLoad = sceneInfos.Length;
179-
if (sceneInfos == null || scenesToLoad == 0)
180-
throw new ArgumentException(nameof(sceneInfos), $"[{GetType().Name}] Provided scene group is null or empty.");
181-
if (setIndexActive >= scenesToLoad)
182-
throw new ArgumentException(nameof(setIndexActive), $"[{GetType().Name}] Provided index to set active is bigger than the provided scene group size.");
183167

184168
ISceneData[] sceneDataArray = new ISceneData[scenesToLoad];
185169
int i;
@@ -189,15 +173,7 @@ async ValueTask<Scene[]> LoadScenesAsync_Internal(ILoadSceneInfo[] sceneInfos, i
189173
sceneDataArray[i].LoadSceneAsync();
190174
}
191175

192-
while (!SceneDataUtilities.HasCompletedAllSceneLoadOperations(sceneDataArray) && !token.IsCancellationRequested)
193-
{
194-
#if USE_UNITASK
195-
await UniTask.Yield(token);
196-
#else
197-
await Task.Yield();
198-
#endif
199-
progress?.Report(SceneDataUtilities.GetAverageSceneLoadOperationProgress(sceneDataArray));
200-
}
176+
await PollProgressAsync(sceneDataArray, progress, token);
201177

202178
token.ThrowIfCancellationRequested();
203179

@@ -212,37 +188,40 @@ async ValueTask<Scene[]> LoadScenesAsync_Internal(ILoadSceneInfo[] sceneInfos, i
212188
if (setIndexActive >= 0)
213189
SetActiveScene(sceneDataArray[setIndexActive].SceneReference);
214190

215-
return SceneDataUtilities.GetScenesFromSceneDataArray(sceneDataArray);
191+
return new SceneResult(SceneDataUtilities.GetScenesFromSceneDataArray(sceneDataArray));
216192
}
217193

218-
async ValueTask<Scene[]> UnloadScenesAsync_Internal(ILoadSceneInfo[] sceneInfos, CancellationToken token)
194+
async Task<SceneResult> UnloadScenesAsync_Internal(ILoadSceneInfo[] sceneInfos, CancellationToken token)
219195
{
220196
if (sceneInfos == null || sceneInfos.Length == 0)
221197
throw new ArgumentException($"[{GetType().Name}] Provided scene group is null or empty.", nameof(sceneInfos));
222198

199+
int sceneCount = sceneInfos.Length;
223200
ISceneData[] sceneDataArray = SceneDataUtilities.GetLoadedSceneDatasWithLoadSceneInfos(sceneInfos, _loadedScenes);
201+
Task[] loadTasks = new Task[sceneCount];
224202

225-
int sceneCount = sceneInfos.Length;
226203
ISceneData tempSceneData;
227204
int i;
228205
for (i = 0; i < sceneCount; i++)
229206
{
230207
tempSceneData = sceneDataArray[i];
231208
_loadedScenes.Remove(tempSceneData);
232209
_unloadingScenes.Add(tempSceneData);
233-
tempSceneData.UnloadSceneAsync();
210+
loadTasks[i] = UnityTaskUtilities.FromAsyncOperation(sceneDataArray[i].UnloadSceneAsync(), token);
234211
}
235212

236-
while (!SceneDataUtilities.HasCompletedAllSceneLoadOperations(sceneDataArray) && !token.IsCancellationRequested)
213+
try
237214
{
238-
#if USE_UNITASK
239-
await UniTask.Yield(token);
240-
#else
241-
await Task.Yield();
242-
#endif
215+
await Task.WhenAll(loadTasks);
216+
}
217+
catch (OperationCanceledException exception)
218+
{
219+
for (i = 0; i < sceneCount; i++)
220+
{
221+
_unloadingScenes.Remove(sceneDataArray[i]);
222+
}
223+
throw exception;
243224
}
244-
245-
token.ThrowIfCancellationRequested();
246225

247226
for (i = 0; i < sceneCount; i++)
248227
{
@@ -253,7 +232,80 @@ async ValueTask<Scene[]> UnloadScenesAsync_Internal(ILoadSceneInfo[] sceneInfos,
253232
SetActiveScene(GetLastLoadedScene());
254233
}
255234

256-
return SceneDataUtilities.GetScenesFromSceneDataArray(sceneDataArray);
235+
return new SceneResult(SceneDataUtilities.GetScenesFromSceneDataArray(sceneDataArray));
236+
}
237+
238+
async Task<SceneResult> TransitionDirectlyAsync(SceneParameters sceneParameters, CancellationToken token)
239+
{
240+
// If only one scene is loaded, create a temporary scene for transition.
241+
Scene tempScene = default;
242+
if (LoadedSceneCount <= 1)
243+
{
244+
tempScene = SceneManager.CreateScene("temp-transition-scene");
245+
}
246+
await UnloadSourceSceneAsync(token);
247+
248+
Scene[] loadedScenes = await LoadAsync(sceneParameters, token: token);
249+
250+
if (tempScene.IsValid())
251+
{
252+
IAsyncSceneOperation unloadOperation = new AsyncSceneOperationStandard(SceneManager.UnloadSceneAsync(tempScene));
253+
await UnityTaskUtilities.FromAsyncOperation(unloadOperation, token);
254+
}
255+
return new SceneResult(loadedScenes);
256+
}
257+
258+
async Task<SceneResult> TransitionWithIntermediateAsync(SceneParameters sceneParameters, ILoadSceneInfo intermediateSceneInfo, CancellationToken token)
259+
{
260+
Scene loadingScene = await LoadAsync(new SceneParameters(intermediateSceneInfo, false), token: token);
261+
intermediateSceneInfo = new LoadSceneInfoScene(loadingScene);
262+
263+
LoadingBehavior loadingBehavior = UnityEngine.Object.FindObjectsByType<LoadingBehavior>(FindObjectsSortMode.None).FirstOrDefault(l => l.gameObject.scene == loadingScene);
264+
return loadingBehavior
265+
? await TransitionWithIntermediateLoadingAsync(sceneParameters, intermediateSceneInfo, loadingBehavior, token)
266+
: await TransitionWithIntermediateNoLoadingAsync(sceneParameters, intermediateSceneInfo, token);
267+
}
268+
269+
async Task<SceneResult> TransitionWithIntermediateLoadingAsync(SceneParameters sceneParameters, ILoadSceneInfo intermediateSceneInfo, LoadingBehavior loadingBehavior, CancellationToken token)
270+
{
271+
LoadingProgress progress = loadingBehavior.Progress;
272+
await progress.TransitionInTask.Task;
273+
await UnloadSourceSceneAsync(token);
274+
275+
Scene[] loadedScenes = await LoadAsync(sceneParameters, progress, token);
276+
progress.SetLoadingCompleted();
277+
278+
await progress.TransitionOutTask.Task;
279+
await UnloadAsync(new SceneParameters(intermediateSceneInfo), token);
280+
return new SceneResult(loadedScenes);
281+
}
282+
283+
async Task<SceneResult> TransitionWithIntermediateNoLoadingAsync(SceneParameters sceneParameters, ILoadSceneInfo intermediateSceneInfo, CancellationToken token)
284+
{
285+
await UnloadSourceSceneAsync(token);
286+
Scene[] loadedScenes = await LoadAsync(sceneParameters, token: token);
287+
await UnloadAsync(new SceneParameters(intermediateSceneInfo), token);
288+
return new SceneResult(loadedScenes);
289+
}
290+
291+
async Task PollProgressAsync(ISceneData[] sceneDataArray, IProgress<float> progress, CancellationToken token = default)
292+
{
293+
bool isDone = false;
294+
while (!isDone && !token.IsCancellationRequested)
295+
{
296+
await Task.Yield();
297+
isDone = SceneDataUtilities.HasCompletedAllSceneLoadOperations(sceneDataArray);
298+
progress?.Report(SceneDataUtilities.GetAverageSceneLoadOperationProgress(sceneDataArray));
299+
}
300+
}
301+
302+
Task<SceneResult> UnloadSourceSceneAsync(CancellationToken token)
303+
{
304+
Scene sourceScene = GetActiveScene();
305+
if (!sourceScene.IsValid())
306+
return Task.FromResult<SceneResult>(default);
307+
308+
return UnloadAsync(new SceneParameters(new LoadSceneInfoScene(sourceScene)), token);
257309
}
258310
}
259311
}

Runtime/Interfaces/IAsyncSceneOperation.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using UnityEngine.SceneManagement;
23

34
namespace MyGameDevTools.SceneLoading
@@ -7,6 +8,11 @@ namespace MyGameDevTools.SceneLoading
78
/// </summary>
89
public interface IAsyncSceneOperation
910
{
11+
/// <summary>
12+
/// Event fired when the async operation is done.
13+
/// </summary>
14+
event Action Completed;
15+
1016
/// <summary>
1117
/// Progress of the load/unload operation from 0 to 1 (float).
1218
/// </summary>

Runtime/Interfaces/ISceneData.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ public interface ISceneData
3737
/// </summary>
3838
void UpdateSceneReference();
3939

40+
/// <summary>
41+
/// Returns whether this <see cref="ISceneData"/> can be matched by the given <paramref name="loadSceneInfo"/>.
42+
/// If the <paramref name="loadSceneInfo"/> is equal to the <see cref="ISceneData.LoadSceneInfo"/> or has a direct reference to the scene, it returns true.
43+
/// </summary>
44+
/// <param name="loadSceneInfo"><see cref="ILoadSceneInfo"/> to validate a match.</param>
45+
bool MatchesLoadSceneInfo(ILoadSceneInfo loadSceneInfo);
46+
4047
/// <summary>
4148
/// Triggers the load async operation and updates the <see cref="AsyncOperation"/> reference.
4249
/// </summary>

0 commit comments

Comments
 (0)