Skip to content

Commit fc810b2

Browse files
committed
Completely scrapped the "recently modified spells" optimization, as it was needlessly complex, bugged, and was ultimately an over-optimization.
Added new test to confirm that things are properly re-evaluated when spells fall off. Minor code refactor/cleanup.
1 parent 5f8d921 commit fc810b2

File tree

6 files changed

+89
-73
lines changed

6 files changed

+89
-73
lines changed

EQTool/Services/MarkupExtensions/EnumBindingSourceExtension.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace EQTool.Services.MarkupExtensions
66
{
7+
// This extension will allow you to take an enum Type and then create a bindable list of enum values for a control.
78
public class EnumBindingSourceExtension : MarkupExtension
89
{
910
public EnumBindingSourceExtension() { }
@@ -13,6 +14,12 @@ public EnumBindingSourceExtension(Type enumType)
1314
EnumType = enumType;
1415
}
1516

17+
/// <summary>
18+
/// Comma-separated list of enum names to exclude
19+
/// e.g. "BySpellExceptYou,SomethingElse"
20+
/// </summary>
21+
public string Exclude { get; set; }
22+
1623
private Type _EnumType;
1724
public Type EnumType
1825
{
@@ -35,12 +42,6 @@ public Type EnumType
3542
}
3643
}
3744

38-
/// <summary>
39-
/// Comma-separated list of enum names to exclude
40-
/// e.g. "BySpellExceptYou,SomethingElse"
41-
/// </summary>
42-
public string Exclude { get; set; }
43-
4445
public override object ProvideValue(IServiceProvider serviceProvider)
4546
{
4647
if (_EnumType == null)

EQTool/Services/SpellGroupingEngine.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace EQTool.Services
99
{
1010
public class SpellGroupingEngine
1111
{
12+
private readonly object _lock = new object();
13+
1214
private readonly PlayerInfo activePlayer;
1315
private readonly EQToolSettings settings;
1416

@@ -22,39 +24,35 @@ public SpellGroupingEngine(PlayerInfo activePlayer, EQToolSettings settings)
2224
this.settings = settings;
2325
}
2426

25-
public void Recategorize(IEnumerable<SpellViewModel> recentSpells = null)
27+
public void Recategorize()
2628
{
27-
if (recentSpells != null && recentSpells.Any())
28-
recentSpells = GetConnectedSpells(recentSpells);
29-
30-
var allRelatedSpells = recentSpells ?? allSpells;
31-
if (!allRelatedSpells.Any())
29+
if (!allSpells.Any())
3230
return;
3331

3432
var nonConciseGroupingSpells = new List<SpellViewModel>();
3533
var adaptiveGroupingSpells = new List<SpellViewModel>();
3634
if (settings.PlayerSpellGroupingType == SpellGroupingType.Adaptive && settings.NpcSpellGroupingType == SpellGroupingType.Adaptive)
3735
{
38-
adaptiveGroupingSpells = allRelatedSpells.Where(s => s.ColumnVisibility == Visibility.Visible).ToList();
36+
adaptiveGroupingSpells = allSpells.Where(s => s.ColumnVisibility == Visibility.Visible).ToList();
3937
}
4038
else if (settings.PlayerSpellGroupingType == SpellGroupingType.Adaptive)
4139
{
42-
nonConciseGroupingSpells.AddRange(allRelatedSpells.Where(s => !s.IsPlayerTarget)); // Handle em all, even the hidden ones
43-
adaptiveGroupingSpells = allRelatedSpells.Where(s => s.ColumnVisibility == Visibility.Visible && s.IsPlayerTarget).ToList();
40+
nonConciseGroupingSpells.AddRange(allSpells.Where(s => !s.IsPlayerTarget)); // Handle em all, even the hidden ones
41+
adaptiveGroupingSpells = allSpells.Where(s => s.ColumnVisibility == Visibility.Visible && s.IsPlayerTarget).ToList();
4442
}
4543
else if (settings.NpcSpellGroupingType == SpellGroupingType.Adaptive)
4644
{
47-
nonConciseGroupingSpells.AddRange(allRelatedSpells.Where(s => s.IsPlayerTarget)); // Handle em all, even the hidden ones
48-
adaptiveGroupingSpells = allRelatedSpells.Where(s => s.ColumnVisibility == Visibility.Visible && !s.IsPlayerTarget).ToList();
45+
nonConciseGroupingSpells.AddRange(allSpells.Where(s => s.IsPlayerTarget)); // Handle em all, even the hidden ones
46+
adaptiveGroupingSpells = allSpells.Where(s => s.ColumnVisibility == Visibility.Visible && !s.IsPlayerTarget).ToList();
4947
}
5048
else
5149
{
52-
nonConciseGroupingSpells.AddRange(allRelatedSpells);
50+
nonConciseGroupingSpells.AddRange(allSpells);
5351
}
5452

5553
foreach (var spell in nonConciseGroupingSpells)
5654
ApplyNonAdaptiveGroupingRules(spell);
57-
55+
5856
if (adaptiveGroupingSpells.Any())
5957
PerformAdaptiveGrouping(adaptiveGroupingSpells);
6058
}
@@ -161,7 +159,9 @@ private SpellGroupingType GetConfiguredGroupingMode(SpellViewModel spell)
161159
: settings.NpcSpellGroupingType;
162160

163161
// -----------------------
164-
// Branch-and-bound algorithm. Determine what should be grouped by target and what should be grouped by Id.
162+
// Branch-and-bound algorithm.
163+
// Determines what should be grouped by target, and what should be grouped by Id.
164+
// It looks a little "voodoo"-ey but it's a decently optimized algo for accomplishing what we want.
165165
// -----------------------
166166
private void PerformAdaptiveGrouping(IEnumerable<SpellViewModel> visibleSpells)
167167
{

EQTool/UI/SpellWindow.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@
220220
<Grid Grid.Row="1" VerticalAlignment="Bottom">
221221
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
222222
<Button Content="R" Padding="0" FontSize="10" Width="14" Height="14" Visibility="{Binding RaidModeToggleButtonVisibility}" ToolTip="{Binding RaidModeButtonToolTip}"
223-
Opacity="{Binding DataContext.ItemOpacity, RelativeSource={RelativeSource AncestorType=local:BaseSaveStateWindow}}" Click="RaidModleToggle" />
223+
Opacity="{Binding DataContext.ItemOpacity, RelativeSource={RelativeSource AncestorType=local:BaseSaveStateWindow}}" Click="RaidModeToggle" />
224224
<Button Content="O" Margin="5, 0, 0, 0" Padding="0" FontSize="10" Width="14" Height="14" Visibility="{Binding GenericButtonVisibility}" ToolTip="Clear all spells that were not cast on you"
225225
Opacity="{Binding DataContext.ItemOpacity, RelativeSource={RelativeSource AncestorType=local:BaseSaveStateWindow}}" Click="ClearSpellsNotCastOnYou" />
226226
<Button Content="Y" Margin="5, 0, 0, 0" Padding="0" FontSize="10" Width="14" Height="14" Visibility="{Binding GenericButtonVisibility}" ToolTip="Clear all spells that were cast by others"

EQTool/UI/SpellWindow.xaml.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,9 @@ public partial class SpellWindow : BaseSaveStateWindow
1818
private readonly SlainHandler slainHandler;
1919

2020
public SpellWindow(
21-
TimersService timersService,
2221
EQToolSettings settings,
2322
SpellWindowViewModel spellWindowViewModel,
2423
SettingsWindowViewModel settingsWindowViewModel,
25-
LogEvents logEvents,
2624
EQToolSettingsLoad toolSettingsLoad,
2725
ActivePlayer activePlayer,
2826
IAppDispatcher appDispatcher,
@@ -39,7 +37,7 @@ public SpellWindow(
3937

4038
private void RemoveSingleItem(object sender, RoutedEventArgs e)
4139
{
42-
var vm = (sender as Button).DataContext;
40+
var vm = (sender as Button)?.DataContext;
4341
appDispatcher.DispatchUI(() =>
4442
{
4543
_ = spellWindowViewModel.SpellList.Remove(vm as PersistentViewModel);
@@ -48,7 +46,7 @@ private void RemoveSingleItem(object sender, RoutedEventArgs e)
4846

4947
private void RemoveFromSpells(object sender, RoutedEventArgs e)
5048
{
51-
var group = ((sender as Button).DataContext as dynamic)?.Name as string;
49+
var group = ((sender as Button)?.DataContext as dynamic)?.Name as string;
5250
appDispatcher.DispatchUI(() =>
5351
{
5452
var items = spellWindowViewModel.SpellList.Where(a => a.DisplayGroup == group).ToList();
@@ -69,7 +67,7 @@ private void ClearSpellsCastByOthers(object sender, RoutedEventArgs e)
6967
spellWindowViewModel.ClearSpellsCastByOthers();
7068
}
7169

72-
private void RaidModleToggle(object sender, RoutedEventArgs e)
70+
private void RaidModeToggle(object sender, RoutedEventArgs e)
7371
{
7472
settingsWindowViewModel.RaidModeDetection = !settingsWindowViewModel.RaidModeDetection;
7573
}

EQTool/ViewModels/SpellWindowViewModel.cs

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Threading.Tasks;
99
using System.Windows;
1010
using System.Windows.Data;
11+
using System.Windows.Documents;
1112
using System.Windows.Media;
1213
using EQTool.Models;
1314
using EQTool.Services;
@@ -32,7 +33,6 @@ public class SpellWindowViewModel : BaseWindowViewModel, INotifyPropertyChanged
3233
private readonly PigParseApi pigParseApi;
3334
private readonly BoatScheduleService boatScheduleService;
3435
private readonly PetViewModel playerPet;
35-
private HashSet<SpellViewModel> recentlyImpactedSpells = new HashSet<SpellViewModel>();
3636

3737
internal int groupRevaluationDebounceTime { get; set; } = 1000; // Debounce delay for re-evaluating the ByTarget / BySpell Grouping of the list. It's internal so that tests can change it for speed.
3838

@@ -79,7 +79,7 @@ private void CreateTriggerList()
7979
if (_SpellList == null)
8080
{
8181
_SpellList = new ObservableCollection<PersistentViewModel>();
82-
SpellList.CollectionChanged += SpellList_CollectionChanged;
82+
_SpellList.CollectionChanged += SpellList_CollectionChanged;
8383
var view = (ListCollectionView)CollectionViewSource.GetDefaultView(_SpellList);
8484
view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(TimerViewModel.DisplayGroup)));
8585
view.LiveGroupingProperties.Add(nameof(TimerViewModel.DisplayGroup));
@@ -800,33 +800,13 @@ public void UpdateAPITimers()
800800
}
801801

802802
private CancellationTokenSource timersModifiedDebounceTs;
803-
private void QueueFullSpellListReevaluation() => QueueSpellGroupingReevaluation(groupRevaluationDebounceTime, useRecentlyModified: false);
804-
private void QueueSpellGroupingReevaluation(int delay, bool useRecentlyModified = true)
803+
private void QueueFullSpellListReevaluation() => QueueSpellGroupingReevaluation(groupRevaluationDebounceTime);
804+
private void QueueSpellGroupingReevaluation(int delay)
805805
{
806806
// We need to queue the re-evaluation with a debounce cancellation because we don't want to be doing constant iteration over the whole list while it is actively being modified.
807-
// There is also an issue when the user removes whole groups at once that can cause it to remove items the user did not mean to remove due to the grouping changing in the middle of that process.
808-
// This debounce is necessary to ensure we do not waste unnecessary time or cause undesirable side effects when doing bulk operations or when several timers fall off at roughly the same time.
809-
810-
var evaluationItemCount = recentlyImpactedSpells.Count;
811-
appDispatcher.DebounceToUI(ref timersModifiedDebounceTs, delay, () =>
812-
{
813-
if (useRecentlyModified)
814-
{
815-
var safeCloneOfRecentlyImpacted = recentlyImpactedSpells.ToList(); // Clone it since we're about to wipe it clean but want to use it.
816-
recentlyImpactedSpells.Clear();
817-
engine.Recategorize(safeCloneOfRecentlyImpacted);
818-
}
819-
else
820-
{
821-
recentlyImpactedSpells.Clear();
822-
engine.Recategorize(recentSpells: null);
823-
}
824-
},
825-
shouldCancel: () => useRecentlyModified && evaluationItemCount != recentlyImpactedSpells.Count);
807+
appDispatcher.DebounceToUI(ref timersModifiedDebounceTs, delay, () => engine.Recategorize());
826808
}
827809

828-
private bool IsAdaptiveMode() => settings.PlayerSpellGroupingType == SpellGroupingType.Adaptive || settings.NpcSpellGroupingType == SpellGroupingType.Adaptive;
829-
830810
private void SpellList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
831811
{
832812
var newSpells = (e.NewItems ?? Array.Empty<SpellViewModel>()).OfType<SpellViewModel>();
@@ -835,8 +815,7 @@ private void SpellList_CollectionChanged(object sender, NotifyCollectionChangedE
835815
engine.AddSpells(newSpells);
836816
engine.RemoveSpells(removedSpells);
837817

838-
recentlyImpactedSpells.UnionWith(newSpells.Concat(removedSpells));
839-
818+
var recentlyImpactedSpells = new List<SpellViewModel>(newSpells.Concat(removedSpells));
840819
if (recentlyImpactedSpells.Any())
841820
QueueSpellGroupingReevaluation(groupRevaluationDebounceTime);
842821
}

0 commit comments

Comments
 (0)