mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 15:07:44 +08:00
Merge branch 'master' into realm-integration/live-queryable-fix
This commit is contained in:
commit
8fdb9ab4e5
@ -852,7 +852,11 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
IQueryable<RealmBeatmapSet>? resultSets = null;
|
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);
|
@"BeatmapSet did not import to the database in allocated time.", timeout);
|
||||||
|
|
||||||
// ensure we were stored to beatmap database backing...
|
// ensure we were stored to beatmap database backing...
|
||||||
@ -865,16 +869,16 @@ namespace osu.Game.Tests.Database
|
|||||||
// ReSharper disable once PossibleUnintendedReferenceComparison
|
// ReSharper disable once PossibleUnintendedReferenceComparison
|
||||||
IEnumerable<RealmBeatmap> queryBeatmaps() => realm.All<RealmBeatmap>().Where(s => s.BeatmapSet != null && s.BeatmapSet == set);
|
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);
|
Assert.AreEqual(12, queryBeatmaps().Count(), @"Beatmap count was not correct");
|
||||||
waitForOrAssert(() => queryBeatmapSets().Count() == 1, @"BeatmapSet did not import to the database in allocated time", timeout);
|
Assert.AreEqual(1, queryBeatmapSets().Count(), @"Beatmapset count was not correct");
|
||||||
|
|
||||||
int countBeatmapSetBeatmaps = 0;
|
int countBeatmapSetBeatmaps;
|
||||||
int countBeatmaps = 0;
|
int countBeatmaps;
|
||||||
|
|
||||||
waitForOrAssert(() =>
|
Assert.AreEqual(
|
||||||
(countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) ==
|
countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count,
|
||||||
(countBeatmaps = queryBeatmaps().Count()),
|
countBeatmaps = queryBeatmaps().Count(),
|
||||||
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout);
|
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).");
|
||||||
|
|
||||||
foreach (RealmBeatmap b in set.Beatmaps)
|
foreach (RealmBeatmap b in set.Beatmaps)
|
||||||
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
|
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
|
||||||
|
@ -5,6 +5,8 @@ using System;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Models;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
@ -33,6 +35,39 @@ namespace osu.Game.Tests.Database
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test to ensure that a `CreateContext` call nested inside a subscription doesn't cause any deadlocks
|
||||||
|
/// due to context fetching semaphores.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestNestedContextCreationWithSubscription()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
|
{
|
||||||
|
bool callbackRan = false;
|
||||||
|
|
||||||
|
using (var context = realmFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var subscription = context.All<RealmBeatmap>().SubscribeForNotifications((sender, changes, error) =>
|
||||||
|
{
|
||||||
|
using (realmFactory.CreateContext())
|
||||||
|
{
|
||||||
|
callbackRan = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Force the callback above to run.
|
||||||
|
using (realmFactory.CreateContext())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.IsTrue(callbackRan);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBlockOperationsWithContention()
|
public void TestBlockOperationsWithContention()
|
||||||
{
|
{
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
beatmaps.Add(testBeatmap);
|
beatmaps.Add(testBeatmap);
|
||||||
|
|
||||||
AddStep("set ruleset", () => Ruleset.Value = rulesetInfo);
|
setRuleset(rulesetInfo);
|
||||||
|
|
||||||
selectBeatmap(testBeatmap);
|
selectBeatmap(testBeatmap);
|
||||||
|
|
||||||
@ -167,6 +167,22 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
label => label.Statistic.Name == "BPM" && label.Statistic.Content == target.ToString(CultureInfo.InvariantCulture)));
|
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)
|
private void selectBeatmap([CanBeNull] IBeatmap b)
|
||||||
{
|
{
|
||||||
Container containerBefore = null;
|
Container containerBefore = null;
|
||||||
|
103
osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDifficultyList.cs
Normal file
103
osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDifficultyList.cs
Normal 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());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
|
||||||
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
|
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));
|
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key, rulesetGrouping, collapsed));
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,8 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly SemaphoreSlim contextCreationLock = new SemaphoreSlim(1);
|
private readonly SemaphoreSlim contextCreationLock = new SemaphoreSlim(1);
|
||||||
|
|
||||||
|
private readonly ThreadLocal<bool> currentThreadCanCreateContexts = new ThreadLocal<bool>();
|
||||||
|
|
||||||
private static readonly GlobalStatistic<int> refreshes = GlobalStatistics.Get<int>(@"Realm", @"Dirty Refreshes");
|
private static readonly GlobalStatistic<int> refreshes = GlobalStatistics.Get<int>(@"Realm", @"Dirty Refreshes");
|
||||||
private static readonly GlobalStatistic<int> contexts_created = GlobalStatistics.Get<int>(@"Realm", @"Contexts (Created)");
|
private static readonly GlobalStatistic<int> contexts_created = GlobalStatistics.Get<int>(@"Realm", @"Contexts (Created)");
|
||||||
|
|
||||||
@ -151,9 +153,22 @@ namespace osu.Game.Database
|
|||||||
if (isDisposed)
|
if (isDisposed)
|
||||||
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
||||||
|
|
||||||
|
bool tookSemaphoreLock = false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
contextCreationLock.Wait();
|
if (!currentThreadCanCreateContexts.Value)
|
||||||
|
{
|
||||||
|
contextCreationLock.Wait();
|
||||||
|
currentThreadCanCreateContexts.Value = true;
|
||||||
|
tookSemaphoreLock = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// the semaphore is used to handle blocking of all context creation during certain periods.
|
||||||
|
// once the semaphore has been taken by this code section, it is safe to create further contexts on the same thread.
|
||||||
|
// this can happen if a realm subscription is active and triggers a callback which has user code that calls `CreateContext`.
|
||||||
|
}
|
||||||
|
|
||||||
contexts_created.Value++;
|
contexts_created.Value++;
|
||||||
|
|
||||||
@ -161,7 +176,11 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
contextCreationLock.Release();
|
if (tookSemaphoreLock)
|
||||||
|
{
|
||||||
|
contextCreationLock.Release();
|
||||||
|
currentThreadCanCreateContexts.Value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Origin = Anchor.TopRight;
|
Origin = Anchor.TopRight;
|
||||||
|
|
||||||
BackgroundColour = Color4.Black.Opacity(0.7f);
|
BackgroundColour = Color4.Black.Opacity(0.7f);
|
||||||
MaxHeight = 400;
|
MaxHeight = 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new DrawableOsuTabDropdownMenuItem(item);
|
protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new DrawableOsuTabDropdownMenuItem(item);
|
||||||
|
@ -377,6 +377,13 @@ namespace osu.Game
|
|||||||
FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None;
|
FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
realmFactory.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||||
dependencies = new DependencyContainer(base.CreateChildDependencies(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);
|
protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage);
|
||||||
|
|
||||||
private void migrateDataToRealm()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
|
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
|
||||||
{
|
{
|
||||||
if (r.NewValue?.Available != true)
|
if (r.NewValue?.Available != true)
|
||||||
|
Loading…
Reference in New Issue
Block a user