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 song select running updates when screen is not active #25943

Merged
merged 1 commit into from
Dec 20, 2023
Merged
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
98 changes: 57 additions & 41 deletions osu.Game/Screens/Select/BeatmapCarousel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ private void beatmapSetsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeS
if (loadedTestBeatmaps)
return;

var setsRequiringUpdate = new HashSet<BeatmapSetInfo>();
var setsRequiringRemoval = new HashSet<Guid>();

if (changes == null)
{
// During initial population, we must manually account for the fact that our original query was done on an async thread.
Expand All @@ -314,67 +317,80 @@ private void beatmapSetsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeS
foreach (var id in realmSets)
{
if (!root.BeatmapSetsByID.ContainsKey(id))
updateBeatmapSet(realm.Realm.Find<BeatmapSetInfo>(id)!.Detach());
setsRequiringUpdate.Add(realm.Realm.Find<BeatmapSetInfo>(id)!.Detach());
}

foreach (var id in root.BeatmapSetsByID.Keys)
{
if (!realmSets.Contains(id))
removeBeatmapSet(id);
setsRequiringRemoval.Add(id);
}

invalidateAfterChange();
BeatmapSetsLoaded = true;
return;
}
else
{
foreach (int i in changes.NewModifiedIndices)
setsRequiringUpdate.Add(sender[i].Detach());

foreach (int i in changes.NewModifiedIndices)
updateBeatmapSet(sender[i].Detach());

foreach (int i in changes.InsertedIndices)
updateBeatmapSet(sender[i].Detach());
foreach (int i in changes.InsertedIndices)
setsRequiringUpdate.Add(sender[i].Detach());
}

if (changes.DeletedIndices.Length > 0 && SelectedBeatmapInfo != null)
// All local operations must be scheduled.
//
// If we don't schedule, beatmaps getting changed while song select is suspended (ie. last played being updated)
// will cause unexpected sounds and operations to occur in the background.
Schedule(() =>
{
// If SelectedBeatmapInfo is non-null, the set should also be non-null.
Debug.Assert(SelectedBeatmapSet != null);

// To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions.
// When an update occurs, the previous beatmap set is either soft or hard deleted.
// Check if the current selection was potentially deleted by re-querying its validity.
bool selectedSetMarkedDeleted = sender.Realm.Find<BeatmapSetInfo>(SelectedBeatmapSet.ID)?.DeletePending != false;
try
{
foreach (var set in setsRequiringRemoval)
removeBeatmapSet(set);

int[] modifiedAndInserted = changes.NewModifiedIndices.Concat(changes.InsertedIndices).ToArray();
foreach (var set in setsRequiringUpdate)
updateBeatmapSet(set);

if (selectedSetMarkedDeleted && modifiedAndInserted.Any())
{
// If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices.
// This relies on the full update operation being in a single transaction, so please don't change that.
foreach (int i in modifiedAndInserted)
if (changes?.DeletedIndices.Length > 0 && SelectedBeatmapInfo != null)
{
var beatmapSetInfo = sender[i];
// If SelectedBeatmapInfo is non-null, the set should also be non-null.
Debug.Assert(SelectedBeatmapSet != null);

foreach (var beatmapInfo in beatmapSetInfo.Beatmaps)
{
if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata))
continue;
// To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions.
// When an update occurs, the previous beatmap set is either soft or hard deleted.
// Check if the current selection was potentially deleted by re-querying its validity.
bool selectedSetMarkedDeleted = realm.Run(r => r.Find<BeatmapSetInfo>(SelectedBeatmapSet.ID)?.DeletePending != false);

// Best effort matching. We can't use ID because in the update flow a new version will get its own GUID.
if (beatmapInfo.DifficultyName == SelectedBeatmapInfo.DifficultyName)
if (selectedSetMarkedDeleted && setsRequiringUpdate.Any())
{
// If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices.
// This relies on the full update operation being in a single transaction, so please don't change that.
foreach (var set in setsRequiringUpdate)
{
SelectBeatmap(beatmapInfo);
return;
foreach (var beatmapInfo in set.Beatmaps)
{
if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata))
continue;

// Best effort matching. We can't use ID because in the update flow a new version will get its own GUID.
if (beatmapInfo.DifficultyName == SelectedBeatmapInfo.DifficultyName)
{
SelectBeatmap(beatmapInfo);
return;
}
}
}

// If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed.
// Let's attempt to follow set-level selection anyway.
SelectBeatmap(setsRequiringUpdate.First().Beatmaps.First());
}
}

// If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed.
// Let's attempt to follow set-level selection anyway.
SelectBeatmap(sender[modifiedAndInserted.First()].Beatmaps.First());
}
}

invalidateAfterChange();
finally
{
BeatmapSetsLoaded = true;
invalidateAfterChange();
}
});
}

private void beatmapsChanged(IRealmCollection<BeatmapInfo> sender, ChangeSet? changes)
Expand Down
Loading