diff --git a/Nebula.Launcher/Models/IRunningSignalConsumer.cs b/Nebula.Launcher/Models/IRunningSignalConsumer.cs new file mode 100644 index 0000000..aa9fa1f --- /dev/null +++ b/Nebula.Launcher/Models/IRunningSignalConsumer.cs @@ -0,0 +1,6 @@ +namespace Nebula.Launcher.Models; + +public interface IRunningSignalConsumer +{ + public void ProcessRunningSignal(bool isRunning); +} \ No newline at end of file diff --git a/Nebula.Launcher/Services/GameRunnerService.cs b/Nebula.Launcher/Services/GameRunnerService.cs new file mode 100644 index 0000000..72d285e --- /dev/null +++ b/Nebula.Launcher/Services/GameRunnerService.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nebula.Launcher.Models; +using Nebula.Launcher.ProcessHelper; +using Nebula.Launcher.ViewModels; +using Nebula.Launcher.ViewModels.Pages; +using Nebula.Launcher.ViewModels.Popup; +using Nebula.Shared; +using Nebula.Shared.Models; +using Nebula.Shared.Services; +using Nebula.Shared.Services.Logging; + +namespace Nebula.Launcher.Services; + +[ServiceRegister] +public class GameRunnerService +{ + private readonly PopupMessageService _popupMessageService; + private readonly ViewHelperService _viewHelperService; + private readonly GameRunnerPreparer _gameRunnerPreparer; + private readonly InstanceRunningContainer _instanceRunningContainer; + private readonly AccountInfoViewModel _accountInfoViewModel; + private readonly ServerViewContainer _container; + private readonly ILogger _logger; + + private readonly Dictionary _robustUrls = new(); + private readonly Dictionary _robustKeys = new(); + + public GameRunnerService(PopupMessageService popupMessageService, + DebugService debugService, + ViewHelperService viewHelperService, + GameRunnerPreparer gameRunnerPreparer, + InstanceRunningContainer instanceRunningContainer, + AccountInfoViewModel accountInfoViewModel, + ServerViewContainer container) + { + _popupMessageService = popupMessageService; + _viewHelperService = viewHelperService; + _gameRunnerPreparer = gameRunnerPreparer; + _instanceRunningContainer = instanceRunningContainer; + _accountInfoViewModel = accountInfoViewModel; + _container = container; + + _logger = debugService.GetLogger("GameRunnerService"); + _instanceRunningContainer.IsRunningChanged += IsRunningChanged; + } + + private void IsRunningChanged(InstanceKey key, bool isRunning) + { + _logger.Debug($"IsRunningChanged {key}: {isRunning}"); + if (!_robustUrls.TryGetValue(key, out var robustUrl)) return; + + if (_container.Get(robustUrl) is IRunningSignalConsumer signalConsumer) + { + _logger.Debug($"IsRunningChanged conf {robustUrl}: {isRunning}"); + signalConsumer.ProcessRunningSignal(isRunning); + } + + if (!isRunning) + { + _robustKeys.Remove(robustUrl); + _robustUrls.Remove(key); + } + } + + public void StopInstance(ServerEntryViewModel serverEntryViewModel) + { + if (_robustKeys.TryGetValue(serverEntryViewModel.Address, out var instanceKey)) + { + _instanceRunningContainer.Stop(instanceKey); + } + } + + public void ReadInstanceLog(ServerEntryViewModel serverEntryViewModel) + { + if (_robustKeys.TryGetValue(serverEntryViewModel.Address, out var instanceKey)) + { + _instanceRunningContainer.Popup(instanceKey); + } + } + + public async Task RunInstanceAsync(ServerEntryViewModel serverEntryViewModel, CancellationToken cancellationToken, bool ignoreLoginCredentials = false) + { + _logger.Log("Running instance..." + serverEntryViewModel.RealName); + if (!ignoreLoginCredentials && _accountInfoViewModel.Credentials.Value is null) + { + var warningContext = _viewHelperService.GetViewModel() + .WithServerEntry(serverEntryViewModel); + + _popupMessageService.Popup(warningContext); + return null; + } + + try + { + using var viewModelLoading = _viewHelperService.GetViewModel(); + viewModelLoading.LoadingName = "Loading instance..."; + + _popupMessageService.Popup(viewModelLoading); + var currProcessStartProvider = + await _gameRunnerPreparer.GetGameProcessStartInfoProvider(serverEntryViewModel.Address, viewModelLoading, cancellationToken); + _logger.Log("Preparing instance..."); + var instanceKey = _instanceRunningContainer.RegisterInstance(currProcessStartProvider); + _robustUrls.Add(instanceKey, serverEntryViewModel.Address); + _robustKeys.Add(serverEntryViewModel.Address, instanceKey); + _instanceRunningContainer.Run(instanceKey); + _logger.Log($"Starting instance... {instanceKey.Id} " + serverEntryViewModel.RealName); + return instanceKey; + } + catch (Exception e) + { + var error = new Exception("Error while attempt run instance", e); + _logger.Error(error); + _popupMessageService.Popup(error); + return null; + } + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Services/InstanceRunningContainer.cs b/Nebula.Launcher/Services/InstanceRunningContainer.cs index ff05808..8302d03 100644 --- a/Nebula.Launcher/Services/InstanceRunningContainer.cs +++ b/Nebula.Launcher/Services/InstanceRunningContainer.cs @@ -12,8 +12,7 @@ namespace Nebula.Launcher.Services; [ServiceRegister] public sealed class InstanceRunningContainer( PopupMessageService popupMessageService, - DebugService debugService, - ServerViewContainer container + DebugService debugService ) { private readonly InstanceKeyPool _keyPool = new(); diff --git a/Nebula.Launcher/ViewModels/InstanceKey.cs b/Nebula.Launcher/ViewModels/InstanceKey.cs new file mode 100644 index 0000000..1157cc6 --- /dev/null +++ b/Nebula.Launcher/ViewModels/InstanceKey.cs @@ -0,0 +1,13 @@ +using System; + +namespace Nebula.Launcher.ViewModels; + +public record struct InstanceKey(int Id): + IEquatable, + IComparable +{ + public static implicit operator InstanceKey(int id) => new InstanceKey(id); + public static implicit operator int(InstanceKey id) => id.Id; + public bool Equals(int other) => Id == other; + public int CompareTo(InstanceKey other) => Id.CompareTo(other.Id); +}; \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/InstanceKeyPool.cs b/Nebula.Launcher/ViewModels/InstanceKeyPool.cs new file mode 100644 index 0000000..404f945 --- /dev/null +++ b/Nebula.Launcher/ViewModels/InstanceKeyPool.cs @@ -0,0 +1,16 @@ +namespace Nebula.Launcher.ViewModels; + +public sealed class InstanceKeyPool +{ + private int _nextId = 1; + + public InstanceKey Take() + { + return new InstanceKey(_nextId++); + } + + public void Free(InstanceKey id) + { + // TODO: make some free logic later + } +} \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs index 9dbee0b..8451c59 100644 --- a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs @@ -58,7 +58,9 @@ public partial class ServerOverviewModel : ViewModelBase private void OnFavoriteRefreshRequired() { if(CurrentServerList.Provider is FavoriteServerListProvider favoriteServerListProvider) + { UpdateRequired(); + } } private void OnHubListChanged(ServerHubRecord[]? value) @@ -105,6 +107,7 @@ public partial class ServerOverviewModel : ViewModelBase public void UpdateRequired() { ServerViewContainer.Clear(); + CurrentServerList.ClearProvider(); CurrentServerList.RefreshFromProvider(); } diff --git a/Nebula.Launcher/ViewModels/ServerEntryViewModel.cs b/Nebula.Launcher/ViewModels/ServerEntryViewModel.cs index 1103bb7..b66a10d 100644 --- a/Nebula.Launcher/ViewModels/ServerEntryViewModel.cs +++ b/Nebula.Launcher/ViewModels/ServerEntryViewModel.cs @@ -5,7 +5,6 @@ using System.Windows.Input; using Avalonia.Controls; using CommunityToolkit.Mvvm.ComponentModel; using Nebula.Launcher.Models; -using Nebula.Launcher.ProcessHelper; using Nebula.Launcher.ServerListProviders; using Nebula.Launcher.Services; using Nebula.Launcher.ViewModels.Pages; @@ -13,7 +12,6 @@ using Nebula.Launcher.ViewModels.Popup; using Nebula.Launcher.Views; using Nebula.Shared.Models; using Nebula.Shared.Services; -using Nebula.Shared.Services.Logging; using Nebula.Shared.Utils; using Nebula.Shared.ViewHelper; @@ -21,7 +19,7 @@ namespace Nebula.Launcher.ViewModels; [ViewModelRegister(typeof(ServerEntryView), false)] [ConstructGenerator] -public sealed partial class ServerEntryViewModel : ViewModelBase, IFilterConsumer, IListEntryModelView, IFavoriteEntryModelView, IEntryNameHolder +public sealed partial class ServerEntryViewModel : ViewModelBase, IFilterConsumer, IListEntryModelView, IFavoriteEntryModelView, IEntryNameHolder, IRunningSignalConsumer { [ObservableProperty] private string _description = "Fetching info..."; [ObservableProperty] private bool _expandInfo; @@ -36,20 +34,16 @@ public sealed partial class ServerEntryViewModel : ViewModelBase, IFilterConsume set => RealName = value ?? Status.Name; } - private ILogger _logger; private ServerInfo? _serverInfo; - private InstanceKey _instanceKey; + public RobustUrl Address { get; private set; } - [GenerateProperty] private AccountInfoViewModel AccountInfoViewModel { get; } [GenerateProperty] private CancellationService CancellationService { get; } = default!; - [GenerateProperty] private DebugService DebugService { get; } = default!; [GenerateProperty] private PopupMessageService PopupMessageService { get; } = default!; [GenerateProperty] private ViewHelperService ViewHelperService { get; } = default!; [GenerateProperty] private RestService RestService { get; } = default!; [GenerateProperty] private MainViewModel MainViewModel { get; } = default!; [GenerateProperty] private FavoriteServerListProvider FavoriteServerListProvider { get; } = default!; - [GenerateProperty] private GameRunnerPreparer GameRunnerPreparer { get; } = default!; - [GenerateProperty] private InstanceRunningContainer InstanceRunningContainer { get; } = default!; + [GenerateProperty] private GameRunnerService GameRunnerService { get; } = default!; public ServerStatus Status { get; private set; } = new( @@ -70,16 +64,17 @@ public sealed partial class ServerEntryViewModel : ViewModelBase, IFilterConsume public async Task GetServerInfo() { - if (_serverInfo == null) - try - { - _serverInfo = await RestService.GetAsync(Address.InfoUri, CancellationService.Token); - } - catch (Exception e) - { - Description = e.Message; - _logger.Error(e); - } + if (_serverInfo != null) + return _serverInfo; + + try + { + _serverInfo = await RestService.GetAsync(Address.InfoUri, CancellationService.Token); + } + catch (Exception e) + { + Description = e.Message; + } return _serverInfo; } @@ -100,16 +95,8 @@ public sealed partial class ServerEntryViewModel : ViewModelBase, IFilterConsume protected override void Initialise() { - _logger = DebugService.GetLogger(this); - InstanceRunningContainer.IsRunningChanged += IsRunningChanged; } - - private void IsRunningChanged(InstanceKey arg1, bool isRunning) - { - if(arg1.Equals(_instanceKey)) - RunVisible = !isRunning; - } - + public void ProcessFilter(ServerFilter? serverFilter) { if (serverFilter == null) @@ -161,55 +148,22 @@ public sealed partial class ServerEntryViewModel : ViewModelBase, IFilterConsume public void RunInstance() { - Task.Run(async ()=> await RunInstanceAsync()); + Task.Run(async ()=> await GameRunnerService.RunInstanceAsync(this, CancellationService.Token)); } public void RunInstanceIgnoreAuth() { - Task.Run(async ()=> await RunInstanceAsync(true)); - } - - private async Task RunInstanceAsync(bool ignoreLoginCredentials = false) - { - _logger.Log("Running instance..." + RealName); - if (!ignoreLoginCredentials && AccountInfoViewModel.Credentials.Value is null) - { - var warningContext = ViewHelperService.GetViewModel() - .WithServerEntry(this); - - PopupMessageService.Popup(warningContext); - return; - } - - try - { - using var viewModelLoading = ViewHelperService.GetViewModel(); - viewModelLoading.LoadingName = "Loading instance..."; - - PopupMessageService.Popup(viewModelLoading); - var currProcessStartProvider = - await GameRunnerPreparer.GetGameProcessStartInfoProvider(Address, viewModelLoading, CancellationService.Token); - _logger.Log("Preparing instance..."); - _instanceKey = InstanceRunningContainer.RegisterInstance(currProcessStartProvider); - InstanceRunningContainer.Run(_instanceKey); - _logger.Log("Starting instance..." + RealName); - } - catch (Exception e) - { - var error = new Exception("Error while attempt run instance", e); - _logger.Error(error); - PopupMessageService.Popup(error); - } + Task.Run(async ()=> await GameRunnerService.RunInstanceAsync(this, CancellationService.Token, true)); } public void StopInstance() { - InstanceRunningContainer.Stop(_instanceKey); + GameRunnerService.StopInstance(this); } public void ReadLog() { - InstanceRunningContainer.Popup(_instanceKey); + GameRunnerService.ReadInstanceLog(this); } public async void ExpandInfoRequired() @@ -226,39 +180,18 @@ public sealed partial class ServerEntryViewModel : ViewModelBase, IFilterConsume if (info.Links is null) return; foreach (var link in info.Links) Links.Add(link); } + + + public void ProcessRunningSignal(bool isRunning) + { + RunVisible = !isRunning; + } public void Dispose() { - _logger.Dispose(); - InstanceRunningContainer.IsRunningChanged -= IsRunningChanged; } } -public sealed class InstanceKeyPool -{ - private int _nextId = 1; - - public InstanceKey Take() - { - return new InstanceKey(_nextId++); - } - - public void Free(InstanceKey id) - { - // TODO: make some free logic later - } -} - -public record struct InstanceKey(int Id): - IEquatable, - IComparable -{ - public static implicit operator InstanceKey(int id) => new InstanceKey(id); - public static implicit operator int(InstanceKey id) => id.Id; - public bool Equals(int other) => Id == other; - public int CompareTo(InstanceKey other) => Id.CompareTo(other.Id); -}; - public sealed class LinkGoCommand : ICommand { public LinkGoCommand()