- fix: memory leak from canalisation part 2
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
release/
|
||||
publish/
|
||||
/.vs
|
||||
@@ -32,6 +32,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0"/>
|
||||
<PackageReference Include="libsodium" Version="1.0.20"/>
|
||||
<PackageReference Include="Robust.Natives" Version="0.2.3" />
|
||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="BuildCheck" AfterTargets="AfterBuild">
|
||||
@@ -63,8 +64,4 @@
|
||||
<ProjectReference Include="..\Nebula.Shared\Nebula.Shared.csproj"/>
|
||||
<ProjectReference Include="..\Nebula.SourceGenerators\Nebula.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Controls\ServerListView.axaml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Nebula.Launcher.Controls;
|
||||
using Nebula.Launcher.ViewModels;
|
||||
using Nebula.Launcher.ViewModels.Pages;
|
||||
|
||||
|
||||
@@ -118,6 +118,14 @@ public sealed class ServerViewContainer
|
||||
private readonly List<string> _favorites = [];
|
||||
private readonly Dictionary<string, string> _customNames = [];
|
||||
|
||||
private readonly Dictionary<string, WeakReference<IListEntryModelView>> _entries = new();
|
||||
|
||||
public ICollection<IListEntryModelView> Items =>
|
||||
_entries.Values
|
||||
.Select(wr => wr.TryGetTarget(out var target) ? target : null)
|
||||
.Where(t => t != null)
|
||||
.ToList()!;
|
||||
|
||||
public ServerViewContainer()
|
||||
{
|
||||
_viewHelperService = new ViewHelperService();
|
||||
@@ -131,21 +139,84 @@ public sealed class ServerViewContainer
|
||||
configurationService.SubscribeVarChanged(LauncherConVar.ServerCustomNames, OnCustomNamesChanged, true);
|
||||
}
|
||||
|
||||
private void OnCustomNamesChanged(Dictionary<string,string>? value)
|
||||
public void Clear()
|
||||
{
|
||||
var oldNames =
|
||||
_customNames.ToDictionary(k => k.Key, v => v.Value); //Clone think
|
||||
foreach (var (_, weakRef) in _entries)
|
||||
{
|
||||
if (weakRef.TryGetTarget(out var value))
|
||||
value.Dispose();
|
||||
}
|
||||
|
||||
_entries.Clear();
|
||||
}
|
||||
|
||||
public IListEntryModelView Get(RobustUrl url, ServerStatus? serverStatus = null)
|
||||
{
|
||||
var key = url.ToString();
|
||||
IListEntryModelView? entry;
|
||||
|
||||
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<ServerEntryModelView>()
|
||||
.WithData(url, customName, serverStatus);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry = _viewHelperService
|
||||
.GetViewModel<ServerCompoundEntryViewModel>()
|
||||
.LoadServerEntry(url, customName, CancellationToken.None);
|
||||
}
|
||||
|
||||
if (_favorites.Contains(key)
|
||||
&& entry is IFavoriteEntryModelView fav)
|
||||
{
|
||||
fav.IsFavorite = true;
|
||||
}
|
||||
|
||||
_entries[key] = new WeakReference<IListEntryModelView>(entry);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
private void OnFavoritesChange(string[]? value)
|
||||
{
|
||||
_favorites.Clear();
|
||||
if (value == null) return;
|
||||
|
||||
foreach (var favorite in value)
|
||||
{
|
||||
_favorites.Add(favorite);
|
||||
if (_entries.TryGetValue(favorite, out var weak)
|
||||
&& weak.TryGetTarget(out var entry)
|
||||
&& entry is IFavoriteEntryModelView fav)
|
||||
{
|
||||
fav.IsFavorite = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCustomNamesChanged(Dictionary<string, string>? value)
|
||||
{
|
||||
var oldNames = _customNames.ToDictionary(x => x.Key, x => x.Value);
|
||||
_customNames.Clear();
|
||||
|
||||
if(value == null)
|
||||
if (value == null)
|
||||
{
|
||||
foreach (var (ip,_) in oldNames)
|
||||
foreach (var (ip, _) in oldNames)
|
||||
{
|
||||
if(!_entries.TryGetValue(ip, out var listEntry) || listEntry is not IEntryNameHolder entryNameHolder)
|
||||
continue;
|
||||
|
||||
entryNameHolder.Name = null;
|
||||
ResetName(ip);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -153,7 +224,7 @@ public sealed class ServerViewContainer
|
||||
|
||||
foreach (var (oldIp, oldName) in oldNames)
|
||||
{
|
||||
if(value.TryGetValue(oldIp, out var newName))
|
||||
if (value.TryGetValue(oldIp, out var newName))
|
||||
{
|
||||
if (oldName == newName)
|
||||
value.Remove(newName);
|
||||
@@ -161,77 +232,30 @@ public sealed class ServerViewContainer
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!_entries.TryGetValue(oldIp, out var listEntry) ||
|
||||
listEntry is not IEntryNameHolder entryNameHolder)
|
||||
continue;
|
||||
|
||||
entryNameHolder.Name = null;
|
||||
ResetName(oldIp);
|
||||
}
|
||||
|
||||
foreach (var (ip, name) in value)
|
||||
{
|
||||
_customNames.Add(ip, name);
|
||||
if(!_entries.TryGetValue(ip, out var listEntry) || listEntry is not IEntryNameHolder entryNameHolder)
|
||||
continue;
|
||||
|
||||
entryNameHolder.Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFavoritesChange(string[]? value)
|
||||
{
|
||||
_favorites.Clear();
|
||||
if(value == null) return;
|
||||
|
||||
foreach (var favorite in value)
|
||||
{
|
||||
_favorites.Add(favorite);
|
||||
if (_entries.TryGetValue(favorite, out var entry) && entry is IFavoriteEntryModelView favoriteView)
|
||||
if (_entries.TryGetValue(ip, out var weak)
|
||||
&& weak.TryGetTarget(out var entry)
|
||||
&& entry is IEntryNameHolder holder)
|
||||
{
|
||||
favoriteView.IsFavorite = true;
|
||||
holder.Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, IListEntryModelView> _entries = new();
|
||||
|
||||
public ICollection<IListEntryModelView> Items => _entries.Values;
|
||||
|
||||
public void Clear()
|
||||
private void ResetName(string ip)
|
||||
{
|
||||
foreach (var (_, value) in _entries)
|
||||
if (_entries.TryGetValue(ip, out var weak)
|
||||
&& weak.TryGetTarget(out var entry)
|
||||
&& entry is IEntryNameHolder holder)
|
||||
{
|
||||
value.Dispose();
|
||||
holder.Name = null;
|
||||
}
|
||||
_entries.Clear();
|
||||
}
|
||||
|
||||
public IListEntryModelView Get(RobustUrl url, ServerStatus? serverStatus = null)
|
||||
{
|
||||
IListEntryModelView? entry;
|
||||
|
||||
lock (_entries)
|
||||
{
|
||||
_customNames.TryGetValue(url.ToString(), out var customName);
|
||||
|
||||
if (_entries.TryGetValue(url.ToString(), out entry))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
if (serverStatus is not null)
|
||||
entry = _viewHelperService.GetViewModel<ServerEntryModelView>().WithData(url, customName, serverStatus);
|
||||
else
|
||||
entry = _viewHelperService.GetViewModel<ServerCompoundEntryViewModel>().LoadServerEntry(url, customName, CancellationToken.None);
|
||||
|
||||
if(_favorites.Contains(url.ToString()) &&
|
||||
entry is IFavoriteEntryModelView favoriteEntryModelView)
|
||||
favoriteEntryModelView.IsFavorite = true;
|
||||
|
||||
_entries.Add(url.ToString(), entry);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Nebula.Launcher.Models;
|
||||
@@ -19,7 +18,7 @@ namespace Nebula.Launcher.ViewModels;
|
||||
public sealed partial class ServerCompoundEntryViewModel :
|
||||
ViewModelBase, IFavoriteEntryModelView, IFilterConsumer, IListEntryModelView, IEntryNameHolder
|
||||
{
|
||||
[ObservableProperty] private ServerEntryModelView? _currentEntry;
|
||||
private ServerEntryModelView? _currentEntry;
|
||||
[ObservableProperty] private string _message = "Loading server entry...";
|
||||
[ObservableProperty] private bool _isFavorite;
|
||||
[ObservableProperty] private bool _loading = true;
|
||||
@@ -28,6 +27,28 @@ public sealed partial class ServerCompoundEntryViewModel :
|
||||
private RobustUrl? _url;
|
||||
private ServerFilter? _currentFilter;
|
||||
|
||||
public ServerEntryModelView? CurrentEntry
|
||||
{
|
||||
get => _currentEntry;
|
||||
set
|
||||
{
|
||||
if (value == _currentEntry) return;
|
||||
|
||||
_currentEntry = value;
|
||||
|
||||
if (_currentEntry != null)
|
||||
{
|
||||
_currentEntry.IsFavorite = IsFavorite;
|
||||
_currentEntry.Name = Name;
|
||||
_currentEntry.ProcessFilter(_currentFilter);
|
||||
}
|
||||
|
||||
Loading = _currentEntry == null;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string? Name
|
||||
{
|
||||
get => _name;
|
||||
@@ -54,31 +75,43 @@ public sealed partial class ServerCompoundEntryViewModel :
|
||||
{
|
||||
}
|
||||
|
||||
public ServerCompoundEntryViewModel LoadServerEntry(RobustUrl url,string? name, CancellationToken cancellationToken)
|
||||
public ServerCompoundEntryViewModel LoadWithEntry(ServerEntryModelView? entry)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
_url = url;
|
||||
try
|
||||
{
|
||||
Message = "Loading server entry...";
|
||||
var status = await RestService.GetAsync<ServerStatus>(_url.StatusUri, cancellationToken);
|
||||
|
||||
CurrentEntry = ServiceProvider.GetService<ServerEntryModelView>()!.WithData(_url, name, status);
|
||||
CurrentEntry.IsFavorite = IsFavorite;
|
||||
CurrentEntry.Loading = false;
|
||||
CurrentEntry.ProcessFilter(_currentFilter);
|
||||
Loading = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Message = e.Message;
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
CurrentEntry = entry;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ServerCompoundEntryViewModel LoadServerEntry(RobustUrl url, string? name, CancellationToken cancellationToken)
|
||||
{
|
||||
_url = url;
|
||||
_name = name;
|
||||
Task.Run(LoadServer, cancellationToken);
|
||||
return this;
|
||||
}
|
||||
|
||||
private async Task LoadServer()
|
||||
{
|
||||
if (_url is null)
|
||||
{
|
||||
Message = "Url is not set";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Message = "Loading server entry...";
|
||||
var status = await RestService.GetAsync<ServerStatus>(_url.StatusUri, CancellationToken.None);
|
||||
|
||||
CurrentEntry = ServiceProvider.GetService<ServerEntryModelView>()!.WithData(_url, null, status);
|
||||
|
||||
Loading = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Message = "Error while fetching data from " + _url + " : " + e.Message;
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleFavorites()
|
||||
{
|
||||
if (CurrentEntry is null && _url is not null)
|
||||
|
||||
@@ -28,8 +28,6 @@ public sealed partial class ServerEntryModelView : ViewModelBase, IFilterConsume
|
||||
[ObservableProperty] private bool _isFavorite;
|
||||
[ObservableProperty] private bool _isVisible;
|
||||
[ObservableProperty] private bool _runVisible = true;
|
||||
[ObservableProperty] private bool _tagDataVisible;
|
||||
[ObservableProperty] private bool _loading;
|
||||
[ObservableProperty] private string _realName;
|
||||
|
||||
public string? Name
|
||||
@@ -131,7 +129,7 @@ public sealed partial class ServerEntryModelView : ViewModelBase, IFilterConsume
|
||||
OnPropertyChanged(nameof(Status));
|
||||
}
|
||||
|
||||
public ServerEntryModelView WithData(RobustUrl url, string? name,ServerStatus serverStatus)
|
||||
public ServerEntryModelView WithData(RobustUrl url, string? name, ServerStatus serverStatus)
|
||||
{
|
||||
Address = url;
|
||||
SetStatus(serverStatus);
|
||||
|
||||
@@ -70,9 +70,9 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Panel IsVisible="{Binding !Loading}">
|
||||
<views:ServerEntryView IsVisible="{Binding !Loading}" DataContext="{Binding CurrentEntry}"/>
|
||||
</Panel>
|
||||
<ContentControl
|
||||
IsVisible="{Binding !Loading}"
|
||||
Content="{Binding CurrentEntry}"/>
|
||||
|
||||
</Panel>
|
||||
</UserControl>
|
||||
|
||||
@@ -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:viewModels="clr-namespace:Nebula.Launcher.Controls"
|
||||
xmlns:services="clr-namespace:Nebula.Launcher.Services"
|
||||
xmlns:viewModels1="clr-namespace:Nebula.Launcher.ViewModels"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
|
||||
@@ -64,18 +64,18 @@ namespace {viewNamespace}
|
||||
}}";
|
||||
|
||||
// Add the source code to the compilation.
|
||||
context.AddSource($"{viewName}_viewConstructAuto.g.cs", SourceText.From(code, Encoding.UTF8));
|
||||
context.AddSource($"{viewModelName}_{viewName}_viewConstructAuto.g.cs", SourceText.From(code, Encoding.UTF8));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var coder1 = $@"
|
||||
// <auto-generated/>
|
||||
// Error!
|
||||
// Error! {e.Message}
|
||||
namespace {viewModelNamespace}
|
||||
{{
|
||||
public partial class {viewModelName}
|
||||
{{
|
||||
// {e.Message}
|
||||
// ERROR: {e.Message}
|
||||
}}
|
||||
}}";
|
||||
|
||||
@@ -84,6 +84,4 @@ namespace {viewModelNamespace}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArchiving_002EUtils_002EWindows_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F27e9f12ad1e4318b9b02849ec3e6a502fa3ee761c4f0522ba756ab30cde1c_003FArchiving_002EUtils_002EWindows_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAssembly_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F501151723a8d43558c75acbd334f26322066fa4b1c82b1297291314bf92ff_003FAssembly_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationHeaderValue_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F88b338246f59cffdb6f3dc3d8dbcfc169599dc71d6f44a8f2732983db7f73a_003FAuthenticationHeaderValue_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAvaloniaList_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F3cc366334cc52275393f9def48cfcbccc8382175579fbd4f75b8c0e4bf33_003FAvaloniaList_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAvaloniaXamlLoader_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F80462644bd1cc7e0b229dc4f5752b48c01cb67b46ae563b1b5078cc2556b98_003FAvaloniaXamlLoader_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABorder_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F5fda7f1253ea19edc15f91b94a33322b857f1a9319fbffea8d26e9d304178_003FBorder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABrushes_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F86e7f7d5cebacb8f8e37f52cb9a1f6a4b8933239631e3d969a4bc881ae92f9_003FBrushes_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
|
||||
Reference in New Issue
Block a user