From 830cb38d9f251f84c932076302e721f9898d815e Mon Sep 17 00:00:00 2001 From: Cinka Date: Fri, 13 Mar 2026 20:29:53 +0300 Subject: [PATCH] - tweak: less memory tweak part 2 --- Nebula.Launcher/Models/ListItemTemplate.cs | 2 +- .../FavoriteServerListProvider.cs | 31 +++---- .../HubServerListProvider.cs | 46 ++++------ .../IServerListProvider.cs | 17 ++-- .../ServerListProviders/TestServerList.cs | 19 ++--- .../Services/InstanceRunningContainer.cs | 7 +- .../ViewModels/Pages/ServerOverviewModel.cs | 83 +++++++++++++------ .../ViewModels/Popup/AddFavoriteViewModel.cs | 6 ++ .../Popup/IsLoginCredentialsNullPopup.cs | 8 +- .../ServerCompoundEntryModelView.cs | 6 +- ...ryModelView.cs => ServerEntryViewModel.cs} | 4 +- .../ViewModels/ServerListViewModel.cs | 24 ++++-- Nebula.Launcher/Views/ServerEntryView.axaml | 6 +- Nebula.Launcher/Views/ServerListView.axaml | 10 +-- Nebula.sln.DotSettings.user | 4 + 15 files changed, 151 insertions(+), 122 deletions(-) rename Nebula.Launcher/ViewModels/{ServerEntryModelView.cs => ServerEntryViewModel.cs} (98%) diff --git a/Nebula.Launcher/Models/ListItemTemplate.cs b/Nebula.Launcher/Models/ListItemTemplate.cs index 11dd4f4..ec87c98 100644 --- a/Nebula.Launcher/Models/ListItemTemplate.cs +++ b/Nebula.Launcher/Models/ListItemTemplate.cs @@ -5,7 +5,7 @@ using Nebula.Launcher.ServerListProviders; namespace Nebula.Launcher.Models; public record ListItemTemplate(Type ModelType, string IconKey, string Label); -public record ServerListTabTemplate(BaseServerListProvider ServerListProvider, string TabName); +public record ServerListTabTemplate(IServerListProvider ServerListProvider, string TabName); public record ServerHubRecord( [property:JsonPropertyName("name")] string Name, [property:JsonPropertyName("url")] string MainUrl); \ No newline at end of file diff --git a/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs b/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs index b09fe26..82dece3 100644 --- a/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs +++ b/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Avalonia; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Layout; using Avalonia.Media; @@ -18,38 +19,32 @@ using Nebula.Shared.Utils; namespace Nebula.Launcher.ServerListProviders; [ServiceRegister, ConstructGenerator] -public sealed partial class FavoriteServerListProvider : BaseServerListProvider +public sealed partial class FavoriteServerListProvider : IServerListProvider { [GenerateProperty] private ConfigurationService ConfigurationService { get; } [GenerateProperty] private IServiceProvider ServiceProvider { get; } [GenerateProperty] private ServerViewContainer ServerViewContainer { get; } - //[GenerateProperty] private ServerOverviewModel ServerOverviewModel { get; } + + public Action? OnRefreshRequired; private string[] _rawServerLists = []; - public override void LoadServerList( - ObservableCollection servers, - ObservableCollection exceptions) + public void LoadServerList( + AvaloniaList servers, + AvaloniaList exceptions) { - base.LoadServerList(servers, exceptions); - foreach (var server in _rawServerLists) { - var container = ServerViewContainer.Get(server.ToRobustUrl()); + var container = ServerViewContainer.Get(server); servers.Add(container); } servers.Add(new AddFavoriteButton(ServiceProvider)); } - public override void Dispose() + public void AddFavorite(ServerEntryViewModel entryViewModel) { - - } - - public void AddFavorite(ServerEntryModelView entryModelView) - { - AddFavorite(entryModelView.Address); + AddFavorite(entryViewModel.Address); } public void AddFavorite(RobustUrl robustUrl) @@ -60,10 +55,10 @@ public sealed partial class FavoriteServerListProvider : BaseServerListProvider if(ServerViewContainer.Get(robustUrl) is IFavoriteEntryModelView favoriteView) favoriteView.IsFavorite = true; } - public void RemoveFavorite(ServerEntryModelView entryModelView) + public void RemoveFavorite(ServerEntryViewModel entryViewModel) { var servers = GetFavoriteEntries(); - servers.Remove(entryModelView.Address.ToString()); + servers.Remove(entryViewModel.Address.ToString()); ConfigurationService.SetConfigValue(LauncherConVar.Favorites, servers.ToArray()); } @@ -93,7 +88,7 @@ public sealed partial class FavoriteServerListProvider : BaseServerListProvider } _rawServerLists = value; - //ServerOverviewModel.UpdateRequired(); + OnRefreshRequired?.Invoke(); } private void InitialiseInDesignMode(){} diff --git a/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs b/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs index 318894e..50d9f25 100644 --- a/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs +++ b/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs @@ -1,22 +1,20 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Threading; -using Nebula.Launcher.Models; using Nebula.Launcher.Services; using Nebula.Launcher.ViewModels.Pages; using Nebula.Shared; using Nebula.Shared.Models; using Nebula.Shared.Services; -using Nebula.Shared.Utils; namespace Nebula.Launcher.ServerListProviders; [ServiceRegister(null, false), ConstructGenerator] -public sealed partial class HubServerListProvider : BaseServerListProvider +public sealed partial class HubServerListProvider : IServerListProvider, IDisposable { private CancellationTokenSource? _cts; private readonly SemaphoreSlim _loadLock = new(1, 1); @@ -32,34 +30,27 @@ public sealed partial class HubServerListProvider : BaseServerListProvider return this; } - public override void LoadServerList( - ObservableCollection servers, - ObservableCollection exceptions) + public void LoadServerList( + AvaloniaList servers, + AvaloniaList exceptions) { - base.LoadServerList(servers, exceptions); - servers.Add(new LoadingServerEntry()); Task.Run(() => LoadServerListAsync(servers, exceptions)); } private void SyncServers(List servers, - ObservableCollection collection) + AvaloniaList collection) { collection.Clear(); - foreach (var server in servers) - { - collection.Add(server); - } + collection.AddRange(servers); } private async Task LoadServerListAsync( - ObservableCollection servers, - ObservableCollection exceptions) + AvaloniaList servers, + AvaloniaList exceptions) { CancellationTokenSource localCts; - var serverList = new List(); - await _loadLock.WaitAsync(); try { @@ -84,26 +75,25 @@ public sealed partial class HubServerListProvider : BaseServerListProvider serversRaw.Sort(new ServerComparer()); localCts.Token.ThrowIfCancellationRequested(); - - foreach (var info in serversRaw) - { - var viewContainer = - ServerViewContainer.Get(info.Address.ToRobustUrl(), info.StatusData); - - serverList.Add(viewContainer); - } Dispatcher.UIThread.Invoke(() => { + var serverList = new List(); + + foreach (var info in serversRaw) + { + serverList.Add(ServerViewContainer.Get(info.Address, info.StatusData)); + } SyncServers(serverList, servers); }); } catch (OperationCanceledException) { - + // Ignore cancel think } catch (Exception e) { + Console.WriteLine(e); exceptions.Add( new Exception( $"Some error while loading server list from {_hubUrl}. See inner exception", @@ -116,7 +106,7 @@ public sealed partial class HubServerListProvider : BaseServerListProvider private void Initialise(){} private void InitialiseInDesignMode(){} - public override void Dispose() + public void Dispose() { _cts?.Dispose(); } diff --git a/Nebula.Launcher/ServerListProviders/IServerListProvider.cs b/Nebula.Launcher/ServerListProviders/IServerListProvider.cs index 7bd359b..4c5dad3 100644 --- a/Nebula.Launcher/ServerListProviders/IServerListProvider.cs +++ b/Nebula.Launcher/ServerListProviders/IServerListProvider.cs @@ -1,19 +1,12 @@ using System; -using System.Collections.ObjectModel; -using CommunityToolkit.Mvvm.ComponentModel; +using Avalonia.Collections; using Nebula.Launcher.ViewModels.Pages; namespace Nebula.Launcher.ServerListProviders; -public abstract class BaseServerListProvider : ObservableObject, IDisposable +public interface IServerListProvider { - public virtual void LoadServerList( - ObservableCollection servers, - ObservableCollection exceptions) - { - servers.Clear(); - exceptions.Clear(); - } - - public abstract void Dispose(); + public void LoadServerList( + AvaloniaList servers, + AvaloniaList exceptions); } \ No newline at end of file diff --git a/Nebula.Launcher/ServerListProviders/TestServerList.cs b/Nebula.Launcher/ServerListProviders/TestServerList.cs index f4c8fdd..a942fef 100644 --- a/Nebula.Launcher/ServerListProviders/TestServerList.cs +++ b/Nebula.Launcher/ServerListProviders/TestServerList.cs @@ -1,26 +1,21 @@ using System; using System.Collections.ObjectModel; +using Avalonia.Collections; using Nebula.Launcher.ViewModels; using Nebula.Launcher.ViewModels.Pages; namespace Nebula.Launcher.ServerListProviders; -public sealed class TestServerList : BaseServerListProvider +public sealed class TestServerList : IServerListProvider { - public override void LoadServerList( - ObservableCollection servers, - ObservableCollection exceptions) + public void LoadServerList( + AvaloniaList servers, + AvaloniaList exceptions) { - base.LoadServerList(servers, exceptions); - servers.Add(new ServerEntryModelView()); - servers.Add(new ServerEntryModelView()); + servers.Add(new ServerEntryViewModel()); + servers.Add(new ServerEntryViewModel()); exceptions.Add(new Exception("Oh no!")); } - - public override void Dispose() - { - - } } \ No newline at end of file diff --git a/Nebula.Launcher/Services/InstanceRunningContainer.cs b/Nebula.Launcher/Services/InstanceRunningContainer.cs index fa7f03f..ff05808 100644 --- a/Nebula.Launcher/Services/InstanceRunningContainer.cs +++ b/Nebula.Launcher/Services/InstanceRunningContainer.cs @@ -3,13 +3,18 @@ using System.Collections.Generic; using Nebula.Launcher.Models; using Nebula.Launcher.ProcessHelper; using Nebula.Launcher.ViewModels; +using Nebula.Launcher.ViewModels.Pages; using Nebula.Shared; using Nebula.Shared.Services; namespace Nebula.Launcher.Services; [ServiceRegister] -public sealed class InstanceRunningContainer(PopupMessageService popupMessageService, DebugService debugService) +public sealed class InstanceRunningContainer( + PopupMessageService popupMessageService, + DebugService debugService, + ServerViewContainer container + ) { private readonly InstanceKeyPool _keyPool = new(); private readonly Dictionary _processCache = new(); diff --git a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs index ff23862..9dbee0b 100644 --- a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading; +using Avalonia.Controls; using CommunityToolkit.Mvvm.ComponentModel; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; @@ -14,6 +15,7 @@ using Nebula.Launcher.Views.Pages; using Nebula.Shared; using Nebula.Shared.Models; using Nebula.Shared.Services; +using Nebula.Shared.Utils; using Nebula.Shared.ViewHelper; namespace Nebula.Launcher.ViewModels.Pages; @@ -49,9 +51,16 @@ public partial class ServerOverviewModel : ViewModelBase //real think protected override void Initialise() { + FavoriteServerListProvider.OnRefreshRequired += OnFavoriteRefreshRequired; ConfigurationService.SubscribeVarChanged(LauncherConVar.Hub, OnHubListChanged, true); } + private void OnFavoriteRefreshRequired() + { + if(CurrentServerList.Provider is FavoriteServerListProvider favoriteServerListProvider) + UpdateRequired(); + } + private void OnHubListChanged(ServerHubRecord[]? value) { var tempItems = new List(); @@ -147,6 +156,8 @@ public sealed class ServerViewContainer _entries.Clear(); } + public IListEntryModelView Get(string url, ServerStatus? serverStatus = null) => Get(url.ToRobustUrl(), serverStatus); + public IListEntryModelView Get(RobustUrl url, ServerStatus? serverStatus = null) { var key = url.ToString(); @@ -154,36 +165,13 @@ public sealed class ServerViewContainer lock (_entries) { - _customNames.TryGetValue(key, out var customName); - if (_entries.TryGetValue(key, out var weakEntry) && weakEntry.TryGetTarget(out entry)) { return entry; } - if (serverStatus is not null) - { - entry = _viewHelperService - .GetViewModel() - .WithData(url, customName, serverStatus); - } - else - { - entry = _viewHelperService - .GetViewModel() - .LoadServerEntry(url, customName, CancellationToken.None); - } - - if (entry is IFavoriteEntryModelView fav) - { - fav.IsFavorite = _favorites.Contains(key); - } - - if (entry is IFilterConsumer filterConsumer) - { - filterConsumer.ProcessFilter(_currentFilter); - } + entry = Create(url, serverStatus); _entries[key] = new WeakReference(entry); } @@ -191,6 +179,40 @@ public sealed class ServerViewContainer return entry; } + private IListEntryModelView Create(RobustUrl url, ServerStatus? serverStatus = null) + { + IListEntryModelView? entry; + var key = url.ToString(); + + _customNames.TryGetValue(key, out var customName); + + if (serverStatus is not null) + { + //entry = new ExampleEntry(serverStatus.Name); + entry = _viewHelperService + .GetViewModel() + .WithData(url, customName, serverStatus); + } + else + { + entry = _viewHelperService + .GetViewModel() + .LoadServerEntry(url, customName, CancellationToken.None); + } + + if (entry is IFavoriteEntryModelView fav) + { + fav.IsFavorite = _favorites.Contains(key); + } + + if (entry is IFilterConsumer filterConsumer) + { + filterConsumer.ProcessFilter(_currentFilter); + } + + return entry; + } + public void ApplyFilter(ServerFilter? filter) { _currentFilter = filter; @@ -276,6 +298,19 @@ public interface IListEntryModelView : IDisposable } +public sealed class ExampleEntry : StackPanel, IListEntryModelView +{ + public ExampleEntry(string name) + { + Children.Add(new Label { Content = name }); + } + + public void Dispose() + { + + } +} + public interface IFavoriteEntryModelView { public bool IsFavorite { get; set; } diff --git a/Nebula.Launcher/ViewModels/Popup/AddFavoriteViewModel.cs b/Nebula.Launcher/ViewModels/Popup/AddFavoriteViewModel.cs index 073d1b7..5daca3a 100644 --- a/Nebula.Launcher/ViewModels/Popup/AddFavoriteViewModel.cs +++ b/Nebula.Launcher/ViewModels/Popup/AddFavoriteViewModel.cs @@ -55,4 +55,10 @@ public partial class AddFavoriteViewModel : PopupViewModelBase _logger.Error(e); } } + + protected override void OnDispose() + { + base.OnDispose(); + _logger.Dispose(); + } } \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/Popup/IsLoginCredentialsNullPopup.cs b/Nebula.Launcher/ViewModels/Popup/IsLoginCredentialsNullPopup.cs index 175e419..79a0321 100644 --- a/Nebula.Launcher/ViewModels/Popup/IsLoginCredentialsNullPopup.cs +++ b/Nebula.Launcher/ViewModels/Popup/IsLoginCredentialsNullPopup.cs @@ -9,7 +9,7 @@ namespace Nebula.Launcher.ViewModels.Popup; [ConstructGenerator, ViewModelRegister(typeof(IsLoginCredentialsNullPopupView))] public partial class IsLoginCredentialsNullPopupViewModel : PopupViewModelBase { - private ServerEntryModelView _entry; + private ServerEntryViewModel _entryView; [GenerateProperty] public override PopupMessageService PopupMessageService { get; } [GenerateProperty, DesignConstruct] private ViewHelperService ViewHelperService { get; } @@ -22,15 +22,15 @@ public partial class IsLoginCredentialsNullPopupViewModel : PopupViewModelBase { } - public IsLoginCredentialsNullPopupViewModel WithServerEntry(ServerEntryModelView entryModelView) + public IsLoginCredentialsNullPopupViewModel WithServerEntry(ServerEntryViewModel entryViewModel) { - _entry = entryModelView; + _entryView = entryViewModel; return this; } public void Proceed() { - _entry.RunInstanceIgnoreAuth(); + _entryView.RunInstanceIgnoreAuth(); Dispose(); } diff --git a/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs index 7d491a7..93be233 100644 --- a/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs +++ b/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs @@ -26,7 +26,7 @@ public sealed partial class ServerCompoundEntryViewModel : private RobustUrl? _url; private ServerFilter? _currentFilter; - public ServerEntryModelView? CurrentEntry + public ServerEntryViewModel? CurrentEntry { get; set @@ -74,7 +74,7 @@ public sealed partial class ServerCompoundEntryViewModel : { } - public ServerCompoundEntryViewModel LoadWithEntry(ServerEntryModelView? entry) + public ServerCompoundEntryViewModel LoadWithEntry(ServerEntryViewModel? entry) { CurrentEntry = entry; return this; @@ -101,7 +101,7 @@ public sealed partial class ServerCompoundEntryViewModel : Message = "Loading server entry..."; var status = await RestService.GetAsync(_url.StatusUri, CancellationToken.None); - CurrentEntry = ServiceProvider.GetService()!.WithData(_url, null, status); + CurrentEntry = ServiceProvider.GetService()!.WithData(_url, null, status); Loading = false; } diff --git a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerEntryViewModel.cs similarity index 98% rename from Nebula.Launcher/ViewModels/ServerEntryModelView.cs rename to Nebula.Launcher/ViewModels/ServerEntryViewModel.cs index b2bf61a..1103bb7 100644 --- a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs +++ b/Nebula.Launcher/ViewModels/ServerEntryViewModel.cs @@ -21,7 +21,7 @@ namespace Nebula.Launcher.ViewModels; [ViewModelRegister(typeof(ServerEntryView), false)] [ConstructGenerator] -public sealed partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, IListEntryModelView, IFavoriteEntryModelView, IEntryNameHolder +public sealed partial class ServerEntryViewModel : ViewModelBase, IFilterConsumer, IListEntryModelView, IFavoriteEntryModelView, IEntryNameHolder { [ObservableProperty] private string _description = "Fetching info..."; [ObservableProperty] private bool _expandInfo; @@ -129,7 +129,7 @@ public sealed partial class ServerEntryModelView : ViewModelBase, IFilterConsume OnPropertyChanged(nameof(Status)); } - public ServerEntryModelView WithData(RobustUrl url, string? name, ServerStatus serverStatus) + public ServerEntryViewModel WithData(RobustUrl url, string? name, ServerStatus serverStatus) { Address = url; SetStatus(serverStatus); diff --git a/Nebula.Launcher/ViewModels/ServerListViewModel.cs b/Nebula.Launcher/ViewModels/ServerListViewModel.cs index 6289c31..572ec8f 100644 --- a/Nebula.Launcher/ViewModels/ServerListViewModel.cs +++ b/Nebula.Launcher/ViewModels/ServerListViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.ObjectModel; +using Avalonia.Collections; using Nebula.Launcher.ServerListProviders; using Nebula.Launcher.ViewModels.Pages; using Nebula.Launcher.Views; @@ -8,24 +9,31 @@ using Nebula.Shared.ViewHelper; namespace Nebula.Launcher.ViewModels; [ViewModelRegister(typeof(ServerListView), false)] -public partial class ServerListViewModel : ViewModelBase +public class ServerListViewModel : ViewModelBase { - public ObservableCollection ServerList { get; private set; } = new(); - public ObservableCollection ErrorList { get; private set; } = new(); - - private BaseServerListProvider? _provider; + public AvaloniaList ServerList { get; private set; } = new(); + public AvaloniaList ErrorList { get; private set; } = new(); + public IServerListProvider? Provider { get; private set; } public void ClearProvider() { + foreach (var serverEntry in ServerList) + { + if (serverEntry is IDisposable disposable) + { + disposable.Dispose(); + } + } + ServerList.Clear(); ErrorList.Clear(); GC.Collect(); GC.WaitForPendingFinalizers(); } - public void SetProvider(BaseServerListProvider provider) + public void SetProvider(IServerListProvider provider) { - _provider = provider; + Provider = provider; OnPropertyChanged(nameof(ServerList)); OnPropertyChanged(nameof(ErrorList)); @@ -35,7 +43,7 @@ public partial class ServerListViewModel : ViewModelBase public void RefreshFromProvider() { - _provider?.LoadServerList(ServerList, ErrorList); + Provider?.LoadServerList(ServerList, ErrorList); } protected override void InitialiseInDesignMode() diff --git a/Nebula.Launcher/Views/ServerEntryView.axaml b/Nebula.Launcher/Views/ServerEntryView.axaml index f559a88..b50e76b 100644 --- a/Nebula.Launcher/Views/ServerEntryView.axaml +++ b/Nebula.Launcher/Views/ServerEntryView.axaml @@ -3,7 +3,7 @@ d:DesignWidth="800" mc:Ignorable="d" x:Class="Nebula.Launcher.Views.ServerEntryView" - x:DataType="viewModels:ServerEntryModelView" + x:DataType="viewModels:ServerEntryViewModel" xmlns="https://github.com/avaloniaui" xmlns:converters="clr-namespace:Nebula.Launcher.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" @@ -16,7 +16,7 @@ xmlns:services="clr-namespace:Nebula.Launcher.Services" IsVisible="{Binding IsVisible}"> - +