1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 13:15:08 +08:00

Add proof of concept flow to ensure RealmBackedResourceStore is invalidated on realm file changes

I'm not at all happy with this, but it does work so let's go with it for
now.
This commit is contained in:
Dean Herbert 2022-03-24 19:09:17 +09:00
parent 66f5eae530
commit 9c3dad9fbf
4 changed files with 67 additions and 24 deletions

View File

@ -11,6 +11,7 @@ using osu.Framework.IO.Stores;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.Database;
using osu.Game.IO;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types;
@ -37,11 +38,11 @@ namespace osu.Game.Skinning
private static IResourceStore<byte[]> createRealmBackedStore(BeatmapInfo beatmapInfo, IStorageResourceProvider? resources)
{
if (resources == null)
if (resources == null || beatmapInfo.BeatmapSet == null)
// should only ever be used in tests.
return new ResourceStore<byte[]>();
return new RealmBackedResourceStore(beatmapInfo.BeatmapSet, resources.Files, new[] { @"ogg" });
return new RealmBackedResourceStore<BeatmapSetInfo>(beatmapInfo.BeatmapSet.ToLive(resources.RealmAccess), resources.Files, resources.RealmAccess);
}
public override Drawable? GetDrawableComponent(ISkinComponent component)

View File

@ -1,51 +1,68 @@
// 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.Diagnostics;
using System.Threading;
using osu.Framework.Extensions;
using osu.Framework.IO.Stores;
using osu.Game.Database;
using osu.Game.Extensions;
using Realms;
namespace osu.Game.Skinning
{
public class RealmBackedResourceStore : ResourceStore<byte[]>
public class RealmBackedResourceStore<T> : ResourceStore<byte[]>
where T : RealmObject, IHasRealmFiles, IHasGuidPrimaryKey
{
private readonly Dictionary<string, string> fileToStoragePathMapping = new Dictionary<string, string>();
private Lazy<Dictionary<string, string>> fileToStoragePathMapping;
public RealmBackedResourceStore(IHasRealmFiles source, IResourceStore<byte[]> underlyingStore, string[] extensions = null)
private readonly Live<T> liveSource;
public RealmBackedResourceStore(Live<T> source, IResourceStore<byte[]> underlyingStore, RealmAccess? realm)
: base(underlyingStore)
{
// Must be initialised before the file cache.
if (extensions != null)
{
foreach (string extension in extensions)
AddExtension(extension);
}
liveSource = source;
initialiseFileCache(source);
invalidateCache();
Debug.Assert(fileToStoragePathMapping != null);
}
private void initialiseFileCache(IHasRealmFiles source)
{
fileToStoragePathMapping.Clear();
foreach (var f in source.Files)
fileToStoragePathMapping[f.Filename.ToLowerInvariant()] = f.File.GetStoragePath();
}
public void Invalidate() => invalidateCache();
protected override IEnumerable<string> GetFilenames(string name)
{
foreach (string filename in base.GetFilenames(name))
{
string path = getPathForFile(filename.ToStandardisedPath());
string? path = getPathForFile(filename.ToStandardisedPath());
if (path != null)
yield return path;
}
}
private string getPathForFile(string filename) =>
fileToStoragePathMapping.TryGetValue(filename.ToLower(), out string path) ? path : null;
private string? getPathForFile(string filename)
{
if (fileToStoragePathMapping.Value.TryGetValue(filename.ToLowerInvariant(), out string path))
return path;
public override IEnumerable<string> GetAvailableResources() => fileToStoragePathMapping.Keys;
return null;
}
private void invalidateCache() => fileToStoragePathMapping = new Lazy<Dictionary<string, string>>(initialiseFileCache, LazyThreadSafetyMode.ExecutionAndPublication);
private Dictionary<string, string> initialiseFileCache() => liveSource.PerformRead(source =>
{
var dictionary = new Dictionary<string, string>();
dictionary.Clear();
foreach (var f in source.Files)
dictionary[f.Filename.ToLowerInvariant()] = f.File.GetStoragePath();
return dictionary;
});
public override IEnumerable<string> GetAvailableResources() => fileToStoragePathMapping.Value.Keys;
}
}

View File

@ -54,6 +54,10 @@ namespace osu.Game.Skinning
where TLookup : notnull
where TValue : notnull;
public void InvalidateCaches() => realmBackedStorage?.Invalidate();
private readonly RealmBackedResourceStore<SkinInfo> realmBackedStorage;
/// <summary>
/// Construct a new skin.
/// </summary>
@ -67,7 +71,9 @@ namespace osu.Game.Skinning
{
SkinInfo = skin.ToLive(resources.RealmAccess);
storage ??= new RealmBackedResourceStore(skin, resources.Files, new[] { @"ogg" });
storage ??= realmBackedStorage = new RealmBackedResourceStore<SkinInfo>(SkinInfo, resources.Files, resources.RealmAccess);
(storage as ResourceStore<byte[]>)?.AddExtension("ogg");
var samples = resources.AudioManager?.GetSampleStore(storage);
if (samples != null)

View File

@ -16,6 +16,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Framework.Threading;
@ -26,6 +27,7 @@ using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Models;
using osu.Game.Overlays.Notifications;
using Realms;
namespace osu.Game.Skinning
{
@ -59,6 +61,8 @@ namespace osu.Game.Skinning
private readonly IResourceStore<byte[]> userFiles;
private IDisposable currentSkinSubscription;
/// <summary>
/// The default skin.
/// </summary>
@ -97,7 +101,16 @@ namespace osu.Game.Skinning
}
});
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin);
CurrentSkinInfo.ValueChanged += skin =>
{
CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin);
scheduler.Add(() =>
{
currentSkinSubscription?.Dispose();
currentSkinSubscription = realm.RegisterForNotifications(r => r.All<SkinInfo>().Where(s => s.ID == skin.NewValue.ID), realmSkinChanged);
});
};
CurrentSkin.Value = DefaultSkin;
CurrentSkin.ValueChanged += skin =>
@ -109,6 +122,12 @@ namespace osu.Game.Skinning
};
}
private void realmSkinChanged<T>(IRealmCollection<T> sender, ChangeSet changes, Exception error) where T : RealmObjectBase
{
Logger.Log("Detected a skin change");
CurrentSkin.Value.InvalidateCaches();
}
public void SelectRandomSkin()
{
realm.Run(r =>