Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix leaking workspaces and some code cleanup #2437

Merged
merged 3 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 53 additions & 41 deletions src/NexusMods.App.UI/Controls/Spine/SpineViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public class SpineViewModel : AViewModel<ISpineViewModel>, ISpineViewModel

private ISpineItemViewModel? _activeSpineItem;

private ReadOnlyObservableCollection<ILeftMenuViewModel> _leftMenus = new([]);
private Dictionary<WorkspaceId, ILeftMenuViewModel> _leftMenus = new([]);
private readonly IConnection _conn;
[Reactive] public ILeftMenuViewModel? LeftMenuViewModel { get; private set; }

Expand Down Expand Up @@ -88,55 +88,67 @@ public SpineViewModel(
var workspaceController = windowManager.ActiveWorkspaceController;

this.WhenActivated(disposables =>
{
var loadouts = Loadout.ObserveAll(_conn);
{
var loadouts = Loadout.ObserveAll(_conn);

loadouts
.Filter(loadout => loadout.IsVisible())
.TransformAsync(async loadout =>
{
await using var iconStream = await ((IGame)loadout.InstallationInstance.Game).Icon.GetStreamAsync();

var vm = serviceProvider.GetRequiredService<IImageButtonViewModel>();
vm.Name = loadout.InstallationInstance.Game.Name + " - " + loadout.Name;
vm.Image = LoadImageFromStream(iconStream);
vm.LoadoutBadgeViewModel = new LoadoutBadgeViewModel(_conn, _syncService, hideOnSingleLoadout: true);
vm.LoadoutBadgeViewModel.LoadoutValue = loadout;
vm.IsActive = false;
vm.WorkspaceContext = new LoadoutContext { LoadoutId = loadout.LoadoutId };
vm.Click = ReactiveCommand.Create(() => ChangeToLoadoutWorkspace(loadout.LoadoutId));
return vm;
}
)
.Sort(LoadoutComparerInstance)
.OnUI()
.Bind(out _loadoutSpineItems)
loadouts
.Filter(loadout => loadout.IsVisible())
.TransformAsync(async loadout =>
{
await using var iconStream = await ((IGame)loadout.InstallationInstance.Game).Icon.GetStreamAsync();

var vm = serviceProvider.GetRequiredService<IImageButtonViewModel>();
vm.Name = loadout.InstallationInstance.Game.Name + " - " + loadout.Name;
vm.Image = LoadImageFromStream(iconStream);
vm.LoadoutBadgeViewModel = new LoadoutBadgeViewModel(_conn, _syncService, hideOnSingleLoadout: true);
vm.LoadoutBadgeViewModel.LoadoutValue = loadout;
vm.IsActive = false;
vm.WorkspaceContext = new LoadoutContext { LoadoutId = loadout.LoadoutId };
vm.Click = ReactiveCommand.Create(() => ChangeToLoadoutWorkspace(loadout.LoadoutId));
return vm;
}
)
.OnUI()
.OnItemRemoved(loadoutSpineItem =>
{
if (loadoutSpineItem.WorkspaceContext is LoadoutContext loadoutContext)
workspaceController.UnregisterWorkspaceByContext<LoadoutContext>(context => loadoutContext == context);
})
.SortAndBind(out _loadoutSpineItems, LoadoutComparerInstance)
.SubscribeWithErrorLogging()
.DisposeWith(disposables);

// Create Left Menus for each workspace on demand
workspaceController.AllWorkspaces
.ToObservableChangeSet()
.Transform(workspace =>
.OnItemAdded(workspace =>
{
if (_leftMenus.TryGetValue(workspace.Id, out _))
{
try
{
var leftMenu = workspaceAttachmentsFactory.CreateLeftMenuFor(
workspace.Context,
workspace.Id,
workspaceController
);
return;
}

try
{
var leftMenu = workspaceAttachmentsFactory.CreateLeftMenuFor(
workspace.Context,
workspace.Id,
workspaceController
);

return leftMenu ?? new EmptyLeftMenuViewModel(workspace.Id, message: $"Missing {workspace.Context.GetType()}");
}
catch (Exception e)
{
_logger.LogError(e, "Exception while creating left menu for context {Context}", workspace.Context);
return new EmptyLeftMenuViewModel(workspace.Id, message: $"Error for {workspace.Context.GetType()}");
}
_leftMenus.Add(
workspace.Id, leftMenu ?? new EmptyLeftMenuViewModel(workspace.Id, message: $"Missing {workspace.Context.GetType()}")
);
}
)
.Bind(out _leftMenus)
catch (Exception e)
{
_logger.LogError(e, "Exception while creating left menu for context {Context}", workspace.Context);
_leftMenus.Add(
workspace.Id, new EmptyLeftMenuViewModel(workspace.Id, message: $"Error for {workspace.Context.GetType()}")
);
}
})
.OnItemRemoved(workspace => _leftMenus.Remove(workspace.Id, out _))
.SubscribeWithErrorLogging()
.DisposeWith(disposables);

Expand All @@ -163,7 +175,7 @@ public SpineViewModel(
// Update the LeftMenuViewModel when the active workspace changes
workspaceController.WhenAnyValue(controller => controller.ActiveWorkspace)
.Select(workspace => workspace.Id)
.Select(workspaceId => _leftMenus.FirstOrDefault(menu => menu.WorkspaceId == workspaceId))
.Select(workspaceId => _leftMenus.GetValueOrDefault(workspaceId))
.BindToVM(this, vm => vm.LeftMenuViewModel)
.DisposeWith(disposables);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ public WorkspaceViewModel(

_panelSource
.Connect()
.Sort(PanelComparer.Instance)
.Bind(out _panels)
.SortAndBind(out _panels, PanelComparer.Instance)
.Do(_ => UpdateStates())
.Do(_ => UpdateResizers())
.SubscribeWithErrorLogging();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ public interface IWorkspaceController
/// </summary>
public IWorkspaceViewModel ChangeOrCreateWorkspaceByContext<TContext>(Func<TContext, bool> predicate, Func<Optional<PageData>> getPageData, Func<TContext> getWorkspaceContext) where TContext : IWorkspaceContext;


/// <summary>
/// Unregisters a workspace identified by its ContextId.
/// </summary>
public void UnregisterWorkspaceByContext<TContext>(Func<TContext, bool> predicate) where TContext : IWorkspaceContext;

/// <summary>
/// Adds a new panel to a workspace.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public IWorkspaceViewModel CreateWorkspace(Optional<IWorkspaceContext> context,

return vm;
}


private void AddDefaultPanel(WorkspaceViewModel vm)
{
Expand All @@ -164,13 +165,24 @@ private void AddDefaultPanel(WorkspaceViewModel vm)
addPanelBehavior
);
}

private void UnregisterWorkspace(WorkspaceViewModel workspaceViewModel)

public void UnregisterWorkspaceByContext<TContext>(Func<TContext, bool> predicate)
where TContext : IWorkspaceContext
{
//TODO: currently unused, we have no cases where we unregister a workspace
// will need this when we support removing loadouts
Dispatcher.UIThread.VerifyAccess();

var workspaces = FindWorkspacesByContext<TContext>();
var existingWorkspace = workspaces.FirstOrOptional(tuple => predicate(tuple.Item2));
if (!existingWorkspace.HasValue) return;
var workspace = existingWorkspace.Value.Item1;

UnregisterWorkspace(workspace);
}

private void UnregisterWorkspace(IWorkspaceViewModel workspaceViewModel)
{
Dispatcher.UIThread.VerifyAccess();

_workspaces.Remove(workspaceViewModel.Id);

if (ReferenceEquals(ActiveWorkspace, workspaceViewModel) && AllWorkspaces.Count > 0)
Expand Down
Loading