diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 5b63db1972..78b8628669 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -35,6 +35,51 @@ namespace osu.Game.Tests.Database [TestFixture] public class BeatmapImporterTests : RealmTest { + [Test] + public void TestDetach() + { + RunTestWithRealmAsync(async (realmFactory, storage) => + { + using (var importer = new BeatmapModelManager(realmFactory, storage)) + using (new RulesetStore(realmFactory, storage)) + { + ILive? imported; + + using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream())) + imported = await importer.Import(reader); + + Assert.NotNull(imported); + Debug.Assert(imported != null); + + BeatmapSetInfo? detached = null; + + imported.PerformRead(live => + { + var timer = new Stopwatch(); + timer.Start(); + detached = live.Detach(); + Logger.Log($"Detach took {timer.ElapsedMilliseconds} ms"); + + Logger.Log($"NamedFiles: {live.Files.Count} {detached.Files.Count}"); + Logger.Log($"Files: {live.Files.Select(f => f.File).Count()} {detached.Files.Select(f => f.File).Count()}"); + Logger.Log($"Difficulties: {live.Beatmaps.Count} {detached.Beatmaps.Count}"); + Logger.Log($"BeatmapDifficulties: {live.Beatmaps.Select(f => f.Difficulty).Count()} {detached.Beatmaps.Select(f => f.Difficulty).Count()}"); + Logger.Log($"Metadata: {live.Metadata} {detached.Metadata}"); + }); + + Logger.Log("Testing detached-ness"); + + Debug.Assert(detached != null); + + Logger.Log($"NamedFiles: {detached.Files.Count}"); + Logger.Log($"Files: {detached.Files.Select(f => f.File).Count()}"); + Logger.Log($"Difficulties: {detached.Beatmaps.Count}"); + Logger.Log($"BeatmapDifficulties: {detached.Beatmaps.Select(f => f.Difficulty).Count()}"); + Logger.Log($"Metadata: {detached.Metadata}"); + } + }); + } + [Test] public void TestImportBeatmapThenCleanup() { diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index ea2508032e..153f40e39d 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using AutoMapper; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Testing; @@ -143,6 +144,7 @@ public bool BackgroundEquals(BeatmapInfo? other) => other != null private int rulesetID; [Ignored] + [IgnoreMap] public int RulesetID { // ReSharper disable once ConstantConditionalAccessQualifier @@ -182,6 +184,8 @@ public BeatmapDifficulty BaseDifficulty public BeatmapInfo Clone() => this.Detach(); + public override string ToString() => Metadata?.ToString() ?? base.ToString(); + #endregion } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d9484a2880..2479390700 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -151,7 +151,7 @@ public void Restore(BeatmapInfo beatmapInfo) public List GetAllUsableBeatmapSets() { using (var context = contextFactory.CreateContext()) - return context.All().Where(b => !b.DeletePending).ToList(); + return context.All().Where(b => !b.DeletePending).Detach(); } /// @@ -199,7 +199,7 @@ public IQueryable QueryBeatmaps(Expression> /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapInfo? QueryBeatmap(Expression> query) => beatmapModelManager.QueryBeatmap(query); + public BeatmapInfo? QueryBeatmap(Expression> query) => beatmapModelManager.QueryBeatmap(query)?.Detach(); /// /// Saves an file against a given . diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 387521ee1d..254babc3a1 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -107,7 +107,7 @@ public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin public BeatmapInfo? QueryBeatmap(Expression> query) { using (var context = ContextFactory.CreateContext()) - return context.All().FirstOrDefault(query); // TODO: ?.ToLive(); + return context.All().FirstOrDefault(query)?.Detach(); } } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index ed16e3a965..1cc6a96f40 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using AutoMapper; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Extensions; @@ -75,7 +76,10 @@ public bool Equals(BeatmapSetInfo? other) public bool Equals(IBeatmapSetInfo? other) => other is BeatmapSetInfo b && Equals(b); + [IgnoreMap] IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; + + [IgnoreMap] IEnumerable IHasNamedFiles.Files => Files; } } diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index beec48becd..6be69b5477 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -82,6 +82,10 @@ public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo) beatmapInfo = BeatmapManager.QueryBeatmap(b => b.ID == lookupId); } + // TODO: FUCK THE WORLD :D + if (beatmapInfo?.IsManaged == true) + beatmapInfo = beatmapInfo.Detach(); + if (beatmapInfo?.BeatmapSet == null) return DefaultBeatmap; diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index c4f991094c..d230e649f7 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -209,7 +209,7 @@ private List readCollections(Stream stream, ProgressNotificat string checksum = sr.ReadString(); - var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum); + var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum)?.Detach(); if (beatmap != null) collection.Beatmaps.Add(beatmap); } diff --git a/osu.Game/Database/RealmLive.cs b/osu.Game/Database/RealmLive.cs index 90b8814c24..45bc0e4200 100644 --- a/osu.Game/Database/RealmLive.cs +++ b/osu.Game/Database/RealmLive.cs @@ -61,8 +61,9 @@ public void PerformRead(Action perform) /// The action to perform. public TReturn PerformRead(Func perform) { - if (typeof(RealmObjectBase).IsAssignableFrom(typeof(TReturn))) - throw new InvalidOperationException(@$"Realm live objects should not exit the scope of {nameof(PerformRead)}."); + // TODO: this is weird and kinda wrong... unmanaged objects should be allowed? + // if (typeof(RealmObjectBase).IsAssignableFrom(typeof(TReturn))) + // throw new InvalidOperationException(@$"Realm live objects should not exit the scope of {nameof(PerformRead)}."); if (!IsManaged) return perform(data); diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index e09f046421..9d7d08e106 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -6,7 +6,10 @@ using System.Linq; using AutoMapper; using osu.Framework.Development; +using osu.Game.Beatmaps; using osu.Game.Input.Bindings; +using osu.Game.Models; +using osu.Game.Rulesets; using Realms; #nullable enable @@ -18,9 +21,23 @@ public static class RealmObjectExtensions private static readonly IMapper mapper = new MapperConfiguration(c => { c.ShouldMapField = fi => false; - c.ShouldMapProperty = pi => pi.SetMethod != null && pi.SetMethod.IsPublic; + c.ShouldMapProperty = pi => true; c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + + c.ForAllMaps((a, b) => + { + b.PreserveReferences(); + b.MaxDepth(2); + }); }).CreateMapper(); /// @@ -32,7 +49,7 @@ public static class RealmObjectExtensions /// A list of managed s to detach. /// The type of object. /// A list containing non-managed copies of provided items. - public static List Detach(this IEnumerable items) where T : RealmObject + public static List Detach(this IEnumerable items) where T : RealmObjectBase { var list = new List(); @@ -51,7 +68,7 @@ public static List Detach(this IEnumerable items) where T : RealmObject /// The managed to detach. /// The type of object. /// A non-managed copy of provided item. Will return the provided item if already detached. - public static T Detach(this T item) where T : RealmObject + public static T Detach(this T item) where T : RealmObjectBase { if (!item.IsManaged) return item; @@ -65,7 +82,8 @@ public static List> ToLiveUnmanaged(this IEnumerable realmList) return realmList.Select(l => new RealmLiveUnmanaged(l)).Cast>().ToList(); } - public static ILive ToLiveUnmanaged(this T realmObject) + public static ILive ToLiveUnmanaged(this T realmObject + ) where T : RealmObject, IHasGuidPrimaryKey { return new RealmLiveUnmanaged(realmObject); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 78e416c64b..226d2df619 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -662,6 +662,10 @@ private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) if (beatmapSet.Beatmaps.All(b => b.Hidden)) return null; + // TODO: FUCK THE WORLD :D + if (beatmapSet?.IsManaged == true) + beatmapSet = beatmapSet.Detach(); + // todo: probably not required any more. // foreach (var b in beatmapSet.Beatmaps) // b.Metadata ??= beatmapSet.Metadata;