- fix: memory leak part 1

This commit is contained in:
2025-12-11 21:47:54 +03:00
parent 0c6bbaadac
commit f7cec5d093
32 changed files with 506 additions and 291 deletions

View File

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

View File

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

View File

@@ -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)
{

View File

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

View File

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

View File

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

View File

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

View 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()
{
}
}