1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-26 20:23:00 +08:00

Merge branch 'master' into test-efficiency

This commit is contained in:
Dan Balasescu 2021-09-07 16:14:12 +09:00 committed by GitHub
commit 79fd2d46f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 175 additions and 67 deletions

View File

@ -51,7 +51,7 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.827.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.907.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.830.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.830.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">

View File

@ -11,6 +11,7 @@ using osu.Framework.Platform;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Input; using osu.Game.Input;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
using Realms; using Realms;
namespace osu.Game.Tests.Database namespace osu.Game.Tests.Database
@ -42,7 +43,7 @@ namespace osu.Game.Tests.Database
KeyBindingContainer testContainer = new TestKeyBindingContainer(); KeyBindingContainer testContainer = new TestKeyBindingContainer();
keyBindingStore.Register(testContainer); keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
Assert.That(queryCount(), Is.EqualTo(3)); Assert.That(queryCount(), Is.EqualTo(3));
@ -66,7 +67,7 @@ namespace osu.Game.Tests.Database
{ {
KeyBindingContainer testContainer = new TestKeyBindingContainer(); KeyBindingContainer testContainer = new TestKeyBindingContainer();
keyBindingStore.Register(testContainer); keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
using (var primaryUsage = realmContextFactory.GetForRead()) using (var primaryUsage = realmContextFactory.GetForRead())
{ {

View File

@ -1,14 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Platform; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Overlays.Settings.Sections.Input;
using osuTK; using osuTK;
@ -17,22 +18,34 @@ namespace osu.Game.Tests.Visual.Settings
[TestFixture] [TestFixture]
public class TestSceneTabletSettings : OsuTestScene public class TestSceneTabletSettings : OsuTestScene
{ {
[BackgroundDependencyLoader] private TestTabletHandler tabletHandler;
private void load(GameHost host) private TabletSettings settings;
{
var tabletHandler = new TestTabletHandler();
AddRange(new Drawable[] [SetUpSteps]
public void SetUpSteps()
{ {
new TabletSettings(tabletHandler) AddStep("create settings", () =>
{
tabletHandler = new TestTabletHandler();
Children = new Drawable[]
{
settings = new TabletSettings(tabletHandler)
{ {
RelativeSizeAxes = Axes.None, RelativeSizeAxes = Axes.None,
Width = SettingsPanel.PANEL_WIDTH, Width = SettingsPanel.PANEL_WIDTH,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
} }
};
}); });
AddStep("set square size", () => tabletHandler.SetTabletSize(new Vector2(100, 100)));
}
[Test]
public void TestVariousTabletSizes()
{
AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100))); AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100)));
AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Vector2(300, 300))); AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Vector2(300, 300)));
AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Vector2(100, 300))); AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Vector2(100, 300)));
@ -40,6 +53,71 @@ namespace osu.Game.Tests.Visual.Settings
AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(Vector2.Zero)); AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(Vector2.Zero));
} }
[Test]
public void TestWideAspectRatioValidity()
{
AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100)));
AddStep("Reset to full area", () => settings.ChildrenOfType<DangerousSettingsButton>().First().TriggerClick());
ensureValid();
AddStep("rotate 10", () => tabletHandler.Rotation.Value = 10);
ensureInvalid();
AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f);
ensureInvalid();
AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f);
ensureInvalid();
AddStep("scale down", () => tabletHandler.AreaSize.Value *= 0.9f);
ensureValid();
}
[Test]
public void TestRotationValidity()
{
AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds);
AddStep("rotate 90", () => tabletHandler.Rotation.Value = 90);
ensureValid();
AddStep("rotate 180", () => tabletHandler.Rotation.Value = 180);
ensureValid();
AddStep("rotate 270", () => tabletHandler.Rotation.Value = 270);
ensureValid();
AddStep("rotate 360", () => tabletHandler.Rotation.Value = 360);
ensureValid();
AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0);
ensureValid();
AddStep("rotate 45", () => tabletHandler.Rotation.Value = 45);
ensureInvalid();
AddStep("rotate 0", () => tabletHandler.Rotation.Value = 0);
ensureValid();
}
[Test]
public void TestOffsetValidity()
{
ensureValid();
AddStep("move right", () => tabletHandler.AreaOffset.Value = Vector2.Zero);
ensureInvalid();
AddStep("move back", () => tabletHandler.AreaOffset.Value = tabletHandler.AreaSize.Value / 2);
ensureValid();
}
private void ensureValid() => AddAssert("area valid", () => settings.AreaSelection.IsWithinBounds);
private void ensureInvalid() => AddAssert("area invalid", () => !settings.AreaSelection.IsWithinBounds);
public class TestTabletHandler : ITabletHandler public class TestTabletHandler : ITabletHandler
{ {
public Bindable<Vector2> AreaOffset { get; } = new Bindable<Vector2>(); public Bindable<Vector2> AreaOffset { get; } = new Bindable<Vector2>();

View File

@ -46,49 +46,50 @@ namespace osu.Game.Input
} }
/// <summary> /// <summary>
/// Register a new type of <see cref="KeyBindingContainer{T}"/>, adding default bindings from <see cref="KeyBindingContainer.DefaultKeyBindings"/>. /// Register all defaults for this store.
/// </summary> /// </summary>
/// <param name="container">The container to populate defaults from.</param> /// <param name="container">The container to populate defaults from.</param>
public void Register(KeyBindingContainer container) => insertDefaults(container.DefaultKeyBindings); /// <param name="rulesets">The rulesets to populate defaults from.</param>
public void Register(KeyBindingContainer container, IEnumerable<RulesetInfo> rulesets)
/// <summary>
/// Register a ruleset, adding default bindings for each of its variants.
/// </summary>
/// <param name="ruleset">The ruleset to populate defaults from.</param>
public void Register(RulesetInfo ruleset)
{
var instance = ruleset.CreateInstance();
foreach (var variant in instance.AvailableVariants)
insertDefaults(instance.GetDefaultKeyBindings(variant), ruleset.ID, variant);
}
private void insertDefaults(IEnumerable<IKeyBinding> defaults, int? rulesetId = null, int? variant = null)
{ {
using (var usage = realmFactory.GetForWrite()) using (var usage = realmFactory.GetForWrite())
{ {
// compare counts in database vs defaults // intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed.
// this is much faster as a result.
var existingBindings = usage.Realm.All<RealmKeyBinding>().ToList();
insertDefaults(usage, existingBindings, container.DefaultKeyBindings);
foreach (var ruleset in rulesets)
{
var instance = ruleset.CreateInstance();
foreach (var variant in instance.AvailableVariants)
insertDefaults(usage, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ID, variant);
}
usage.Commit();
}
}
private void insertDefaults(RealmContextFactory.RealmUsage usage, List<RealmKeyBinding> existingBindings, IEnumerable<IKeyBinding> defaults, int? rulesetId = null, int? variant = null)
{
// compare counts in database vs defaults for each action type.
foreach (var defaultsForAction in defaults.GroupBy(k => k.Action)) foreach (var defaultsForAction in defaults.GroupBy(k => k.Action))
{ {
int existingCount = usage.Realm.All<RealmKeyBinding>().Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key); // avoid performing redundant queries when the database is empty and needs to be re-filled.
int existingCount = existingBindings.Count(k => k.RulesetID == rulesetId && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key);
if (defaultsForAction.Count() <= existingCount) if (defaultsForAction.Count() <= existingCount)
continue; continue;
foreach (var k in defaultsForAction.Skip(existingCount))
{
// insert any defaults which are missing. // insert any defaults which are missing.
usage.Realm.Add(new RealmKeyBinding usage.Realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding
{ {
KeyCombinationString = k.KeyCombination.ToString(), KeyCombinationString = k.KeyCombination.ToString(),
ActionInt = (int)k.Action, ActionInt = (int)k.Action,
RulesetID = rulesetId, RulesetID = rulesetId,
Variant = variant Variant = variant
}); }));
}
}
usage.Commit();
} }
} }

View File

@ -327,10 +327,7 @@ namespace osu.Game
base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); base.Content.Add(CreateScalingContainer().WithChildren(mainContent));
KeyBindingStore = new RealmKeyBindingStore(realmFactory); KeyBindingStore = new RealmKeyBindingStore(realmFactory);
KeyBindingStore.Register(globalBindings); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets);
foreach (var r in RulesetStore.AvailableRulesets)
KeyBindingStore.Register(r);
dependencies.Cache(globalBindings); dependencies.Cache(globalBindings);

View File

@ -4,10 +4,13 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.MatrixExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Utils;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK; using osuTK;
@ -17,6 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
public class TabletAreaSelection : CompositeDrawable public class TabletAreaSelection : CompositeDrawable
{ {
public bool IsWithinBounds { get; private set; }
private readonly ITabletHandler handler; private readonly ITabletHandler handler;
private Container tabletContainer; private Container tabletContainer;
@ -109,29 +114,30 @@ namespace osu.Game.Overlays.Settings.Sections.Input
areaOffset.BindTo(handler.AreaOffset); areaOffset.BindTo(handler.AreaOffset);
areaOffset.BindValueChanged(val => areaOffset.BindValueChanged(val =>
{ {
usableAreaContainer.MoveTo(val.NewValue, 100, Easing.OutQuint) usableAreaContainer.MoveTo(val.NewValue, 100, Easing.OutQuint);
.OnComplete(_ => checkBounds()); // required as we are using SSDQ. checkBounds();
}, true); }, true);
areaSize.BindTo(handler.AreaSize); areaSize.BindTo(handler.AreaSize);
areaSize.BindValueChanged(val => areaSize.BindValueChanged(val =>
{ {
usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint) usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint);
.OnComplete(_ => checkBounds()); // required as we are using SSDQ.
int x = (int)val.NewValue.X; int x = (int)val.NewValue.X;
int y = (int)val.NewValue.Y; int y = (int)val.NewValue.Y;
int commonDivider = greatestCommonDivider(x, y); int commonDivider = greatestCommonDivider(x, y);
usableAreaText.Text = $"{(float)x / commonDivider}:{(float)y / commonDivider}"; usableAreaText.Text = $"{(float)x / commonDivider}:{(float)y / commonDivider}";
checkBounds();
}, true); }, true);
rotation.BindTo(handler.Rotation); rotation.BindTo(handler.Rotation);
rotation.BindValueChanged(val => rotation.BindValueChanged(val =>
{ {
usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint);
tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint); tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint);
usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint)
.OnComplete(_ => checkBounds()); // required as we are using SSDQ. checkBounds();
}, true); }, true);
tablet.BindTo(handler.Tablet); tablet.BindTo(handler.Tablet);
@ -169,12 +175,35 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (tablet.Value == null) if (tablet.Value == null)
return; return;
var usableSsdq = usableAreaContainer.ScreenSpaceDrawQuad; // allow for some degree of floating point error, as we don't care about being perfect here.
const float lenience = 0.5f;
bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft + new Vector2(1)) && var tabletArea = new Quad(-lenience, -lenience, tablet.Value.Size.X + lenience * 2, tablet.Value.Size.Y + lenience * 2);
tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight - new Vector2(1));
usableFill.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); var halfUsableArea = areaSize.Value / 2;
var offset = areaOffset.Value;
var usableAreaQuad = new Quad(
new Vector2(-halfUsableArea.X, -halfUsableArea.Y),
new Vector2(halfUsableArea.X, -halfUsableArea.Y),
new Vector2(-halfUsableArea.X, halfUsableArea.Y),
new Vector2(halfUsableArea.X, halfUsableArea.Y)
);
var matrix = Matrix3.Identity;
MatrixExtensions.TranslateFromLeft(ref matrix, offset);
MatrixExtensions.RotateFromLeft(ref matrix, MathUtils.DegreesToRadians(rotation.Value));
usableAreaQuad *= matrix;
IsWithinBounds =
tabletArea.Contains(usableAreaQuad.TopLeft) &&
tabletArea.Contains(usableAreaQuad.TopRight) &&
tabletArea.Contains(usableAreaQuad.BottomLeft) &&
tabletArea.Contains(usableAreaQuad.BottomRight);
usableFill.FadeColour(IsWithinBounds ? colour.Blue : colour.RedLight, 100);
} }
protected override void Update() protected override void Update()

View File

@ -20,6 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
public class TabletSettings : SettingsSubsection public class TabletSettings : SettingsSubsection
{ {
public TabletAreaSelection AreaSelection { get; private set; }
private readonly ITabletHandler tabletHandler; private readonly ITabletHandler tabletHandler;
private readonly Bindable<bool> enabled = new BindableBool(true); private readonly Bindable<bool> enabled = new BindableBool(true);
@ -121,7 +123,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
new TabletAreaSelection(tabletHandler) AreaSelection = new TabletAreaSelection(tabletHandler)
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 300, Height = 300,

View File

@ -37,7 +37,7 @@
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.3.0" /> <PackageReference Include="Realm" Version="10.3.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.830.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.830.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.827.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.907.0" />
<PackageReference Include="Sentry" Version="3.9.0" /> <PackageReference Include="Sentry" Version="3.9.0" />
<PackageReference Include="SharpCompress" Version="0.28.3" /> <PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />

View File

@ -71,7 +71,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.830.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.830.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.827.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.907.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
<PropertyGroup> <PropertyGroup>