mirror of
https://github.com/osukey/osukey.git
synced 2025-08-07 16:43:52 +09: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:
@ -11,6 +11,7 @@ using osu.Framework.IO.Stores;
|
|||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Rulesets.Objects.Legacy;
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -37,11 +38,11 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
private static IResourceStore<byte[]> createRealmBackedStore(BeatmapInfo beatmapInfo, IStorageResourceProvider? resources)
|
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.
|
// should only ever be used in tests.
|
||||||
return new ResourceStore<byte[]>();
|
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)
|
public override Drawable? GetDrawableComponent(ISkinComponent component)
|
||||||
|
@ -1,51 +1,68 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
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)
|
: base(underlyingStore)
|
||||||
{
|
{
|
||||||
// Must be initialised before the file cache.
|
liveSource = source;
|
||||||
if (extensions != null)
|
|
||||||
{
|
invalidateCache();
|
||||||
foreach (string extension in extensions)
|
Debug.Assert(fileToStoragePathMapping != null);
|
||||||
AddExtension(extension);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initialiseFileCache(source);
|
public void Invalidate() => invalidateCache();
|
||||||
}
|
|
||||||
|
|
||||||
private void initialiseFileCache(IHasRealmFiles source)
|
|
||||||
{
|
|
||||||
fileToStoragePathMapping.Clear();
|
|
||||||
foreach (var f in source.Files)
|
|
||||||
fileToStoragePathMapping[f.Filename.ToLowerInvariant()] = f.File.GetStoragePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IEnumerable<string> GetFilenames(string name)
|
protected override IEnumerable<string> GetFilenames(string name)
|
||||||
{
|
{
|
||||||
foreach (string filename in base.GetFilenames(name))
|
foreach (string filename in base.GetFilenames(name))
|
||||||
{
|
{
|
||||||
string path = getPathForFile(filename.ToStandardisedPath());
|
string? path = getPathForFile(filename.ToStandardisedPath());
|
||||||
if (path != null)
|
if (path != null)
|
||||||
yield return path;
|
yield return path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string getPathForFile(string filename) =>
|
private string? getPathForFile(string filename)
|
||||||
fileToStoragePathMapping.TryGetValue(filename.ToLower(), out string path) ? path : null;
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,10 @@ namespace osu.Game.Skinning
|
|||||||
where TLookup : notnull
|
where TLookup : notnull
|
||||||
where TValue : notnull;
|
where TValue : notnull;
|
||||||
|
|
||||||
|
public void InvalidateCaches() => realmBackedStorage?.Invalidate();
|
||||||
|
|
||||||
|
private readonly RealmBackedResourceStore<SkinInfo> realmBackedStorage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a new skin.
|
/// Construct a new skin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -67,7 +71,9 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
SkinInfo = skin.ToLive(resources.RealmAccess);
|
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);
|
var samples = resources.AudioManager?.GetSampleStore(storage);
|
||||||
if (samples != null)
|
if (samples != null)
|
||||||
|
@ -16,6 +16,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.OpenGL.Textures;
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
@ -26,6 +27,7 @@ using osu.Game.IO;
|
|||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
@ -59,6 +61,8 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
private readonly IResourceStore<byte[]> userFiles;
|
private readonly IResourceStore<byte[]> userFiles;
|
||||||
|
|
||||||
|
private IDisposable currentSkinSubscription;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default skin.
|
/// The default skin.
|
||||||
/// </summary>
|
/// </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.Value = DefaultSkin;
|
||||||
CurrentSkin.ValueChanged += skin =>
|
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()
|
public void SelectRandomSkin()
|
||||||
{
|
{
|
||||||
realm.Run(r =>
|
realm.Run(r =>
|
||||||
|
Reference in New Issue
Block a user