diff --git a/osu.Android.props b/osu.Android.props
index 584fe0a3ef..ceea60a1c1 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs
index 07e60c82d0..414879f42d 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs
@@ -1,12 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Skinning;
using osuTK.Graphics;
@@ -18,8 +18,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private readonly ISkin skin;
- private Sprite layerNd;
- private Sprite layerSpec;
+ [Resolved(canBeNull: true)]
+ private DrawableHitObject? parentObject { get; set; }
+
+ private Sprite layerNd = null!;
+ private Sprite layerSpec = null!;
public LegacySliderBall(Drawable animationContent, ISkin skin)
{
@@ -58,6 +61,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
};
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ if (parentObject != null)
+ {
+ parentObject.ApplyCustomUpdateState += updateStateTransforms;
+ updateStateTransforms(parentObject, parentObject.State.Value);
+ }
+ }
+
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
@@ -68,5 +82,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
layerNd.Rotation = -appliedRotation;
layerSpec.Rotation = -appliedRotation;
}
+
+ private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState _)
+ {
+ // Gets called by slider ticks, tails, etc., leading to duplicated
+ // animations which in this case have no visual impact (due to
+ // instant fade) but may negatively affect performance
+ if (drawableObject is not DrawableSlider)
+ return;
+
+ using (BeginAbsoluteSequence(drawableObject.StateUpdateTime))
+ this.FadeIn();
+
+ using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
+ this.FadeOut();
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (parentObject != null)
+ parentObject.ApplyCustomUpdateState -= updateStateTransforms;
+ }
}
}
diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs
index 5b6f7a0a53..fd0b391d0d 100644
--- a/osu.Game.Tests/Database/GeneralUsageTests.cs
+++ b/osu.Game.Tests/Database/GeneralUsageTests.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realm, _) =>
{
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("testing"))
{
}
});
@@ -56,7 +56,7 @@ namespace osu.Game.Tests.Database
{
Task writeTask;
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("testing"))
{
writeTask = realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
Thread.Sleep(100);
@@ -169,7 +169,7 @@ namespace osu.Game.Tests.Database
Assert.Throws(() =>
{
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("testing"))
{
}
});
@@ -177,7 +177,7 @@ namespace osu.Game.Tests.Database
stopThreadedUsage.Set();
// Ensure we can block a second time after the usage has ended.
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("testing"))
{
}
});
diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs
index fd1f564f59..a50eb22c67 100644
--- a/osu.Game.Tests/Database/RealmLiveTests.cs
+++ b/osu.Game.Tests/Database/RealmLiveTests.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.Database
{
migratedStorage.DeleteDirectory(string.Empty);
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("testing"))
{
storage.Migrate(migratedStorage);
}
diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
index c74341b5c9..4ee302bbd0 100644
--- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
+++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs
@@ -136,7 +136,7 @@ namespace osu.Game.Tests.Database
resolvedItems = null;
lastChanges = null;
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("testing"))
Assert.That(resolvedItems, Is.Empty);
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
@@ -154,7 +154,7 @@ namespace osu.Game.Tests.Database
testEventsArriving(false);
// And make sure even after another context loss we don't get firings.
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("testing"))
Assert.That(resolvedItems, Is.Null);
realm.Write(r => r.Add(TestResources.CreateTestBeatmapSetInfo()));
@@ -212,7 +212,7 @@ namespace osu.Game.Tests.Database
Assert.That(beatmapSetInfo, Is.Not.Null);
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("testing"))
{
// custom disposal action fired when context lost.
Assert.That(beatmapSetInfo, Is.Null);
@@ -226,7 +226,7 @@ namespace osu.Game.Tests.Database
Assert.That(beatmapSetInfo, Is.Null);
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("testing"))
Assert.That(beatmapSetInfo, Is.Null);
realm.Run(r => r.Refresh());
@@ -251,7 +251,7 @@ namespace osu.Game.Tests.Database
Assert.That(receivedValue, Is.Not.Null);
receivedValue = null;
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("testing"))
{
}
@@ -262,7 +262,7 @@ namespace osu.Game.Tests.Database
subscription.Dispose();
receivedValue = null;
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("testing"))
Assert.That(receivedValue, Is.Null);
realm.Run(r => r.Refresh());
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 247ea52648..0b982a5745 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -576,12 +576,13 @@ namespace osu.Game.Tests.Visual.Online
private Channel createAnnounceChannel()
{
- int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1);
+ const int announce_channel_id = 133337;
+
return new Channel
{
- Name = $"Announce {id}",
+ Name = $"Announce {announce_channel_id}",
Type = ChannelType.Announce,
- Id = id,
+ Id = announce_channel_id,
};
}
diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs
index 896d111989..8f2ff600d8 100644
--- a/osu.Game/Database/EFToRealmMigrator.cs
+++ b/osu.Game/Database/EFToRealmMigrator.cs
@@ -126,7 +126,7 @@ namespace osu.Game.Database
string backupSuffix = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
// required for initial backup.
- var realmBlockOperations = realm.BlockAllOperations();
+ var realmBlockOperations = realm.BlockAllOperations("EF migration");
Task.Factory.StartNew(() =>
{
diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs
index 8cf9bb4a47..ed56049064 100644
--- a/osu.Game/Database/RealmAccess.cs
+++ b/osu.Game/Database/RealmAccess.cs
@@ -780,7 +780,7 @@ namespace osu.Game.Database
public void CreateBackup(string backupFilename, IDisposable? blockAllOperations = null)
{
- using (blockAllOperations ?? BlockAllOperations())
+ using (blockAllOperations ?? BlockAllOperations("creating backup"))
{
Logger.Log($"Creating full realm database backup at {backupFilename}", LoggingTarget.Database);
@@ -811,9 +811,12 @@ namespace osu.Game.Database
/// This should be used in places we need to ensure no ongoing reads/writes are occurring with realm.
/// ie. to move the realm backing file to a new location.
///
+ /// The reason for blocking. Used for logging purposes.
/// An which should be disposed to end the blocking section.
- public IDisposable BlockAllOperations()
+ public IDisposable BlockAllOperations(string reason)
{
+ Logger.Log($@"Attempting to block all realm operations for {reason}.", LoggingTarget.Database);
+
if (!ThreadSafety.IsUpdateThread)
throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread.");
@@ -843,10 +846,10 @@ namespace osu.Game.Database
updateRealm = null;
}
- Logger.Log(@"Blocking realm operations.", LoggingTarget.Database);
+ Logger.Log(@"Lock acquired for blocking operations", LoggingTarget.Database);
const int sleep_length = 200;
- int timeout = 5000;
+ int timeSpent = 0;
try
{
@@ -854,10 +857,10 @@ namespace osu.Game.Database
while (!Compact())
{
Thread.Sleep(sleep_length);
- timeout -= sleep_length;
+ timeSpent += sleep_length;
- if (timeout < 0)
- throw new TimeoutException(@"Took too long to acquire lock");
+ if (timeSpent > 5000)
+ throw new TimeoutException($@"Realm compact failed after {timeSpent / sleep_length} attempts over {timeSpent / 1000} seconds");
}
}
catch (RealmException e)
@@ -867,6 +870,8 @@ namespace osu.Game.Database
Logger.Log($"Realm compact failed with error {e}", LoggingTarget.Database);
}
+ Logger.Log(@"Realm usage isolated via compact", LoggingTarget.Database);
+
// In order to ensure events arrive in the correct order, these *must* be fired post disposal of the update realm,
// and must be posted to the synchronization context.
// This is because realm may fire event callbacks between the `unregisterAllSubscriptions` and `updateRealm.Dispose`
diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
index 3356153e17..7cd89e5b87 100644
--- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
+++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
@@ -165,6 +165,9 @@ namespace osu.Game.Graphics.UserInterface
base.OnHoverLost(e);
}
+ protected override bool ShouldHandleAsRelativeDrag(MouseDownEvent e)
+ => Nub.ReceivePositionalInputAt(e.ScreenSpaceMouseDownPosition);
+
protected override void OnDragEnd(DragEndEvent e)
{
updateGlow();
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 9a001c92cd..ead3eeb0dc 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -446,7 +446,7 @@ namespace osu.Game
Scheduler.Add(() =>
{
- realmBlocker = realm.BlockAllOperations();
+ realmBlocker = realm.BlockAllOperations("migration");
readyToRun.Set();
}, false);
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index 69cb3a49fc..04c424461e 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -6,6 +6,8 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -28,6 +30,9 @@ namespace osu.Game.Overlays.Mods
{
public const int BUTTON_WIDTH = 200;
+ protected override string PopInSampleName => "";
+ protected override string PopOutSampleName => @"SongSelect/mod-select-overlay-pop-out";
+
[Cached]
public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty());
@@ -101,17 +106,21 @@ namespace osu.Game.Overlays.Mods
private ShearedToggleButton? customisationButton;
+ private Sample? columnAppearSample;
+
protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
: base(colourScheme)
{
}
[BackgroundDependencyLoader]
- private void load(OsuGameBase game, OsuColour colours)
+ private void load(OsuGameBase game, OsuColour colours, AudioManager audio)
{
Header.Title = ModSelectOverlayStrings.ModSelectTitle;
Header.Description = ModSelectOverlayStrings.ModSelectDescription;
+ columnAppearSample = audio.Samples.Get(@"SongSelect/mod-column-pop-in");
+
AddRange(new Drawable[]
{
new ClickToReturnContainer
@@ -453,8 +462,31 @@ namespace osu.Game.Overlays.Mods
.MoveToY(0, duration, Easing.OutQuint)
.FadeIn(duration, Easing.OutQuint);
- if (!allFiltered)
- nonFilteredColumnCount += 1;
+ if (allFiltered)
+ continue;
+
+ int columnNumber = nonFilteredColumnCount;
+ Scheduler.AddDelayed(() =>
+ {
+ var channel = columnAppearSample?.GetChannel();
+ if (channel == null) return;
+
+ // Still play sound effects for off-screen columns up to a certain point.
+ if (columnNumber > 5 && !column.Active.Value) return;
+
+ // use X position of the column on screen as a basis for panning the sample
+ float balance = column.Parent.BoundingBox.Centre.X / RelativeToAbsoluteFactor.X;
+
+ // dip frequency and ramp volume of sample over the first 5 displayed columns
+ float progress = Math.Min(1, columnNumber / 5f);
+
+ channel.Frequency.Value = 1.3 - (progress * 0.3) + RNG.NextDouble(0.1);
+ channel.Volume.Value = Math.Max(progress, 0.2);
+ channel.Balance.Value = -1 + balance * 2;
+ channel.Play();
+ }, delay);
+
+ nonFilteredColumnCount += 1;
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs
index 77eede0e46..42ac4adb34 100644
--- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
Action = () =>
{
// Blocking operations implicitly causes a Compact().
- using (realm.BlockAllOperations())
+ using (realm.BlockAllOperations("compact"))
{
}
}
@@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
{
try
{
- var token = realm.BlockAllOperations();
+ var token = realm.BlockAllOperations("maintenance");
blockAction.Enabled.Value = false;
diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs
index c251e94ef2..3f8cf2e13a 100644
--- a/osu.Game/Screens/Select/FooterButton.cs
+++ b/osu.Game/Screens/Select/FooterButton.cs
@@ -4,19 +4,18 @@
#nullable disable
using System;
-using osuTK;
-using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
-using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
using osu.Game.Input.Bindings;
-using osu.Framework.Input.Bindings;
-using osu.Game.Graphics.UserInterface;
+using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Screens.Select
{
@@ -68,7 +67,6 @@ namespace osu.Game.Screens.Select
private readonly Box light;
public FooterButton()
- : base(HoverSampleSet.Toolbar)
{
AutoSizeAxes = Axes.Both;
Shear = SHEAR;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index ff223f5107..560f7409ff 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,8 +36,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index b8a4aca02e..0133c6334c 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -61,8 +61,8 @@
-
-
+
+
@@ -84,7 +84,7 @@
-
+