- fix: memory leak part 1
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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<ServerFolderContentEntry>()!;
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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<ServerListTabTemplate> Items { get; private set; }
|
||||
[ObservableProperty] private ServerListTabTemplate _selectedItem;
|
||||
|
||||
[GenerateProperty, DesignConstruct] private ServerViewContainer ServerViewContainer { get; }
|
||||
|
||||
private Dictionary<string, ServerListView> _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<string> _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
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ public abstract class PopupViewModelBase : ViewModelBase, IDisposable
|
||||
|
||||
public abstract string Title { get; }
|
||||
public abstract bool IsClosable { get; }
|
||||
public Action<PopupViewModelBase>? OnDisposing;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
OnDispose();
|
||||
OnDisposing?.Invoke(this);
|
||||
PopupMessageService.ClosePopup(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ServerStatus>(_url.StatusUri, cancellationToken);
|
||||
|
||||
CurrentEntry = ServiceProvider.GetService<ServerEntryModelView>()!.WithData(_url,name, status);
|
||||
CurrentEntry = ServiceProvider.GetService<ServerEntryModelView>()!.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();
|
||||
}
|
||||
}
|
||||
@@ -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<GameProcessStartInfoProvider>? _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<LogPopupModelView>();
|
||||
_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<GameProcessStartInfoProvider> 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<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()
|
||||
{
|
||||
|
||||
148
Nebula.Launcher/ViewModels/ServerListViewModel.cs
Normal file
148
Nebula.Launcher/ViewModels/ServerListViewModel.cs
Normal file
@@ -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<IListEntryModelView> ServerList { get; } = new();
|
||||
public ObservableCollection<Exception> 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user