using System; using System.Threading; using System.Threading.Tasks; using Cysharp.Threading.Tasks; using Game.Logic; using Game.Messages; using Game.Services; using Game.StateMachines; using GameLovers.GameData; using GameLovers.Services; using GameLovers.Services.Commands; using GameLovers.Services.Pooling; using GameLovers.UiService; using UnityEngine; using UnityEngine.InputSystem.EnhancedTouch; namespace Game { /// /// Composes the game and owns the application lifetime. /// public class Main : MonoBehaviour { [SerializeField, Tooltip("Requires a linked Unity Cloud project and granted AnalyticsIntent consent before startup.")] private bool _enableAnalytics; private readonly CancellationTokenSource _lifetime = new CancellationTokenSource(); private GameStateMachine _stateMachine; private GameServicesLocator _services; private IGameLogicLocator _gameLogic; private IDataService _dataService; private GameUiService _uiService; private bool _isPaused; private bool _isQuitting; /// Whether the initial game loading flow has completed. public bool IsReady => _stateMachine != null && _stateMachine.IsReady; /// The loading failure preventing the game from continuing, if any. public Exception StartupFailure => _stateMachine?.Failure; private void Awake() { if (MainInstaller.TryResolve(out _)) { Destroy(gameObject); return; } var installer = new Installer(); _uiService = new GameUiService(new AddressablesUiAssetLoader()); installer.Bind(new MessageBrokerService()); installer.Bind(new TimeService()); installer.Bind(_uiService); installer.Bind(new PoolService()); installer.Bind(new TickService()); installer.Bind(new CoroutineService()); installer.Bind(new AssetResolverService()); installer.Bind(new ConfigsProvider()); installer.Bind(new DataService()); installer.Bind(new AnalyticsService(installer.Resolve(), installer.Resolve())); var gameLogic = new GameLogicLocator(installer); installer.Bind(gameLogic); installer.Bind(gameLogic); installer.Bind>(new CommandService(gameLogic, installer.Resolve())); _services = new GameServicesLocator(installer); installer.Bind(_services); MainInstaller.Bind(gameLogic); MainInstaller.Bind(_services); _dataService = installer.Resolve(); _gameLogic = gameLogic; _stateMachine = new GameStateMachine(installer, _lifetime.Token); Screen.sleepTimeout = SleepTimeout.NeverSleep; TaskScheduler.UnobservedTaskException += TaskExceptionLogging; DontDestroyOnLoad(gameObject); } private void Start() { if (_services != null) { StartGameAsync().Forget(Debug.LogException); } } private void OnApplicationFocus(bool hasFocus) { if (!hasFocus) { SaveData(); } } private void OnApplicationPause(bool isPaused) { if (_services == null || _isQuitting || _isPaused == isPaused) { return; } _isPaused = isPaused; if (isPaused) { SaveData(); _services.AnalyticsService.FlushEvents(); } _services.MessageBrokerService.Publish(new ApplicationPausedMessage { IsPaused = isPaused }); } private void OnApplicationQuit() { if (_isQuitting || _services == null) { return; } _isQuitting = true; SaveData(); try { _services.MessageBrokerService.Publish(new ApplicationQuitMessage()); } finally { _services.AnalyticsService.SessionCalls.SessionEnd(_gameLogic.AppLogic.QuitReason); _services.AnalyticsService.FlushEvents(); } } private void OnDestroy() { _stateMachine?.Dispose(); _lifetime.Cancel(); _lifetime.Dispose(); if (_services == null) { return; } TaskScheduler.UnobservedTaskException -= TaskExceptionLogging; _uiService.Dispose(); _services.Dispose(); EnhancedTouchSupport.Disable(); MainInstaller.Clean(); } private async UniTask StartGameAsync() { try { EnhancedTouchSupport.Enable(); await _services.AnalyticsService.InitializeAsync(_enableAnalytics, _lifetime.Token); _lifetime.Token.ThrowIfCancellationRequested(); _stateMachine.Run(); } catch (OperationCanceledException) when (_lifetime.IsCancellationRequested) { } } private void SaveData() { try { _dataService?.SaveAllData(); } catch (Exception exception) { Debug.LogException(exception); } } private static void TaskExceptionLogging(object sender, UnobservedTaskExceptionEventArgs eventArgs) { Debug.LogException(eventArgs.Exception); eventArgs.SetObserved(); } } }