2022-01-23 19:28:13 +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;
|
|
|
|
using System.Linq;
|
2022-03-08 13:42:59 +08:00
|
|
|
using System.Threading;
|
2022-03-01 17:31:33 +08:00
|
|
|
using System.Threading.Tasks;
|
2022-01-23 19:28:13 +08:00
|
|
|
using NUnit.Framework;
|
|
|
|
using osu.Framework.Allocation;
|
2022-03-01 17:31:33 +08:00
|
|
|
using osu.Framework.Extensions;
|
2022-01-23 19:28:13 +08:00
|
|
|
using osu.Game.Beatmaps;
|
2022-03-08 13:42:59 +08:00
|
|
|
using osu.Game.Database;
|
2022-01-23 19:28:13 +08:00
|
|
|
using osu.Game.Rulesets;
|
|
|
|
using osu.Game.Tests.Resources;
|
|
|
|
using Realms;
|
|
|
|
|
|
|
|
namespace osu.Game.Tests.Database
|
|
|
|
{
|
|
|
|
[TestFixture]
|
|
|
|
public class RealmSubscriptionRegistrationTests : RealmTest
|
|
|
|
{
|
2022-03-08 13:42:59 +08:00
|
|
|
[Test]
|
|
|
|
public void TestSubscriptionCollectionAndPropertyChanges()
|
|
|
|
{
|
|
|
|
int collectionChanges = 0;
|
|
|
|
int propertyChanges = 0;
|
|
|
|
|
|
|
|
ChangeSet? lastChanges = null;
|
|
|
|
|
|
|
|
RunTestWithRealm((realm, _) =>
|
|
|
|
{
|
|
|
|
var registration = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>(), onChanged);
|
|
|
|
|
|
|
|
realm.Run(r => r.Refresh());
|
|
|
|
|
|
|
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
|
|
|
realm.Run(r => r.Refresh());
|
|
|
|
|
|
|
|
Assert.That(collectionChanges, Is.EqualTo(1));
|
|
|
|
Assert.That(propertyChanges, Is.EqualTo(0));
|
|
|
|
Assert.That(lastChanges?.InsertedIndices, Has.One.Items);
|
|
|
|
Assert.That(lastChanges?.ModifiedIndices, Is.Empty);
|
|
|
|
Assert.That(lastChanges?.NewModifiedIndices, Is.Empty);
|
|
|
|
|
|
|
|
realm.Write(r => r.All<BeatmapSetInfo>().First().Beatmaps.First().CountdownOffset = 5);
|
|
|
|
realm.Run(r => r.Refresh());
|
|
|
|
|
|
|
|
Assert.That(collectionChanges, Is.EqualTo(1));
|
|
|
|
Assert.That(propertyChanges, Is.EqualTo(1));
|
|
|
|
Assert.That(lastChanges?.InsertedIndices, Is.Empty);
|
|
|
|
Assert.That(lastChanges?.ModifiedIndices, Has.One.Items);
|
|
|
|
Assert.That(lastChanges?.NewModifiedIndices, Has.One.Items);
|
|
|
|
|
|
|
|
registration.Dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
void onChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes, Exception error)
|
|
|
|
{
|
|
|
|
lastChanges = changes;
|
|
|
|
|
|
|
|
if (changes == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (changes.HasCollectionChanges())
|
|
|
|
{
|
|
|
|
Interlocked.Increment(ref collectionChanges);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Interlocked.Increment(ref propertyChanges);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-01 17:31:33 +08:00
|
|
|
[Test]
|
|
|
|
public void TestSubscriptionWithAsyncWrite()
|
|
|
|
{
|
|
|
|
ChangeSet? lastChanges = null;
|
|
|
|
|
|
|
|
RunTestWithRealm((realm, _) =>
|
|
|
|
{
|
|
|
|
var registration = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>(), onChanged);
|
|
|
|
|
|
|
|
realm.Run(r => r.Refresh());
|
|
|
|
|
|
|
|
// Without forcing the write onto its own thread, realm will internally run the operation synchronously, which can cause a deadlock with `WaitSafely`.
|
|
|
|
Task.Run(async () =>
|
|
|
|
{
|
|
|
|
await realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
|
|
|
}).WaitSafely();
|
|
|
|
|
|
|
|
realm.Run(r => r.Refresh());
|
|
|
|
|
|
|
|
Assert.That(lastChanges?.InsertedIndices, Has.One.Items);
|
|
|
|
|
|
|
|
registration.Dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
void onChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes, Exception error) => lastChanges = changes;
|
|
|
|
}
|
|
|
|
|
2022-03-03 16:56:49 +08:00
|
|
|
[Test]
|
|
|
|
public void TestPropertyChangedSubscription()
|
|
|
|
{
|
|
|
|
RunTestWithRealm((realm, _) =>
|
|
|
|
{
|
|
|
|
bool? receivedValue = null;
|
|
|
|
|
|
|
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
|
|
|
|
|
|
|
using (realm.SubscribeToPropertyChanged(r => r.All<BeatmapSetInfo>().First(), setInfo => setInfo.Protected, val => receivedValue = val))
|
|
|
|
{
|
|
|
|
Assert.That(receivedValue, Is.False);
|
|
|
|
|
|
|
|
realm.Write(r => r.All<BeatmapSetInfo>().First().Protected = true);
|
|
|
|
|
|
|
|
realm.Run(r => r.Refresh());
|
|
|
|
|
|
|
|
Assert.That(receivedValue, Is.True);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-01-23 19:28:13 +08:00
|
|
|
[Test]
|
|
|
|
public void TestSubscriptionWithContextLoss()
|
|
|
|
{
|
|
|
|
IEnumerable<BeatmapSetInfo>? resolvedItems = null;
|
|
|
|
ChangeSet? lastChanges = null;
|
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
RunTestWithRealm((realm, _) =>
|
2022-01-23 19:28:13 +08:00
|
|
|
{
|
2022-01-24 18:59:58 +08:00
|
|
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
2022-01-23 19:28:13 +08:00
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
var registration = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>(), onChanged);
|
2022-01-23 19:28:13 +08:00
|
|
|
|
|
|
|
testEventsArriving(true);
|
|
|
|
|
|
|
|
// All normal until here.
|
|
|
|
// Now let's yank the main realm context.
|
|
|
|
resolvedItems = null;
|
|
|
|
lastChanges = null;
|
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
using (realm.BlockAllOperations())
|
2022-01-23 19:28:13 +08:00
|
|
|
Assert.That(resolvedItems, Is.Empty);
|
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
2022-01-23 19:28:13 +08:00
|
|
|
|
|
|
|
testEventsArriving(true);
|
|
|
|
|
|
|
|
// Now let's try unsubscribing.
|
|
|
|
resolvedItems = null;
|
|
|
|
lastChanges = null;
|
|
|
|
|
|
|
|
registration.Dispose();
|
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
2022-01-23 19:28:13 +08:00
|
|
|
|
|
|
|
testEventsArriving(false);
|
|
|
|
|
|
|
|
// And make sure even after another context loss we don't get firings.
|
2022-01-24 18:59:58 +08:00
|
|
|
using (realm.BlockAllOperations())
|
2022-01-23 19:28:13 +08:00
|
|
|
Assert.That(resolvedItems, Is.Null);
|
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
2022-01-23 19:28:13 +08:00
|
|
|
|
|
|
|
testEventsArriving(false);
|
|
|
|
|
|
|
|
void testEventsArriving(bool shouldArrive)
|
|
|
|
{
|
2022-01-24 18:59:58 +08:00
|
|
|
realm.Run(r => r.Refresh());
|
2022-01-23 19:28:13 +08:00
|
|
|
|
|
|
|
if (shouldArrive)
|
|
|
|
Assert.That(resolvedItems, Has.One.Items);
|
|
|
|
else
|
|
|
|
Assert.That(resolvedItems, Is.Null);
|
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
realm.Write(r =>
|
2022-01-23 19:28:13 +08:00
|
|
|
{
|
2022-01-24 18:59:58 +08:00
|
|
|
r.RemoveAll<BeatmapSetInfo>();
|
|
|
|
r.RemoveAll<RulesetInfo>();
|
2022-01-23 19:28:13 +08:00
|
|
|
});
|
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
realm.Run(r => r.Refresh());
|
2022-01-23 19:28:13 +08:00
|
|
|
|
|
|
|
if (shouldArrive)
|
|
|
|
Assert.That(lastChanges?.DeletedIndices, Has.One.Items);
|
|
|
|
else
|
|
|
|
Assert.That(lastChanges, Is.Null);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
void onChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes, Exception error)
|
|
|
|
{
|
|
|
|
if (changes == null)
|
|
|
|
resolvedItems = sender;
|
|
|
|
|
|
|
|
lastChanges = changes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestCustomRegisterWithContextLoss()
|
|
|
|
{
|
2022-01-24 18:59:58 +08:00
|
|
|
RunTestWithRealm((realm, _) =>
|
2022-01-23 19:28:13 +08:00
|
|
|
{
|
|
|
|
BeatmapSetInfo? beatmapSetInfo = null;
|
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
2022-01-23 19:28:13 +08:00
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
var subscription = realm.RegisterCustomSubscription(r =>
|
2022-01-23 19:28:13 +08:00
|
|
|
{
|
2022-01-24 18:59:58 +08:00
|
|
|
beatmapSetInfo = r.All<BeatmapSetInfo>().First();
|
2022-01-23 19:28:13 +08:00
|
|
|
|
|
|
|
return new InvokeOnDisposal(() => beatmapSetInfo = null);
|
|
|
|
});
|
|
|
|
|
|
|
|
Assert.That(beatmapSetInfo, Is.Not.Null);
|
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
using (realm.BlockAllOperations())
|
2022-01-23 19:28:13 +08:00
|
|
|
{
|
|
|
|
// custom disposal action fired when context lost.
|
|
|
|
Assert.That(beatmapSetInfo, Is.Null);
|
|
|
|
}
|
|
|
|
|
|
|
|
// re-registration after context restore.
|
2022-01-24 18:59:58 +08:00
|
|
|
realm.Run(r => r.Refresh());
|
2022-01-23 19:28:13 +08:00
|
|
|
Assert.That(beatmapSetInfo, Is.Not.Null);
|
|
|
|
|
|
|
|
subscription.Dispose();
|
|
|
|
|
|
|
|
Assert.That(beatmapSetInfo, Is.Null);
|
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
using (realm.BlockAllOperations())
|
2022-01-23 19:28:13 +08:00
|
|
|
Assert.That(beatmapSetInfo, Is.Null);
|
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
realm.Run(r => r.Refresh());
|
2022-01-23 19:28:13 +08:00
|
|
|
Assert.That(beatmapSetInfo, Is.Null);
|
|
|
|
});
|
|
|
|
}
|
2022-03-03 16:56:49 +08:00
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestPropertyChangedSubscriptionWithContextLoss()
|
|
|
|
{
|
|
|
|
RunTestWithRealm((realm, _) =>
|
|
|
|
{
|
|
|
|
bool? receivedValue = null;
|
|
|
|
|
|
|
|
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
|
|
|
|
|
|
|
|
var subscription = realm.SubscribeToPropertyChanged(
|
|
|
|
r => r.All<BeatmapSetInfo>().First(),
|
|
|
|
setInfo => setInfo.Protected,
|
|
|
|
val => receivedValue = val);
|
|
|
|
|
|
|
|
Assert.That(receivedValue, Is.Not.Null);
|
|
|
|
receivedValue = null;
|
|
|
|
|
|
|
|
using (realm.BlockAllOperations())
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
// re-registration after context restore.
|
|
|
|
realm.Run(r => r.Refresh());
|
|
|
|
Assert.That(receivedValue, Is.Not.Null);
|
|
|
|
|
|
|
|
subscription.Dispose();
|
|
|
|
receivedValue = null;
|
|
|
|
|
|
|
|
using (realm.BlockAllOperations())
|
|
|
|
Assert.That(receivedValue, Is.Null);
|
|
|
|
|
|
|
|
realm.Run(r => r.Refresh());
|
|
|
|
Assert.That(receivedValue, Is.Null);
|
|
|
|
});
|
|
|
|
}
|
2022-01-23 19:28:13 +08:00
|
|
|
}
|
|
|
|
}
|