revival/game/Assets/Scripts/Flow/Bootstrap.cs

318 lines
8.6 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using NaughtyAttributes;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using TMPro;
enum LoadState
{
Idle,
FadeOut,
UnityUnload,
Cleanup,
UnityLoad,
SceneSetup,
Sync,
FadeIn,
COUNT
}
public class Bootstrap : MonoBehaviour
{
[Header( "Debug" )]
[SerializeField]
private bool _debugMode;
[Header( "Config" )]
[SerializeField] [Expandable]
private BootstrapConfig _config;
private static Bootstrap _instance;
private LoadState _loadState;
private Coroutine _currentSceneLoad;
private SceneType _activeSceneType;
private SceneLoader _activeScene;
private string _activeSceneName;
[Header( "References" )]
[SerializeField]
private Image _fader;
[SerializeField]
private TMP_Text _debugDisplay;
//private LevelDescriptor _activeLevelDescriptor;
private List<LevelDescriptor> _loadedLevels = new List<LevelDescriptor>();
private Queue<LevelDescriptor> _toLoadLevels = new Queue<LevelDescriptor>();
private AsyncOperation _levelLoadAsync = null;
private Queue<LevelDescriptor> _toUnloadLevels = new Queue<LevelDescriptor>();
private AsyncOperation _levelUnloadAsync = null;
public bool IsLoading => _loadState != LoadState.Idle;
private void QueryLoadState()
{
if ( _activeScene != null && _activeScene.ToLoad != SceneType.None )
{
LoadScene( _activeScene.ToLoad );
}
}
private void Awake()
{
_instance = this;
if ( _debugMode )
{
StartCoroutine( DebugCR() );
}
}
void Start()
{
LoadScene( SceneType.BootSequence );
}
public static void LoadScene( SceneType scene )
{
if ( _instance.IsLoading )
{
Debug.LogError( "[Bootstrap]: couldn't load scene: another load operation already active." );
return;
}
_instance._currentSceneLoad = _instance.StartCoroutine( _instance.LoadSceneCR( scene ) );
}
public static void LoadLevel( LevelDescriptor descriptor )
{
_instance._toLoadLevels.Enqueue( descriptor );
}
private IEnumerator DebugCR()
{
LoadState loadState = LoadState.COUNT;
StringBuilder sb = new StringBuilder();
while ( true )
{
if ( _loadState != loadState )
{
loadState = _loadState;
sb.Clear();
sb.Append( "LoadState::" );
sb.AppendLine( _loadState.ToString() );
if ( _activeSceneName != null )
{
sb.AppendFormat( "S: {0} ({1})", _activeSceneType, _activeSceneName );
}
else
{
sb.AppendFormat( "S: [none]" );
}
sb.Append( "\n" );
if ( (_loadedLevels?.Count ?? 0) > 0 )
{
sb.Append( "L: " );
for ( int i = 0; i < _loadedLevels.Count; ++i )
{
sb.Append( _loadedLevels[ i ].LevelScene );
if ( i != _loadedLevels.Count - 1 )
{
sb.Append( ", " );
}
}
}
else
{
sb.AppendFormat( "L: [none]" );
}
sb.Append( "\n" );
sb.AppendFormat( "Load: {0} ({1} queued)\n", _levelLoadAsync?.isDone ?? true ? "idle" : "busy" , _toLoadLevels.Count );
sb.AppendFormat( "Unload: {0} ({1} queued)\n", _levelUnloadAsync?.isDone ?? true ? "idle" : "busy" , _toUnloadLevels.Count );
_debugDisplay.SetText( sb.ToString() );
}
yield return null;
}
}
private bool ProcessLevelUnloads()
{
if (_levelUnloadAsync?.isDone ?? true)
{
while (true)
{
if ( _toUnloadLevels.Count == 0 )
{
break;
}
var target = _toUnloadLevels.Dequeue();
if ( _loadedLevels.Contains( target ) == false )
{
continue;
}
_levelUnloadAsync = SceneManager.UnloadSceneAsync( target.LevelScene );
_loadedLevels.Remove( target );
return false;
}
}
return (_levelUnloadAsync?.isDone ?? true) && _toUnloadLevels.Count == 0;
}
private bool ProcessLevelLoads()
{
if (_levelLoadAsync?.isDone ?? true)
{
while (true)
{
if ( _toLoadLevels.Count == 0 )
{
break;
}
var target = _toLoadLevels.Dequeue();
if ( _loadedLevels.Contains( target ) )
{
continue;
}
_levelLoadAsync = SceneManager.LoadSceneAsync( target.LevelScene, LoadSceneMode.Additive );
_loadedLevels.Add( target );
return false;
}
}
return (_levelLoadAsync?.isDone ?? true) && _toLoadLevels.Count == 0;
}
private IEnumerator LoadSceneCR( SceneType scene )
{
AsyncOperation asyncOp;
// fade out
_loadState = LoadState.FadeOut;
_fader.CrossFadeAlpha( 1f, _config.TransitionDuration, true );
yield return new WaitForSecondsRealtime( _config.TransitionDuration );
// unload unity's scenes
_loadState = LoadState.UnityUnload;
if ( _activeSceneName != null )
{
// unload main scene
asyncOp = SceneManager.UnloadSceneAsync( _activeSceneName );
while ( asyncOp.isDone == false )
yield return null;
_activeScene = null;
_activeSceneName = null;
}
// cleanup
_loadState = LoadState.Cleanup;
if ( scene == SceneType.Gameplay )
{
_toUnloadLevels.Clear();
_toLoadLevels.Clear();
_loadedLevels.Clear();
_toLoadLevels.Enqueue( _config.StartLevel );
}
else
{
_toLoadLevels.Clear();
for ( int i = 0; i < _loadedLevels.Count; ++i )
{
if ( _toUnloadLevels.Contains( _loadedLevels[ i ] ) == false )
{
_toUnloadLevels.Enqueue( _loadedLevels[ i ] );
}
}
}
// 6 collections, as that is the deepest unity hierarchies go.
for ( int i = 0; i < 6; ++i )
{
GC.Collect();
yield return null;
}
// unity loading
_loadState = LoadState.UnityLoad;
_activeSceneType = scene;
_activeSceneName = _config.GetScene( scene );
asyncOp = SceneManager.LoadSceneAsync( _activeSceneName, LoadSceneMode.Additive );
while ( asyncOp.isDone == false )
yield return null;
// setup
_loadState = LoadState.SceneSetup;
_activeScene = FindObjectOfType<SceneLoader>();
while ( _activeScene.LoadComplete == false )
yield return null;
// sync
_loadState = LoadState.Sync;
while ( ProcessLevelUnloads() == false )
{
yield return null;
}
while ( ProcessLevelLoads() == false )
{
yield return null;
}
// fade in
_loadState = LoadState.FadeIn;
_fader.CrossFadeAlpha( 0f, _config.TransitionDuration, true );
yield return new WaitForSecondsRealtime( _config.TransitionDuration );
yield return null;
_loadState = LoadState.Idle;
_currentSceneLoad = null;
}
private void Update()
{
if ( !IsLoading )
{
QueryLoadState();
if ( _activeSceneType == SceneType.Gameplay )
{
if ( ProcessLevelUnloads() )
{
ProcessLevelLoads();
}
}
}
}
}