diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 3e0f0cb7f6..d9d23dea6b 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -27,8 +27,8 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 728af5124e..42f70151ac 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -5,7 +5,7 @@
-
+
WinExe
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index af16f39563..e51b20c9fe 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -5,7 +5,7 @@
-
+
WinExe
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index 3d2d1f3fec..f1f75148ef 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -5,7 +5,7 @@
-
+
WinExe
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index fa00922706..c9a320bdd5 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -5,7 +5,7 @@
-
+
WinExe
diff --git a/osu.Game.Tests/Resources/skin-with-space.ini b/osu.Game.Tests/Resources/skin-with-space.ini
new file mode 100644
index 0000000000..3e64257a3e
--- /dev/null
+++ b/osu.Game.Tests/Resources/skin-with-space.ini
@@ -0,0 +1,2 @@
+[General]
+Version: 2
\ No newline at end of file
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index aedf26ee75..dcb866c99f 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -91,6 +91,15 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion);
}
+ [Test]
+ public void TestStripWhitespace()
+ {
+ var decoder = new LegacySkinDecoder();
+ using (var resStream = TestResources.OpenResource("skin-with-space.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion);
+ }
+
[Test]
public void TestDecodeLatestVersion()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
index faa5d9e6fc..8cfe5d8af2 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
@@ -56,6 +56,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
beatmaps.Add(new BeatmapInfo
{
Ruleset = rulesets.GetRuleset(i % 4),
+ RulesetID = i % 4, // workaround for efcore 5 compatibility.
OnlineBeatmapID = beatmapId,
Length = length,
BPM = bpm,
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
index 53a956c77c..9b8b74e6f6 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
@@ -186,6 +186,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Metadata = metadata,
BaseDifficulty = new BeatmapDifficulty(),
Ruleset = ruleset,
+ RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility.
StarDifficulty = difficultyIndex + 1,
Version = $"SR{difficultyIndex + 1}"
}).ToList()
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 35c6d62cb7..2d192ae207 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -911,9 +911,11 @@ namespace osu.Game.Tests.Visual.SongSelect
int length = RNG.Next(30000, 200000);
double bpm = RNG.NextSingle(80, 200);
+ var ruleset = getRuleset();
beatmaps.Add(new BeatmapInfo
{
- Ruleset = getRuleset(),
+ Ruleset = ruleset,
+ RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility.
OnlineBeatmapID = beatmapId,
Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
Length = length,
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index e36b3cdc74..6f8e0fac6f 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index cb0b3a8d09..b291edd19d 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -17,12 +17,12 @@ namespace osu.Game.Beatmaps
public abstract class BeatmapConverter : IBeatmapConverter
where T : HitObject
{
- private event Action> ObjectConverted;
+ private event Action> objectConverted;
event Action> IBeatmapConverter.ObjectConverted
{
- add => ObjectConverted += value;
- remove => ObjectConverted -= value;
+ add => objectConverted += value;
+ remove => objectConverted -= value;
}
public IBeatmap Beatmap { get; }
@@ -92,10 +92,10 @@ namespace osu.Game.Beatmaps
var converted = ConvertHitObject(obj, beatmap, cancellationToken);
- if (ObjectConverted != null)
+ if (objectConverted != null)
{
converted = converted.ToList();
- ObjectConverted.Invoke(obj, converted);
+ objectConverted.Invoke(obj, converted);
}
foreach (var c in converted)
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 29b3f5d3a3..115d1b33bb 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -113,8 +113,6 @@ namespace osu.Game.Beatmaps
{
var metadata = new BeatmapMetadata
{
- Artist = "artist",
- Title = "title",
Author = user,
};
@@ -128,7 +126,6 @@ namespace osu.Game.Beatmaps
BaseDifficulty = new BeatmapDifficulty(),
Ruleset = ruleset,
Metadata = metadata,
- Version = "difficulty"
}
}
};
@@ -174,6 +171,8 @@ namespace osu.Game.Beatmaps
if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null))
throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}.");
+ beatmapSet.Requery(ContextFactory);
+
// check if a set already exists with the same online id, delete if it does.
if (beatmapSet.OnlineBeatmapSetID != null)
{
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 99dffa7041..40bc75e847 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -67,16 +67,14 @@ namespace osu.Game.Beatmaps.Formats
protected override void ParseLine(Beatmap beatmap, Section section, string line)
{
- var strippedLine = StripComments(line);
-
switch (section)
{
case Section.General:
- handleGeneral(strippedLine);
+ handleGeneral(line);
return;
case Section.Editor:
- handleEditor(strippedLine);
+ handleEditor(line);
return;
case Section.Metadata:
@@ -84,19 +82,19 @@ namespace osu.Game.Beatmaps.Formats
return;
case Section.Difficulty:
- handleDifficulty(strippedLine);
+ handleDifficulty(line);
return;
case Section.Events:
- handleEvent(strippedLine);
+ handleEvent(line);
return;
case Section.TimingPoints:
- handleTimingPoint(strippedLine);
+ handleTimingPoint(line);
return;
case Section.HitObjects:
- handleHitObject(strippedLine);
+ handleHitObject(line);
return;
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index df940e8c8e..d06478b9de 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -471,9 +471,6 @@ namespace osu.Game.Beatmaps.Formats
private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo)
{
- if (hitSampleInfo == null)
- return "0";
-
if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy)
return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture);
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index 2fb24c24e0..10a716963e 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -36,6 +36,8 @@ namespace osu.Game.Beatmaps.Formats
if (ShouldSkipLine(line))
continue;
+ line = StripComments(line).TrimEnd();
+
if (line.StartsWith('[') && line.EndsWith(']'))
{
if (!Enum.TryParse(line[1..^1], out section))
@@ -71,8 +73,6 @@ namespace osu.Game.Beatmaps.Formats
protected virtual void ParseLine(T output, Section section, string line)
{
- line = StripComments(line);
-
switch (section)
{
case Section.Colours:
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index b9bf6823b5..6301c42deb 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -45,8 +45,6 @@ namespace osu.Game.Beatmaps.Formats
protected override void ParseLine(Storyboard storyboard, Section section, string line)
{
- line = StripComments(line);
-
switch (section)
{
case Section.General:
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index d809dbcb01..64428882ac 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -462,6 +462,8 @@ namespace osu.Game.Database
// Dereference the existing file info, since the file model will be removed.
if (file.FileInfo != null)
{
+ file.Requery(usage.Context);
+
Files.Dereference(file.FileInfo);
// This shouldn't be required, but here for safety in case the provided TModel is not being change tracked
@@ -635,10 +637,12 @@ namespace osu.Game.Database
{
using (Stream s = reader.GetStream(file))
{
+ var fileInfo = files.Add(s);
fileInfos.Add(new TFileModel
{
Filename = file.Substring(prefix.Length).ToStandardisedPath(),
- FileInfo = files.Add(s)
+ FileInfo = fileInfo,
+ FileInfoID = fileInfo.ID // workaround for efcore 5 compatibility.
});
}
}
diff --git a/osu.Game/Database/DatabaseWorkaroundExtensions.cs b/osu.Game/Database/DatabaseWorkaroundExtensions.cs
new file mode 100644
index 0000000000..a3a982f232
--- /dev/null
+++ b/osu.Game/Database/DatabaseWorkaroundExtensions.cs
@@ -0,0 +1,71 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Scoring;
+using osu.Game.Skinning;
+
+namespace osu.Game.Database
+{
+ ///
+ /// Extension methods which contain workarounds to make EFcore 5.x work with our existing (incorrect) thread safety.
+ /// The intention is to avoid blocking package updates while we consider the future of the database backend, with a potential backend switch imminent.
+ ///
+ public static class DatabaseWorkaroundExtensions
+ {
+ ///
+ /// Re-query the provided model to ensure it is in a sane state. This method requires explicit implementation per model type.
+ ///
+ ///
+ ///
+ public static void Requery(this IHasPrimaryKey model, IDatabaseContextFactory contextFactory)
+ {
+ switch (model)
+ {
+ case SkinInfo skinInfo:
+ requeryFiles(skinInfo.Files, contextFactory);
+ break;
+
+ case ScoreInfo scoreInfo:
+ requeryFiles(scoreInfo.Beatmap.BeatmapSet.Files, contextFactory);
+ requeryFiles(scoreInfo.Files, contextFactory);
+ break;
+
+ case BeatmapSetInfo beatmapSetInfo:
+ var context = contextFactory.Get();
+
+ foreach (var beatmap in beatmapSetInfo.Beatmaps)
+ {
+ // Workaround System.InvalidOperationException
+ // The instance of entity type 'RulesetInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked.
+ beatmap.Ruleset = context.RulesetInfo.Find(beatmap.RulesetID);
+ }
+
+ requeryFiles(beatmapSetInfo.Files, contextFactory);
+ break;
+
+ default:
+ throw new ArgumentException($"{nameof(Requery)} does not have support for the provided model type", nameof(model));
+ }
+
+ void requeryFiles(List files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo
+ {
+ var dbContext = databaseContextFactory.Get();
+
+ foreach (var file in files)
+ {
+ Requery(file, dbContext);
+ }
+ }
+ }
+
+ public static void Requery(this INamedFileInfo file, OsuDbContext dbContext)
+ {
+ // Workaround System.InvalidOperationException
+ // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked.
+ file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID);
+ }
+ }
+}
diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs
index 2aae62edea..2342ab07d4 100644
--- a/osu.Game/Database/OsuDbContext.cs
+++ b/osu.Game/Database/OsuDbContext.cs
@@ -3,7 +3,6 @@
using System;
using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
using osu.Framework.Logging;
using osu.Framework.Statistics;
@@ -111,10 +110,10 @@ namespace osu.Game.Database
{
base.OnConfiguring(optionsBuilder);
optionsBuilder
- // this is required for the time being due to the way we are querying in places like BeatmapStore.
- // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled.
- .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning))
- .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10))
+ .UseSqlite(connectionString,
+ sqliteOptions => sqliteOptions
+ .CommandTimeout(10)
+ .UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery))
.UseLoggerFactory(logger.Value);
}
diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs
index 4aeda74be8..266eb11319 100644
--- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterfaceV2
@@ -53,6 +54,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
CornerRadius = CORNER_RADIUS,
};
+ public override bool AcceptsFocus => true;
+
+ protected override void OnFocus(FocusEvent e)
+ {
+ base.OnFocus(e);
+ GetContainingInputManager().ChangeFocus(Component);
+ }
+
protected override OsuTextBox CreateComponent() => CreateTextBox().With(t =>
{
t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText);
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index dd775888a1..7d11029a9c 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -886,7 +886,8 @@ namespace osu.Game
return true;
case GlobalAction.ToggleGameplayMouseButtons:
- LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons));
+ var mouseDisableButtons = LocalConfig.GetBindable(OsuSetting.MouseDisableButtons);
+ mouseDisableButtons.Value = !mouseDisableButtons.Value;
return true;
case GlobalAction.RandomSkin:
diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs
index 2925107766..662f55317b 100644
--- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs
+++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs
@@ -138,7 +138,6 @@ namespace osu.Game.Overlays.Profile.Header
if (!string.IsNullOrEmpty(user.Twitter))
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}");
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Discord, user.Discord);
- anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat");
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtocol, user.Website);
// If no information was added to the bottomLinkContainer, hide it to avoid unwanted padding
diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs
index d4d0976724..cdb24b784c 100644
--- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs
+++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs
@@ -23,51 +23,24 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
{
this.user.BindTo(user);
CountSection total;
- CountSection avaliable;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Masking = true;
CornerRadius = 3;
- Children = new Drawable[]
- {
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(5, 0),
- Children = new[]
- {
- total = new CountTotal(),
- avaliable = new CountAvailable()
- }
- }
- };
- this.user.ValueChanged += u =>
- {
- total.Count = u.NewValue?.Kudosu.Total ?? 0;
- avaliable.Count = u.NewValue?.Kudosu.Available ?? 0;
- };
+ Child = total = new CountTotal();
+
+ this.user.ValueChanged += u => total.Count = u.NewValue?.Kudosu.Total ?? 0;
}
protected override bool OnClick(ClickEvent e) => true;
- private class CountAvailable : CountSection
- {
- public CountAvailable()
- : base("Kudosu Avaliable")
- {
- DescriptionText.Text = "Kudosu can be traded for kudosu stars, which will help your beatmap get more attention. This is the number of kudosu you haven't traded in yet.";
- }
- }
-
private class CountTotal : CountSection
{
public CountTotal()
: base("Total Kudosu Earned")
{
DescriptionText.AddText("Based on how much of a contribution the user has made to beatmap moderation. See ");
- DescriptionText.AddLink("this link", "https://osu.ppy.sh/wiki/Kudosu");
+ DescriptionText.AddLink("this page", "https://osu.ppy.sh/wiki/Kudosu");
DescriptionText.AddText(" for more information.");
}
}
@@ -80,13 +53,12 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
public new int Count
{
- set => valueText.Text = value.ToString();
+ set => valueText.Text = value.ToString("N0");
}
public CountSection(string header)
{
RelativeSizeAxes = Axes.X;
- Width = 0.5f;
AutoSizeAxes = Axes.Y;
Padding = new MarginPadding { Top = 10, Bottom = 20 };
Child = new FillFlowContainer
@@ -131,7 +103,6 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
private void load(OverlayColourProvider colourProvider)
{
lineBackground.Colour = colourProvider.Highlight1;
- DescriptionText.Colour = colourProvider.Foreground1;
}
}
}
diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs
index f5192f3a40..78101991f6 100644
--- a/osu.Game/Scoring/ScoreInfo.cs
+++ b/osu.Game/Scoring/ScoreInfo.cs
@@ -73,7 +73,7 @@ namespace osu.Game.Scoring
}
set
{
- modsJson = null;
+ modsJson = JsonConvert.SerializeObject(value.Select(m => new DeserializedMod { Acronym = m.Acronym }));
mods = value;
}
}
@@ -86,16 +86,7 @@ namespace osu.Game.Scoring
[Column("Mods")]
public string ModsJson
{
- get
- {
- if (modsJson != null)
- return modsJson;
-
- if (mods == null)
- return null;
-
- return modsJson = JsonConvert.SerializeObject(mods.Select(m => new DeserializedMod { Acronym = m.Acronym }));
- }
+ get => modsJson;
set
{
modsJson = value;
diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs
index 96ec9644b5..1e90ee1ac7 100644
--- a/osu.Game/Scoring/ScoreManager.cs
+++ b/osu.Game/Scoring/ScoreManager.cs
@@ -52,6 +52,11 @@ namespace osu.Game.Scoring
this.configManager = configManager;
}
+ protected override void PreImport(ScoreInfo model)
+ {
+ model.Requery(ContextFactory);
+ }
+
protected override ScoreInfo CreateModel(ArchiveReader archive)
{
if (archive == null)
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
index 2f4721f63e..9d6b44e207 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
@@ -113,16 +113,25 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (e.Repeat || !e.ControlPressed)
return false;
+ bool runOperationFromHotkey(Func operation)
+ {
+ operationStarted();
+ bool result = operation?.Invoke() ?? false;
+ operationEnded();
+
+ return result;
+ }
+
switch (e.Key)
{
case Key.G:
- return CanReverse && OnReverse?.Invoke() == true;
+ return CanReverse && runOperationFromHotkey(OnReverse);
case Key.H:
- return CanScaleX && OnFlip?.Invoke(Direction.Horizontal) == true;
+ return CanScaleX && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Horizontal) ?? false);
case Key.J:
- return CanScaleY && OnFlip?.Invoke(Direction.Vertical) == true;
+ return CanScaleY && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Vertical) ?? false);
}
return base.OnKeyDown(e);
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 0ba202b082..0c24eb6a4d 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -74,7 +74,6 @@ namespace osu.Game.Screens.Edit
private string lastSavedHash;
- private Box bottomBackground;
private Container screenContainer;
private EditorScreen currentScreen;
@@ -106,26 +105,29 @@ namespace osu.Game.Screens.Edit
[BackgroundDependencyLoader]
private void load(OsuColour colours, GameHost host, OsuConfigManager config)
{
- if (Beatmap.Value is DummyWorkingBeatmap)
+ var loadableBeatmap = Beatmap.Value;
+
+ if (loadableBeatmap is DummyWorkingBeatmap)
{
isNewBeatmap = true;
- var newBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
+ loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
+
+ // required so we can get the track length in EditorClock.
+ // this is safe as nothing has yet got a reference to this new beatmap.
+ loadableBeatmap.LoadTrack();
// this is a bit haphazard, but guards against setting the lease Beatmap bindable if
// the editor has already been exited.
if (!ValidForPush)
return;
-
- // this probably shouldn't be set in the asynchronous load method, but everything following relies on it.
- Beatmap.Value = newBeatmap;
}
- beatDivisor.Value = Beatmap.Value.BeatmapInfo.BeatDivisor;
- beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue);
+ beatDivisor.Value = loadableBeatmap.BeatmapInfo.BeatDivisor;
+ beatDivisor.BindValueChanged(divisor => loadableBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue);
// Todo: should probably be done at a DrawableRuleset level to share logic with Player.
- clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false };
+ clock = new EditorClock(loadableBeatmap, beatDivisor) { IsCoupled = false };
UpdateClockSource();
@@ -139,7 +141,7 @@ namespace osu.Game.Screens.Edit
try
{
- playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
+ playableBeatmap = loadableBeatmap.GetPlayableBeatmap(loadableBeatmap.BeatmapInfo.Ruleset);
// clone these locally for now to avoid incurring overhead on GetPlayableBeatmap usages.
// eventually we will want to improve how/where this is done as there are issues with *not* cloning it in all cases.
@@ -153,13 +155,21 @@ namespace osu.Game.Screens.Edit
return;
}
- AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, Beatmap.Value.Skin));
+ AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.Skin));
dependencies.CacheAs(editorBeatmap);
changeHandler = new EditorChangeHandler(editorBeatmap);
dependencies.CacheAs(changeHandler);
updateLastSavedHash();
+ Schedule(() =>
+ {
+ // we need to avoid changing the beatmap from an asynchronous load thread. it can potentially cause weirdness including crashes.
+ // this assumes that nothing during the rest of this load() method is accessing Beatmap.Value (loadableBeatmap should be preferred).
+ // generally this is quite safe, as the actual load of editor content comes after menuBar.Mode.ValueChanged is fired in its own LoadComplete.
+ Beatmap.Value = loadableBeatmap;
+ });
+
OsuMenuItem undoMenuItem;
OsuMenuItem redoMenuItem;
@@ -167,17 +177,6 @@ namespace osu.Game.Screens.Edit
EditorMenuItem copyMenuItem;
EditorMenuItem pasteMenuItem;
- var fileMenuItems = new List