Rewrite mod instance management again to pass tests

This commit is contained in:
Bartłomiej Dach
2022-05-03 21:44:44 +02:00
parent 216dfb7e91
commit f5fa41356e
4 changed files with 111 additions and 62 deletions

View File

@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Mods
foreach (var column in columnFlow.Columns)
{
column.SelectedMods.BindValueChanged(updateBindableFromSelection);
column.SelectionChangedByUser += updateBindableFromSelection;
}
customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true);
@ -250,33 +250,26 @@ namespace osu.Game.Overlays.Mods
private void updateSelectionFromBindable()
{
// note that selectionBindableSyncInProgress is purposefully not checked here.
// this is because in the case of mod selection in solo gameplay, a user selection of a mod can actually lead to deselection of other incompatible mods.
// to synchronise state correctly, updateBindableFromSelection() computes the final mods (including incompatibility rules) and updates SelectedMods,
// and this method then runs unconditionally again to make sure the new visual selection accurately reflects the final set of selected mods.
// selectionBindableSyncInProgress ensures that mutual infinite recursion does not happen after that unconditional call.
// `SelectedMods` may contain mod references that come from external sources.
// to ensure isolation, first pull in the potentially-external change into the mod columns...
foreach (var column in columnFlow.Columns)
column.SelectedMods.Value = SelectedMods.Value.Where(mod => mod.Type == column.ModType).ToArray();
column.SetSelection(SelectedMods.Value);
// and then, when done, replace the potentially-external mod references in `SelectedMods` with ones we own.
updateBindableFromSelection();
}
private bool selectionBindableSyncInProgress;
private void updateBindableFromSelection(ValueChangedEvent<IReadOnlyList<Mod>> modSelectionChange)
private void updateBindableFromSelection()
{
if (selectionBindableSyncInProgress)
var candidateSelection = columnFlow.Columns.SelectMany(column => column.SelectedMods).ToArray();
if (candidateSelection.SequenceEqual(SelectedMods.Value))
return;
selectionBindableSyncInProgress = true;
SelectedMods.Value = ComputeNewModsFromSelection(
modSelectionChange.NewValue.Except(modSelectionChange.OldValue),
modSelectionChange.OldValue.Except(modSelectionChange.NewValue));
selectionBindableSyncInProgress = false;
SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection);
}
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IEnumerable<Mod> addedMods, IEnumerable<Mod> removedMods)
=> columnFlow.Columns.SelectMany(column => column.SelectedMods.Value).ToArray();
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection;
protected override void PopIn()
{