- add: game service

This commit is contained in:
2026-03-15 15:47:20 +03:00
parent 830cb38d9f
commit 7a77af2d80
7 changed files with 184 additions and 94 deletions

View File

@@ -0,0 +1,6 @@
namespace Nebula.Launcher.Models;
public interface IRunningSignalConsumer
{
public void ProcessRunningSignal(bool isRunning);
}

View File

@@ -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<InstanceKey, RobustUrl> _robustUrls = new();
private readonly Dictionary<RobustUrl, InstanceKey> _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<InstanceKey?> 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<IsLoginCredentialsNullPopupViewModel>()
.WithServerEntry(serverEntryViewModel);
_popupMessageService.Popup(warningContext);
return null;
}
try
{
using var viewModelLoading = _viewHelperService.GetViewModel<LoadingContextViewModel>();
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;
}
}
}

View File

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

View File

@@ -0,0 +1,13 @@
using System;
namespace Nebula.Launcher.ViewModels;
public record struct InstanceKey(int Id):
IEquatable<int>,
IComparable<InstanceKey>
{
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);
};

View File

@@ -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
}
}

View File

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

View File

@@ -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<ServerInfo?> GetServerInfo()
{
if (_serverInfo == null)
try
{
_serverInfo = await RestService.GetAsync<ServerInfo>(Address.InfoUri, CancellationService.Token);
}
catch (Exception e)
{
Description = e.Message;
_logger.Error(e);
}
if (_serverInfo != null)
return _serverInfo;
try
{
_serverInfo = await RestService.GetAsync<ServerInfo>(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<IsLoginCredentialsNullPopupViewModel>()
.WithServerEntry(this);
PopupMessageService.Popup(warningContext);
return;
}
try
{
using var viewModelLoading = ViewHelperService.GetViewModel<LoadingContextViewModel>();
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<int>,
IComparable<InstanceKey>
{
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()