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

Provide a realm factory to usages of ToLive/RealmLive

This commit is contained in:
Dean Herbert 2021-12-14 14:21:23 +09:00
parent f9a2db5ec6
commit 441b7baa93
11 changed files with 67 additions and 44 deletions

View File

@ -56,13 +56,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
[Test]
public void TestDefaultSkin()
{
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLive());
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged());
}
[Test]
public void TestLegacySkin()
{
AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLive());
AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged());
}
}
}

View File

@ -22,9 +22,9 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
ILive<RealmBeatmap> beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive();
ILive<RealmBeatmap> beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(realmFactory);
ILive<RealmBeatmap> beatmap2 = realmFactory.CreateContext().All<RealmBeatmap>().First().ToLive();
ILive<RealmBeatmap> beatmap2 = realmFactory.CreateContext().All<RealmBeatmap>().First().ToLive(realmFactory);
Assert.AreEqual(beatmap, beatmap2);
});
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Database
{
context.Write(r => r.Add(beatmap));
liveBeatmap = beatmap.ToLive();
liveBeatmap = beatmap.ToLive(realmFactory);
}
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
@ -64,7 +64,7 @@ namespace osu.Game.Tests.Database
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive();
var liveBeatmap = beatmap.ToLive(realmFactory);
using (var context = realmFactory.CreateContext())
context.Write(r => r.Add(beatmap));
@ -77,7 +77,7 @@ namespace osu.Game.Tests.Database
public void TestAccessNonManaged()
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive();
var liveBeatmap = beatmap.ToLiveUnmanaged();
Assert.IsFalse(beatmap.Hidden);
Assert.IsFalse(liveBeatmap.Value.Hidden);
@ -102,7 +102,7 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive();
liveBeatmap = beatmap.ToLive(realmFactory);
}
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -131,7 +131,7 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive();
liveBeatmap = beatmap.ToLive(realmFactory);
}
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -151,7 +151,7 @@ namespace osu.Game.Tests.Database
RunTestWithRealm((realmFactory, _) =>
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive();
var liveBeatmap = beatmap.ToLive(realmFactory);
Assert.DoesNotThrow(() =>
{
@ -173,7 +173,7 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive();
liveBeatmap = beatmap.ToLive(realmFactory);
}
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -211,7 +211,7 @@ namespace osu.Game.Tests.Database
{
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive();
liveBeatmap = beatmap.ToLive(realmFactory);
}
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -250,7 +250,7 @@ namespace osu.Game.Tests.Database
// not just a refresh from the resolved Live.
threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive();
liveBeatmap = beatmap.ToLive(realmFactory);
}
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();

View File

@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Background
private void setCustomSkin()
{
// feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin.
AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo().ToLive());
AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo().ToLiveUnmanaged());
}
private void setDefaultSkin() => AddStep("set default skin", () => skins.CurrentSkinInfo.SetDefault());

View File

@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("setup skins", () =>
{
skinManager.CurrentSkinInfo.Value = gameCurrentSkin.ToLive();
skinManager.CurrentSkinInfo.Value = gameCurrentSkin.ToLiveUnmanaged();
currentBeatmapSkin = getBeatmapSkin();
});
});

View File

@ -24,14 +24,22 @@ namespace osu.Game.Database
/// </summary>
private readonly T data;
private readonly RealmContextFactory? realmFactory;
/// <summary>
/// Construct a new instance of live realm data.
/// </summary>
/// <param name="data">The realm data.</param>
public RealmLive(T data)
/// <param name="realmFactory">The realm factory the data was sourced from. May be null for an unmanaged object.</param>
public RealmLive(T data, RealmContextFactory? realmFactory)
{
this.data = data;
if (IsManaged && realmFactory == null)
throw new ArgumentException(@"Realm factory must be provided for a managed instance", nameof(realmFactory));
this.realmFactory = realmFactory;
ID = data.ID;
}
@ -47,7 +55,10 @@ namespace osu.Game.Database
return;
}
using (var realm = Realm.GetInstance(data.Realm.Config))
if (realmFactory == null)
throw new ArgumentException(@"Realm factory must be provided for a managed instance", nameof(realmFactory));
using (var realm = realmFactory.CreateContext())
perform(realm.Find<T>(ID));
}
@ -58,12 +69,15 @@ namespace osu.Game.Database
public TReturn PerformRead<TReturn>(Func<T, TReturn> perform)
{
if (typeof(RealmObjectBase).IsAssignableFrom(typeof(TReturn)))
throw new InvalidOperationException($"Realm live objects should not exit the scope of {nameof(PerformRead)}.");
throw new InvalidOperationException(@$"Realm live objects should not exit the scope of {nameof(PerformRead)}.");
if (!IsManaged)
return perform(data);
using (var realm = Realm.GetInstance(data.Realm.Config))
if (realmFactory == null)
throw new ArgumentException(@"Realm factory must be provided for a managed instance", nameof(realmFactory));
using (var realm = realmFactory.CreateContext())
return perform(realm.Find<T>(ID));
}
@ -74,7 +88,7 @@ namespace osu.Game.Database
public void PerformWrite(Action<T> perform)
{
if (!IsManaged)
throw new InvalidOperationException("Can't perform writes on a non-managed underlying value");
throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value");
PerformRead(t =>
{
@ -94,11 +108,7 @@ namespace osu.Game.Database
if (!ThreadSafety.IsUpdateThread)
throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads");
// When using Value, we rely on garbage collection for the realm instance used to retrieve the instance.
// As we are sure that this is on the update thread, there should always be an open and constantly refreshing realm instance to ensure file size growth is a non-issue.
var realm = Realm.GetInstance(data.Realm.Config);
return realm.Find<T>(ID);
return realmFactory!.Context.Find<T>(ID);
}
}

View File

@ -53,16 +53,28 @@ namespace osu.Game.Database
return mapper.Map<T>(item);
}
public static List<ILive<T>> ToLive<T>(this IEnumerable<T> realmList)
public static List<ILive<T>> ToLiveUnmanaged<T>(this IEnumerable<T> realmList)
where T : RealmObject, IHasGuidPrimaryKey
{
return realmList.Select(l => new RealmLive<T>(l)).Cast<ILive<T>>().ToList();
return realmList.Select(l => new RealmLive<T>(l, null)).Cast<ILive<T>>().ToList();
}
public static ILive<T> ToLive<T>(this T realmObject)
public static ILive<T> ToLiveUnmanaged<T>(this T realmObject)
where T : RealmObject, IHasGuidPrimaryKey
{
return new RealmLive<T>(realmObject);
return new RealmLive<T>(realmObject, null);
}
public static List<ILive<T>> ToLive<T>(this IEnumerable<T> realmList, RealmContextFactory realmContextFactory)
where T : RealmObject, IHasGuidPrimaryKey
{
return realmList.Select(l => new RealmLive<T>(l, realmContextFactory)).Cast<ILive<T>>().ToList();
}
public static ILive<T> ToLive<T>(this T realmObject, RealmContextFactory realmContextFactory)
where T : RealmObject, IHasGuidPrimaryKey
{
return new RealmLive<T>(realmObject, realmContextFactory);
}
/// <summary>

View File

@ -255,10 +255,10 @@ namespace osu.Game
if (skinInfo == null)
{
if (guid == SkinInfo.CLASSIC_SKIN)
skinInfo = DefaultLegacySkin.CreateInfo().ToLive();
skinInfo = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged();
}
SkinManager.CurrentSkinInfo.Value = skinInfo ?? DefaultSkin.CreateInfo().ToLive();
SkinManager.CurrentSkinInfo.Value = skinInfo ?? DefaultSkin.CreateInfo().ToLiveUnmanaged();
};
configSkin.TriggerChange();

View File

@ -32,14 +32,14 @@ namespace osu.Game.Overlays.Settings.Sections
Icon = FontAwesome.Solid.PaintBrush
};
private readonly Bindable<ILive<SkinInfo>> dropdownBindable = new Bindable<ILive<SkinInfo>> { Default = DefaultSkin.CreateInfo().ToLive() };
private readonly Bindable<ILive<SkinInfo>> dropdownBindable = new Bindable<ILive<SkinInfo>> { Default = DefaultSkin.CreateInfo().ToLiveUnmanaged() };
private readonly Bindable<string> configBindable = new Bindable<string>();
private static readonly ILive<SkinInfo> random_skin_info = new SkinInfo
{
ID = SkinInfo.RANDOM_SKIN,
Name = "<Random Skin>",
}.ToLive();
}.ToLiveUnmanaged();
private List<ILive<SkinInfo>> skinItems;
@ -133,7 +133,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
int protectedCount = realmSkins.Count(s => s.Protected);
skinItems = realmSkins.ToLive();
skinItems = realmSkins.ToLive(realmFactory);
skinItems.Insert(protectedCount, random_skin_info);

View File

@ -43,7 +43,7 @@ namespace osu.Game.Skinning
protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null)
{
SkinInfo = skin.ToLive();
SkinInfo = skin.ToLive(resources.RealmContextFactory);
this.resources = resources;
configurationStream ??= getConfigurationStream();

View File

@ -47,9 +47,9 @@ namespace osu.Game.Skinning
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>();
public readonly Bindable<ILive<SkinInfo>> CurrentSkinInfo = new Bindable<ILive<SkinInfo>>(Skinning.DefaultSkin.CreateInfo().ToLive())
public readonly Bindable<ILive<SkinInfo>> CurrentSkinInfo = new Bindable<ILive<SkinInfo>>(Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged())
{
Default = Skinning.DefaultSkin.CreateInfo().ToLive()
Default = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()
};
private readonly SkinModelManager skinModelManager;
@ -119,13 +119,13 @@ namespace osu.Game.Skinning
if (randomChoices.Length == 0)
{
CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLive();
CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged();
return;
}
var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length));
CurrentSkinInfo.Value = chosen.ToLive();
CurrentSkinInfo.Value = chosen.ToLive(contextFactory);
}
}
@ -182,7 +182,7 @@ namespace osu.Game.Skinning
public ILive<SkinInfo> Query(Expression<Func<SkinInfo, bool>> query)
{
using (var context = contextFactory.CreateContext())
return context.All<SkinInfo>().FirstOrDefault(query)?.ToLive();
return context.All<SkinInfo>().FirstOrDefault(query)?.ToLive(contextFactory);
}
public event Action SourceChanged;
@ -237,6 +237,7 @@ namespace osu.Game.Skinning
AudioManager IStorageResourceProvider.AudioManager => audio;
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
IResourceStore<byte[]> IStorageResourceProvider.Files => userFiles;
RealmContextFactory IStorageResourceProvider.RealmContextFactory => contextFactory;
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore);
#endregion
@ -302,7 +303,7 @@ namespace osu.Game.Skinning
Guid currentUserSkin = CurrentSkinInfo.Value.ID;
if (items.Any(s => s.ID == currentUserSkin))
scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLive());
scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged());
skinModelManager.Delete(items.ToList(), silent);
}

View File

@ -352,7 +352,7 @@ namespace osu.Game.Stores
transaction.Commit();
}
return existing.ToLive();
return existing.ToLive(ContextFactory);
}
LogForModel(item, @"Found existing (optimised) but failed pre-check.");
@ -387,7 +387,7 @@ namespace osu.Game.Stores
existing.DeletePending = false;
transaction.Commit();
return existing.ToLive();
return existing.ToLive(ContextFactory);
}
LogForModel(item, @"Found existing but failed re-use check.");
@ -416,7 +416,7 @@ namespace osu.Game.Stores
throw;
}
return item.ToLive();
return item.ToLive(ContextFactory);
}
}