1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 11:37:28 +08:00

Allow realm subscriptions to be initiated from a non-update thread

This commit is contained in:
Dean Herbert 2022-03-24 22:28:26 +09:00
parent 2938f44e6c
commit b04ca111c6
2 changed files with 18 additions and 14 deletions

View File

@ -1,6 +1,8 @@
// 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.
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel;
@ -17,6 +19,7 @@ using osu.Framework.Input.Bindings;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Statistics;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Input.Bindings;
@ -28,8 +31,6 @@ using osu.Game.Stores;
using Realms;
using Realms.Exceptions;
#nullable enable
namespace osu.Game.Database
{
/// <summary>
@ -46,6 +47,8 @@ namespace osu.Game.Database
private readonly IDatabaseContextFactory? efContextFactory;
private readonly SynchronizationContext? updateThreadSyncContext;
/// <summary>
/// Version history:
/// 6 ~2021-10-18 First tracked version.
@ -143,12 +146,15 @@ namespace osu.Game.Database
/// </summary>
/// <param name="storage">The game storage which will be used to create the realm backing file.</param>
/// <param name="filename">The filename to use for the realm backing file. A ".realm" extension will be added automatically if not specified.</param>
/// <param name="updateThread">The game update thread, used to post realm operations into a thread-safe context.</param>
/// <param name="efContextFactory">An EF factory used only for migration purposes.</param>
public RealmAccess(Storage storage, string filename, IDatabaseContextFactory? efContextFactory = null)
public RealmAccess(Storage storage, string filename, GameThread? updateThread = null, IDatabaseContextFactory? efContextFactory = null)
{
this.storage = storage;
this.efContextFactory = efContextFactory;
updateThreadSyncContext = updateThread?.SynchronizationContext ?? SynchronizationContext.Current;
Filename = filename;
if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal))
@ -379,9 +385,6 @@ namespace osu.Game.Database
public IDisposable RegisterForNotifications<T>(Func<Realm, IQueryable<T>> query, NotificationCallbackDelegate<T> callback)
where T : RealmObjectBase
{
if (!ThreadSafety.IsUpdateThread)
throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread.");
lock (realmLock)
{
Func<Realm, IDisposable?> action = realm => query(realm).QueryAsyncWithNotifications(callback);
@ -459,23 +462,24 @@ namespace osu.Game.Database
/// <returns>An <see cref="IDisposable"/> which should be disposed to unsubscribe any inner subscription.</returns>
public IDisposable RegisterCustomSubscription(Func<Realm, IDisposable?> action)
{
if (!ThreadSafety.IsUpdateThread)
throw new InvalidOperationException(@$"{nameof(RegisterForNotifications)} must be called from the update thread.");
var syncContext = SynchronizationContext.Current;
if (updateThreadSyncContext == null)
throw new InvalidOperationException("Attempted to register a realm subscription before update thread registration.");
total_subscriptions.Value++;
registerSubscription(action);
if (ThreadSafety.IsUpdateThread)
updateThreadSyncContext.Send(_ => registerSubscription(action), null);
else
updateThreadSyncContext.Post(_ => registerSubscription(action), null);
// This token is returned to the consumer.
// When disposed, it will cause the registration to be permanently ceased (unsubscribed with realm and unregistered by this class).
return new InvokeOnDisposal(() =>
{
if (ThreadSafety.IsUpdateThread)
syncContext.Send(_ => unsubscribe(), null);
updateThreadSyncContext.Send(_ => unsubscribe(), null);
else
syncContext.Post(_ => unsubscribe(), null);
updateThreadSyncContext.Post(_ => unsubscribe(), null);
void unsubscribe()
{

View File

@ -200,7 +200,7 @@ namespace osu.Game
if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME))
dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage));
dependencies.Cache(realm = new RealmAccess(Storage, "client", EFContextFactory));
dependencies.Cache(realm = new RealmAccess(Storage, "client", Host.UpdateThread, EFContextFactory));
dependencies.CacheAs<RulesetStore>(RulesetStore = new RealmRulesetStore(realm, Storage));
dependencies.CacheAs<IRulesetStore>(RulesetStore);