diff --git a/Nebula.Launcher/Assets/lang/en-US.ftl b/Nebula.Launcher/Assets/lang/en-US.ftl
index f12fa75..4cedaf0 100644
--- a/Nebula.Launcher/Assets/lang/en-US.ftl
+++ b/Nebula.Launcher/Assets/lang/en-US.ftl
@@ -70,4 +70,5 @@ popup-login-credentials-warning-cancel = Cancel
popup-login-credentials-warning-proceed = Proceed
goto-path-home = Root folder
-tab-favorite = Favorite
\ No newline at end of file
+tab-favorite = Favorite
+server-list-loading = Loading server list.. Please wait
\ No newline at end of file
diff --git a/Nebula.Launcher/Assets/lang/ru-RU.ftl b/Nebula.Launcher/Assets/lang/ru-RU.ftl
index 04baa10..6410348 100644
--- a/Nebula.Launcher/Assets/lang/ru-RU.ftl
+++ b/Nebula.Launcher/Assets/lang/ru-RU.ftl
@@ -70,4 +70,5 @@ popup-login-credentials-warning-cancel = Отмена
popup-login-credentials-warning-proceed = Продолжить
goto-path-home = Корн. папка
-tab-favorite = Избранное
\ No newline at end of file
+tab-favorite = Избранное
+server-list-loading = Загрузка списка серверов. Пожалуйста, подождите...
\ No newline at end of file
diff --git a/Nebula.Launcher/Controls/ServerListView.axaml b/Nebula.Launcher/Controls/ServerListView.axaml
deleted file mode 100644
index ec2de2c..0000000
--- a/Nebula.Launcher/Controls/ServerListView.axaml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/Nebula.Launcher/Controls/ServerListView.axaml.cs b/Nebula.Launcher/Controls/ServerListView.axaml.cs
deleted file mode 100644
index 28b1c54..0000000
--- a/Nebula.Launcher/Controls/ServerListView.axaml.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-using Avalonia.Controls;
-using Nebula.Launcher.Models;
-using Nebula.Launcher.ServerListProviders;
-using Nebula.Launcher.ViewModels;
-using Nebula.Launcher.ViewModels.Pages;
-
-namespace Nebula.Launcher.Controls;
-
-public partial class ServerListView : UserControl
-{
- private IServerListProvider _provider = default!;
- private ServerFilter? _currentFilter;
-
- public bool IsLoading { get; private set; }
-
- public ServerListView()
- {
- InitializeComponent();
- }
-
- public static ServerListView TakeFrom(IServerListProvider provider)
- {
- var serverListView = new ServerListView();
- if (provider is IServerListDirtyInvoker invoker)
- {
- invoker.Dirty += serverListView.OnDirty;
- }
- serverListView._provider = provider;
- serverListView.RefreshFromProvider();
- return serverListView;
- }
-
- public void RefreshFromProvider()
- {
- if (IsLoading)
- return;
-
- Clear();
- StartLoading();
-
- _provider.LoadServerList();
-
- if (_provider.IsLoaded) PasteServersFromList();
- else _provider.OnLoaded += RefreshRequired;
- }
-
- public void RequireStatusUpdate()
- {
- foreach (var rawView in ServerList.Items)
- {
- if (rawView is ServerEntryModelView serverEntryModelView)
- {
- //serverEntryModelView.UpdateStatusIfNecessary();
- }
- }
- }
-
- public void ApplyFilter(ServerFilter? filter)
- {
- _currentFilter = filter;
-
- if(IsLoading)
- return;
-
- foreach (var serverView in ServerList.Items)
- {
- if(serverView is IFilterConsumer filterConsumer)
- filterConsumer.ProcessFilter(filter);
- }
- }
-
- private void OnDirty()
- {
- RefreshFromProvider();
- }
-
- private void Clear()
- {
- ErrorList.Items.Clear();
- ServerList.Items.Clear();
- }
-
- private void PasteServersFromList()
- {
- foreach (var serverEntry in _provider.GetServers())
- {
- ServerList.Items.Add(serverEntry);
- if(serverEntry is IFilterConsumer serverFilter)
- serverFilter.ProcessFilter(_currentFilter);
- }
-
- foreach (var error in _provider.GetErrors())
- {
- ErrorList.Items.Add(error);
- }
-
- EndLoading();
- }
-
- private void RefreshRequired()
- {
- PasteServersFromList();
- _provider.OnLoaded -= RefreshRequired;
- }
-
- private void StartLoading()
- {
- Clear();
- IsLoading = true;
- LoadingLabel.IsVisible = true;
- }
-
- private void EndLoading()
- {
- IsLoading = false;
- LoadingLabel.IsVisible = false;
- }
-}
\ No newline at end of file
diff --git a/Nebula.Launcher/Converters/TypeConverters.cs b/Nebula.Launcher/Converters/TypeConverters.cs
index 05c65c4..ce33663 100644
--- a/Nebula.Launcher/Converters/TypeConverters.cs
+++ b/Nebula.Launcher/Converters/TypeConverters.cs
@@ -2,6 +2,7 @@ using System;
using Avalonia.Data.Converters;
using Avalonia.Media;
using Avalonia.Platform;
+using Nebula.Launcher.Utils;
using Nebula.Launcher.ViewModels.Pages;
using Color = System.Drawing.Color;
diff --git a/Nebula.Launcher/Models/ContentLogConsumer.cs b/Nebula.Launcher/Models/ContentLogConsumer.cs
index 881ebf4..1d9d750 100644
--- a/Nebula.Launcher/Models/ContentLogConsumer.cs
+++ b/Nebula.Launcher/Models/ContentLogConsumer.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Collections.Generic;
using Nebula.Launcher.ProcessHelper;
using Nebula.Launcher.ViewModels.Popup;
using Nebula.Shared.Services;
@@ -6,27 +8,53 @@ namespace Nebula.Launcher.Models;
public sealed class ContentLogConsumer : IProcessLogConsumer
{
- private readonly LogPopupModelView _currLog;
- private readonly PopupMessageService _popupMessageService;
+ private readonly List _outMessages = [];
+
+ private LogPopupModelView? _currentLogPopup;
+
+ public int MaxMessages { get; set; } = 100;
- public ContentLogConsumer(LogPopupModelView currLog, PopupMessageService popupMessageService)
+ public void Popup(PopupMessageService popupMessageService)
{
- _currLog = currLog;
- _popupMessageService = popupMessageService;
+ if(_currentLogPopup is not null)
+ return;
+
+ _currentLogPopup = new LogPopupModelView(popupMessageService);
+ _currentLogPopup.OnDisposing += OnLogPopupDisposing;
+
+ foreach (var message in _outMessages.ToArray())
+ {
+ _currentLogPopup.Append(message);
+ }
+
+ popupMessageService.Popup(_currentLogPopup);
+ }
+
+ private void OnLogPopupDisposing(PopupViewModelBase obj)
+ {
+ if(_currentLogPopup == null)
+ return;
+
+ _currentLogPopup.OnDisposing -= OnLogPopupDisposing;
+ _currentLogPopup = null;
}
public void Out(string text)
{
- _currLog.Append(text);
+ _outMessages.Add(text);
+ if(_outMessages.Count >= MaxMessages)
+ _outMessages.RemoveAt(0);
+
+ _currentLogPopup?.Append(text);
}
public void Error(string text)
{
- _currLog.Append(text);
+ Out(text);
}
public void Fatal(string text)
{
- _popupMessageService.Popup("Fatal error while stop instance:" + text);
+ throw new Exception("Error while running programm: " + text);
}
}
\ No newline at end of file
diff --git a/Nebula.Launcher/Nebula.Launcher.csproj b/Nebula.Launcher/Nebula.Launcher.csproj
index 1aac8fe..5f6b17a 100644
--- a/Nebula.Launcher/Nebula.Launcher.csproj
+++ b/Nebula.Launcher/Nebula.Launcher.csproj
@@ -34,21 +34,6 @@
-
-
- ServerListTab.axaml
- Code
-
-
- AddFavoriteView.axaml
- Code
-
-
- ServerListView.axaml
- Code
-
-
-
diff --git a/Nebula.Launcher/ProcessHelper/GameRunnerPreparer.cs b/Nebula.Launcher/ProcessHelper/GameRunnerPreparer.cs
index d522859..59700eb 100644
--- a/Nebula.Launcher/ProcessHelper/GameRunnerPreparer.cs
+++ b/Nebula.Launcher/ProcessHelper/GameRunnerPreparer.cs
@@ -13,7 +13,7 @@ namespace Nebula.Launcher.ProcessHelper;
[ServiceRegister]
public sealed class GameRunnerPreparer(IServiceProvider provider, ContentService contentService, EngineService engineService)
{
- public async Task> GetGameProcessStartInfoProvider(RobustUrl address, ILoadingHandlerFactory loadingHandlerFactory, CancellationToken cancellationToken = default)
+ public async Task GetGameProcessStartInfoProvider(RobustUrl address, ILoadingHandlerFactory loadingHandlerFactory, CancellationToken cancellationToken = default)
{
var buildInfo = await contentService.GetBuildInfo(address, cancellationToken);
@@ -39,11 +39,9 @@ public sealed class GameRunnerPreparer(IServiceProvider provider, ContentService
await stream.DisposeAsync();
}
- var gameInfo =
+ return
provider.GetService()!.WithBuildInfo(buildInfo.BuildInfo.Auth.PublicKey,
address);
- var gameProcessRunHandler = new ProcessRunHandler(gameInfo);
- return gameProcessRunHandler;
}
}
\ No newline at end of file
diff --git a/Nebula.Launcher/ProcessHelper/ProcessRunHandler.cs b/Nebula.Launcher/ProcessHelper/ProcessRunHandler.cs
index 35eeab8..7a1eda3 100644
--- a/Nebula.Launcher/ProcessHelper/ProcessRunHandler.cs
+++ b/Nebula.Launcher/ProcessHelper/ProcessRunHandler.cs
@@ -6,29 +6,27 @@ using Nebula.Shared.Services.Logging;
namespace Nebula.Launcher.ProcessHelper;
-public class ProcessRunHandler : IProcessConsumerCollection, IDisposable where T: IProcessStartInfoProvider
+public class ProcessRunHandler : IDisposable
{
private ProcessStartInfo? _processInfo;
private Task? _processInfoTask;
private Process? _process;
- private ProcessLogConsumerCollection _consumerCollection = new();
+ private readonly IProcessLogConsumer _logConsumer;
private string _lastError = string.Empty;
- private readonly T _currentProcessStartInfoProvider;
+ private readonly IProcessStartInfoProvider _currentProcessStartInfoProvider;
- public T GetCurrentProcessStartInfo() => _currentProcessStartInfoProvider;
+ public IProcessStartInfoProvider GetCurrentProcessStartInfo() => _currentProcessStartInfoProvider;
public bool IsRunning => _processInfo is not null;
- public Action>? OnProcessExited;
-
- public void RegisterLogger(IProcessLogConsumer consumer)
- {
- _consumerCollection.RegisterLogger(consumer);
- }
+ public Action? OnProcessExited;
- public ProcessRunHandler(T processStartInfoProvider)
+ public bool Disposed { get; private set; }
+
+ public ProcessRunHandler(IProcessStartInfoProvider processStartInfoProvider, IProcessLogConsumer logConsumer)
{
_currentProcessStartInfoProvider = processStartInfoProvider;
+ _logConsumer = logConsumer;
_processInfoTask = _currentProcessStartInfoProvider.GetProcessStartInfo();
_processInfoTask.GetAwaiter().OnCompleted(OnInfoProvided);
}
@@ -42,8 +40,18 @@ public class ProcessRunHandler : IProcessConsumerCollection, IDisposable wher
_processInfoTask = null;
}
+ private void CheckIfDisposed()
+ {
+ if (!Disposed) return;
+ throw new ObjectDisposedException(nameof(ProcessRunHandler));
+ }
+
public void Start()
{
+ CheckIfDisposed();
+ if(_process is not null)
+ throw new InvalidOperationException("Already running");
+
if (_processInfoTask != null)
{
_processInfoTask.Wait();
@@ -66,7 +74,8 @@ public class ProcessRunHandler : IProcessConsumerCollection, IDisposable wher
public void Stop()
{
- _process?.CloseMainWindow();
+ CheckIfDisposed();
+ Dispose();
}
private void OnExited(object? sender, EventArgs e)
@@ -79,12 +88,13 @@ public class ProcessRunHandler : IProcessConsumerCollection, IDisposable wher
if (_process.ExitCode != 0)
- _consumerCollection.Fatal(_lastError);
+ _logConsumer.Fatal(_lastError);
_process.Dispose();
_process = null;
OnProcessExited?.Invoke(this);
+ Dispose();
}
private void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
@@ -92,7 +102,7 @@ public class ProcessRunHandler : IProcessConsumerCollection, IDisposable wher
if (e.Data != null)
{
_lastError = e.Data;
- _consumerCollection.Error(e.Data);
+ _logConsumer.Error(e.Data);
}
}
@@ -100,14 +110,22 @@ public class ProcessRunHandler : IProcessConsumerCollection, IDisposable wher
{
if (e.Data != null)
{
- _consumerCollection.Out(e.Data);
+ _logConsumer.Out(e.Data);
}
}
public void Dispose()
{
+ if (_process is not null)
+ {
+ _process.CloseMainWindow();
+ return;
+ }
+
+ CheckIfDisposed();
+
_processInfoTask?.Dispose();
- _process?.Dispose();
+ Disposed = true;
}
}
diff --git a/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs b/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs
index 468e067..ad8efb7 100644
--- a/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs
+++ b/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs
@@ -28,6 +28,7 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS
public bool IsLoaded { get; private set; }
public Action? OnLoaded { get; set; }
+ public Action? OnDisposed { get; set; }
public Action? Dirty { get; set; }
public IEnumerable GetServers()
{
@@ -108,9 +109,14 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS
}
private void InitialiseInDesignMode(){}
+
+ public void Dispose()
+ {
+ OnDisposed?.Invoke();
+ }
}
-public class AddFavoriteButton: Border, IListEntryModelView{
+public sealed class AddFavoriteButton: Border, IListEntryModelView{
private Button _addFavoriteButton = new Button();
public AddFavoriteButton(IServiceProvider serviceProvider)
@@ -128,4 +134,9 @@ public class AddFavoriteButton: Border, IListEntryModelView{
Child = _addFavoriteButton;
}
public bool IsFavorite { get; set; }
+
+ public void Dispose()
+ {
+
+ }
}
\ No newline at end of file
diff --git a/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs b/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs
index a26f075..7e16b8e 100644
--- a/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs
+++ b/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs
@@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
-using Microsoft.Extensions.DependencyInjection;
-using Nebula.Launcher.ViewModels;
using Nebula.Launcher.ViewModels.Pages;
using Nebula.Shared;
using Nebula.Shared.Models;
@@ -22,6 +20,7 @@ public sealed partial class HubServerListProvider : IServerListProvider
public bool IsLoaded { get; private set; }
public Action? OnLoaded { get; set; }
+ public Action? OnDisposed { get; set; }
private CancellationTokenSource? _cts;
private readonly List _servers = [];
@@ -83,4 +82,10 @@ public sealed partial class HubServerListProvider : IServerListProvider
private void Initialise(){}
private void InitialiseInDesignMode(){}
+
+ public void Dispose()
+ {
+ OnDisposed?.Invoke();
+ _cts?.Dispose();
+ }
}
\ No newline at end of file
diff --git a/Nebula.Launcher/ServerListProviders/IServerListProvider.cs b/Nebula.Launcher/ServerListProviders/IServerListProvider.cs
index d7d2528..6d87045 100644
--- a/Nebula.Launcher/ServerListProviders/IServerListProvider.cs
+++ b/Nebula.Launcher/ServerListProviders/IServerListProvider.cs
@@ -5,10 +5,11 @@ using Nebula.Launcher.ViewModels.Pages;
namespace Nebula.Launcher.ServerListProviders;
-public interface IServerListProvider
+public interface IServerListProvider : IDisposable
{
public bool IsLoaded { get; }
public Action? OnLoaded { get; set; }
+ public Action? OnDisposed { get; set; }
public IEnumerable GetServers();
public IEnumerable GetErrors();
diff --git a/Nebula.Launcher/ServerListProviders/TestServerList.cs b/Nebula.Launcher/ServerListProviders/TestServerList.cs
index dca0fe0..5a8e863 100644
--- a/Nebula.Launcher/ServerListProviders/TestServerList.cs
+++ b/Nebula.Launcher/ServerListProviders/TestServerList.cs
@@ -10,6 +10,8 @@ public sealed class TestServerList : IServerListProvider
{
public bool IsLoaded => true;
public Action? OnLoaded { get; set; }
+ public Action? OnDisposed { get; set; }
+
public IEnumerable GetServers()
{
return [new ServerEntryModelView(),new ServerEntryModelView()];
@@ -24,4 +26,9 @@ public sealed class TestServerList : IServerListProvider
{
}
+
+ public void Dispose()
+ {
+ OnDisposed?.Invoke();
+ }
}
\ No newline at end of file
diff --git a/Nebula.Launcher/Services/InstanceRunningContainer.cs b/Nebula.Launcher/Services/InstanceRunningContainer.cs
new file mode 100644
index 0000000..88eb987
--- /dev/null
+++ b/Nebula.Launcher/Services/InstanceRunningContainer.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using Nebula.Launcher.Models;
+using Nebula.Launcher.ProcessHelper;
+using Nebula.Launcher.ViewModels;
+using Nebula.Shared;
+using Nebula.Shared.Services;
+
+namespace Nebula.Launcher.Services;
+
+[ServiceRegister]
+public sealed class InstanceRunningContainer(PopupMessageService popupMessageService, DebugService debugService)
+{
+ private readonly InstanceKeyPool _keyPool = new();
+ private readonly Dictionary _processCache = new();
+ private readonly Dictionary _contentLoggerCache = new();
+ private readonly Dictionary _keyCache = new();
+
+ public Action? IsRunningChanged;
+
+ public InstanceKey RegisterInstance(IProcessStartInfoProvider provider)
+ {
+ var id = _keyPool.Take();
+
+ var currentContentLogConsumer = new ContentLogConsumer();
+ var logBridge = new DebugLoggerBridge(debugService.GetLogger("PROCESS_"+id.Id));
+ var logContainer = new ProcessLogConsumerCollection();
+ logContainer.RegisterLogger(currentContentLogConsumer);
+ logContainer.RegisterLogger(logBridge);
+
+ var handler = new ProcessRunHandler(provider, logContainer);
+ handler.OnProcessExited += OnProcessExited;
+
+ _processCache[id] = handler;
+ _contentLoggerCache[id] = currentContentLogConsumer;
+ _keyCache[handler] = id;
+
+ return id;
+ }
+
+ public void Popup(InstanceKey instanceKey)
+ {
+ if(!_contentLoggerCache.TryGetValue(instanceKey, out var handler))
+ return;
+
+ handler.Popup(popupMessageService);
+ }
+
+ public void Run(InstanceKey instanceKey)
+ {
+ if(!_processCache.TryGetValue(instanceKey, out var process))
+ return;
+
+ process.Start();
+ IsRunningChanged?.Invoke(instanceKey, true);
+ }
+
+ public void Stop(InstanceKey instanceKey)
+ {
+ if(!_processCache.TryGetValue(instanceKey, out var process))
+ return;
+
+ process.Stop();
+ }
+
+ public bool IsRunning(InstanceKey instanceKey)
+ {
+ return _processCache.ContainsKey(instanceKey);
+ }
+
+ private void RemoveProcess(ProcessRunHandler handler)
+ {
+ if(handler.Disposed) return;
+
+ var key = _keyCache[handler];
+ IsRunningChanged?.Invoke(key, false);
+ _processCache.Remove(key);
+ _keyCache.Remove(handler);
+ _contentLoggerCache.Remove(key);
+ }
+
+ private void OnProcessExited(ProcessRunHandler obj)
+ {
+ obj.OnProcessExited -= OnProcessExited;
+ RemoveProcess(obj);
+ }
+}
\ No newline at end of file
diff --git a/Nebula.Launcher/Services/LocalizationService.cs b/Nebula.Launcher/Services/LocalizationService.cs
index d3fabaa..d0bb3d5 100644
--- a/Nebula.Launcher/Services/LocalizationService.cs
+++ b/Nebula.Launcher/Services/LocalizationService.cs
@@ -11,7 +11,7 @@ using Nebula.Shared.Services;
namespace Nebula.Launcher.Services;
[ConstructGenerator, ServiceRegister]
-public partial class LocalizationService
+public sealed partial class LocalizationService
{
[GenerateProperty] private ConfigurationService ConfigurationService { get; }
[GenerateProperty] private DebugService DebugService { get; }
@@ -40,7 +40,6 @@ public partial class LocalizationService
Console.WriteLine(error);
}
-
_currentMessageContext = mc;
} catch (Exception e) {
DebugService.GetLogger("localisationService").Error(e);
diff --git a/Nebula.Launcher/ColorUtils.cs b/Nebula.Launcher/Utils/ColorUtils.cs
similarity index 91%
rename from Nebula.Launcher/ColorUtils.cs
rename to Nebula.Launcher/Utils/ColorUtils.cs
index 8b1a930..64c5e4a 100644
--- a/Nebula.Launcher/ColorUtils.cs
+++ b/Nebula.Launcher/Utils/ColorUtils.cs
@@ -3,7 +3,7 @@ using System.Security.Cryptography;
using System.Text;
using Avalonia.Media;
-namespace Nebula.Launcher.ViewModels.Pages;
+namespace Nebula.Launcher.Utils;
public static class ColorUtils
{
diff --git a/Nebula.Launcher/Services/ExplorerService.cs b/Nebula.Launcher/Utils/ExplorerUtils.cs
similarity index 89%
rename from Nebula.Launcher/Services/ExplorerService.cs
rename to Nebula.Launcher/Utils/ExplorerUtils.cs
index 192c30a..b70ff99 100644
--- a/Nebula.Launcher/Services/ExplorerService.cs
+++ b/Nebula.Launcher/Utils/ExplorerUtils.cs
@@ -1,12 +1,11 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
-using Nebula.Shared;
-namespace Nebula.Launcher.Services;
+namespace Nebula.Launcher.Utils;
-public static class ExplorerHelper
+public static class ExplorerUtils
{
public static void OpenFolder(string path)
{
diff --git a/Nebula.Launcher/Utils/VCRuntimeDllChecker.cs b/Nebula.Launcher/Utils/VCRuntimeDllChecker.cs
new file mode 100644
index 0000000..db4f9f8
--- /dev/null
+++ b/Nebula.Launcher/Utils/VCRuntimeDllChecker.cs
@@ -0,0 +1,29 @@
+using System;
+using System.IO;
+
+namespace Nebula.Launcher.Utils;
+
+public static class VCRuntimeDllChecker
+{
+ public static bool AreVCRuntimeDllsPresent()
+ {
+ if (!OperatingSystem.IsWindows()) return true;
+
+ string systemDir = Environment.SystemDirectory;
+ string[] requiredDlls = {
+ "msvcp140.dll",
+ "vcruntime140.dll"
+ };
+
+ foreach (var dll in requiredDlls)
+ {
+ var path = Path.Combine(systemDir, dll);
+ if (!File.Exists(path))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/Nebula.Launcher/ViewModels/MainViewModel.cs b/Nebula.Launcher/ViewModels/MainViewModel.cs
index 336a003..e0844f8 100644
--- a/Nebula.Launcher/ViewModels/MainViewModel.cs
+++ b/Nebula.Launcher/ViewModels/MainViewModel.cs
@@ -8,6 +8,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Nebula.Launcher.Models;
using Nebula.Launcher.Services;
+using Nebula.Launcher.Utils;
using Nebula.Launcher.ViewModels.Pages;
using Nebula.Launcher.ViewModels.Popup;
using Nebula.Launcher.Views;
@@ -208,7 +209,7 @@ public partial class MainViewModel : ViewModelBase
public void OpenRootPath()
{
- ExplorerHelper.OpenFolder(FileService.RootPath);
+ ExplorerUtils.OpenFolder(FileService.RootPath);
}
public void OpenLink()
@@ -248,16 +249,18 @@ public partial class MainViewModel : ViewModelBase
else
_viewQueue.Remove(viewModelBase);
}
-
-
- [RelayCommand]
- private void TriggerPane()
+
+ public void TriggerPane()
{
IsPaneOpen = !IsPaneOpen;
}
- [RelayCommand]
- public void ClosePopup()
+ public void CloseCurrentPopup()
+ {
+ CurrentPopup?.Dispose();
+ }
+
+ private void ClosePopup()
{
var viewModelBase = _viewQueue.FirstOrDefault();
if (viewModelBase is null)
@@ -272,29 +275,4 @@ public partial class MainViewModel : ViewModelBase
CurrentPopup = viewModelBase;
}
-}
-
-public static class VCRuntimeDllChecker
-{
- public static bool AreVCRuntimeDllsPresent()
- {
- if (!OperatingSystem.IsWindows()) return true;
-
- string systemDir = Environment.SystemDirectory;
- string[] requiredDlls = {
- "msvcp140.dll",
- "vcruntime140.dll"
- };
-
- foreach (var dll in requiredDlls)
- {
- var path = Path.Combine(systemDir, dll);
- if (!File.Exists(path))
- {
- return false;
- }
- }
-
- return true;
- }
}
\ No newline at end of file
diff --git a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs
index 0b2dee9..71d25f2 100644
--- a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs
+++ b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs
@@ -5,6 +5,7 @@ using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Nebula.Launcher.Services;
+using Nebula.Launcher.Utils;
using Nebula.Launcher.ViewModels.Popup;
using Nebula.Launcher.Views.Pages;
using Nebula.Shared;
@@ -69,7 +70,7 @@ public partial class ConfigurationViewModel : ViewModelBase
public void OpenDataFolder()
{
- ExplorerHelper.OpenFolder(FileService.RootPath);
+ ExplorerUtils.OpenFolder(FileService.RootPath);
}
public void ExportLogs()
@@ -79,7 +80,7 @@ public partial class ConfigurationViewModel : ViewModelBase
Directory.CreateDirectory(path);
ZipFile.CreateFromDirectory(logPath, Path.Join(path, DateTime.Now.ToString("yyyy-MM-dd") + ".zip"));
- ExplorerHelper.OpenFolder(path);
+ ExplorerUtils.OpenFolder(path);
}
public void RemoveAllContent()
diff --git a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs
index 5d3db86..53e66f3 100644
--- a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs
+++ b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs
@@ -11,6 +11,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Launcher.Models;
using Nebula.Launcher.Services;
+using Nebula.Launcher.Utils;
using Nebula.Launcher.ViewModels.Popup;
using Nebula.Launcher.Views;
using Nebula.Launcher.Views.Pages;
@@ -63,7 +64,7 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase, IContentHol
ContentService.Unpack(serverEntry.FileApi, myTempDir, loading.CreateLoadingContext());
loading.Dispose();
});
- ExplorerHelper.OpenFolder(tmpDir);
+ ExplorerUtils.OpenFolder(tmpDir);
}
public void OnGoEnter()
@@ -80,10 +81,7 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase, IContentHol
var cur = ServiceProvider.GetService()!;
cur.Init(this, ServerText.ToRobustUrl());
var curContent = cur.Go(new ContentPath(SearchText), CancellationService.Token);
- if(curContent == null)
- throw new NullReferenceException($"{SearchText} not found in {ServerText}");
-
- CurrentEntry = curContent;
+ CurrentEntry = curContent ?? throw new NullReferenceException($"{SearchText} not found in {ServerText}");
}
catch (Exception e)
{
diff --git a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs
index 382ec5f..4ba302d 100644
--- a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs
+++ b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs
@@ -23,22 +23,16 @@ namespace Nebula.Launcher.ViewModels.Pages;
public partial class ServerOverviewModel : ViewModelBase
{
[ObservableProperty] private string _searchText = string.Empty;
-
[ObservableProperty] private bool _isFilterVisible;
-
- [ObservableProperty] private ServerListView _currentServerList = new();
public readonly ServerFilter CurrentFilter = new();
-
[GenerateProperty] private IServiceProvider ServiceProvider { get; }
[GenerateProperty] private ConfigurationService ConfigurationService { get; }
[GenerateProperty] private FavoriteServerListProvider FavoriteServerListProvider { get; }
public ObservableCollection Items { get; private set; }
[ObservableProperty] private ServerListTabTemplate _selectedItem;
-
[GenerateProperty, DesignConstruct] private ServerViewContainer ServerViewContainer { get; }
-
- private Dictionary _viewCache = [];
+ [GenerateProperty, DesignConstruct] public ServerListViewModel CurrentServerList { get; }
//Design think
@@ -106,26 +100,19 @@ public partial class ServerOverviewModel : ViewModelBase
{
ServerViewContainer.Clear();
CurrentServerList.RefreshFromProvider();
- CurrentServerList.RequireStatusUpdate();
CurrentServerList.ApplyFilter(CurrentFilter);
}
partial void OnSelectedItemChanged(ServerListTabTemplate value)
{
- if (!_viewCache.TryGetValue(value.TabName, out var view))
- {
- view = ServerListView.TakeFrom(value.ServerListProvider);
- _viewCache[value.TabName] = view;
- }
-
- CurrentServerList = view;
+ CurrentServerList.Provider = value.ServerListProvider;
ApplyFilter();
}
}
[ServiceRegister]
-public class ServerViewContainer
+public sealed class ServerViewContainer
{
private readonly ViewHelperService _viewHelperService;
private readonly List _favorites = [];
@@ -212,6 +199,10 @@ public class ServerViewContainer
public void Clear()
{
+ foreach (var (_, value) in _entries)
+ {
+ value.Dispose();
+ }
_entries.Clear();
}
@@ -244,7 +235,7 @@ public class ServerViewContainer
}
}
-public interface IListEntryModelView
+public interface IListEntryModelView : IDisposable
{
}
diff --git a/Nebula.Launcher/ViewModels/Popup/PopupViewModelBase.cs b/Nebula.Launcher/ViewModels/Popup/PopupViewModelBase.cs
index cab3ed2..3593bbd 100644
--- a/Nebula.Launcher/ViewModels/Popup/PopupViewModelBase.cs
+++ b/Nebula.Launcher/ViewModels/Popup/PopupViewModelBase.cs
@@ -9,10 +9,12 @@ public abstract class PopupViewModelBase : ViewModelBase, IDisposable
public abstract string Title { get; }
public abstract bool IsClosable { get; }
+ public Action? OnDisposing;
public void Dispose()
{
OnDispose();
+ OnDisposing?.Invoke(this);
PopupMessageService.ClosePopup(this);
}
diff --git a/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs
index 7e32636..40b8ca5 100644
--- a/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs
+++ b/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs
@@ -2,8 +2,6 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
-using Avalonia.Media;
-using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Launcher.Models;
@@ -13,7 +11,6 @@ using Nebula.Launcher.Views;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
using Nebula.Shared.ViewHelper;
-using BindingFlags = System.Reflection.BindingFlags;
namespace Nebula.Launcher.ViewModels;
@@ -23,7 +20,6 @@ public sealed partial class ServerCompoundEntryViewModel :
ViewModelBase, IFavoriteEntryModelView, IFilterConsumer, IListEntryModelView, IEntryNameHolder
{
[ObservableProperty] private ServerEntryModelView? _currentEntry;
- [ObservableProperty] private Control? _entryControl;
[ObservableProperty] private string _message = "Loading server entry...";
[ObservableProperty] private bool _isFavorite;
[ObservableProperty] private bool _loading = true;
@@ -68,7 +64,7 @@ public sealed partial class ServerCompoundEntryViewModel :
Message = "Loading server entry...";
var status = await RestService.GetAsync(_url.StatusUri, cancellationToken);
- CurrentEntry = ServiceProvider.GetService()!.WithData(_url,name, status);
+ CurrentEntry = ServiceProvider.GetService()!.WithData(_url, name, status);
CurrentEntry.IsFavorite = IsFavorite;
CurrentEntry.Loading = false;
CurrentEntry.ProcessFilter(_currentFilter);
@@ -102,4 +98,9 @@ public sealed partial class ServerCompoundEntryViewModel :
if(CurrentEntry is IFilterConsumer filterConsumer)
filterConsumer.ProcessFilter(serverFilter);
}
+
+ public void Dispose()
+ {
+ CurrentEntry?.Dispose();
+ }
}
\ No newline at end of file
diff --git a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs
index 6cd2ff4..c801993 100644
--- a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs
+++ b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs
@@ -1,7 +1,5 @@
using System;
-using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Controls;
@@ -23,7 +21,7 @@ namespace Nebula.Launcher.ViewModels;
[ViewModelRegister(typeof(ServerEntryView), false)]
[ConstructGenerator]
-public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, IListEntryModelView, IFavoriteEntryModelView, IEntryNameHolder
+public sealed partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, IListEntryModelView, IFavoriteEntryModelView, IEntryNameHolder
{
[ObservableProperty] private string _description = "Fetching info...";
[ObservableProperty] private bool _expandInfo;
@@ -42,10 +40,7 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis
private ILogger _logger;
private ServerInfo? _serverInfo;
- private ContentLogConsumer _currentContentLogConsumer;
- private ProcessRunHandler? _currentInstance;
-
- public LogPopupModelView CurrLog;
+ private InstanceKey _instanceKey;
public RobustUrl Address { get; private set; }
[GenerateProperty] private AccountInfoViewModel AccountInfoViewModel { get; }
[GenerateProperty] private CancellationService CancellationService { get; } = default!;
@@ -56,6 +51,7 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis
[GenerateProperty] private MainViewModel MainViewModel { get; } = default!;
[GenerateProperty] private FavoriteServerListProvider FavoriteServerListProvider { get; } = default!;
[GenerateProperty] private GameRunnerPreparer GameRunnerPreparer { get; } = default!;
+ [GenerateProperty] private InstanceRunningContainer InstanceRunningContainer { get; } = default!;
public ServerStatus Status { get; private set; } =
new(
@@ -101,14 +97,19 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis
["rp:hrp", "18+"],
"Antag", 15, 5, 1, false
, DateTime.Now, 100);
- Address = "ss14://localhost".ToRobustUrl();
+ Address = "ss14://localhost";
}
protected override void Initialise()
{
_logger = DebugService.GetLogger(this);
- CurrLog = ViewHelperService.GetViewModel();
- _currentContentLogConsumer = new(CurrLog, PopupMessageService);
+ InstanceRunningContainer.IsRunningChanged += IsRunningChanged;
+ }
+
+ private void IsRunningChanged(InstanceKey arg1, bool isRunning)
+ {
+ if(arg1.Equals(_instanceKey))
+ RunVisible = !isRunning;
}
public void ProcessFilter(ServerFilter? serverFilter)
@@ -162,13 +163,11 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis
public void RunInstance()
{
- CurrLog.Clear();
Task.Run(async ()=> await RunInstanceAsync());
}
public void RunInstanceIgnoreAuth()
{
- CurrLog.Clear();
Task.Run(async ()=> await RunInstanceAsync(true));
}
@@ -190,14 +189,11 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis
viewModelLoading.LoadingName = "Loading instance...";
PopupMessageService.Popup(viewModelLoading);
- _currentInstance =
+ var currProcessStartProvider =
await GameRunnerPreparer.GetGameProcessStartInfoProvider(Address, viewModelLoading, CancellationService.Token);
_logger.Log("Preparing instance...");
- _currentInstance.RegisterLogger(_currentContentLogConsumer);
- _currentInstance.RegisterLogger(new DebugLoggerBridge(DebugService.GetLogger($"PROCESS_{Random.Shared.Next(65535)}")));
- _currentInstance.OnProcessExited += OnProcessExited;
- RunVisible = false;
- _currentInstance.Start();
+ _instanceKey = InstanceRunningContainer.RegisterInstance(currProcessStartProvider);
+ InstanceRunningContainer.Run(_instanceKey);
_logger.Log("Starting instance..." + RealName);
}
catch (Exception e)
@@ -205,28 +201,17 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis
var error = new Exception("Error while attempt run instance", e);
_logger.Error(error);
PopupMessageService.Popup(error);
- RunVisible = true;
}
}
- private void OnProcessExited(ProcessRunHandler obj)
- {
- RunVisible = true;
- if (_currentInstance == null) return;
-
- _currentInstance.OnProcessExited -= OnProcessExited;
- _currentInstance.Dispose();
- _currentInstance = null;
- }
-
public void StopInstance()
{
- _currentInstance?.Stop();
+ InstanceRunningContainer.Stop(_instanceKey);
}
public void ReadLog()
{
- PopupMessageService.Popup(CurrLog);
+ InstanceRunningContainer.Popup(_instanceKey);
}
public async void ExpandInfoRequired()
@@ -243,9 +228,39 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis
if (info.Links is null) return;
foreach (var link in info.Links) Links.Add(link);
}
+
+ public void Dispose()
+ {
+ _logger.Dispose();
+ }
}
-public class LinkGoCommand : ICommand
+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()
{
diff --git a/Nebula.Launcher/ViewModels/ServerListViewModel.cs b/Nebula.Launcher/ViewModels/ServerListViewModel.cs
new file mode 100644
index 0000000..d9cd09b
--- /dev/null
+++ b/Nebula.Launcher/ViewModels/ServerListViewModel.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Collections.ObjectModel;
+using Avalonia.Controls;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Nebula.Launcher.Models;
+using Nebula.Launcher.ServerListProviders;
+using Nebula.Launcher.ViewModels.Pages;
+using Nebula.Launcher.Views;
+using Nebula.Shared.ViewHelper;
+
+namespace Nebula.Launcher.ViewModels;
+
+[ViewModelRegister(typeof(ServerListView), false)]
+public partial class ServerListViewModel : ViewModelBase
+{
+ [ObservableProperty] private bool _isLoading;
+
+ public ServerListViewModel()
+ {
+ if (Design.IsDesignMode)
+ {
+ Provider = new TestServerList();
+ }
+ }
+
+ private IServerListProvider? _provider;
+
+ public ObservableCollection ServerList { get; } = new();
+ public ObservableCollection ErrorList { get; } = new();
+
+ public IServerListProvider Provider
+ {
+ get => _provider ?? throw new Exception();
+
+ set
+ {
+ _provider = value;
+ _provider.OnDisposed += OnProviderDisposed;
+ if (_provider is IServerListDirtyInvoker invoker)
+ {
+ invoker.Dirty += OnDirty;
+ }
+
+ if(!_provider.IsLoaded)
+ RefreshFromProvider();
+ else
+ {
+ Clear();
+ PasteServersFromList();
+ }
+ }
+ }
+
+ private void OnProviderDisposed()
+ {
+ Provider.OnLoaded -= RefreshRequired;
+ Provider.OnDisposed -= OnProviderDisposed;
+ if (Provider is IServerListDirtyInvoker invoker)
+ {
+ invoker.Dirty -= OnDirty;
+ }
+
+ _provider = null;
+ }
+
+ private ServerFilter? _currentFilter;
+
+ public void RefreshFromProvider()
+ {
+ if (IsLoading)
+ return;
+
+ Clear();
+ StartLoading();
+
+ Provider.LoadServerList();
+
+ if (Provider.IsLoaded) PasteServersFromList();
+ else Provider.OnLoaded += RefreshRequired;
+ }
+
+ public void ApplyFilter(ServerFilter? filter)
+ {
+ _currentFilter = filter;
+
+ if(IsLoading)
+ return;
+
+ foreach (var serverView in ServerList)
+ {
+ if(serverView is IFilterConsumer filterConsumer)
+ filterConsumer.ProcessFilter(filter);
+ }
+ }
+
+ private void OnDirty()
+ {
+ RefreshFromProvider();
+ }
+
+ private void Clear()
+ {
+ ErrorList.Clear();
+ ServerList.Clear();
+ }
+
+ private void PasteServersFromList()
+ {
+ foreach (var serverEntry in Provider.GetServers())
+ {
+ ServerList.Add(serverEntry);
+ if(serverEntry is IFilterConsumer serverFilter)
+ serverFilter.ProcessFilter(_currentFilter);
+ }
+
+ foreach (var error in Provider.GetErrors())
+ {
+ ErrorList.Add(error);
+ }
+
+ EndLoading();
+ }
+
+ private void RefreshRequired()
+ {
+ PasteServersFromList();
+ Provider.OnLoaded -= RefreshRequired;
+ }
+
+ private void StartLoading()
+ {
+ Clear();
+ IsLoading = true;
+ }
+
+ private void EndLoading()
+ {
+ IsLoading = false;
+ }
+
+ protected override void InitialiseInDesignMode()
+ {
+ }
+
+ protected override void Initialise()
+ {
+ }
+}
\ No newline at end of file
diff --git a/Nebula.Launcher/Views/MainView.axaml b/Nebula.Launcher/Views/MainView.axaml
index c97c5aa..6285864 100644
--- a/Nebula.Launcher/Views/MainView.axaml
+++ b/Nebula.Launcher/Views/MainView.axaml
@@ -77,7 +77,7 @@