From 8f66bf9f099ee14e37f97cc84cca82219bd6d299 Mon Sep 17 00:00:00 2001 From: Cinka Date: Thu, 12 Mar 2026 21:25:38 +0300 Subject: [PATCH] - tweak: less memory tweak --- Nebula.Launcher/Models/ListItemTemplate.cs | 2 +- .../FavoriteServerListProvider.cs | 82 ++++------ .../HubServerListProvider.cs | 142 ++++++++++++------ .../IServerListProvider.cs | 26 ++-- .../ServerListProviders/TestServerList.cs | 31 ++-- .../ViewModels/Pages/ServerOverviewModel.cs | 42 ++++-- .../ViewModels/ServerListViewModel.cs | 131 ++-------------- Nebula.Launcher/Views/ServerListView.axaml | 5 - Nebula.Runner/Services/HarmonyService.cs | 23 ++- 9 files changed, 209 insertions(+), 275 deletions(-) diff --git a/Nebula.Launcher/Models/ListItemTemplate.cs b/Nebula.Launcher/Models/ListItemTemplate.cs index ec87c98..11dd4f4 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(IServerListProvider ServerListProvider, string TabName); +public record ServerListTabTemplate(BaseServerListProvider 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 ad8efb7..b09fe26 100644 --- a/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs +++ b/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Avalonia; using Avalonia.Controls; @@ -17,47 +18,35 @@ using Nebula.Shared.Utils; namespace Nebula.Launcher.ServerListProviders; [ServiceRegister, ConstructGenerator] -public sealed partial class FavoriteServerListProvider : IServerListProvider, IServerListDirtyInvoker +public sealed partial class FavoriteServerListProvider : BaseServerListProvider { [GenerateProperty] private ConfigurationService ConfigurationService { get; } [GenerateProperty] private IServiceProvider ServiceProvider { get; } [GenerateProperty] private ServerViewContainer ServerViewContainer { get; } - - private List _serverLists = []; - private string[] rawServerLists = []; + //[GenerateProperty] private ServerOverviewModel ServerOverviewModel { get; } - public bool IsLoaded { get; private set; } - public Action? OnLoaded { get; set; } - public Action? OnDisposed { get; set; } - public Action? Dirty { get; set; } - public IEnumerable GetServers() - { - return _serverLists; - } - - public IEnumerable GetErrors() - { - return []; - } - - public void LoadServerList() - { - IsLoaded = false; - _serverLists.Clear(); - var servers = GetFavoriteEntries(); - - var serverEntries = servers.Select(s => - ServerViewContainer.Get(s.ToRobustUrl()) - ); - - _serverLists.AddRange(serverEntries); - - _serverLists.Add(new AddFavoriteButton(ServiceProvider)); - - IsLoaded = true; - OnLoaded?.Invoke(); - } + private string[] _rawServerLists = []; + public override void LoadServerList( + ObservableCollection servers, + ObservableCollection exceptions) + { + base.LoadServerList(servers, exceptions); + + foreach (var server in _rawServerLists) + { + var container = ServerViewContainer.Get(server.ToRobustUrl()); + servers.Add(container); + } + + servers.Add(new AddFavoriteButton(ServiceProvider)); + } + + public override void Dispose() + { + + } + public void AddFavorite(ServerEntryModelView entryModelView) { AddFavorite(entryModelView.Address); @@ -87,7 +76,7 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS private List GetFavoriteEntries() { - return rawServerLists.ToList(); + return _rawServerLists.ToList(); } private void Initialise() @@ -99,26 +88,20 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS { if (value == null) { - rawServerLists = []; - Dirty?.Invoke(); + _rawServerLists = []; return; } - rawServerLists = value; - Dirty?.Invoke(); + _rawServerLists = value; + //ServerOverviewModel.UpdateRequired(); } private void InitialiseInDesignMode(){} - - public void Dispose() - { - OnDisposed?.Invoke(); - } } public sealed class AddFavoriteButton: Border, IListEntryModelView{ - private Button _addFavoriteButton = new Button(); + private readonly Button _addFavoriteButton = new(); public AddFavoriteButton(IServiceProvider serviceProvider) { Margin = new Thickness(5, 5, 5, 20); @@ -133,10 +116,5 @@ public sealed class AddFavoriteButton: Border, IListEntryModelView{ _addFavoriteButton.Content = "Add Favorite"; Child = _addFavoriteButton; } - public bool IsFavorite { get; set; } - - public void Dispose() - { - - } + public void Dispose(){} } \ No newline at end of file diff --git a/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs b/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs index 7e16b8e..318894e 100644 --- a/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs +++ b/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs @@ -1,7 +1,12 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Collections.ObjectModel; using System.Threading; +using System.Threading.Tasks; +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; @@ -11,81 +16,118 @@ using Nebula.Shared.Utils; namespace Nebula.Launcher.ServerListProviders; [ServiceRegister(null, false), ConstructGenerator] -public sealed partial class HubServerListProvider : IServerListProvider +public sealed partial class HubServerListProvider : BaseServerListProvider { + private CancellationTokenSource? _cts; + private readonly SemaphoreSlim _loadLock = new(1, 1); + [GenerateProperty] private RestService RestService { get; } [GenerateProperty] private ServerViewContainer ServerViewContainer { get; } - - public string HubUrl { get; set; } - - public bool IsLoaded { get; private set; } - public Action? OnLoaded { get; set; } - public Action? OnDisposed { get; set; } - private CancellationTokenSource? _cts; - private readonly List _servers = []; - private readonly List _errors = []; + private string _hubUrl; public HubServerListProvider With(string hubUrl) { - HubUrl = hubUrl; + _hubUrl = hubUrl; return this; } - - public IEnumerable GetServers() - { - return _servers; - } - public IEnumerable GetErrors() + public override void LoadServerList( + ObservableCollection servers, + ObservableCollection exceptions) { - return _errors; - } - - public async void LoadServerList() - { - if (_cts != null) - { - await _cts.CancelAsync(); - _cts = null; - } + base.LoadServerList(servers, exceptions); - _servers.Clear(); - _errors.Clear(); - IsLoaded = false; - _cts = new CancellationTokenSource(); + servers.Add(new LoadingServerEntry()); + Task.Run(() => LoadServerListAsync(servers, exceptions)); + } + + private void SyncServers(List servers, + ObservableCollection collection) + { + collection.Clear(); + foreach (var server in servers) + { + collection.Add(server); + } + } + + private async Task LoadServerListAsync( + ObservableCollection servers, + ObservableCollection exceptions) + { + CancellationTokenSource localCts; + + var serverList = new List(); + + await _loadLock.WaitAsync(); + try + { + _cts?.Cancel(); + _cts?.Dispose(); + + _cts = new CancellationTokenSource(); + localCts = _cts; + } + finally + { + _loadLock.Release(); + } try { - var servers = - await RestService.GetAsync>(new Uri(HubUrl), _cts.Token); - - servers.Sort(new ServerComparer()); - - if(_cts.Token.IsCancellationRequested) return; - - _servers.AddRange( - servers.Select(h=> - ServerViewContainer.Get(h.Address.ToRobustUrl(), h.StatusData) - ) + var serversRaw = await RestService.GetAsync>( + new Uri(_hubUrl), + localCts.Token ); + + 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(() => + { + SyncServers(serverList, servers); + }); + } + catch (OperationCanceledException) + { + } catch (Exception e) { - _errors.Add(new Exception($"Some error while loading server list from {HubUrl}. See inner exception", e)); - _errors.Add(e); + exceptions.Add( + new Exception( + $"Some error while loading server list from {_hubUrl}. See inner exception", + e + ) + ); } - - IsLoaded = true; - OnLoaded?.Invoke(); } private void Initialise(){} private void InitialiseInDesignMode(){} - public void Dispose() + public override void Dispose() { - OnDisposed?.Invoke(); _cts?.Dispose(); } +} + +public sealed class LoadingServerEntry : Label, IListEntryModelView +{ + public LoadingServerEntry() + { + Content = LocalizationService.GetString("server-list-loading"); + } + public void Dispose() + {} } \ No newline at end of file diff --git a/Nebula.Launcher/ServerListProviders/IServerListProvider.cs b/Nebula.Launcher/ServerListProviders/IServerListProvider.cs index 6d87045..7bd359b 100644 --- a/Nebula.Launcher/ServerListProviders/IServerListProvider.cs +++ b/Nebula.Launcher/ServerListProviders/IServerListProvider.cs @@ -1,23 +1,19 @@ using System; -using System.Collections.Generic; -using Nebula.Launcher.ViewModels; +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; using Nebula.Launcher.ViewModels.Pages; namespace Nebula.Launcher.ServerListProviders; -public interface IServerListProvider : IDisposable +public abstract class BaseServerListProvider : ObservableObject, IDisposable { - public bool IsLoaded { get; } - public Action? OnLoaded { get; set; } - public Action? OnDisposed { get; set; } - - public IEnumerable GetServers(); - public IEnumerable GetErrors(); - - public void LoadServerList(); -} + public virtual void LoadServerList( + ObservableCollection servers, + ObservableCollection exceptions) + { + servers.Clear(); + exceptions.Clear(); + } -public interface IServerListDirtyInvoker -{ - public Action? Dirty { get; set; } + public abstract void Dispose(); } \ No newline at end of file diff --git a/Nebula.Launcher/ServerListProviders/TestServerList.cs b/Nebula.Launcher/ServerListProviders/TestServerList.cs index a899b66..f4c8fdd 100644 --- a/Nebula.Launcher/ServerListProviders/TestServerList.cs +++ b/Nebula.Launcher/ServerListProviders/TestServerList.cs @@ -1,33 +1,26 @@ using System; -using System.Collections.Generic; +using System.Collections.ObjectModel; using Nebula.Launcher.ViewModels; using Nebula.Launcher.ViewModels.Pages; namespace Nebula.Launcher.ServerListProviders; -public sealed class TestServerList : IServerListProvider +public sealed class TestServerList : BaseServerListProvider { - public bool IsLoaded => true; - public Action? OnLoaded { get; set; } - public Action? OnDisposed { get; set; } - - public IEnumerable GetServers() + public override void LoadServerList( + ObservableCollection servers, + ObservableCollection exceptions) { - return [new ServerEntryModelView(),new ServerEntryModelView()]; + base.LoadServerList(servers, exceptions); + + servers.Add(new ServerEntryModelView()); + servers.Add(new ServerEntryModelView()); + + exceptions.Add(new Exception("Oh no!")); } - public IEnumerable GetErrors() - { - return [new Exception("On no!")]; - } - - public void LoadServerList() + public override void Dispose() { } - - public void Dispose() - { - OnDisposed?.Invoke(); - } } \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs index c1422bd..ff23862 100644 --- a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs @@ -24,16 +24,17 @@ public partial class ServerOverviewModel : ViewModelBase { [ObservableProperty] private string _searchText = string.Empty; [ObservableProperty] private bool _isFilterVisible; - - 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; } [GenerateProperty, DesignConstruct] public ServerListViewModel CurrentServerList { get; } + public ServerFilter CurrentFilter { get; } = new(); + public ObservableCollection Items { get; private set; } + //Design think protected override void InitialiseInDesignMode() @@ -73,13 +74,9 @@ public partial class ServerOverviewModel : ViewModelBase ApplyFilter(); } - public void ApplyFilter() + private void ApplyFilter() { - foreach (var entry in ServerViewContainer.Items) - { - if(entry is IFilterConsumer filterConsumer) - filterConsumer.ProcessFilter(CurrentFilter); - } + ServerViewContainer.ApplyFilter(CurrentFilter); } public void OnFilterChanged(FilterBoxChangedEventArgs args) @@ -100,15 +97,14 @@ public partial class ServerOverviewModel : ViewModelBase { ServerViewContainer.Clear(); CurrentServerList.RefreshFromProvider(); - CurrentServerList.ApplyFilter(CurrentFilter); } partial void OnSelectedItemChanged(ServerListTabTemplate value) { - CurrentServerList.Provider = value.ServerListProvider; + CurrentServerList.ClearProvider(); + CurrentServerList.SetProvider(value.ServerListProvider); ApplyFilter(); } - } [ServiceRegister] @@ -119,6 +115,7 @@ public sealed class ServerViewContainer private readonly Dictionary _customNames = []; private readonly Dictionary> _entries = new(); + private ServerFilter? _currentFilter; public ICollection Items => _entries.Values @@ -178,10 +175,14 @@ public sealed class ServerViewContainer .LoadServerEntry(url, customName, CancellationToken.None); } - if (_favorites.Contains(key) - && entry is IFavoriteEntryModelView fav) + if (entry is IFavoriteEntryModelView fav) { - fav.IsFavorite = true; + fav.IsFavorite = _favorites.Contains(key); + } + + if (entry is IFilterConsumer filterConsumer) + { + filterConsumer.ProcessFilter(_currentFilter); } _entries[key] = new WeakReference(entry); @@ -189,6 +190,17 @@ public sealed class ServerViewContainer return entry; } + + public void ApplyFilter(ServerFilter? filter) + { + _currentFilter = filter; + + foreach (var serverView in Items) + { + if(serverView is IFilterConsumer filterConsumer) + filterConsumer.ProcessFilter(filter); + } + } private void OnFavoritesChange(string[]? value) { diff --git a/Nebula.Launcher/ViewModels/ServerListViewModel.cs b/Nebula.Launcher/ViewModels/ServerListViewModel.cs index d9cd09b..6289c31 100644 --- a/Nebula.Launcher/ViewModels/ServerListViewModel.cs +++ b/Nebula.Launcher/ViewModels/ServerListViewModel.cs @@ -1,8 +1,5 @@ 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; @@ -13,133 +10,37 @@ namespace Nebula.Launcher.ViewModels; [ViewModelRegister(typeof(ServerListView), false)] public partial class ServerListViewModel : ViewModelBase { - [ObservableProperty] private bool _isLoading; + public ObservableCollection ServerList { get; private set; } = new(); + public ObservableCollection ErrorList { get; private set; } = new(); - public ServerListViewModel() + private BaseServerListProvider? _provider; + + public void ClearProvider() { - 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(); - } - } + ServerList.Clear(); + ErrorList.Clear(); + GC.Collect(); + GC.WaitForPendingFinalizers(); } - private void OnProviderDisposed() + public void SetProvider(BaseServerListProvider provider) { - Provider.OnLoaded -= RefreshRequired; - Provider.OnDisposed -= OnProviderDisposed; - if (Provider is IServerListDirtyInvoker invoker) - { - invoker.Dirty -= OnDirty; - } + _provider = provider; - _provider = null; - } - - private ServerFilter? _currentFilter; - - public void RefreshFromProvider() - { - if (IsLoading) - return; + OnPropertyChanged(nameof(ServerList)); + OnPropertyChanged(nameof(ErrorList)); - 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() + public void RefreshFromProvider() { - ErrorList.Clear(); - ServerList.Clear(); + _provider?.LoadServerList(ServerList, ErrorList); } - 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() { + SetProvider(new TestServerList()); } protected override void Initialise() diff --git a/Nebula.Launcher/Views/ServerListView.axaml b/Nebula.Launcher/Views/ServerListView.axaml index 88f5690..272fdb1 100644 --- a/Nebula.Launcher/Views/ServerListView.axaml +++ b/Nebula.Launcher/Views/ServerListView.axaml @@ -2,7 +2,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:services="clr-namespace:Nebula.Launcher.Services" xmlns:viewModels1="clr-namespace:Nebula.Launcher.ViewModels" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Nebula.Launcher.Views.ServerListView" @@ -15,10 +14,6 @@ Margin="5,0,0,10" Padding="0,0,10,0"> -