From 0adeccbf03fa872b0d937f4a0ce3142324809ede Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jun 2022 14:40:21 +0900 Subject: [PATCH] Add full latency testing flow --- .../Settings/TestSceneLatencyComparer.cs | 242 ----------- osu.Game/Screens/LatencyComparerScreen.cs | 385 ++++++++++++++++++ 2 files changed, 385 insertions(+), 242 deletions(-) create mode 100644 osu.Game/Screens/LatencyComparerScreen.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneLatencyComparer.cs b/osu.Game.Tests/Visual/Settings/TestSceneLatencyComparer.cs index cf9a01f0b1..8a5db7bd95 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneLatencyComparer.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneLatencyComparer.cs @@ -4,18 +4,7 @@ #nullable enable using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Framework.Input.Events; -using osu.Framework.Screens; using osu.Game.Screens; -using osuTK; -using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Tests.Visual.Settings { @@ -27,235 +16,4 @@ namespace osu.Game.Tests.Visual.Settings AddStep("Load screen", () => LoadScreen(new LatencyComparerScreen())); } } - - public class LatencyComparerScreen : OsuScreen - { - private FrameSync previousFrameSyncMode; - - public override bool HideOverlaysOnEnter => true; - - public override bool CursorVisible => false; - - [Resolved] - private FrameworkConfigManager config { get; set; } = null!; - - public LatencyComparerScreen() - { - InternalChildren = new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 100), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 100), - }, - Content = new[] - { - new Drawable[] - { - // header content - }, - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new LatencyArea(10) - { - Width = 0.5f, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - }, - new LatencyArea(0) - { - Width = 0.5f, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, - new Box - { - Colour = Color4.Black, - Width = 50, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, - } - }, - }, - new Drawable[] - { - // footer content - }, - } - } - }; - } - - public override void OnEntering(ScreenTransitionEvent e) - { - base.OnEntering(e); - - previousFrameSyncMode = config.Get(FrameworkSetting.FrameSync); - config.SetValue(FrameworkSetting.FrameSync, FrameSync.Unlimited); - // host.AllowBenchmarkUnlimitedFrames = true; - } - - public override bool OnExiting(ScreenExitEvent e) - { - // host.AllowBenchmarkUnlimitedFrames = false; - config.SetValue(FrameworkSetting.FrameSync, previousFrameSyncMode); - return base.OnExiting(e); - } - - public class LatencyArea : CompositeDrawable - { - private readonly int inducedLatency; - - public LatencyArea(int inducedLatency) - { - this.inducedLatency = inducedLatency; - RelativeSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = Color4.Blue, - RelativeSizeAxes = Axes.Both, - }, - new LatencyMovableBox - { - RelativeSizeAxes = Axes.Both, - }, - new LatencyCursorContainer - { - RelativeSizeAxes = Axes.Both, - } - }; - } - - private long frameCount; - - public override bool UpdateSubTree() - { - if (inducedLatency > 0 && ++frameCount % inducedLatency != 0) - return false; - - return base.UpdateSubTree(); - } - - public class LatencyMovableBox : CompositeDrawable - { - private Box box = null!; - private InputManager inputManager = null!; - - public LatencyMovableBox() - { - Masking = true; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - - InternalChild = box = new Box - { - Size = new Vector2(40), - Position = DrawSize / 2, - Origin = Anchor.Centre, - }; - } - - protected override bool OnHover(HoverEvent e) => false; - - private double? lastFrameTime; - - protected override void Update() - { - base.Update(); - - if (!IsHovered) - return; - - if (lastFrameTime != null) - { - float movementAmount = (float)(Clock.CurrentTime - lastFrameTime); - - foreach (var key in inputManager.CurrentState.Keyboard.Keys) - { - switch (key) - { - case Key.Up: - box.Y -= movementAmount; - break; - - case Key.Down: - box.Y += movementAmount; - break; - - case Key.Left: - box.X -= movementAmount; - break; - - case Key.Right: - box.X += movementAmount; - break; - } - } - } - - lastFrameTime = Clock.CurrentTime; - } - } - - public class LatencyCursorContainer : CompositeDrawable - { - private readonly Circle cursor; - private InputManager inputManager = null!; - - public LatencyCursorContainer() - { - Masking = true; - - InternalChild = cursor = new Circle - { - Size = new Vector2(40), - Origin = Anchor.Centre, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - } - - protected override bool OnHover(HoverEvent e) => false; - - protected override void Update() - { - if (IsHovered) - { - cursor.Position = ToLocalSpace(inputManager.CurrentState.Mouse.Position); - cursor.Alpha = 1; - } - else - { - cursor.Alpha = 0; - } - - base.Update(); - } - } - } - } } diff --git a/osu.Game/Screens/LatencyComparerScreen.cs b/osu.Game/Screens/LatencyComparerScreen.cs new file mode 100644 index 0000000000..b247337e4e --- /dev/null +++ b/osu.Game/Screens/LatencyComparerScreen.cs @@ -0,0 +1,385 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Framework.Screens; +using osu.Framework.Utils; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Overlays.Settings; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Screens +{ + public class LatencyComparerScreen : OsuScreen + { + private FrameSync previousFrameSyncMode; + + private readonly OsuSpriteText statusText; + + public override bool HideOverlaysOnEnter => true; + + public override bool CursorVisible => false; + + public override float BackgroundParallaxAmount => 0; + + private readonly Container latencyAreaContainer; + + [Cached] + private readonly OverlayColourProvider overlayColourProvider = new OverlayColourProvider(OverlayColourScheme.Orange); + + [Resolved] + private FrameworkConfigManager config { get; set; } = null!; + + public LatencyComparerScreen() + { + InternalChildren = new Drawable[] + { + latencyAreaContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + // Make sure the edge between the two comparisons can't be used to ascertain latency. + new Box + { + Name = "separator", + Colour = ColourInfo.GradientHorizontal(overlayColourProvider.Background6, overlayColourProvider.Background6.Opacity(0)), + Width = 50, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopLeft, + }, + new Box + { + Name = "separator", + Colour = ColourInfo.GradientHorizontal(overlayColourProvider.Background6.Opacity(0), overlayColourProvider.Background6), + Width = 50, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopRight, + }, + statusText = new OsuSpriteText + { + Font = OsuFont.Default.With(size: 40), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + } + + public override void OnEntering(ScreenTransitionEvent e) + { + base.OnEntering(e); + + previousFrameSyncMode = config.Get(FrameworkSetting.FrameSync); + config.SetValue(FrameworkSetting.FrameSync, FrameSync.Unlimited); + // host.AllowBenchmarkUnlimitedFrames = true; + } + + public override bool OnExiting(ScreenExitEvent e) + { + // host.AllowBenchmarkUnlimitedFrames = false; + config.SetValue(FrameworkSetting.FrameSync, previousFrameSyncMode); + return base.OnExiting(e); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + loadNextRound(); + } + + private int round; + + private const int rounds_to_complete = 10; + + private int correctCount; + + private void recordResult(bool correct) + { + if (correct) + correctCount++; + + if (round < rounds_to_complete) + loadNextRound(); + else + { + showResults(); + } + } + + private void loadNextRound() + { + round++; + statusText.Text = $"Round {round} of {rounds_to_complete}"; + + latencyAreaContainer.Clear(); + + const int induced_latency = 1; + + int betterSide = RNG.Next(0, 2); + + latencyAreaContainer.Add(new LatencyArea(betterSide == 1 ? induced_latency : 0) + { + Width = 0.5f, + ReportBetter = () => recordResult(betterSide == 0) + }); + + latencyAreaContainer.Add(new LatencyArea(betterSide == 0 ? induced_latency : 0) + { + Width = 0.5f, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + ReportBetter = () => recordResult(betterSide == 1) + }); + } + + private void showResults() + { + AddInternal(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = overlayColourProvider.Background1, + RelativeSizeAxes = Axes.Both, + }, + + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 40)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextAnchor = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Text = $"You scored {correctCount} out of {rounds_to_complete} ({(float)correctCount / rounds_to_complete:P0})!" + } + } + }); + } + + public class LatencyArea : CompositeDrawable + { + [Resolved] + private OverlayColourProvider overlayColourProvider { get; set; } = null!; + + public Action? ReportBetter { get; set; } + + private Drawable background = null!; + + private readonly int inducedLatency; + + public LatencyArea(int inducedLatency) + { + this.inducedLatency = inducedLatency; + + RelativeSizeAxes = Axes.Both; + Masking = true; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + InternalChildren = new[] + { + background = new Box + { + Colour = overlayColourProvider.Background6, + RelativeSizeAxes = Axes.Both, + }, + new LatencyMovableBox + { + RelativeSizeAxes = Axes.Both, + }, + new LatencyCursorContainer + { + RelativeSizeAxes = Axes.Both, + }, + new Button + { + Text = "Feels better", + Y = 20, + Width = 0.8f, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Action = () => ReportBetter?.Invoke(), + }, + }; + + base.LoadComplete(); + this.FadeInFromZero(500, Easing.OutQuint); + } + + public class Button : SettingsButton + { + [Resolved] + private OverlayColourProvider overlayColourProvider { get; set; } = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Height = 50; + SpriteText.Colour = overlayColourProvider.Background6; + SpriteText.Font = OsuFont.TorusAlternate.With(size: 34); + } + } + + protected override bool OnHover(HoverEvent e) + { + background.FadeColour(overlayColourProvider.Background4, 200, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + background.FadeColour(overlayColourProvider.Background6, 200, Easing.OutQuint); + base.OnHoverLost(e); + } + + private long frameCount; + + public override bool UpdateSubTree() + { + if (inducedLatency > 0 && ++frameCount % inducedLatency != 0) + return false; + + return base.UpdateSubTree(); + } + + public class LatencyMovableBox : CompositeDrawable + { + private Box box = null!; + private InputManager inputManager = null!; + + [Resolved] + private OverlayColourProvider overlayColourProvider { get; set; } = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + + InternalChild = box = new Box + { + Size = new Vector2(40), + RelativePositionAxes = Axes.Both, + Position = new Vector2(0.5f), + Origin = Anchor.Centre, + Colour = overlayColourProvider.Colour1, + }; + } + + protected override bool OnHover(HoverEvent e) => false; + + private double? lastFrameTime; + + protected override void Update() + { + base.Update(); + + if (!IsHovered) + { + lastFrameTime = null; + return; + } + + if (lastFrameTime != null) + { + float movementAmount = (float)(Clock.CurrentTime - lastFrameTime) / 400; + + foreach (var key in inputManager.CurrentState.Keyboard.Keys) + { + switch (key) + { + case Key.Up: + box.Y = MathHelper.Clamp(box.Y - movementAmount, 0.1f, 0.9f); + break; + + case Key.Down: + box.Y = MathHelper.Clamp(box.Y + movementAmount, 0.1f, 0.9f); + break; + + case Key.Z: + case Key.Left: + box.X = MathHelper.Clamp(box.X - movementAmount, 0.1f, 0.9f); + break; + + case Key.X: + case Key.Right: + box.X = MathHelper.Clamp(box.X + movementAmount, 0.1f, 0.9f); + break; + } + } + } + + lastFrameTime = Clock.CurrentTime; + } + } + + public class LatencyCursorContainer : CompositeDrawable + { + private Circle cursor = null!; + private InputManager inputManager = null!; + + [Resolved] + private OverlayColourProvider overlayColourProvider { get; set; } = null!; + + public LatencyCursorContainer() + { + Masking = true; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + InternalChild = cursor = new Circle + { + Size = new Vector2(40), + Origin = Anchor.Centre, + Colour = overlayColourProvider.Colour2, + }; + + inputManager = GetContainingInputManager(); + } + + protected override bool OnHover(HoverEvent e) => false; + + protected override void Update() + { + if (IsHovered) + { + cursor.Position = ToLocalSpace(inputManager.CurrentState.Mouse.Position); + cursor.Alpha = 1; + } + else + { + cursor.Alpha = 0; + } + + base.Update(); + } + } + } + } +}