Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bail from score submission if audio playback rate is too far from reality #26138

Merged
merged 2 commits into from
Dec 26, 2023

Conversation

peppy
Copy link
Member

@peppy peppy commented Dec 26, 2023

Closes #23149.

Values are ballparked (will trigger with ~1% rate difference over a 2.5 minute beatmap, ie. 300 * 5 total discrepancy). The count is added to avoid once-off discrepancies to not immediately cancel submission, ie. a large lag spike. This mostly matches how stable handles this.

Can be tested using:

diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs
index 587e6bbeed..b1ed3235d6 100644
--- a/osu.Game/Beatmaps/FramedBeatmapClock.cs
+++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs
@@ -61,9 +61,11 @@ public FramedBeatmapClock(bool applyOffsets, bool requireDecoupling, IClock? sou
 
             decoupledTrack = new DecouplingFramedClock(source) { AllowDecoupling = requireDecoupling };
 
+            var veryBadClock = new VeryBadClock(decoupledTrack);
+
             // An interpolating clock is used to ensure precise time values even when the host audio subsystem is not reporting
             // high precision times (on windows there's generally only 5-10ms reporting intervals, as an example).
-            var interpolatedTrack = new InterpolatingFramedClock(decoupledTrack);
+            var interpolatedTrack = new InterpolatingFramedClock(veryBadClock);
 
             if (applyOffsets)
             {
@@ -196,4 +198,29 @@ protected override void Dispose(bool isDisposing)
             beatmapOffsetSubscription?.Dispose();
         }
     }
+
+    public class VeryBadClock : IFrameBasedClock
+    {
+        private readonly IFrameBasedClock source;
+
+        public VeryBadClock(IFrameBasedClock source)
+        {
+            this.source = source;
+        }
+
+        public double CurrentTime => source.CurrentTime * 1.2;
+
+        public double Rate => source.Rate;
+
+        public bool IsRunning => source.IsRunning;
+
+        public void ProcessFrame()
+        {
+            source.ProcessFrame();
+        }
+
+        public double ElapsedFrameTime => source.ElapsedFrameTime;
+
+        public double FramesPerSecond => source.FramesPerSecond;
+    }
 }

Dunno how to make automated testing for this. Probably not required?

if (playbackRateValid.Value)
{
playbackRateValid.Value = false;
Logger.Log("System audio playback is not working as expected. Some online functionality will not work.\n\nPlease check your audio drivers.", level: LogLevel.Important);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be a localizable notification, as this is a user-facing string.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@peppy peppy Dec 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's an edge case that only 1 in 10000000 will see, but sure, at some opint. the main goal is just to ensure some validity before score submit to stop bugged drivers from submitting.

Comment on lines 230 to 231
elapsedValidationTime ??= elapsedGameplayClockTime;
elapsedValidationTime += GameplayClock.Rate * Time.Elapsed;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this work? If elapsedValidationTime is null in a frame, doesn't this cause said frame to be double-counted?

I'd expect something closer to

                if (elapsedValidationTime == null)
                    elapsedValidationTime = elapsedGameplayClockTime;
                else
                    elapsedValidationTime += GameplayClock.Rate * Time.Elapsed;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that looks better. Good catch.

@bdach
Copy link
Collaborator

bdach commented Dec 26, 2023

One other way to test this is to use my broken ass wasapi device switching patch and rapidly switch output devices until it breaks (it'll usually speed up when broken). And this does catch it, for whatever that's worth.

So aside from the one doubt I have above, this appears to be doing the right thing as far as my somewhat limited testing shows.

@bdach bdach merged commit 668ce93 into ppy:master Dec 26, 2023
@peppy peppy deleted the fix-audio-rate-playback-not-fail branch January 4, 2024 03:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Timing: Bugged audio drivers / hardware can cause gameplay to run too slowly
3 participants