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();
}
}
}