Skip to content

Commit 0fefc8c

Browse files
committed
ModelMerge comments
1 parent 6b1b4cb commit 0fefc8c

File tree

6 files changed

+168
-148
lines changed

6 files changed

+168
-148
lines changed

App/Utils/ModelMerge.cs

Lines changed: 0 additions & 85 deletions
This file was deleted.

App/Utils/ModelUpdate.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace Coder.Desktop.App.Utils;
6+
7+
public interface IModelUpdateable<in T>
8+
{
9+
/// <summary>
10+
/// Applies changes from obj to `this` if they represent the same
11+
/// object based on some identifier like an ID or fixed name.
12+
/// </summary>
13+
/// <returns>
14+
/// True if the two objects represent the same item and the changes
15+
/// were applied.
16+
/// </returns>
17+
public bool TryApplyChanges(T obj);
18+
}
19+
20+
/// <summary>
21+
/// A static utility class providing methods for applying model updates
22+
/// with as little UI updates as possible.
23+
/// The main goal of the utilities in this class is to prevent redraws in
24+
/// ItemsRepeater items when nothing has changed.
25+
/// </summary>
26+
public static class ModelUpdate
27+
{
28+
/// <summary>
29+
/// Takes all items in `update` and either applies them to existing
30+
/// items in `target`, or adds them to `target` if there are no
31+
/// matching items.
32+
/// Any items in `target` that don't have a corresponding item in
33+
/// `update` will be removed from `target`.
34+
/// Items are inserted in their correct sort position according to
35+
/// `sorter`. It's assumed that the target list is already sorted by
36+
/// `sorter`.
37+
/// </summary>
38+
/// <param name="target">Target list to be updated</param>
39+
/// <param name="update">Incoming list to apply to `target`</param>
40+
/// <param name="sorter">
41+
/// Comparison to use for sorting. Note that the sort order does not
42+
/// need to be the ID/name field used in the <c>IModelUpdateable</c>
43+
/// implementation, and can be by any order.
44+
/// New items will be sorted after existing items.
45+
/// </param>
46+
public static void ApplyLists<T>(IList<T> target, IEnumerable<T> update, Comparison<T> sorter)
47+
where T : IModelUpdateable<T>
48+
{
49+
var newItems = update.ToList();
50+
51+
// Update and remove existing items. We use index-based for loops here
52+
// because we remove items, and removing items while using the list as
53+
// an IEnumerable will throw an exception.
54+
for (var i = 0; i < target.Count; i++)
55+
{
56+
// Even though we're removing items before a "break", we still use
57+
// index-based for loops here to avoid exceptions.
58+
for (var j = 0; j < newItems.Count; j++)
59+
{
60+
if (!target[i].TryApplyChanges(newItems[j])) continue;
61+
62+
// Prevent it from being added below, or checked again. We
63+
// don't need to decrement `j` here because we're breaking
64+
// out of this inner loop.
65+
newItems.RemoveAt(j);
66+
goto OuterLoopEnd; // continue outer loop
67+
}
68+
69+
// A merge couldn't occur, so we need to remove the old item and
70+
// decrement `i` for the next iteration.
71+
target.RemoveAt(i);
72+
i--;
73+
74+
OuterLoopEnd: ;
75+
}
76+
77+
// Add any items that were missing into their correct sorted place.
78+
// It's assumed the list is already sorted.
79+
foreach (var newItem in newItems)
80+
{
81+
for (var i = 0; i < target.Count; i++)
82+
// If the new item sorts before the current item, insert it
83+
// after.
84+
if (sorter(newItem, target[i]) < 0)
85+
{
86+
target.Insert(i, newItem);
87+
goto OuterLoopEnd;
88+
}
89+
90+
// Handle the case where target is empty or the new item is
91+
// equal to or after every other item.
92+
target.Add(newItem);
93+
94+
OuterLoopEnd: ;
95+
}
96+
}
97+
}

App/ViewModels/AgentAppViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public AgentAppViewModel Create(Uuid id, string name, Uri appUri, Uri? iconUrl)
3838
}
3939
}
4040

41-
public partial class AgentAppViewModel : ObservableObject, IModelMergeable<AgentAppViewModel>
41+
public partial class AgentAppViewModel : ObservableObject, IModelUpdateable<AgentAppViewModel>
4242
{
4343
private readonly ILogger<AgentAppViewModel> _logger;
4444

@@ -97,7 +97,7 @@ public AgentAppViewModel(ILogger<AgentAppViewModel> logger)
9797
_logger = logger;
9898
}
9999

100-
public bool ApplyMerge(AgentAppViewModel obj)
100+
public bool TryApplyChanges(AgentAppViewModel obj)
101101
{
102102
if (Id != obj.Id) return false;
103103

App/ViewModels/AgentViewModel.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public enum AgentConnectionStatus
6565
Gray,
6666
}
6767

68-
public partial class AgentViewModel : ObservableObject, IModelMergeable<AgentViewModel>
68+
public partial class AgentViewModel : ObservableObject, IModelUpdateable<AgentViewModel>
6969
{
7070
private const string DefaultDashboardUrl = "https://coder.com";
7171
private const int MaxAppsPerRow = 6;
@@ -182,7 +182,7 @@ public AgentViewModel(ILogger<AgentViewModel> logger, ICoderApiClientFactory cod
182182
};
183183
}
184184

185-
public bool ApplyMerge(AgentViewModel model)
185+
public bool TryApplyChanges(AgentViewModel model)
186186
{
187187
if (Id != model.Id) return false;
188188

@@ -285,7 +285,7 @@ private void ContinueFetchApps(Task<WorkspaceAgent> task)
285285
}
286286

287287
// Sort by name.
288-
ModelMerge.MergeLists(Apps, apps, (a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
288+
ModelUpdate.ApplyLists(Apps, apps, (a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
289289
}
290290

291291
[RelayCommand]

App/ViewModels/TrayWindowViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ private void UpdateFromRpcModel(RpcModel rpcModel)
191191
workspace.Name));
192192

193193
// Sort by status green, red, gray, then by hostname.
194-
ModelMerge.MergeLists(Agents, agents, (a, b) =>
194+
ModelUpdate.ApplyLists(Agents, agents, (a, b) =>
195195
{
196196
if (a.ConnectionStatus != b.ConnectionStatus)
197197
return a.ConnectionStatus.CompareTo(b.ConnectionStatus);

0 commit comments

Comments
 (0)