1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-05 02:23:38 +08:00
Files
osu-lazer/osu.Game.Tests/Input/ReplayInputHandlerTest.cs
T
2025-02-28 21:12:21 +01:00

398 lines
22 KiB
C#

// 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.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges.Events;
using osu.Framework.IO.Stores;
using osu.Framework.Lists;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Input
{
[HeadlessTest]
public partial class ReplayInputHandlerTest : OsuTestScene
{
// There are commented out assertions that will always fail as Replay inputs don't go through the typical input flow
// Related framework issue: https://github.com/ppy/osu-framework/issues/6037
[Test]
public void TestNoSimultaneousBindings()
{
Clear();
TestRulesetInputManager rulesetInputManager = new TestRulesetInputManager(SimultaneousBindingMode.None);
Add(rulesetInputManager);
RulesetInputManagerInputState<TestAction> state = new RulesetInputManagerInputState<TestAction>(rulesetInputManager.CurrentState);
List<TestAction> actions = new List<TestAction>();
AddAssert("No actions are pressed.", () => rulesetInputManager.PressedActions.Count == 0);
AddLabel("Test single action");
AddStep("Add TestKey1", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddAssert("1 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Remove action", () => actions.Remove(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is not pressed anymore", () => rulesetInputManager.PressedActions.Count == 0);
AddAssert("1 Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Reset actions", resetActions);
AddLabel("Test multiple unique actions");
AddStep("Add TestKey1", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddAssert("1 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Add TestKey2", () => actions.Add(TestAction.TestKey2));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is not pressed anymore", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 0);
AddAssert("TestKey2 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey2) == 1);
AddAssert("1 Press event for TestKey2", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey2) == 1);
// Pressing TestKey2 should've released TestKey1 in this mode
// AddAssert("1 Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Remove TestKey2", () => actions.Remove(TestAction.TestKey2));
AddStep("Apply actions", applyActions);
AddAssert("TestKey2 is not pressed anymore", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey2) == 0);
AddAssert("TestKey1 is still not pressed anymore", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 0);
AddAssert("1 Release event for TestKey2", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey2) == 1);
AddStep("Reset actions", resetActions);
AddLabel("Test multiple identical actions");
AddStep("Add TestKey1", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddAssert("1 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Add TestKey1 again", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
// Again, this normally shouldn't fail, but it does due to the same reason as above.
// AddAssert("No Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 0);
AddAssert("No Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 0);
AddStep("Remove TestKey1", () => actions.Remove(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is not pressed anymore", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 0);
AddAssert("1 Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Remove TestKey1 again", () => actions.Remove(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
// Normally removing the first binding would be a no-op, because that has already been done when the second binding was pressed
// AddAssert("TestKey1 is not pressed anymore", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 0);
// AddAssert("No Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 0);
return;
void applyActions() => new ReplayInputHandler.ReplayState<TestAction>()
{
PressedActions = actions.ToList(),
}.Apply(state, rulesetInputManager);
void resetActions()
{
actions.Clear();
applyActions();
}
}
[Test]
public void TestUniqueSimultaneousBindings()
{
Clear();
TestRulesetInputManager rulesetInputManager = new TestRulesetInputManager(SimultaneousBindingMode.Unique);
Add(rulesetInputManager);
List<TestAction> actions = new List<TestAction>();
AddAssert("No actions are pressed.", () => rulesetInputManager.PressedActions.Count == 0);
AddLabel("Test single action");
AddStep("Add TestKey1", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddAssert("1 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Remove action", () => actions.Remove(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is not pressed anymore", () => rulesetInputManager.PressedActions.Count == 0);
AddAssert("1 Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Reset actions", resetActions);
AddLabel("Test multiple unique actions");
AddStep("Add TestKey1", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddAssert("1 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Add TestKey2", () => actions.Add(TestAction.TestKey2));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is still pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddAssert("No release events for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 0);
AddAssert("TestKey2 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey2) == 1);
AddAssert("1 Press event for TestKey2", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey2) == 1);
AddStep("Remove both keys", () =>
{
actions.Remove(TestAction.TestKey1);
actions.Remove(TestAction.TestKey2);
});
AddStep("Apply actions", applyActions);
AddAssert("No keys are pressed", () => rulesetInputManager.PressedActions.Count == 0);
AddAssert("1 release event for each key", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 1 && rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey2) == 1);
AddStep("Reset actions", resetActions);
AddLabel("Test multiple identical actions");
AddStep("Add TestKey1", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddAssert("1 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Add TestKey1 again", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
// SimultaneousBindingMode.Unique will only fire the press event on the first press, subsequent presses of the same action will not trigger an event
// AddAssert("No Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 0);
AddAssert("No Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 0);
AddStep("Remove TestKey1", () => actions.Remove(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
// SimultaneousBindingMode.Unique will only release when all bindings to the same action is released
// But TriggerReleased bypasses that check...
// AddAssert("TestKey1 is still pressed", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
// AddAssert("No Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 0);
AddStep("Remove TestKey1 again", () => actions.Remove(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is not pressed anymore", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 0);
AddAssert("1 Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 1);
return;
void applyActions() => new ReplayInputHandler.ReplayState<TestAction>()
{
PressedActions = actions.ToList(),
}.Apply(rulesetInputManager.CurrentState, rulesetInputManager);
void resetActions()
{
actions.Clear();
applyActions();
}
}
[Test]
public void TestAllSimultaneousBindings()
{
Clear();
TestRulesetInputManager rulesetInputManager = new TestRulesetInputManager(SimultaneousBindingMode.All);
Add(rulesetInputManager);
List<TestAction> actions = new List<TestAction>();
AddAssert("No actions are pressed.", () => rulesetInputManager.PressedActions.Count == 0);
AddLabel("Test single action");
AddStep("Add TestKey1", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddAssert("1 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Remove action", () => actions.Remove(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is not pressed anymore", () => rulesetInputManager.PressedActions.Count == 0);
AddAssert("1 Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Reset actions", resetActions);
AddLabel("Test multiple unique actions");
AddStep("Add TestKey1", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddAssert("1 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Add TestKey2", () => actions.Add(TestAction.TestKey2));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is still pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddAssert("No release events for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 0);
AddAssert("TestKey2 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey2) == 1);
AddAssert("1 Press event for TestKey2", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey2) == 1);
AddStep("Remove both keys", () =>
{
actions.Remove(TestAction.TestKey1);
actions.Remove(TestAction.TestKey2);
});
AddStep("Apply actions", applyActions);
AddAssert("No keys are pressed", () => rulesetInputManager.PressedActions.Count == 0);
AddAssert("1 release event for each key", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 1 && rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey2) == 1);
AddStep("Reset actions", resetActions);
AddLabel("Test multiple identical actions");
AddStep("Add TestKey1", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddAssert("1 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Add TestKey1 again", () => actions.Add(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("TestKey1 is pressed twice", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 2);
AddAssert("No Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 0);
AddAssert("1 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddStep("Remove TestKey1", () => actions.Remove(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("1 Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddAssert("0 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 0);
AddAssert("TestKey1 is pressed once", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 1);
AddStep("Remove TestKey1", () => actions.Remove(TestAction.TestKey1));
AddStep("Apply actions", applyActions);
AddAssert("1 Release event for TestKey1", () => rulesetInputManager.ReleaseEventsSinceLastObservation(TestAction.TestKey1) == 1);
AddAssert("0 Press event for TestKey1", () => rulesetInputManager.PressEventsSinceLastObservation(TestAction.TestKey1) == 0);
AddAssert("TestKey1 is not pressed", () => rulesetInputManager.PressedActions.Count(k => k == TestAction.TestKey1) == 0);
return;
void applyActions() => new ReplayInputHandler.ReplayState<TestAction>()
{
PressedActions = actions.ToList(),
}.Apply(rulesetInputManager.CurrentState, rulesetInputManager);
void resetActions()
{
actions.Clear();
applyActions();
}
}
private enum TestAction
{
TestKey1,
TestKey2,
}
private partial class TestRulesetInputManager : RulesetInputManager<TestAction>
{
public TestRulesetInputManager(SimultaneousBindingMode mode)
: base(new TestRuleset().RulesetInfo, 0, mode)
{
Add(new DummyInputConsumer
{
PressedActionCounts = pressedActionCounts,
ReleasedActionCounts = releasedActionCounts,
});
}
public SlimReadOnlyListWrapper<TestAction> PressedActions => KeyBindingContainer.PressedActions;
private readonly Dictionary<TestAction, int> pressedActionCounts = new Dictionary<TestAction, int>
{
{
TestAction.TestKey1, 0
},
{
TestAction.TestKey2, 0
},
};
private readonly Dictionary<TestAction, int> releasedActionCounts = new Dictionary<TestAction, int>
{
{
TestAction.TestKey1, 0
},
{
TestAction.TestKey2, 0
},
};
public int PressEventsSinceLastObservation(TestAction action)
{
if (!pressedActionCounts.TryGetValue(action, out int count))
return 0;
pressedActionCounts[action] = 0;
return count;
}
public int ReleaseEventsSinceLastObservation(TestAction action)
{
if (!releasedActionCounts.TryGetValue(action, out int count))
return 0;
releasedActionCounts[action] = 0;
return count;
}
private partial class DummyInputConsumer : Drawable, IKeyBindingHandler<TestAction>
{
public required Dictionary<TestAction, int> ReleasedActionCounts;
public required Dictionary<TestAction, int> PressedActionCounts;
public bool OnPressed(KeyBindingPressEvent<TestAction> e)
{
PressedActionCounts[e.Action]++;
return true;
}
public void OnReleased(KeyBindingReleaseEvent<TestAction> e)
{
ReleasedActionCounts[e.Action]++;
}
}
}
public class TestRuleset : Ruleset
{
public override string Description => string.Empty;
public override string ShortName => string.Empty;
public TestRuleset()
{
// temporary ID to let RulesetConfigCache pass our
// config manager to the ruleset dependencies.
RulesetInfo.OnlineID = -1;
}
public override IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(TestResources.GetStore(), @"Resources");
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new TestRulesetConfigManager();
public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>();
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null) => null!;
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null!;
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null!;
}
private class TestRulesetConfigManager : IRulesetConfigManager
{
public void Load()
{
}
public bool Save() => true;
public TrackedSettings CreateTrackedSettings() => new TrackedSettings();
public void LoadInto(TrackedSettings settings)
{
}
public void Dispose()
{
}
}
}
}