Update all read queries to use direct realm subscriptions/queries

This commit is contained in:
Dean Herbert
2021-01-13 16:53:04 +09:00
parent 765d9cfae1
commit 192e58e0c6
7 changed files with 97 additions and 93 deletions

View File

@ -37,18 +37,20 @@ namespace osu.Game.Tests.Database
[Test] [Test]
public void TestDefaultsPopulationAndQuery() public void TestDefaultsPopulationAndQuery()
{ {
Assert.That(keyBindingStore.Query().Count, Is.EqualTo(0)); Assert.That(query().Count, Is.EqualTo(0));
KeyBindingContainer testContainer = new TestKeyBindingContainer(); KeyBindingContainer testContainer = new TestKeyBindingContainer();
keyBindingStore.Register(testContainer); keyBindingStore.Register(testContainer);
Assert.That(keyBindingStore.Query().Count, Is.EqualTo(3)); Assert.That(query().Count, Is.EqualTo(3));
Assert.That(keyBindingStore.Query(GlobalAction.Back).Count, Is.EqualTo(1)); Assert.That(query().Where(k => k.Action == (int)GlobalAction.Back).Count, Is.EqualTo(1));
Assert.That(keyBindingStore.Query(GlobalAction.Select).Count, Is.EqualTo(2)); Assert.That(query().Where(k => k.Action == (int)GlobalAction.Select).Count, Is.EqualTo(2));
} }
private IQueryable<RealmKeyBinding> query() => realmContextFactory.Get().All<RealmKeyBinding>();
[Test] [Test]
public void TestUpdateViaQueriedReference() public void TestUpdateViaQueriedReference()
{ {
@ -56,7 +58,7 @@ namespace osu.Game.Tests.Database
keyBindingStore.Register(testContainer); keyBindingStore.Register(testContainer);
var backBinding = keyBindingStore.Query(GlobalAction.Back).Single(); var backBinding = query().Single(k => k.Action == (int)GlobalAction.Back);
Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape })); Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape }));
@ -65,7 +67,7 @@ namespace osu.Game.Tests.Database
Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
// check still correct after re-query. // check still correct after re-query.
backBinding = keyBindingStore.Query(GlobalAction.Back).Single(); backBinding = query().Single(k => k.Action == (int)GlobalAction.Back);
Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace })); Assert.That(((IKeyBinding)backBinding).KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
} }

View File

@ -11,11 +11,11 @@ namespace osu.Game.Database
{ {
protected readonly Storage? Storage; protected readonly Storage? Storage;
protected readonly IRealmFactory ContextFactory; protected readonly IRealmFactory RealmFactory;
protected RealmBackedStore(IRealmFactory contextFactory, Storage? storage = null) protected RealmBackedStore(IRealmFactory realmFactory, Storage? storage = null)
{ {
ContextFactory = contextFactory; RealmFactory = realmFactory;
Storage = storage; Storage = storage;
} }

View File

@ -77,7 +77,7 @@ namespace osu.Game.Database
try try
{ {
context = getContextForCurrentThread(); context = createContext();
currentWriteTransaction ??= context.BeginWrite(); currentWriteTransaction ??= context.BeginWrite();
} }

View File

@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using Realms;
namespace osu.Game.Input.Bindings namespace osu.Game.Input.Bindings
{ {
@ -22,6 +23,9 @@ namespace osu.Game.Input.Bindings
private readonly int? variant; private readonly int? variant;
private IDisposable realmSubscription;
private IQueryable<RealmKeyBinding> realmKeyBindings;
[Resolved] [Resolved]
private RealmKeyBindingStore store { get; set; } private RealmKeyBindingStore store { get; set; }
@ -49,35 +53,42 @@ namespace osu.Game.Input.Bindings
protected override void LoadComplete() protected override void LoadComplete()
{ {
var realm = realmFactory.Get();
if (ruleset == null || ruleset.ID.HasValue)
{
var rulesetId = ruleset?.ID;
realmKeyBindings = realm.All<RealmKeyBinding>()
.Where(b => b.RulesetID == rulesetId && b.Variant == variant);
realmSubscription = realmKeyBindings
.SubscribeForNotifications((sender, changes, error) =>
{
// first subscription ignored as we are handling this in LoadComplete.
if (changes == null)
return;
ReloadMappings();
});
}
base.LoadComplete(); base.LoadComplete();
store.KeyBindingChanged += ReloadMappings; }
protected override void ReloadMappings()
{
if (realmKeyBindings != null)
KeyBindings = realmKeyBindings.Detach();
else
base.ReloadMappings();
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
if (store != null) realmSubscription?.Dispose();
store.KeyBindingChanged -= ReloadMappings;
}
protected override void ReloadMappings()
{
if (ruleset != null && !ruleset.ID.HasValue)
// if the provided ruleset is not stored to the database, we have no way to retrieve custom bindings.
// fallback to defaults instead.
KeyBindings = DefaultKeyBindings;
else
{
var rulesetId = ruleset?.ID;
// #1
KeyBindings = store.Query(rulesetId, variant).Detach();
// #2 (Clearly shows lifetime of realm context access)
using (var realm = realmFactory.Get())
KeyBindings = realm.All<RealmKeyBinding>().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach();
}
} }
} }
} }

View File

@ -21,8 +21,8 @@ namespace osu.Game.Input
/// </summary> /// </summary>
public event Action? KeyBindingChanged; public event Action? KeyBindingChanged;
public RealmKeyBindingStore(RealmContextFactory contextFactory, Storage? storage = null) public RealmKeyBindingStore(RealmContextFactory realmFactory, Storage? storage = null)
: base(contextFactory, storage) : base(realmFactory, storage)
{ {
} }
@ -57,35 +57,13 @@ namespace osu.Game.Input
{ {
var instance = ruleset.CreateInstance(); var instance = ruleset.CreateInstance();
using (ContextFactory.GetForWrite()) using (RealmFactory.GetForWrite())
{ {
foreach (var variant in instance.AvailableVariants) foreach (var variant in instance.AvailableVariants)
insertDefaults(instance.GetDefaultKeyBindings(variant), ruleset.ID, variant); insertDefaults(instance.GetDefaultKeyBindings(variant), ruleset.ID, variant);
} }
} }
/// <summary>
/// Retrieve all key bindings for the provided specification.
/// </summary>
/// <param name="rulesetId">An optional ruleset ID. If null, global bindings are returned.</param>
/// <param name="variant">An optional ruleset variant. If null, the no-variant bindings are returned.</param>
/// <returns>A list of all key bindings found for the query, detached from the database.</returns>
public List<RealmKeyBinding> Query(int? rulesetId = null, int? variant = null) => query(rulesetId, variant).ToList();
/// <summary>
/// Retrieve all key bindings for the provided action type.
/// </summary>
/// <param name="action">The action to lookup.</param>
/// <typeparam name="T">The enum type of the action.</typeparam>
/// <returns>A list of all key bindings found for the query, detached from the database.</returns>
public List<RealmKeyBinding> Query<T>(T action)
where T : Enum
{
int lookup = (int)(object)action;
return query(null, null).Where(rkb => rkb.Action == lookup).ToList();
}
/// <summary> /// <summary>
/// Update the database mapping for the provided key binding. /// Update the database mapping for the provided key binding.
/// </summary> /// </summary>
@ -96,7 +74,7 @@ namespace osu.Game.Input
// the incoming instance could already be a live access object. // the incoming instance could already be a live access object.
Live<RealmKeyBinding>? realmBinding = keyBinding as Live<RealmKeyBinding>; Live<RealmKeyBinding>? realmBinding = keyBinding as Live<RealmKeyBinding>;
using (var realm = ContextFactory.GetForWrite()) using (var realm = RealmFactory.GetForWrite())
{ {
if (realmBinding == null) if (realmBinding == null)
{ {
@ -105,7 +83,7 @@ namespace osu.Game.Input
// if neither of the above cases succeeded, retrieve a realm object for further processing. // if neither of the above cases succeeded, retrieve a realm object for further processing.
rkb = realm.Context.Find<RealmKeyBinding>(keyBinding.ID); rkb = realm.Context.Find<RealmKeyBinding>(keyBinding.ID);
realmBinding = new Live<RealmKeyBinding>(rkb, ContextFactory); realmBinding = new Live<RealmKeyBinding>(rkb, RealmFactory);
} }
realmBinding.PerformUpdate(modification); realmBinding.PerformUpdate(modification);
@ -116,7 +94,7 @@ namespace osu.Game.Input
private void insertDefaults(IEnumerable<IKeyBinding> defaults, int? rulesetId = null, int? variant = null) private void insertDefaults(IEnumerable<IKeyBinding> defaults, int? rulesetId = null, int? variant = null)
{ {
using (var usage = ContextFactory.GetForWrite()) using (var usage = RealmFactory.GetForWrite())
{ {
// compare counts in database vs defaults // compare counts in database vs defaults
foreach (var group in defaults.GroupBy(k => k.Action)) foreach (var group in defaults.GroupBy(k => k.Action))
@ -149,6 +127,6 @@ namespace osu.Game.Input
/// <param name="rulesetId">An optional ruleset ID. If null, global bindings are returned.</param> /// <param name="rulesetId">An optional ruleset ID. If null, global bindings are returned.</param>
/// <param name="variant">An optional ruleset variant. If null, the no-variant bindings are returned.</param> /// <param name="variant">An optional ruleset variant. If null, the no-variant bindings are returned.</param>
private IQueryable<RealmKeyBinding> query(int? rulesetId = null, int? variant = null) => private IQueryable<RealmKeyBinding> query(int? rulesetId = null, int? variant = null) =>
ContextFactory.Get().All<RealmKeyBinding>().Where(b => b.RulesetID == rulesetId && b.Variant == variant); RealmFactory.Get().All<RealmKeyBinding>().Where(b => b.RulesetID == rulesetId && b.Variant == variant);
} }
} }

View File

@ -9,7 +9,7 @@ using osu.Framework.Graphics;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Input; using osu.Game.Input.Bindings;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osuTK; using osuTK;
@ -33,20 +33,25 @@ namespace osu.Game.Overlays.KeyBinding
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(RealmKeyBindingStore store) private void load(RealmContextFactory realmFactory)
{ {
var bindings = store.Query(Ruleset?.ID, variant).Detach(); var rulesetId = Ruleset?.ID;
foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) using (var realm = realmFactory.Get())
{ {
int intKey = (int)defaultGroup.Key; var bindings = realm.All<RealmKeyBinding>().Where(b => b.RulesetID == rulesetId && b.Variant == variant).Detach();
// one row per valid action. foreach (var defaultGroup in Defaults.GroupBy(d => d.Action))
Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.Action.Equals(intKey)))
{ {
AllowMainMouseButtons = Ruleset != null, int intKey = (int)defaultGroup.Key;
Defaults = defaultGroup.Select(d => d.KeyCombination)
}); // one row per valid action.
Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.Action.Equals(intKey)))
{
AllowMainMouseButtons = Ruleset != null,
Defaults = defaultGroup.Select(d => d.KeyCombination)
});
}
} }
Add(new ResetButton Add(new ResetButton

View File

@ -3,7 +3,6 @@
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -13,12 +12,12 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Database;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -75,7 +74,7 @@ namespace osu.Game.Overlays.Toolbar
protected FillFlowContainer Flow; protected FillFlowContainer Flow;
[Resolved] [Resolved]
private RealmKeyBindingStore keyBindings { get; set; } private RealmContextFactory realmFactory { get; set; }
protected ToolbarButton() protected ToolbarButton()
: base(HoverSampleSet.Loud) : base(HoverSampleSet.Loud)
@ -158,32 +157,28 @@ namespace osu.Game.Overlays.Toolbar
}; };
} }
private readonly Cached tooltipKeyBinding = new Cached(); private RealmKeyBinding realmKeyBinding;
[BackgroundDependencyLoader] protected override void LoadComplete()
private void load()
{ {
keyBindings.KeyBindingChanged += () => tooltipKeyBinding.Invalidate(); base.LoadComplete();
updateKeyBindingTooltip();
}
private void updateKeyBindingTooltip()
{
if (tooltipKeyBinding.IsValid)
return;
keyBindingTooltip.Text = string.Empty;
if (Hotkey != null) if (Hotkey != null)
{ {
KeyCombination? binding = keyBindings.Query(Hotkey.Value).FirstOrDefault()?.KeyCombination; var realm = realmFactory.Get();
var keyBindingString = binding?.ReadableString(); realmKeyBinding = realm.All<RealmKeyBinding>().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.Action == (int)Hotkey.Value);
if (!string.IsNullOrEmpty(keyBindingString)) if (realmKeyBinding != null)
keyBindingTooltip.Text = $" ({keyBindingString})"; {
realmKeyBinding.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == nameof(realmKeyBinding.KeyCombination))
updateKeyBindingTooltip();
};
}
updateKeyBindingTooltip();
} }
tooltipKeyBinding.Validate();
} }
protected override bool OnMouseDown(MouseDownEvent e) => true; protected override bool OnMouseDown(MouseDownEvent e) => true;
@ -224,6 +219,19 @@ namespace osu.Game.Overlays.Toolbar
public void OnReleased(GlobalAction action) public void OnReleased(GlobalAction action)
{ {
} }
private void updateKeyBindingTooltip()
{
if (realmKeyBinding != null)
{
KeyCombination? binding = ((IKeyBinding)realmKeyBinding).KeyCombination;
var keyBindingString = binding?.ReadableString();
if (!string.IsNullOrEmpty(keyBindingString))
keyBindingTooltip.Text = $" ({keyBindingString})";
}
}
} }
public class OpaqueBackground : Container public class OpaqueBackground : Container