diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 4ddf0077ca..097e620e12 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -74,21 +74,10 @@ namespace osu.Game.Database private static readonly IMapper mapper = new MapperConfiguration(c => { - c.ShouldMapField = fi => false; + applyCommonConfiguration(c); - // This is specifically to avoid mapping explicit interface implementations. - // If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>. - // Takes a bit of effort to determine whether this is the case though, see https://stackoverflow.com/questions/951536/how-do-i-tell-whether-a-type-implements-ilist - c.ShouldMapProperty = pi => pi.GetMethod?.IsPublic == true; - - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); - c.CreateMap(); + // This can be further optimised to reduce cyclic retrievals, similar to the optimised set mapper below. + // Only hasn't been done yet as we detach at the point of BeatmapInfo less often. c.CreateMap() .MaxDepth(2) .AfterMap((s, d) => @@ -102,13 +91,29 @@ namespace osu.Game.Database } } }); - c.CreateMap() - .MaxDepth(2) - .AfterMap((s, d) => - { - foreach (var beatmap in d.Beatmaps) - beatmap.BeatmapSet = d; - }); + }).CreateMapper(); + + /// + /// A slightly optimised mapper that avoids double-fetches in cyclic reference. + /// + private static readonly IMapper beatmap_set_mapper = new MapperConfiguration(c => + { + applyCommonConfiguration(c); + + c.CreateMap() + .MaxDepth(1) + // This is not required as it will be populated in the `AfterMap` call from the `BeatmapInfo`'s parent. + .ForMember(b => b.BeatmapSet, cc => cc.Ignore()); + }).CreateMapper(); + + private static void applyCommonConfiguration(IMapperConfigurationExpression c) + { + c.ShouldMapField = fi => false; + + // This is specifically to avoid mapping explicit interface implementations. + // If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>. + // Takes a bit of effort to determine whether this is the case though, see https://stackoverflow.com/questions/951536/how-do-i-tell-whether-a-type-implements-ilist + c.ShouldMapProperty = pi => pi.GetMethod?.IsPublic == true; c.Internal().ForAllMaps((typeMap, expression) => { @@ -118,7 +123,23 @@ namespace osu.Game.Database m.Ignore(); }); }); - }).CreateMapper(); + + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap(); + c.CreateMap() + .MaxDepth(2) + .AfterMap((s, d) => + { + foreach (var beatmap in d.Beatmaps) + beatmap.BeatmapSet = d; + }); + } /// /// Create a detached copy of the each item in the collection. @@ -153,6 +174,9 @@ namespace osu.Game.Database if (!item.IsManaged) return item; + if (item is BeatmapSetInfo) + return beatmap_set_mapper.Map(item); + return mapper.Map(item); }