1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 08:22:56 +08:00

Merge branch 'master' into realm-nested-context-creation-deadlock-fix

This commit is contained in:
Dan Balasescu 2021-11-30 18:01:00 +09:00 committed by GitHub
commit daa7135381
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 252 additions and 47 deletions

View File

@ -852,7 +852,11 @@ namespace osu.Game.Tests.Database
{
IQueryable<RealmBeatmapSet>? resultSets = null;
waitForOrAssert(() => (resultSets = realm.All<RealmBeatmapSet>().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any(),
waitForOrAssert(() =>
{
realm.Refresh();
return (resultSets = realm.All<RealmBeatmapSet>().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any();
},
@"BeatmapSet did not import to the database in allocated time.", timeout);
// ensure we were stored to beatmap database backing...
@ -865,16 +869,16 @@ namespace osu.Game.Tests.Database
// ReSharper disable once PossibleUnintendedReferenceComparison
IEnumerable<RealmBeatmap> queryBeatmaps() => realm.All<RealmBeatmap>().Where(s => s.BeatmapSet != null && s.BeatmapSet == set);
waitForOrAssert(() => queryBeatmaps().Count() == 12, @"Beatmaps did not import to the database in allocated time", timeout);
waitForOrAssert(() => queryBeatmapSets().Count() == 1, @"BeatmapSet did not import to the database in allocated time", timeout);
Assert.AreEqual(12, queryBeatmaps().Count(), @"Beatmap count was not correct");
Assert.AreEqual(1, queryBeatmapSets().Count(), @"Beatmapset count was not correct");
int countBeatmapSetBeatmaps = 0;
int countBeatmaps = 0;
int countBeatmapSetBeatmaps;
int countBeatmaps;
waitForOrAssert(() =>
(countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) ==
(countBeatmaps = queryBeatmaps().Count()),
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout);
Assert.AreEqual(
countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count,
countBeatmaps = queryBeatmaps().Count(),
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).");
foreach (RealmBeatmap b in set.Beatmaps)
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));

View File

@ -29,6 +29,22 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestAccessAfterAttach()
{
RunTestWithRealm((realmFactory, _) =>
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive();
using (var context = realmFactory.CreateContext())
context.Write(r => r.Add(beatmap));
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
});
}
[Test]
public void TestAccessNonManaged()
{
@ -51,7 +67,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
RealmLive<RealmBeatmap>? liveBeatmap = null;
ILive<RealmBeatmap>? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
@ -88,7 +104,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
RealmLive<RealmBeatmap>? liveBeatmap = null;
ILive<RealmBeatmap>? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
@ -117,7 +133,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
RealmLive<RealmBeatmap>? liveBeatmap = null;
ILive<RealmBeatmap>? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
@ -143,7 +159,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
RealmLive<RealmBeatmap>? liveBeatmap = null;
ILive<RealmBeatmap>? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
@ -176,7 +192,7 @@ namespace osu.Game.Tests.Database
using (var updateThreadContext = realmFactory.CreateContext())
{
updateThreadContext.All<RealmBeatmap>().SubscribeForNotifications(gotChange);
RealmLive<RealmBeatmap>? liveBeatmap = null;
ILive<RealmBeatmap>? liveBeatmap = null;
Task.Factory.StartNew(() =>
{

View File

@ -0,0 +1,71 @@
// 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.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Beatmaps
{
public class TestSceneBeatmapCardDifficultyList : OsuTestScene
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
var beatmapSet = new APIBeatmapSet
{
Beatmaps = new[]
{
new APIBeatmap { RulesetID = 1, StarRating = 5.76, DifficultyName = "Oni" },
new APIBeatmap { RulesetID = 1, StarRating = 3.20, DifficultyName = "Muzukashii" },
new APIBeatmap { RulesetID = 1, StarRating = 2.45, DifficultyName = "Futsuu" },
new APIBeatmap { RulesetID = 0, StarRating = 2.04, DifficultyName = "Normal" },
new APIBeatmap { RulesetID = 0, StarRating = 3.51, DifficultyName = "Hard" },
new APIBeatmap { RulesetID = 0, StarRating = 5.25, DifficultyName = "Insane" },
new APIBeatmap { RulesetID = 2, StarRating = 2.64, DifficultyName = "Salad" },
new APIBeatmap { RulesetID = 2, StarRating = 3.56, DifficultyName = "Platter" },
new APIBeatmap { RulesetID = 2, StarRating = 4.65, DifficultyName = "Rain" },
new APIBeatmap { RulesetID = 3, StarRating = 1.93, DifficultyName = "[7K] Normal" },
new APIBeatmap { RulesetID = 3, StarRating = 3.18, DifficultyName = "[7K] Hyper" },
new APIBeatmap { RulesetID = 3, StarRating = 4.82, DifficultyName = "[7K] Another" },
new APIBeatmap { RulesetID = 4, StarRating = 9.99, DifficultyName = "Unknown?!" },
}
};
Child = new Container
{
Width = 300,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background2
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(10),
Child = new BeatmapCardDifficultyList(beatmapSet)
}
}
};
}
}
}

View File

@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.SongSelect
beatmaps.Add(testBeatmap);
AddStep("set ruleset", () => Ruleset.Value = rulesetInfo);
setRuleset(rulesetInfo);
selectBeatmap(testBeatmap);
@ -167,6 +167,22 @@ namespace osu.Game.Tests.Visual.SongSelect
label => label.Statistic.Name == "BPM" && label.Statistic.Content == target.ToString(CultureInfo.InvariantCulture)));
}
private void setRuleset(RulesetInfo rulesetInfo)
{
Container containerBefore = null;
AddStep("set ruleset", () =>
{
// wedge content is only refreshed if the ruleset changes, so only wait for load in that case.
if (!rulesetInfo.Equals(Ruleset.Value))
containerBefore = infoWedge.DisplayedContent;
Ruleset.Value = rulesetInfo;
});
AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore);
}
private void selectBeatmap([CanBeNull] IBeatmap b)
{
Container containerBefore = null;

View File

@ -288,9 +288,9 @@ namespace osu.Game.Beatmaps
#region Implementation of IModelFileManager<in BeatmapSetInfo,in BeatmapSetFileInfo>
public void ReplaceFile(BeatmapSetInfo model, BeatmapSetFileInfo file, Stream contents, string filename = null)
public void ReplaceFile(BeatmapSetInfo model, BeatmapSetFileInfo file, Stream contents)
{
beatmapModelManager.ReplaceFile(model, file, contents, filename);
beatmapModelManager.ReplaceFile(model, file, contents);
}
public void DeleteFile(BeatmapSetInfo model, BeatmapSetFileInfo file)

View File

@ -0,0 +1,103 @@
// 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.
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Chat;
using osu.Game.Rulesets;
using osuTK;
namespace osu.Game.Beatmaps.Drawables.Cards
{
public class BeatmapCardDifficultyList : CompositeDrawable
{
public BeatmapCardDifficultyList(IBeatmapSetInfo beatmapSetInfo)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
FillFlowContainer flow;
InternalChild = flow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 3)
};
bool firstGroup = true;
foreach (var group in beatmapSetInfo.Beatmaps.GroupBy(beatmap => beatmap.Ruleset.OnlineID).OrderBy(group => group.Key))
{
if (!firstGroup)
{
flow.Add(Empty().With(s =>
{
s.RelativeSizeAxes = Axes.X;
s.Height = 4;
}));
}
foreach (var difficulty in group.OrderBy(b => b.StarRating))
flow.Add(new BeatmapCardDifficultyRow(difficulty));
firstGroup = false;
}
}
private class BeatmapCardDifficultyRow : CompositeDrawable
{
private readonly IBeatmapInfo beatmapInfo;
public BeatmapCardDifficultyRow(IBeatmapInfo beatmapInfo)
{
this.beatmapInfo = beatmapInfo;
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(4, 0),
Children = new[]
{
(rulesets.GetRuleset(beatmapInfo.Ruleset.OnlineID)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }).With(icon =>
{
icon.Anchor = icon.Origin = Anchor.CentreLeft;
icon.Size = new Vector2(16);
}),
new StarRatingDisplay(new StarDifficulty(beatmapInfo.StarRating, 0), StarRatingDisplaySize.Small)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
new LinkFlowContainer(s =>
{
s.Font = OsuFont.Default.With(size: 14, weight: FontWeight.SemiBold);
}).With(d =>
{
d.AutoSizeAxes = Axes.Both;
d.Anchor = Anchor.CentreLeft;
d.Origin = Anchor.CentreLeft;
d.Padding = new MarginPadding { Bottom = 2 };
d.AddLink(beatmapInfo.DifficultyName, LinkAction.OpenBeatmap, beatmapInfo.OnlineID.ToString());
})
}
};
}
}
}
}

View File

@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps.Drawables
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset.OnlineID))
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset.OnlineID).OrderBy(group => group.Key))
{
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key, rulesetGrouping, collapsed));
}

View File

@ -453,13 +453,12 @@ namespace osu.Game.Database
/// <param name="model">The item to operate on.</param>
/// <param name="file">The existing file to be replaced.</param>
/// <param name="contents">The new file contents.</param>
/// <param name="filename">An optional filename for the new file. Will use the previous filename if not specified.</param>
public void ReplaceFile(TModel model, TFileModel file, Stream contents, string filename = null)
public void ReplaceFile(TModel model, TFileModel file, Stream contents)
{
using (ContextFactory.GetForWrite())
{
DeleteFile(model, file);
AddFile(model, contents, filename ?? file.Filename);
AddFile(model, contents, file.Filename);
}
}

View File

@ -15,8 +15,7 @@ namespace osu.Game.Database
/// <param name="model">The item to operate on.</param>
/// <param name="file">The existing file to be replaced.</param>
/// <param name="contents">The new file contents.</param>
/// <param name="filename">An optional filename for the new file. Will use the previous filename if not specified.</param>
void ReplaceFile(TModel model, TFileModel file, Stream contents, string filename = null);
void ReplaceFile(TModel model, TFileModel file, Stream contents);
/// <summary>
/// Delete an existing file.

View File

@ -17,7 +17,7 @@ namespace osu.Game.Database
{
public Guid ID { get; }
public bool IsManaged { get; }
public bool IsManaged => data.IsManaged;
private readonly SynchronizationContext? fetchedContext;
private readonly int fetchedThreadId;
@ -37,8 +37,6 @@ namespace osu.Game.Database
if (data.IsManaged)
{
IsManaged = true;
fetchedContext = SynchronizationContext.Current;
fetchedThreadId = Thread.CurrentThread.ManagedThreadId;
}

View File

@ -49,13 +49,13 @@ namespace osu.Game.Database
return mapper.Map<T>(item);
}
public static List<RealmLive<T>> ToLive<T>(this IEnumerable<T> realmList)
public static List<ILive<T>> ToLive<T>(this IEnumerable<T> realmList)
where T : RealmObject, IHasGuidPrimaryKey
{
return realmList.Select(l => new RealmLive<T>(l)).ToList();
return realmList.Select(l => new RealmLive<T>(l)).Cast<ILive<T>>().ToList();
}
public static RealmLive<T> ToLive<T>(this T realmObject)
public static ILive<T> ToLive<T>(this T realmObject)
where T : RealmObject, IHasGuidPrimaryKey
{
return new RealmLive<T>(realmObject);

View File

@ -66,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.TopRight;
BackgroundColour = Color4.Black.Opacity(0.7f);
MaxHeight = 400;
MaxHeight = 200;
}
protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new DrawableOsuTabDropdownMenuItem(item);

View File

@ -16,6 +16,7 @@ namespace osu.Game.Models
{
public RealmFile File { get; set; } = null!;
// [Indexed] cannot be used on `EmbeddedObject`s as it only applies to top-level queries. May need to reconsider this if performance becomes a concern.
public string Filename { get; set; } = null!;
public RealmNamedFileUsage(RealmFile file, string filename)

View File

@ -377,6 +377,13 @@ namespace osu.Game
FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None;
}
protected override void Update()
{
base.Update();
realmFactory.Refresh();
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
@ -442,10 +449,6 @@ namespace osu.Game
protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage);
private void migrateDataToRealm()
{
}
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
{
if (r.NewValue?.Available != true)

View File

@ -78,8 +78,8 @@ namespace osu.Game.Screens.Edit.Setup
using (var stream = info.OpenRead())
{
if (oldFile != null)
beatmaps.ReplaceFile(set, oldFile, stream, info.Name);
else
beatmaps.DeleteFile(set, oldFile);
beatmaps.AddFile(set, stream, info.Name);
}
@ -105,8 +105,7 @@ namespace osu.Game.Screens.Edit.Setup
using (var stream = info.OpenRead())
{
if (oldFile != null)
beatmaps.ReplaceFile(set, oldFile, stream, info.Name);
else
beatmaps.DeleteFile(set, oldFile);
beatmaps.AddFile(set, stream, info.Name);
}

View File

@ -171,7 +171,7 @@ namespace osu.Game.Skinning
var oldFile = skin.SkinInfo.Files.FirstOrDefault(f => f.Filename == filename);
if (oldFile != null)
skinModelManager.ReplaceFile(skin.SkinInfo, oldFile, streamContent, oldFile.Filename);
skinModelManager.ReplaceFile(skin.SkinInfo, oldFile, streamContent);
else
skinModelManager.AddFile(skin.SkinInfo, streamContent, filename);
}

View File

@ -294,12 +294,8 @@ namespace osu.Game.Stores
/// <remarks>
/// In the case of no matching files, a hash will be generated from the passed archive's <see cref="ArchiveReader.Name"/>.
/// </remarks>
protected virtual string ComputeHash(TModel item, ArchiveReader? reader = null)
protected virtual string ComputeHash(TModel item)
{
if (reader != null)
// fast hashing for cases where the item's files may not be populated.
return computeHashFast(reader);
// for now, concatenate all hashable files in the set to create a unique hash.
MemoryStream hashable = new MemoryStream();
@ -374,7 +370,7 @@ namespace osu.Game.Stores
// TODO: look into rollback of file additions (or delayed commit).
item.Files.AddRange(createFileInfos(archive, Files, realm));
item.Hash = ComputeHash(item, archive);
item.Hash = ComputeHash(item);
// TODO: we may want to run this outside of the transaction.
await Populate(item, archive, realm, cancellationToken).ConfigureAwait(false);