1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-15 14:27:51 +08:00
osu-lazer/osu.Game/Database/DetachedBeatmapStore.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

164 lines
5.4 KiB
C#
Raw Normal View History

2024-08-27 16:37:15 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
2024-08-27 16:37:15 +08:00
using System.Linq;
using System.Threading;
2024-08-28 16:46:36 +08:00
using System.Threading.Tasks;
2024-08-27 16:37:15 +08:00
using osu.Framework.Allocation;
2024-08-27 17:13:52 +08:00
using osu.Framework.Bindables;
2024-08-27 16:37:15 +08:00
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
2024-08-27 16:37:15 +08:00
using Realms;
namespace osu.Game.Database
{
public partial class DetachedBeatmapStore : Component
{
private readonly ManualResetEventSlim loaded = new ManualResetEventSlim();
2024-08-27 17:13:52 +08:00
private readonly BindableList<BeatmapSetInfo> detachedBeatmapSets = new BindableList<BeatmapSetInfo>();
2024-08-27 16:37:15 +08:00
2024-08-27 17:13:52 +08:00
private IDisposable? realmSubscription;
2024-08-27 16:37:15 +08:00
private readonly Queue<OperationArgs> pendingOperations = new Queue<OperationArgs>();
2024-08-27 16:37:15 +08:00
[Resolved]
private RealmAccess realm { get; set; } = null!;
2024-08-28 18:19:04 +08:00
public IBindableList<BeatmapSetInfo> GetDetachedBeatmaps(CancellationToken? cancellationToken)
2024-08-27 16:37:15 +08:00
{
2024-08-28 18:19:04 +08:00
loaded.Wait(cancellationToken ?? CancellationToken.None);
2024-08-27 17:13:52 +08:00
return detachedBeatmapSets.GetBoundCopy();
2024-08-27 16:37:15 +08:00
}
[BackgroundDependencyLoader]
2024-08-28 18:19:04 +08:00
private void load()
2024-08-27 16:37:15 +08:00
{
2024-08-28 16:46:36 +08:00
realmSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected), beatmapSetsChanged);
2024-08-27 16:37:15 +08:00
}
private void beatmapSetsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes)
{
if (changes == null)
{
2024-10-09 13:18:34 +08:00
if (sender is RealmResetEmptySet<BeatmapSetInfo>)
2024-08-27 16:37:15 +08:00
{
// Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm.
// Additionally, user should not be at song select when realm is blocking all operations in the first place.
//
// Note that due to the catch-up logic below, once operations are restored we will still be in a roughly
// correct state. The only things that this return will change is the carousel will not empty *during* the blocking
// operation.
return;
}
2024-08-28 16:46:36 +08:00
// Detaching beatmaps takes some time, so let's make sure it doesn't run on the update thread.
var frozenSets = sender.Freeze();
Task.Factory.StartNew(() =>
{
try
2024-08-28 16:46:36 +08:00
{
realm.Run(_ =>
{
var detached = frozenSets.Detach();
2024-08-28 16:46:36 +08:00
detachedBeatmapSets.Clear();
detachedBeatmapSets.AddRange(detached);
});
}
finally
{
2024-08-28 16:46:36 +08:00
loaded.Set();
}
}, TaskCreationOptions.LongRunning).FireAndForget();
2024-08-27 16:37:15 +08:00
return;
}
foreach (int i in changes.DeletedIndices.OrderDescending())
{
pendingOperations.Enqueue(new OperationArgs
{
Type = OperationType.Remove,
Index = i,
});
}
2024-08-27 16:37:15 +08:00
foreach (int i in changes.InsertedIndices)
{
pendingOperations.Enqueue(new OperationArgs
{
Type = OperationType.Insert,
BeatmapSet = sender[i].Detach(),
Index = i,
});
}
2024-08-27 16:37:15 +08:00
foreach (int i in changes.NewModifiedIndices)
{
pendingOperations.Enqueue(new OperationArgs
{
Type = OperationType.Update,
BeatmapSet = sender[i].Detach(),
Index = i,
});
}
2024-08-28 16:46:36 +08:00
}
protected override void Update()
2024-08-28 16:46:36 +08:00
{
base.Update();
2024-08-28 16:46:36 +08:00
// We can't start processing operations until we have finished detaching the initial list.
if (!loaded.IsSet)
return;
2024-08-28 16:46:36 +08:00
// If this ever leads to performance issues, we could dequeue a limited number of operations per update frame.
while (pendingOperations.TryDequeue(out var op))
{
switch (op.Type)
{
case OperationType.Insert:
detachedBeatmapSets.Insert(op.Index, op.BeatmapSet!);
break;
case OperationType.Update:
detachedBeatmapSets.ReplaceRange(op.Index, 1, new[] { op.BeatmapSet! });
break;
case OperationType.Remove:
detachedBeatmapSets.RemoveAt(op.Index);
break;
}
}
2024-08-27 16:37:15 +08:00
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
2024-08-30 17:44:51 +08:00
loaded.Set();
2024-08-30 17:44:51 +08:00
loaded.Dispose();
2024-08-27 17:13:52 +08:00
realmSubscription?.Dispose();
2024-08-27 16:37:15 +08:00
}
private record OperationArgs
{
public OperationType Type;
public BeatmapSetInfo? BeatmapSet;
public int Index;
}
private enum OperationType
{
Insert,
Update,
Remove
}
2024-08-27 16:37:15 +08:00
}
}