- tweak: less memory tweak

This commit is contained in:
2026-03-12 21:25:38 +03:00
parent 755fa51adc
commit 8f66bf9f09
9 changed files with 209 additions and 275 deletions

View File

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

View File

@@ -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<IListEntryModelView> _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<IListEntryModelView> GetServers()
{
return _serverLists;
}
public IEnumerable<Exception> 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<IListEntryModelView> servers,
ObservableCollection<Exception> 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<string> 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(){}
}

View File

@@ -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<IListEntryModelView> _servers = [];
private readonly List<Exception> _errors = [];
private string _hubUrl;
public HubServerListProvider With(string hubUrl)
{
HubUrl = hubUrl;
_hubUrl = hubUrl;
return this;
}
public IEnumerable<IListEntryModelView> GetServers()
{
return _servers;
}
public IEnumerable<Exception> GetErrors()
public override void LoadServerList(
ObservableCollection<IListEntryModelView> servers,
ObservableCollection<Exception> 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<IListEntryModelView> servers,
ObservableCollection<IListEntryModelView> collection)
{
collection.Clear();
foreach (var server in servers)
{
collection.Add(server);
}
}
private async Task LoadServerListAsync(
ObservableCollection<IListEntryModelView> servers,
ObservableCollection<Exception> exceptions)
{
CancellationTokenSource localCts;
var serverList = new List<IListEntryModelView>();
await _loadLock.WaitAsync();
try
{
_cts?.Cancel();
_cts?.Dispose();
_cts = new CancellationTokenSource();
localCts = _cts;
}
finally
{
_loadLock.Release();
}
try
{
var servers =
await RestService.GetAsync<List<ServerHubInfo>>(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<List<ServerHubInfo>>(
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()
{}
}

View File

@@ -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<IListEntryModelView> GetServers();
public IEnumerable<Exception> GetErrors();
public void LoadServerList();
}
public virtual void LoadServerList(
ObservableCollection<IListEntryModelView> servers,
ObservableCollection<Exception> exceptions)
{
servers.Clear();
exceptions.Clear();
}
public interface IServerListDirtyInvoker
{
public Action? Dirty { get; set; }
public abstract void Dispose();
}

View File

@@ -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<IListEntryModelView> GetServers()
public override void LoadServerList(
ObservableCollection<IListEntryModelView> servers,
ObservableCollection<Exception> 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<Exception> GetErrors()
{
return [new Exception("On no!")];
}
public void LoadServerList()
public override void Dispose()
{
}
public void Dispose()
{
OnDisposed?.Invoke();
}
}

View File

@@ -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<ServerListTabTemplate> 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<ServerListTabTemplate> 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<string, string> _customNames = [];
private readonly Dictionary<string, WeakReference<IListEntryModelView>> _entries = new();
private ServerFilter? _currentFilter;
public ICollection<IListEntryModelView> 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<IListEntryModelView>(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)
{

View File

@@ -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<IListEntryModelView> ServerList { get; private set; } = new();
public ObservableCollection<Exception> ErrorList { get; private set; } = new();
public ServerListViewModel()
private BaseServerListProvider? _provider;
public void ClearProvider()
{
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();
}
}
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()

View File

@@ -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">
<StackPanel Margin="0,0,0,30">
<Label IsVisible="{Binding IsLoading}"
x:Name="LoadingLabel"
Margin="10" HorizontalAlignment="Center"
Content="{services:LocaledText 'server-list-loading'}"/>
<ItemsControl
ItemsSource="{Binding ErrorList}"
Margin="10,0,10,0" />

View File

@@ -29,20 +29,37 @@ public class HarmonyService(ReflectionService reflectionService)
}
/// <summary>
/// Я помню пенис большой,Я помню пенис большой, Я помню пенис большой, я помню....
/// Я не понимаю суть античитов в сосаке.
/// Эту хуйню может обойти любой школьник!
/// Нет.. я не хочу вводить читы, просто мне нужно поменять некоторые штучки :)
/// </summary>
private void UnShittyWizard()
{
var method = reflectionService.GetType("Robust.Client.GameController").TypeInitializer;
_instance!.Harmony.Patch(method, new HarmonyMethod(Prefix));
_instance!.Harmony.Patch(method, new HarmonyMethod(IgnorePrefix));
var method2 = typeof(Type).Method(nameof(Type.GetType), new[] { typeof(string) });
_instance!.Harmony.Patch(method2, new HarmonyMethod(HidifyPrefix));
}
static bool Prefix()
static bool IgnorePrefix()
{
return false;
}
static bool HidifyPrefix(ref Type? __result, string typeName)
{
if (typeName.Contains("Harmony"))
{
__result = null;
return false;
}
return true;
}
}
public class HarmonyInstance
{
public readonly Harmony Harmony;