mirror of
https://github.com/osukey/osukey.git
synced 2025-08-03 22:56:36 +09:00
Merge remote-tracking branch 'upstream/master' into tournament-tools
This commit is contained in:
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
custom: https://osu.ppy.sh/home/support
|
21
README.md
21
README.md
@ -10,6 +10,8 @@ This project is still heavily under development, but is in a state where users a
|
||||
|
||||
We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
|
||||
|
||||
Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh/home/changelog).
|
||||
|
||||
## Requirements
|
||||
|
||||
- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
|
||||
@ -20,17 +22,24 @@ We are accepting bug reports (please report with as much detail as possible). Fe
|
||||
|
||||
### Releases
|
||||
|
||||
If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled.
|
||||

|
||||
|
||||
- Windows (x64) users should download and run `install.exe`.
|
||||
- macOS users (10.12 "Sierra" and higher) should download and run `osu.app.zip`.
|
||||
- iOS users can join the [TestFlight beta program](https://t.co/xQJmHkfC18).
|
||||
If you are not interested in developing the game, you can consume our [binary releases](https://github.com/ppy/osu/releases).
|
||||
|
||||
**Latest build:***
|
||||
|
||||
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) |
|
||||
| ------------- | ------------- |
|
||||
|
||||
- **Linux** users are recommended to self-compile until we have official deployment in place.
|
||||
- **iOS** users can join the [TestFlight beta program](https://t.co/PasE1zrHhw) (note that due to high demand this is regularly full).
|
||||
- **Android** users can self-compile, and expect a public beta soon.
|
||||
|
||||
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
||||
|
||||
### Downloading the source code
|
||||
|
||||
Clone the repository **including submodules**:
|
||||
Clone the repository:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/ppy/osu
|
||||
@ -45,7 +54,7 @@ git pull
|
||||
|
||||
### Building
|
||||
|
||||
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided below.
|
||||
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing).
|
||||
|
||||
> Visual Studio Code users must run the `Restore` task before any build attempt.
|
||||
|
||||
|
@ -7,7 +7,6 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Desktop.Overlays;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game;
|
||||
using osuTK.Input;
|
||||
@ -56,7 +55,7 @@ namespace osu.Desktop
|
||||
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, v =>
|
||||
{
|
||||
Add(v);
|
||||
v.State = Visibility.Visible;
|
||||
v.Show();
|
||||
});
|
||||
|
||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
|
||||
@ -74,13 +73,11 @@ namespace osu.Desktop
|
||||
{
|
||||
case Intro _:
|
||||
case MainMenu _:
|
||||
if (versionManager != null)
|
||||
versionManager.State = Visibility.Visible;
|
||||
versionManager?.Show();
|
||||
break;
|
||||
|
||||
default:
|
||||
if (versionManager != null)
|
||||
versionManager.State = Visibility.Hidden;
|
||||
versionManager?.Hide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
@ -25,15 +23,13 @@ namespace osu.Desktop.Overlays
|
||||
private OsuConfigManager config;
|
||||
private OsuGameBase game;
|
||||
private NotificationOverlay notificationOverlay;
|
||||
private GameHost host;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config, GameHost host)
|
||||
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config)
|
||||
{
|
||||
notificationOverlay = notification;
|
||||
this.config = config;
|
||||
this.game = game;
|
||||
this.host = host;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Anchor = Anchor.BottomCentre;
|
||||
@ -102,27 +98,31 @@ namespace osu.Desktop.Overlays
|
||||
|
||||
// only show a notification if we've previously saved a version to the config file (ie. not the first run).
|
||||
if (!string.IsNullOrEmpty(lastVersion))
|
||||
notificationOverlay.Post(new UpdateCompleteNotification(version, host.OpenUrlExternally));
|
||||
notificationOverlay.Post(new UpdateCompleteNotification(version));
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateCompleteNotification : SimpleNotification
|
||||
{
|
||||
public UpdateCompleteNotification(string version, Action<string> openUrl = null)
|
||||
private readonly string version;
|
||||
|
||||
public UpdateCompleteNotification(string version)
|
||||
{
|
||||
this.version = version;
|
||||
Text = $"You are now running osu!lazer {version}.\nClick to see what's new!";
|
||||
Icon = FontAwesome.Solid.CheckSquare;
|
||||
Activated = delegate
|
||||
{
|
||||
openUrl?.Invoke($"https://osu.ppy.sh/home/changelog/lazer/{version}");
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load(OsuColour colours, ChangelogOverlay changelog)
|
||||
{
|
||||
Icon = FontAwesome.Solid.CheckSquare;
|
||||
IconBackgound.Colour = colours.BlueDark;
|
||||
|
||||
Activated = delegate
|
||||
{
|
||||
changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,14 +20,14 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(Ruleset ruleset)
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 },
|
||||
Ruleset = ruleset.RulesetInfo
|
||||
Ruleset = ruleset
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -29,14 +29,14 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(Ruleset ruleset)
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
|
||||
Ruleset = ruleset.RulesetInfo
|
||||
Ruleset = ruleset
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -16,14 +16,14 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(Ruleset ruleset)
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
|
||||
Ruleset = ruleset.RulesetInfo
|
||||
Ruleset = ruleset
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -23,13 +23,13 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(Ruleset ruleset)
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = ruleset.RulesetInfo,
|
||||
Ruleset = ruleset,
|
||||
BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.6f }
|
||||
}
|
||||
};
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new CatchDifficultyAttributes { Mods = mods };
|
||||
return new CatchDifficultyAttributes { Mods = mods, Skills = skills };
|
||||
|
||||
// this is the same as osu!, so there's potential to share the implementation... maybe
|
||||
double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||
@ -41,7 +41,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor,
|
||||
Mods = mods,
|
||||
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet))
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
|
||||
Skills = skills
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -379,8 +379,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
|
||||
|
||||
// Correct overshooting.
|
||||
if (hyperDashDirection > 0 && hyperDashTargetPosition < X ||
|
||||
hyperDashDirection < 0 && hyperDashTargetPosition > X)
|
||||
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
||||
(hyperDashDirection < 0 && hyperDashTargetPosition > X))
|
||||
{
|
||||
X = hyperDashTargetPosition;
|
||||
SetHyperDashState();
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new ManiaDifficultyAttributes { Mods = mods };
|
||||
return new ManiaDifficultyAttributes { Mods = mods, Skills = skills };
|
||||
|
||||
return new ManiaDifficultyAttributes
|
||||
{
|
||||
@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
Mods = mods,
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
|
||||
Skills = skills
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -17,14 +17,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(Ruleset ruleset)
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
|
||||
Ruleset = ruleset.RulesetInfo
|
||||
Ruleset = ruleset
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -46,11 +46,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
AddStep("move mouse away", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.TopLeft));
|
||||
AddStep("click", () => osuInputManager.GameClick());
|
||||
AddAssert("not dismissed", () => !resumeFired && resume.State == Visibility.Visible);
|
||||
AddAssert("not dismissed", () => !resumeFired && resume.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("move mouse back", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre));
|
||||
AddStep("click", () => osuInputManager.GameClick());
|
||||
AddAssert("dismissed", () => resumeFired && resume.State == Visibility.Hidden);
|
||||
AddAssert("dismissed", () => resumeFired && resume.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
private class ManualOsuInputManager : OsuInputManager
|
||||
|
@ -20,7 +20,6 @@ using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
@ -299,7 +298,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
AddStep("load player", () =>
|
||||
{
|
||||
Beatmap.Value = new TestWorkingBeatmap(new Beatmap<OsuHitObject>
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap<OsuHitObject>
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
@ -323,7 +322,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
|
||||
Ruleset = new OsuRuleset().RulesetInfo
|
||||
},
|
||||
}, Clock);
|
||||
});
|
||||
|
||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
break;
|
||||
|
||||
if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance
|
||||
|| stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance)
|
||||
|| (stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance))
|
||||
{
|
||||
stackBaseIndex = n;
|
||||
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new OsuDifficultyAttributes { Mods = mods };
|
||||
return new OsuDifficultyAttributes { Mods = mods, Skills = skills };
|
||||
|
||||
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
||||
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
||||
@ -50,7 +50,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
SpeedStrain = speedRating,
|
||||
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
||||
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
||||
MaxCombo = maxCombo
|
||||
MaxCombo = maxCombo,
|
||||
Skills = skills
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -11,7 +14,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
internal class OsuModGrow : Mod, IApplicableToDrawableHitObjects
|
||||
internal class OsuModGrow : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
|
||||
{
|
||||
public override string Name => "Grow";
|
||||
|
||||
@ -25,9 +28,16 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>();
|
||||
|
||||
public void ReadFromConfig(OsuConfigManager config)
|
||||
{
|
||||
increaseFirstObjectVisibility = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility);
|
||||
}
|
||||
|
||||
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||
{
|
||||
foreach (var drawable in drawables)
|
||||
foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
|
||||
{
|
||||
switch (drawable)
|
||||
{
|
||||
|
@ -37,11 +37,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
if (time < osuHit.HitObject.StartTime - relax_leniency) continue;
|
||||
|
||||
if (osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime || osuHit.IsHit)
|
||||
if ((osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime) || osuHit.IsHit)
|
||||
continue;
|
||||
|
||||
requiresHit |= osuHit is DrawableHitCircle && osuHit.IsHovered && osuHit.HitObject.HitWindows.CanBeHit(relativetime);
|
||||
requiresHold |= osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered) || osuHit is DrawableSpinner;
|
||||
requiresHold |= (osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered)) || osuHit is DrawableSpinner;
|
||||
}
|
||||
|
||||
if (requiresHit)
|
||||
|
@ -2,13 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Lines;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Graphics.ES30;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
@ -19,8 +16,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
private readonly SliderPath path;
|
||||
protected Path Path => path;
|
||||
|
||||
private readonly BufferedContainer container;
|
||||
|
||||
public float PathRadius
|
||||
{
|
||||
get => path.PathRadius;
|
||||
@ -44,8 +39,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
return;
|
||||
|
||||
path.AccentColour = value;
|
||||
|
||||
container.ForceRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,8 +54,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
return;
|
||||
|
||||
path.BorderColour = value;
|
||||
|
||||
container.ForceRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,23 +69,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
return;
|
||||
|
||||
path.BorderSize = value;
|
||||
|
||||
container.ForceRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
public Quad PathDrawQuad => container.ScreenSpaceDrawQuad;
|
||||
|
||||
protected SliderBody()
|
||||
{
|
||||
InternalChild = container = new BufferedContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CacheDrawnFrameBuffer = true,
|
||||
Child = path = new SliderPath { Blending = BlendingMode.None }
|
||||
};
|
||||
|
||||
container.Attach(RenderbufferInternalFormat.DepthComponent16);
|
||||
InternalChild = path = new SliderPath();
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos);
|
||||
@ -103,11 +83,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
/// Sets the vertices of the path which should be drawn by this <see cref="SliderBody"/>.
|
||||
/// </summary>
|
||||
/// <param name="vertices">The vertices</param>
|
||||
protected void SetVertices(IReadOnlyList<Vector2> vertices)
|
||||
{
|
||||
path.Vertices = vertices;
|
||||
container.ForceRedraw();
|
||||
}
|
||||
protected void SetVertices(IReadOnlyList<Vector2> vertices) => path.Vertices = vertices;
|
||||
|
||||
private class SliderPath : SmoothPath
|
||||
{
|
||||
|
@ -6,7 +6,7 @@ using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.OpenGL.Buffers;
|
||||
using osu.Framework.Graphics.Batches;
|
||||
using osu.Framework.Graphics.OpenGL.Vertices;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
@ -54,8 +54,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
for (int i = 0; i < max_sprites; i++)
|
||||
{
|
||||
parts[i].InvalidationID = 0;
|
||||
parts[i].WasUpdated = true;
|
||||
// InvalidationID 1 forces an update of each part of the cursor trail the first time ApplyState is run on the draw node
|
||||
// This is to prevent garbage data from being sent to the vertex shader, resulting in visual issues on some platforms
|
||||
parts[i].InvalidationID = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +148,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
public Vector2 Position;
|
||||
public float Time;
|
||||
public long InvalidationID;
|
||||
public bool WasUpdated;
|
||||
}
|
||||
|
||||
private class TrailDrawNode : DrawNode
|
||||
@ -162,16 +162,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||
private Vector2 size;
|
||||
|
||||
private readonly VertexBuffer<TexturedTrailVertex> vertexBuffer = new QuadVertexBuffer<TexturedTrailVertex>(max_sprites, BufferUsageHint.DynamicDraw);
|
||||
private readonly VertexBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||
|
||||
public TrailDrawNode(CursorTrail source)
|
||||
: base(source)
|
||||
{
|
||||
for (int i = 0; i < max_sprites; i++)
|
||||
{
|
||||
parts[i].InvalidationID = 0;
|
||||
parts[i].WasUpdated = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ApplyState()
|
||||
@ -192,55 +189,29 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
public override void Draw(Action<TexturedVertex2D> vertexAction)
|
||||
{
|
||||
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
||||
|
||||
int updateStart = -1, updateEnd = 0;
|
||||
|
||||
for (int i = 0; i < parts.Length; ++i)
|
||||
{
|
||||
if (parts[i].WasUpdated)
|
||||
{
|
||||
if (updateStart == -1)
|
||||
updateStart = i;
|
||||
updateEnd = i + 1;
|
||||
|
||||
int start = i * 4;
|
||||
int end = start;
|
||||
|
||||
Vector2 pos = parts[i].Position;
|
||||
float localTime = parts[i].Time;
|
||||
|
||||
texture.DrawQuad(
|
||||
new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y),
|
||||
DrawColourInfo.Colour,
|
||||
null,
|
||||
v => vertexBuffer.Vertices[end++] = new TexturedTrailVertex
|
||||
{
|
||||
Position = v.Position,
|
||||
TexturePosition = v.TexturePosition,
|
||||
Time = localTime + 1,
|
||||
Colour = v.Colour,
|
||||
});
|
||||
|
||||
parts[i].WasUpdated = false;
|
||||
}
|
||||
else if (updateStart != -1)
|
||||
{
|
||||
vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
|
||||
updateStart = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Update all remaining vertices that have been changed.
|
||||
if (updateStart != -1)
|
||||
vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
|
||||
|
||||
base.Draw(vertexAction);
|
||||
|
||||
shader.Bind();
|
||||
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
||||
|
||||
texture.TextureGL.Bind();
|
||||
vertexBuffer.Draw();
|
||||
for (int i = 0; i < parts.Length; ++i)
|
||||
{
|
||||
Vector2 pos = parts[i].Position;
|
||||
float localTime = parts[i].Time;
|
||||
|
||||
DrawQuad(
|
||||
texture,
|
||||
new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y),
|
||||
DrawColourInfo.Colour,
|
||||
null,
|
||||
v => vertexBatch.Add(new TexturedTrailVertex
|
||||
{
|
||||
Position = v.Position,
|
||||
TexturePosition = v.TexturePosition,
|
||||
Time = localTime + 1,
|
||||
Colour = v.Colour,
|
||||
}));
|
||||
}
|
||||
|
||||
shader.Unbind();
|
||||
}
|
||||
@ -249,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
vertexBuffer.Dispose();
|
||||
vertexBatch.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
private GameplayCursorContainer localCursorContainer;
|
||||
|
||||
public override CursorContainer LocalCursor => State == Visibility.Visible ? localCursorContainer : null;
|
||||
public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null;
|
||||
|
||||
protected override string Message => "Click the orange cursor to resume";
|
||||
|
||||
|
@ -18,7 +18,6 @@ using osu.Game.Rulesets.Taiko.Judgements;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -64,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
controlPointInfo.TimingPoints.Add(new TimingControlPoint());
|
||||
|
||||
WorkingBeatmap beatmap = new TestWorkingBeatmap(new Beatmap
|
||||
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject> { new CentreHit() },
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
@ -79,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
Ruleset = new TaikoRuleset().RulesetInfo
|
||||
},
|
||||
ControlPointInfo = controlPointInfo
|
||||
}, Clock);
|
||||
});
|
||||
|
||||
Add(playfieldContainer = new Container
|
||||
{
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new TaikoDifficultyAttributes { Mods = mods };
|
||||
return new TaikoDifficultyAttributes { Mods = mods, Skills = skills };
|
||||
|
||||
return new TaikoDifficultyAttributes
|
||||
{
|
||||
@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
|
||||
Skills = skills
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,9 @@ using NUnit.Framework;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.IPC;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Tests.Resources;
|
||||
using SharpCompress.Archives.Zip;
|
||||
|
||||
@ -21,14 +23,14 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
public class ImportBeatmapTest
|
||||
{
|
||||
[Test]
|
||||
public void TestImportWhenClosed()
|
||||
public async Task TestImportWhenClosed()
|
||||
{
|
||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed"))
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadOszIntoOsu(loadOsu(host));
|
||||
await LoadOszIntoOsu(loadOsu(host));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -38,7 +40,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImportThenDelete()
|
||||
public async Task TestImportThenDelete()
|
||||
{
|
||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete"))
|
||||
@ -47,7 +49,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
|
||||
var imported = LoadOszIntoOsu(osu);
|
||||
var imported = await LoadOszIntoOsu(osu);
|
||||
|
||||
deleteBeatmapSet(imported, osu);
|
||||
}
|
||||
@ -59,7 +61,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImportThenImport()
|
||||
public async Task TestImportThenImport()
|
||||
{
|
||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport"))
|
||||
@ -68,17 +70,15 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
|
||||
var imported = LoadOszIntoOsu(osu);
|
||||
var importedSecondTime = LoadOszIntoOsu(osu);
|
||||
var imported = await LoadOszIntoOsu(osu);
|
||||
var importedSecondTime = await LoadOszIntoOsu(osu);
|
||||
|
||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
||||
checkBeatmapSetCount(osu, 1);
|
||||
checkSingleReferencedFileCount(osu, 18);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -88,30 +88,41 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRollbackOnFailure()
|
||||
public async Task TestRollbackOnFailure()
|
||||
{
|
||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure"))
|
||||
{
|
||||
try
|
||||
{
|
||||
int itemAddRemoveFireCount = 0;
|
||||
int loggedExceptionCount = 0;
|
||||
|
||||
Logger.NewEntry += l =>
|
||||
{
|
||||
if (l.Target == LoggingTarget.Database && l.Exception != null)
|
||||
Interlocked.Increment(ref loggedExceptionCount);
|
||||
};
|
||||
|
||||
var osu = loadOsu(host);
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
int fireCount = 0;
|
||||
|
||||
// ReSharper disable once AccessToModifiedClosure
|
||||
manager.ItemAdded += (_, __) => fireCount++;
|
||||
manager.ItemRemoved += _ => fireCount++;
|
||||
manager.ItemAdded += (_, __) => Interlocked.Increment(ref itemAddRemoveFireCount);
|
||||
manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
|
||||
|
||||
var imported = LoadOszIntoOsu(osu);
|
||||
var imported = await LoadOszIntoOsu(osu);
|
||||
|
||||
Assert.AreEqual(0, fireCount -= 1);
|
||||
Assert.AreEqual(0, itemAddRemoveFireCount -= 1);
|
||||
|
||||
imported.Hash += "-changed";
|
||||
manager.Update(imported);
|
||||
|
||||
Assert.AreEqual(0, fireCount -= 2);
|
||||
Assert.AreEqual(0, itemAddRemoveFireCount -= 2);
|
||||
|
||||
checkBeatmapSetCount(osu, 1);
|
||||
checkBeatmapCount(osu, 12);
|
||||
checkSingleReferencedFileCount(osu, 18);
|
||||
|
||||
var breakTemp = TestResources.GetTestBeatmapForImport();
|
||||
|
||||
@ -127,19 +138,24 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
||||
Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
|
||||
|
||||
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
|
||||
manager.Import(breakTemp);
|
||||
try
|
||||
{
|
||||
await manager.Import(breakTemp);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// no events should be fired in the case of a rollback.
|
||||
Assert.AreEqual(0, fireCount);
|
||||
Assert.AreEqual(0, itemAddRemoveFireCount);
|
||||
|
||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
||||
Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
|
||||
checkBeatmapSetCount(osu, 1);
|
||||
checkBeatmapCount(osu, 12);
|
||||
|
||||
checkSingleReferencedFileCount(osu, 18);
|
||||
|
||||
Assert.AreEqual(1, loggedExceptionCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -149,7 +165,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImportThenImportDifferentHash()
|
||||
public async Task TestImportThenImportDifferentHash()
|
||||
{
|
||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash"))
|
||||
@ -159,19 +175,18 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
var osu = loadOsu(host);
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
var imported = LoadOszIntoOsu(osu);
|
||||
var imported = await LoadOszIntoOsu(osu);
|
||||
|
||||
imported.Hash += "-changed";
|
||||
manager.Update(imported);
|
||||
|
||||
var importedSecondTime = LoadOszIntoOsu(osu);
|
||||
var importedSecondTime = await LoadOszIntoOsu(osu);
|
||||
|
||||
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
|
||||
|
||||
// only one beatmap will exist as the online set ID matched, causing purging of the first import.
|
||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
||||
checkBeatmapSetCount(osu, 1);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -181,7 +196,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImportThenDeleteThenImport()
|
||||
public async Task TestImportThenDeleteThenImport()
|
||||
{
|
||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport"))
|
||||
@ -190,11 +205,11 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
|
||||
var imported = LoadOszIntoOsu(osu);
|
||||
var imported = await LoadOszIntoOsu(osu);
|
||||
|
||||
deleteBeatmapSet(imported, osu);
|
||||
|
||||
var importedSecondTime = LoadOszIntoOsu(osu);
|
||||
var importedSecondTime = await LoadOszIntoOsu(osu);
|
||||
|
||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||
@ -209,7 +224,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
|
||||
public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
|
||||
{
|
||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}"))
|
||||
@ -218,7 +233,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
|
||||
var imported = LoadOszIntoOsu(osu);
|
||||
var imported = await LoadOszIntoOsu(osu);
|
||||
|
||||
if (set)
|
||||
imported.OnlineBeatmapSetID = 1234;
|
||||
@ -229,7 +244,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
deleteBeatmapSet(imported, osu);
|
||||
|
||||
var importedSecondTime = LoadOszIntoOsu(osu);
|
||||
var importedSecondTime = await LoadOszIntoOsu(osu);
|
||||
|
||||
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
|
||||
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||
@ -243,7 +258,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImportWithDuplicateBeatmapIDs()
|
||||
public async Task TestImportWithDuplicateBeatmapIDs()
|
||||
{
|
||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID"))
|
||||
@ -284,7 +299,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
var imported = manager.Import(toImport);
|
||||
var imported = await manager.Import(toImport);
|
||||
|
||||
Assert.NotNull(imported);
|
||||
Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID);
|
||||
@ -330,7 +345,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImportWhenFileOpen()
|
||||
public async Task TestImportWhenFileOpen()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen"))
|
||||
{
|
||||
@ -339,7 +354,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
var osu = loadOsu(host);
|
||||
var temp = TestResources.GetTestBeatmapForImport();
|
||||
using (File.OpenRead(temp))
|
||||
osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
||||
await osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
||||
ensureLoaded(osu);
|
||||
File.Delete(temp);
|
||||
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
|
||||
@ -351,13 +366,13 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
}
|
||||
}
|
||||
|
||||
public static BeatmapSetInfo LoadOszIntoOsu(OsuGameBase osu, string path = null)
|
||||
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null)
|
||||
{
|
||||
var temp = path ?? TestResources.GetTestBeatmapForImport();
|
||||
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
manager.Import(temp);
|
||||
await manager.Import(temp);
|
||||
|
||||
var imported = manager.GetAllUsableBeatmapSets();
|
||||
|
||||
@ -373,11 +388,32 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
manager.Delete(imported);
|
||||
|
||||
Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0);
|
||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
||||
checkBeatmapSetCount(osu, 0);
|
||||
checkBeatmapSetCount(osu, 1, true);
|
||||
checkSingleReferencedFileCount(osu, 0);
|
||||
|
||||
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
|
||||
}
|
||||
|
||||
private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false)
|
||||
{
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
Assert.AreEqual(expected, includeDeletePending
|
||||
? manager.QueryBeatmapSets(_ => true).ToList().Count
|
||||
: manager.GetAllUsableBeatmapSets().Count);
|
||||
}
|
||||
|
||||
private void checkBeatmapCount(OsuGameBase osu, int expected)
|
||||
{
|
||||
Assert.AreEqual(expected, osu.Dependencies.Get<BeatmapManager>().QueryBeatmaps(_ => true).ToList().Count);
|
||||
}
|
||||
|
||||
private void checkSingleReferencedFileCount(OsuGameBase osu, int expected)
|
||||
{
|
||||
Assert.AreEqual(expected, osu.Dependencies.Get<FileStore>().QueryFiles(f => f.ReferenceCount == 1).Count());
|
||||
}
|
||||
|
||||
private OsuGameBase loadOsu(GameHost host)
|
||||
{
|
||||
var osu = new OsuGameBase();
|
||||
|
@ -23,13 +23,13 @@ namespace osu.Game.Tests.Scores.IO
|
||||
public class ImportScoreTest
|
||||
{
|
||||
[Test]
|
||||
public void TestBasicImport()
|
||||
public async Task TestBasicImport()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
var osu = await loadOsu(host);
|
||||
|
||||
var toImport = new ScoreInfo
|
||||
{
|
||||
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
OnlineScoreID = 12345,
|
||||
};
|
||||
|
||||
var imported = loadIntoOsu(osu, toImport);
|
||||
var imported = await loadIntoOsu(osu, toImport);
|
||||
|
||||
Assert.AreEqual(toImport.Rank, imported.Rank);
|
||||
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
|
||||
@ -62,20 +62,20 @@ namespace osu.Game.Tests.Scores.IO
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImportMods()
|
||||
public async Task TestImportMods()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
var osu = await loadOsu(host);
|
||||
|
||||
var toImport = new ScoreInfo
|
||||
{
|
||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
||||
};
|
||||
|
||||
var imported = loadIntoOsu(osu, toImport);
|
||||
var imported = await loadIntoOsu(osu, toImport);
|
||||
|
||||
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
|
||||
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
|
||||
@ -88,13 +88,13 @@ namespace osu.Game.Tests.Scores.IO
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImportStatistics()
|
||||
public async Task TestImportStatistics()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
var osu = await loadOsu(host);
|
||||
|
||||
var toImport = new ScoreInfo
|
||||
{
|
||||
@ -105,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
}
|
||||
};
|
||||
|
||||
var imported = loadIntoOsu(osu, toImport);
|
||||
var imported = await loadIntoOsu(osu, toImport);
|
||||
|
||||
Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]);
|
||||
Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]);
|
||||
@ -117,7 +117,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
}
|
||||
}
|
||||
|
||||
private ScoreInfo loadIntoOsu(OsuGameBase osu, ScoreInfo score)
|
||||
private async Task<ScoreInfo> loadIntoOsu(OsuGameBase osu, ScoreInfo score)
|
||||
{
|
||||
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
@ -125,20 +125,24 @@ namespace osu.Game.Tests.Scores.IO
|
||||
score.Ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
||||
scoreManager.Import(score);
|
||||
await scoreManager.Import(score);
|
||||
|
||||
return scoreManager.GetAllUsableScores().First();
|
||||
}
|
||||
|
||||
private OsuGameBase loadOsu(GameHost host)
|
||||
private async Task<OsuGameBase> loadOsu(GameHost host)
|
||||
{
|
||||
var osu = new OsuGameBase();
|
||||
|
||||
#pragma warning disable 4014
|
||||
Task.Run(() => host.Run(osu));
|
||||
#pragma warning restore 4014
|
||||
|
||||
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
||||
|
||||
var beatmapFile = TestResources.GetTestBeatmapForImport();
|
||||
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
||||
beatmapManager.Import(beatmapFile);
|
||||
await beatmapManager.Import(beatmapFile);
|
||||
|
||||
return osu;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -54,7 +55,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
private RulesetStore rulesets;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
factory = new DatabaseContextFactory(LocalStorage);
|
||||
factory.ResetDatabase();
|
||||
@ -68,10 +69,10 @@ namespace osu.Game.Tests.Visual.Background
|
||||
usage.Migrate();
|
||||
|
||||
Dependencies.Cache(rulesets = new RulesetStore(factory));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, host, Beatmap.Default));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, Beatmap.Default));
|
||||
Dependencies.Cache(new OsuConfigManager(LocalStorage));
|
||||
|
||||
manager.Import(TestResources.GetTestBeatmapForImport());
|
||||
manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
|
||||
|
||||
Beatmap.SetDefault();
|
||||
}
|
||||
|
@ -111,16 +111,19 @@ namespace osu.Game.Tests.Visual.Components
|
||||
|
||||
private class TestPreviewTrackManager : PreviewTrackManager
|
||||
{
|
||||
protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager) => new TestPreviewTrack(beatmapSetInfo, trackManager);
|
||||
protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore);
|
||||
|
||||
protected class TestPreviewTrack : TrackManagerPreviewTrack
|
||||
{
|
||||
public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager)
|
||||
private readonly ITrackStore trackManager;
|
||||
|
||||
public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackManager)
|
||||
: base(beatmapSetInfo, trackManager)
|
||||
{
|
||||
this.trackManager = trackManager;
|
||||
}
|
||||
|
||||
protected override Track GetTrack() => new TrackVirtual { Length = 100000 };
|
||||
protected override Track GetTrack() => trackManager.GetVirtual(100000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit.Compose;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editor
|
||||
{
|
||||
@ -19,7 +18,7 @@ namespace osu.Game.Tests.Visual.Editor
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, Clock);
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
Child = new ComposeScreen();
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -30,9 +31,9 @@ namespace osu.Game.Tests.Visual.Editor
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
Beatmap.Value = new WaveformTestBeatmap();
|
||||
Beatmap.Value = new WaveformTestBeatmap(audio);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
// 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.
|
||||
|
||||
using NUnit.Framework;
|
||||
@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -48,7 +47,7 @@ namespace osu.Game.Tests.Visual.Editor
|
||||
}
|
||||
};
|
||||
|
||||
Beatmap.Value = new TestWorkingBeatmap(testBeatmap, Clock);
|
||||
Beatmap.Value = CreateWorkingBeatmap(testBeatmap);
|
||||
|
||||
Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock };
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editor
|
||||
@ -21,7 +20,7 @@ namespace osu.Game.Tests.Visual.Editor
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, null);
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
|
||||
Add(new SummaryTimeline
|
||||
{
|
||||
|
@ -18,7 +18,6 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit.Compose;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editor
|
||||
@ -45,7 +44,7 @@ namespace osu.Game.Tests.Visual.Editor
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Beatmap.Value = new TestWorkingBeatmap(new Beatmap
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
// 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.
|
||||
|
||||
using NUnit.Framework;
|
||||
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editor
|
||||
@ -29,7 +28,7 @@ namespace osu.Game.Tests.Visual.Editor
|
||||
Size = new Vector2(200, 100)
|
||||
};
|
||||
|
||||
Beatmap.Value = new TestWorkingBeatmap(new Beatmap(), Clock);
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap());
|
||||
|
||||
Child = playback;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
@ -10,6 +11,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editor
|
||||
@ -20,9 +22,9 @@ namespace osu.Game.Tests.Visual.Editor
|
||||
private WorkingBeatmap waveformBeatmap;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
waveformBeatmap = new WaveformTestBeatmap();
|
||||
waveformBeatmap = new WaveformTestBeatmap(audio);
|
||||
}
|
||||
|
||||
[TestCase(1f)]
|
||||
@ -91,7 +93,7 @@ namespace osu.Game.Tests.Visual.Editor
|
||||
Child = graph = new TestWaveformGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Waveform = new DummyWorkingBeatmap().Waveform,
|
||||
Waveform = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).Waveform,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
50
osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
Normal file
50
osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
Normal file
@ -0,0 +1,50 @@
|
||||
// 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 osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneFailAnimation : AllPlayersTestScene
|
||||
{
|
||||
protected override Player CreatePlayer(Ruleset ruleset)
|
||||
{
|
||||
Mods.Value = Array.Empty<Mod>();
|
||||
return new FailPlayer();
|
||||
}
|
||||
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(AllPlayersTestScene),
|
||||
typeof(TestPlayer),
|
||||
typeof(Player),
|
||||
};
|
||||
|
||||
protected override void AddCheckSteps()
|
||||
{
|
||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
||||
AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
private class FailPlayer : TestPlayer
|
||||
{
|
||||
public new FailOverlay FailOverlay => base.FailOverlay;
|
||||
|
||||
public FailPlayer()
|
||||
: base(false, false)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
ScoreProcessor.FailConditions += _ => true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("Show overlay", () => pauseOverlay.Show());
|
||||
|
||||
AddStep("Press select", () => press(GlobalAction.Select));
|
||||
AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible);
|
||||
AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("Hide overlay", () => pauseOverlay.Hide());
|
||||
}
|
||||
@ -237,7 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
|
||||
AddAssert("Action was triggered", () => triggered);
|
||||
AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
|
||||
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -272,7 +272,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
return triggered;
|
||||
});
|
||||
AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
|
||||
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
private void press(Key key)
|
||||
|
@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public void TestPauseAfterFail()
|
||||
{
|
||||
AddUntilStep("wait for fail", () => Player.HasFailed);
|
||||
AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
|
||||
AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible);
|
||||
|
||||
confirmClockRunning(false);
|
||||
|
||||
@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown);
|
||||
|
||||
private void confirmClockRunning(bool isRunning) =>
|
||||
AddAssert("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning);
|
||||
AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning);
|
||||
|
||||
protected override bool AllowFail => true;
|
||||
|
||||
@ -203,9 +203,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||
|
||||
public bool FailOverlayVisible => FailOverlay.State == Visibility.Visible;
|
||||
public bool FailOverlayVisible => FailOverlay.State.Value == Visibility.Visible;
|
||||
|
||||
public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible;
|
||||
public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;
|
||||
|
||||
public override void OnEntering(IScreen last)
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
@ -16,7 +16,6 @@ using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
@ -29,7 +28,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both };
|
||||
Beatmap.Value = new TestWorkingBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), Clock);
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
});
|
||||
|
||||
[Test]
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -43,9 +42,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
}
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock clock)
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
var working = base.CreateWorkingBeatmap(beatmap, clock);
|
||||
var working = base.CreateWorkingBeatmap(beatmap);
|
||||
workingWeakReferences.Add(working);
|
||||
return working;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
Origin = Anchor.TopRight,
|
||||
Anchor = Anchor.TopRight,
|
||||
State = Visibility.Visible,
|
||||
State = { Value = Visibility.Visible },
|
||||
});
|
||||
|
||||
AddStep("Restart", restart);
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
|
||||
public TestSceneToolbar()
|
||||
{
|
||||
var toolbar = new Toolbar { State = Visibility.Visible };
|
||||
var toolbar = new Toolbar { State = { Value = Visibility.Visible } };
|
||||
ToolbarNotificationButton notificationButton = null;
|
||||
|
||||
AddStep("create toolbar", () =>
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
@ -37,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
settings = new TestRoomSettings
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = Visibility.Visible
|
||||
State = { Value = Visibility.Visible }
|
||||
};
|
||||
|
||||
Child = settings;
|
||||
@ -57,7 +56,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("set name", () => Room.Name.Value = "Room name");
|
||||
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
|
||||
|
||||
AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = new DummyWorkingBeatmap().BeatmapInfo }));
|
||||
AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo }));
|
||||
AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value);
|
||||
|
||||
AddStep("clear name", () => Room.Name.Value = "");
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
api.Logout();
|
||||
api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
|
||||
|
||||
AddStep("show", () => accountCreation.State = Visibility.Visible);
|
||||
AddStep("show", () => accountCreation.Show());
|
||||
AddStep("logout", () => api.Logout());
|
||||
}
|
||||
}
|
||||
|
70
osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
Normal file
70
osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
Normal file
@ -0,0 +1,70 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Changelog;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneChangelogOverlay : OsuTestScene
|
||||
{
|
||||
private ChangelogOverlay changelog;
|
||||
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(UpdateStreamBadgeArea),
|
||||
typeof(UpdateStreamBadge),
|
||||
typeof(ChangelogHeader),
|
||||
typeof(ChangelogContent),
|
||||
typeof(ChangelogListing),
|
||||
typeof(ChangelogSingleBuild),
|
||||
typeof(ChangelogBuild),
|
||||
};
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Add(changelog = new ChangelogOverlay());
|
||||
AddStep(@"Show", changelog.Show);
|
||||
AddStep(@"Hide", changelog.Hide);
|
||||
|
||||
AddWaitStep("wait for hide", 3);
|
||||
|
||||
AddStep(@"Show with Lazer 2018.712.0", () =>
|
||||
{
|
||||
changelog.ShowBuild(new APIChangelogBuild
|
||||
{
|
||||
Version = "2018.712.0",
|
||||
DisplayVersion = "2018.712.0",
|
||||
UpdateStream = new APIUpdateStream { Name = OsuGameBase.CLIENT_STREAM_NAME },
|
||||
ChangelogEntries = new List<APIChangelogEntry>
|
||||
{
|
||||
new APIChangelogEntry
|
||||
{
|
||||
Category = "Test",
|
||||
Title = "Title",
|
||||
MessageHtml = "Message",
|
||||
}
|
||||
}
|
||||
});
|
||||
changelog.Show();
|
||||
});
|
||||
|
||||
AddWaitStep("wait for show", 3);
|
||||
AddStep(@"Hide", changelog.Hide);
|
||||
AddWaitStep("wait for hide", 3);
|
||||
|
||||
AddStep(@"Show with listing", () =>
|
||||
{
|
||||
changelog.ShowListing();
|
||||
changelog.Show();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Children = new Drawable[]
|
||||
{
|
||||
channelManager,
|
||||
new ChatOverlay { State = Visibility.Visible }
|
||||
new ChatOverlay { State = { Value = Visibility.Visible } }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
@ -25,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var beatmap = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, null);
|
||||
var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
beatmap.BeatmapSetInfo.OnlineInfo.HasVideo = true;
|
||||
beatmap.BeatmapSetInfo.OnlineInfo.HasStoryboard = true;
|
||||
|
||||
|
@ -18,8 +18,24 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
int fireCount = 0;
|
||||
|
||||
Add(overlay = new TestFullscreenOverlay());
|
||||
AddStep(@"toggle", overlay.ToggleVisibility);
|
||||
|
||||
overlay.State.ValueChanged += _ => fireCount++;
|
||||
|
||||
AddStep(@"show", overlay.Show);
|
||||
|
||||
AddAssert("fire count 1", () => fireCount == 1);
|
||||
|
||||
AddStep(@"show again", overlay.Show);
|
||||
|
||||
// this logic is specific to FullscreenOverlay
|
||||
AddAssert("fire count 2", () => fireCount == 2);
|
||||
|
||||
AddStep(@"hide", overlay.Hide);
|
||||
|
||||
AddAssert("fire count 3", () => fireCount == 3);
|
||||
}
|
||||
|
||||
private class TestFullscreenOverlay : FullscreenOverlay
|
||||
|
@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
FlagName = @"TH",
|
||||
},
|
||||
},
|
||||
Rank = ScoreRank.F,
|
||||
Rank = ScoreRank.D,
|
||||
PP = 160,
|
||||
MaxCombo = 1234,
|
||||
TotalScore = 123456,
|
||||
|
55
osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs
Normal file
55
osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs
Normal file
@ -0,0 +1,55 @@
|
||||
// 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 osu.Game.Overlays.Profile.Sections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public class TestSceneShowMoreButton : OsuTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(ShowMoreButton),
|
||||
};
|
||||
|
||||
public TestSceneShowMoreButton()
|
||||
{
|
||||
ShowMoreButton button = null;
|
||||
|
||||
int fireCount = 0;
|
||||
|
||||
Add(button = new ShowMoreButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Action = () =>
|
||||
{
|
||||
fireCount++;
|
||||
// ReSharper disable once AccessToModifiedClosure
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
Scheduler.AddDelayed(() => button.IsLoading = false, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("click button", () => button.Click());
|
||||
|
||||
AddAssert("action fired once", () => fireCount == 1);
|
||||
AddAssert("is in loading state", () => button.IsLoading);
|
||||
|
||||
AddStep("click button", () => button.Click());
|
||||
|
||||
AddAssert("action not fired", () => fireCount == 1);
|
||||
AddAssert("is in loading state", () => button.IsLoading);
|
||||
|
||||
AddUntilStep("wait for loaded", () => !button.IsLoading);
|
||||
|
||||
AddStep("click button", () => button.Click());
|
||||
|
||||
AddAssert("action fired twice", () => fireCount == 2);
|
||||
AddAssert("is in loading state", () => button.IsLoading);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Users;
|
||||
@ -12,10 +13,12 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[TestFixture]
|
||||
public class TestSceneUserPanel : OsuTestScene
|
||||
{
|
||||
private readonly UserPanel peppy;
|
||||
|
||||
public TestSceneUserPanel()
|
||||
{
|
||||
UserPanel flyte;
|
||||
UserPanel peppy;
|
||||
|
||||
Add(new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
@ -44,13 +47,31 @@ namespace osu.Game.Tests.Visual.Online
|
||||
});
|
||||
|
||||
flyte.Status.Value = new UserStatusOnline();
|
||||
peppy.Status.Value = new UserStatusSoloGame();
|
||||
peppy.Status.Value = null;
|
||||
}
|
||||
|
||||
AddStep(@"spectating", () => { flyte.Status.Value = new UserStatusSpectating(); });
|
||||
AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); });
|
||||
AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); });
|
||||
AddStep(@"offline", () => { flyte.Status.Value = new UserStatusOffline(); });
|
||||
AddStep(@"null status", () => { flyte.Status.Value = null; });
|
||||
[Test]
|
||||
public void UserStatusesTests()
|
||||
{
|
||||
AddStep("online", () => { peppy.Status.Value = new UserStatusOnline(); });
|
||||
AddStep(@"do not disturb", () => { peppy.Status.Value = new UserStatusDoNotDisturb(); });
|
||||
AddStep(@"offline", () => { peppy.Status.Value = new UserStatusOffline(); });
|
||||
AddStep(@"null status", () => { peppy.Status.Value = null; });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UserActivitiesTests()
|
||||
{
|
||||
Bindable<UserActivity> activity = new Bindable<UserActivity>();
|
||||
|
||||
peppy.Activity.BindTo(activity);
|
||||
|
||||
AddStep("idle", () => { activity.Value = null; });
|
||||
AddStep("spectating", () => { activity.Value = new UserActivity.Spectating(); });
|
||||
AddStep("solo", () => { activity.Value = new UserActivity.SoloGame(null, null); });
|
||||
AddStep("choosing", () => { activity.Value = new UserActivity.ChoosingBeatmap(); });
|
||||
AddStep("editing", () => { activity.Value = new UserActivity.Editing(null); });
|
||||
AddStep("modding", () => { activity.Value = new UserActivity.Modding(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,33 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneSettings : OsuTestScene
|
||||
public class TestSceneSettingsPanel : OsuTestScene
|
||||
{
|
||||
private readonly SettingsPanel settings;
|
||||
private readonly DialogOverlay dialogOverlay;
|
||||
|
||||
public TestSceneSettings()
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(SettingsFooter),
|
||||
typeof(SettingsOverlay),
|
||||
};
|
||||
|
||||
public TestSceneSettingsPanel()
|
||||
{
|
||||
settings = new SettingsOverlay
|
||||
{
|
||||
State = Visibility.Visible
|
||||
State = { Value = Visibility.Visible }
|
||||
};
|
||||
Add(dialogOverlay = new DialogOverlay
|
||||
{
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select;
|
||||
@ -18,7 +19,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(BeatmapDetails) };
|
||||
|
||||
public TestSceneBeatmapDetailArea()
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase game)
|
||||
{
|
||||
BeatmapDetailArea detailsArea;
|
||||
Add(detailsArea = new BeatmapDetailArea
|
||||
@ -28,7 +30,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Size = new Vector2(550f, 450f),
|
||||
});
|
||||
|
||||
AddStep("all metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap
|
||||
AddStep("all metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
@ -56,7 +58,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}
|
||||
);
|
||||
|
||||
AddStep("all except source", () => detailsArea.Beatmap = new DummyWorkingBeatmap
|
||||
AddStep("all except source", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
@ -82,7 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("ratings", () => detailsArea.Beatmap = new DummyWorkingBeatmap
|
||||
AddStep("ratings", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
@ -107,7 +109,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("fails+retries", () => detailsArea.Beatmap = new DummyWorkingBeatmap
|
||||
AddStep("fails+retries", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
@ -133,7 +135,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("null metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap
|
||||
AddStep("null metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null)
|
||||
{
|
||||
BeatmapInfo =
|
||||
{
|
||||
|
@ -7,7 +7,6 @@ using JetBrains.Annotations;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets;
|
||||
@ -18,7 +17,6 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
@ -49,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddStep("show", () =>
|
||||
{
|
||||
infoWedge.State = Visibility.Visible;
|
||||
infoWedge.Show();
|
||||
infoWedge.Beatmap = Beatmap.Value;
|
||||
});
|
||||
|
||||
@ -58,11 +56,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddWaitStep("wait for select", 3);
|
||||
|
||||
AddStep("hide", () => { infoWedge.State = Visibility.Hidden; });
|
||||
AddStep("hide", () => { infoWedge.Hide(); });
|
||||
|
||||
AddWaitStep("wait for hide", 3);
|
||||
|
||||
AddStep("show", () => { infoWedge.State = Visibility.Visible; });
|
||||
AddStep("show", () => { infoWedge.Show(); });
|
||||
|
||||
foreach (var rulesetInfo in rulesets.AvailableRulesets)
|
||||
{
|
||||
@ -136,7 +134,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () =>
|
||||
{
|
||||
infoBefore = infoWedge.Info;
|
||||
infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : new TestWorkingBeatmap(b);
|
||||
infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for async load", () => infoWedge.Info != infoBefore);
|
||||
|
@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
},
|
||||
new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.F,
|
||||
Rank = ScoreRank.D,
|
||||
Accuracy = 0.6025,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
@ -206,7 +206,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
},
|
||||
new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.F,
|
||||
Rank = ScoreRank.D,
|
||||
Accuracy = 0.5140,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
@ -224,7 +224,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
},
|
||||
new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.F,
|
||||
Rank = ScoreRank.D,
|
||||
Accuracy = 0.4222,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
|
@ -8,6 +8,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.MathUtils;
|
||||
@ -79,7 +80,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
factory = new DatabaseContextFactory(LocalStorage);
|
||||
factory.ResetDatabase();
|
||||
@ -93,7 +94,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
usage.Migrate();
|
||||
|
||||
Dependencies.Cache(rulesets = new RulesetStore(factory));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, host, defaultBeatmap = Beatmap.Default));
|
||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default));
|
||||
|
||||
Beatmap.SetDefault();
|
||||
}
|
||||
@ -137,7 +138,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
createSongSelect();
|
||||
changeRuleset(2);
|
||||
importForRuleset(0);
|
||||
addRulesetImportStep(0);
|
||||
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
|
||||
}
|
||||
|
||||
@ -146,8 +147,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
createSongSelect();
|
||||
changeRuleset(2);
|
||||
importForRuleset(2);
|
||||
importForRuleset(1);
|
||||
addRulesetImportStep(2);
|
||||
addRulesetImportStep(1);
|
||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
|
||||
|
||||
changeRuleset(1);
|
||||
@ -209,7 +210,52 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddAssert("start not requested", () => !startRequested);
|
||||
}
|
||||
|
||||
private void importForRuleset(int id) => AddStep($"import test map for ruleset {id}", () => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())));
|
||||
[Test]
|
||||
public void TestAddNewBeatmapWhileSelectingRandom()
|
||||
{
|
||||
const int test_count = 10;
|
||||
int beatmapChangedCount = 0;
|
||||
int debounceCount = 0;
|
||||
createSongSelect();
|
||||
AddStep("Setup counters", () =>
|
||||
{
|
||||
beatmapChangedCount = 0;
|
||||
debounceCount = 0;
|
||||
songSelect.Carousel.SelectionChanged += _ => beatmapChangedCount++;
|
||||
});
|
||||
AddRepeatStep($"Create beatmaps {test_count} times", () =>
|
||||
{
|
||||
importForRuleset(0);
|
||||
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
// Wait for debounce
|
||||
songSelect.Carousel.SelectNextRandom();
|
||||
++debounceCount;
|
||||
}, 400);
|
||||
}, test_count);
|
||||
|
||||
AddUntilStep("Debounce limit reached", () => debounceCount == test_count);
|
||||
|
||||
// The selected beatmap should have changed an additional 2 times since both initially loading songselect and the first import also triggers selectionChanged
|
||||
AddAssert($"Beatmap changed {test_count + 2} times", () => beatmapChangedCount == test_count + 2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideSetSelectsCorrectBeatmap()
|
||||
{
|
||||
int? previousID = null;
|
||||
createSongSelect();
|
||||
addRulesetImportStep(0);
|
||||
AddStep("Move to last difficulty", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.Last()));
|
||||
AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmap.ID);
|
||||
AddStep("Hide first beatmap", () => manager.Hide(songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First()));
|
||||
AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmap.ID == previousID);
|
||||
}
|
||||
|
||||
private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id));
|
||||
|
||||
private void importForRuleset(int id) => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())).Wait();
|
||||
|
||||
private static int importId;
|
||||
private int getImportId() => ++importId;
|
||||
@ -231,7 +277,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
|
||||
|
||||
for (int i = 0; i < 100; i += 10)
|
||||
manager.Import(createTestBeatmapSet(i, usableRulesets));
|
||||
manager.Import(createTestBeatmapSet(i, usableRulesets)).Wait();
|
||||
});
|
||||
}
|
||||
|
||||
|
50
osu.Game.Tests/Visual/UserInterface/OsuGridTestScene.cs
Normal file
50
osu.Game.Tests/Visual/UserInterface/OsuGridTestScene.cs
Normal file
@ -0,0 +1,50 @@
|
||||
// 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 osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// An abstract test case which exposes small cells arranged in a grid.
|
||||
/// Useful for displaying multiple configurations of a tested component at a glance.
|
||||
/// </summary>
|
||||
public abstract class OsuGridTestScene : OsuTestScene
|
||||
{
|
||||
private readonly Drawable[,] cells;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of rows in the grid.
|
||||
/// </summary>
|
||||
protected readonly int Rows;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of columns in the grid.
|
||||
/// </summary>
|
||||
protected readonly int Cols;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a grid test case with the given dimensions.
|
||||
/// </summary>
|
||||
protected OsuGridTestScene(int rows, int cols)
|
||||
{
|
||||
Rows = rows;
|
||||
Cols = cols;
|
||||
|
||||
GridContainer testContainer;
|
||||
Add(testContainer = new GridContainer { RelativeSizeAxes = Axes.Both });
|
||||
|
||||
cells = new Drawable[rows, cols];
|
||||
for (int r = 0; r < rows; r++)
|
||||
for (int c = 0; c < cols; c++)
|
||||
cells[r, c] = new Container { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
testContainer.Content = cells.ToJagged();
|
||||
}
|
||||
|
||||
protected Container Cell(int index) => (Container)cells[index / Cols, index % Cols];
|
||||
protected Container Cell(int row, int col) => (Container)cells[row, col];
|
||||
}
|
||||
}
|
@ -84,7 +84,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
testLocalCursor();
|
||||
testUserCursorOverride();
|
||||
testMultipleLocalCursors();
|
||||
ReturnUserInput();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -177,7 +176,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
/// Checks if a cursor is visible.
|
||||
/// </summary>
|
||||
/// <param name="cursorContainer">The cursor to check.</param>
|
||||
private bool checkVisible(CursorContainer cursorContainer) => cursorContainer.State == Visibility.Visible;
|
||||
private bool checkVisible(CursorContainer cursorContainer) => cursorContainer.State.Value == Visibility.Visible;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a cursor is at the current inputmanager screen position.
|
||||
@ -193,7 +192,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
public CursorContainer Cursor { get; }
|
||||
public bool ProvidingUserCursor { get; }
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || SmoothTransition && !ProvidingUserCursor;
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor);
|
||||
|
||||
private readonly Box background;
|
||||
|
||||
@ -219,7 +218,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
},
|
||||
Cursor = new TestCursorContainer
|
||||
{
|
||||
State = providesUserCursor ? Visibility.Hidden : Visibility.Visible,
|
||||
State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -3,13 +3,12 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneLoadingAnimation : GridTestScene //todo: this should be an OsuTestScene
|
||||
public class TestSceneLoadingAnimation : OsuGridTestScene
|
||||
{
|
||||
public TestSceneLoadingAnimation()
|
||||
: base(2, 2)
|
||||
|
@ -23,8 +23,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
};
|
||||
Add(mc);
|
||||
|
||||
AddToggleStep(@"toggle visibility", state => mc.State = state ? Visibility.Visible : Visibility.Hidden);
|
||||
AddStep(@"show", () => mc.State = Visibility.Visible);
|
||||
AddToggleStep(@"toggle visibility", state => mc.State.Value = state ? Visibility.Visible : Visibility.Hidden);
|
||||
AddStep(@"show", () => mc.Show());
|
||||
AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state);
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
Content.Add(displayedCount);
|
||||
|
||||
void setState(Visibility state) => AddStep(state.ToString(), () => manager.State = state);
|
||||
void setState(Visibility state) => AddStep(state.ToString(), () => manager.State.Value = state);
|
||||
void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected);
|
||||
|
||||
manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; };
|
||||
|
@ -0,0 +1,72 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneOsuAnimatedButton : OsuGridTestScene
|
||||
{
|
||||
public TestSceneOsuAnimatedButton()
|
||||
: base(3, 2)
|
||||
{
|
||||
Cell(0).Add(new BaseContainer("relative sized")
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
});
|
||||
|
||||
Cell(1).Add(new BaseContainer("auto sized")
|
||||
{
|
||||
AutoSizeAxes = Axes.Both
|
||||
});
|
||||
|
||||
Cell(2).Add(new BaseContainer("relative Y auto X")
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X
|
||||
});
|
||||
|
||||
Cell(3).Add(new BaseContainer("relative X auto Y")
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
});
|
||||
|
||||
Cell(4).Add(new BaseContainer("fixed")
|
||||
{
|
||||
Size = new Vector2(100),
|
||||
});
|
||||
|
||||
Cell(5).Add(new BaseContainer("fixed")
|
||||
{
|
||||
Size = new Vector2(100, 50),
|
||||
});
|
||||
|
||||
AddToggleStep("toggle enabled", toggle =>
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
((BaseContainer)Cell(i).Child).Action = toggle ? () => { } : (Action)null;
|
||||
});
|
||||
}
|
||||
|
||||
public class BaseContainer : OsuAnimatedButton
|
||||
{
|
||||
public BaseContainer(string text)
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Add(new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = text
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneOsuHoverContainer : ManualInputManagerTestScene
|
||||
{
|
||||
private OsuHoverTestContainer hoverContainer;
|
||||
private Box colourContainer;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = hoverContainer = new OsuHoverTestContainer
|
||||
{
|
||||
Enabled = { Value = true },
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(100),
|
||||
Child = colourContainer = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
};
|
||||
|
||||
doMoveOut();
|
||||
});
|
||||
|
||||
[Description("Checks IsHovered property value on a container when it is hovered/unhovered.")]
|
||||
[TestCase(true, TestName = "Enabled_Check_IsHovered")]
|
||||
[TestCase(false, TestName = "Disabled_Check_IsHovered")]
|
||||
public void TestIsHoveredHasProperValue(bool isEnabled)
|
||||
{
|
||||
setContainerEnabledTo(isEnabled);
|
||||
|
||||
checkNotHovered();
|
||||
|
||||
moveToText();
|
||||
checkHovered();
|
||||
|
||||
moveOut();
|
||||
checkNotHovered();
|
||||
|
||||
moveToText();
|
||||
checkHovered();
|
||||
|
||||
moveOut();
|
||||
checkNotHovered();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Checks colour fading on an enabled container when it is hovered/unhovered.")]
|
||||
public void TestTransitionWhileEnabled()
|
||||
{
|
||||
enableContainer();
|
||||
|
||||
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
|
||||
moveToText();
|
||||
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
|
||||
|
||||
moveOut();
|
||||
waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
|
||||
moveToText();
|
||||
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
|
||||
|
||||
moveOut();
|
||||
waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Checks colour fading on a disabled container when it is hovered/unhovered.")]
|
||||
public void TestNoTransitionWhileDisabled()
|
||||
{
|
||||
disableContainer();
|
||||
|
||||
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
|
||||
moveToText();
|
||||
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
|
||||
moveOut();
|
||||
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
|
||||
moveToText();
|
||||
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
|
||||
moveOut();
|
||||
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Checks that when a disabled & hovered container gets enabled, colour fading happens")]
|
||||
public void TestBecomesEnabledTransition()
|
||||
{
|
||||
disableContainer();
|
||||
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
|
||||
moveToText();
|
||||
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
|
||||
enableContainer();
|
||||
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Checks that when an enabled & hovered container gets disabled, colour fading happens")]
|
||||
public void TestBecomesDisabledTransition()
|
||||
{
|
||||
enableContainer();
|
||||
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
|
||||
moveToText();
|
||||
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
|
||||
|
||||
disableContainer();
|
||||
waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Checks that when a hovered container gets enabled and disabled multiple times, colour fading happens")]
|
||||
public void TestDisabledChangesMultipleTimes()
|
||||
{
|
||||
enableContainer();
|
||||
checkColour(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
|
||||
moveToText();
|
||||
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
|
||||
|
||||
disableContainer();
|
||||
waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
|
||||
enableContainer();
|
||||
waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR);
|
||||
|
||||
disableContainer();
|
||||
waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR);
|
||||
}
|
||||
|
||||
private void enableContainer() => setContainerEnabledTo(true);
|
||||
|
||||
private void disableContainer() => setContainerEnabledTo(false);
|
||||
|
||||
private void setContainerEnabledTo(bool newValue)
|
||||
{
|
||||
string word = newValue ? "Enable" : "Disable";
|
||||
AddStep($"{word} container", () => hoverContainer.Enabled.Value = newValue);
|
||||
}
|
||||
|
||||
private void moveToText() => AddStep("Move mouse to text", () => InputManager.MoveMouseTo(hoverContainer));
|
||||
|
||||
private void moveOut() => AddStep("Move out", doMoveOut);
|
||||
|
||||
private void checkHovered() => AddAssert("Check hovered", () => hoverContainer.IsHovered);
|
||||
|
||||
private void checkNotHovered() => AddAssert("Check not hovered", () => !hoverContainer.IsHovered);
|
||||
|
||||
private void checkColour(ColourInfo expectedColour)
|
||||
=> AddAssert($"Check colour to be '{expectedColour}'", () => currentColour.Equals(expectedColour));
|
||||
|
||||
private void waitUntilColourIs(ColourInfo expectedColour)
|
||||
=> AddUntilStep($"Wait until hover colour is {expectedColour}", () => currentColour.Equals(expectedColour));
|
||||
|
||||
private ColourInfo currentColour => colourContainer.DrawColourInfo.Colour;
|
||||
|
||||
/// <summary>
|
||||
/// Moves the cursor to top left corner of the screen
|
||||
/// </summary>
|
||||
private void doMoveOut()
|
||||
=> InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.TopLeft.X, InputManager.ScreenSpaceDrawQuad.TopLeft.Y));
|
||||
|
||||
private sealed class OsuHoverTestContainer : OsuHoverContainer
|
||||
{
|
||||
public static readonly Color4 HOVER_COLOUR = Color4.Red;
|
||||
public static readonly Color4 IDLE_COLOUR = Color4.Green;
|
||||
|
||||
public OsuHoverTestContainer()
|
||||
{
|
||||
HoverColour = HOVER_COLOUR;
|
||||
IdleColour = IDLE_COLOUR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
var popup = new PopupDialog
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = Framework.Graphics.Containers.Visibility.Visible,
|
||||
State = { Value = Framework.Graphics.Containers.Visibility.Visible },
|
||||
Icon = FontAwesome.Solid.AssistiveListeningSystems,
|
||||
HeaderText = @"This is a test popup",
|
||||
BodyText = "I can say lots of stuff and even wrap my words!",
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
this.api = api;
|
||||
this.rulesets = rulesets;
|
||||
|
||||
testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu);
|
||||
testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result;
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
TestUpdateableBeatmapBackgroundSprite background = null;
|
||||
|
||||
AddStep("load null beatmap", () => Child = background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both });
|
||||
AddUntilStep("wait for load", () => background.ContentLoaded);
|
||||
AddUntilStep("content loaded", () => background.ContentLoaded);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -19,12 +20,14 @@ namespace osu.Game.Tests
|
||||
{
|
||||
private readonly ZipArchiveReader reader;
|
||||
private readonly Stream stream;
|
||||
private readonly ITrackStore trackStore;
|
||||
|
||||
public WaveformTestBeatmap()
|
||||
: base(new BeatmapInfo())
|
||||
public WaveformTestBeatmap(AudioManager audioManager)
|
||||
: base(new BeatmapInfo(), audioManager)
|
||||
{
|
||||
stream = TestResources.GetTestBeatmapStream();
|
||||
reader = new ZipArchiveReader(stream);
|
||||
trackStore = audioManager.GetTrackStore(reader);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
@ -32,17 +35,19 @@ namespace osu.Game.Tests
|
||||
base.Dispose();
|
||||
stream?.Dispose();
|
||||
reader?.Dispose();
|
||||
trackStore?.Dispose();
|
||||
}
|
||||
|
||||
protected override IBeatmap GetBeatmap() => createTestBeatmap();
|
||||
|
||||
protected override Texture GetBackground() => null;
|
||||
|
||||
protected override Waveform GetWaveform() => new Waveform(getAudioStream());
|
||||
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
||||
|
||||
protected override Track GetTrack() => new TrackBass(getAudioStream());
|
||||
protected override Track GetTrack() => trackStore.Get(firstAudioFile);
|
||||
|
||||
private string firstAudioFile => reader.Filenames.First(f => f.EndsWith(".mp3"));
|
||||
|
||||
private Stream getAudioStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".mp3")));
|
||||
private Stream getBeatmapStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")));
|
||||
|
||||
private Beatmap createTestBeatmap()
|
||||
|
@ -1,6 +1,10 @@
|
||||
// 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.IO;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
@ -12,27 +16,28 @@ using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// A central store for the retrieval of <see cref="PreviewTrack"/>s.
|
||||
/// </summary>
|
||||
public class PreviewTrackManager : Component
|
||||
{
|
||||
private readonly BindableDouble muteBindable = new BindableDouble();
|
||||
|
||||
private AudioManager audio;
|
||||
private TrackManager trackManager;
|
||||
private PreviewTrackStore trackStore;
|
||||
|
||||
private TrackManagerPreviewTrack current;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, FrameworkConfigManager config)
|
||||
{
|
||||
trackManager = new TrackManager(new OnlineStore());
|
||||
// this is a temporary solution to get around muting ourselves.
|
||||
// todo: update this once we have a BackgroundTrackManager or similar.
|
||||
trackStore = new PreviewTrackStore(new OnlineStore());
|
||||
|
||||
audio.AddItem(trackStore);
|
||||
trackStore.AddAdjustment(AdjustableProperty.Volume, audio.VolumeTrack);
|
||||
|
||||
this.audio = audio;
|
||||
audio.AddItem(trackManager);
|
||||
|
||||
config.BindWith(FrameworkSetting.VolumeMusic, trackManager.Volume);
|
||||
config.BindWith(FrameworkSetting.VolumeMusic, trackStore.Volume);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -42,19 +47,19 @@ namespace osu.Game.Audio
|
||||
/// <returns>The playable <see cref="PreviewTrack"/>.</returns>
|
||||
public PreviewTrack Get(BeatmapSetInfo beatmapSetInfo)
|
||||
{
|
||||
var track = CreatePreviewTrack(beatmapSetInfo, trackManager);
|
||||
var track = CreatePreviewTrack(beatmapSetInfo, trackStore);
|
||||
|
||||
track.Started += () =>
|
||||
{
|
||||
current?.Stop();
|
||||
current = track;
|
||||
audio.Track.AddAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||
audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||
};
|
||||
|
||||
track.Stopped += () =>
|
||||
{
|
||||
current = null;
|
||||
audio.Track.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||
audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||
};
|
||||
|
||||
return track;
|
||||
@ -81,16 +86,16 @@ namespace osu.Game.Audio
|
||||
/// <summary>
|
||||
/// Creates the <see cref="TrackManagerPreviewTrack"/>.
|
||||
/// </summary>
|
||||
protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager) => new TrackManagerPreviewTrack(beatmapSetInfo, trackManager);
|
||||
protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TrackManagerPreviewTrack(beatmapSetInfo, trackStore);
|
||||
|
||||
protected class TrackManagerPreviewTrack : PreviewTrack
|
||||
{
|
||||
public IPreviewTrackOwner Owner { get; private set; }
|
||||
|
||||
private readonly BeatmapSetInfo beatmapSetInfo;
|
||||
private readonly TrackManager trackManager;
|
||||
private readonly ITrackStore trackManager;
|
||||
|
||||
public TrackManagerPreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager)
|
||||
public TrackManagerPreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackManager)
|
||||
{
|
||||
this.beatmapSetInfo = beatmapSetInfo;
|
||||
this.trackManager = trackManager;
|
||||
@ -104,5 +109,46 @@ namespace osu.Game.Audio
|
||||
|
||||
protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo?.OnlineBeatmapSetID}.mp3");
|
||||
}
|
||||
|
||||
private class PreviewTrackStore : AudioCollectionManager<AdjustableAudioComponent>, ITrackStore
|
||||
{
|
||||
private readonly IResourceStore<byte[]> store;
|
||||
|
||||
internal PreviewTrackStore(IResourceStore<byte[]> store)
|
||||
{
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public Track GetVirtual(double length = double.PositiveInfinity)
|
||||
{
|
||||
if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}");
|
||||
|
||||
var track = new TrackVirtual(length);
|
||||
AddItem(track);
|
||||
return track;
|
||||
}
|
||||
|
||||
public Track Get(string name)
|
||||
{
|
||||
if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}");
|
||||
|
||||
if (string.IsNullOrEmpty(name)) return null;
|
||||
|
||||
var dataStream = store.GetStream(name);
|
||||
|
||||
if (dataStream == null)
|
||||
return null;
|
||||
|
||||
Track track = new TrackBass(dataStream);
|
||||
AddItem(track);
|
||||
return track;
|
||||
}
|
||||
|
||||
public Task<Track> GetAsync(string name) => Task.Run(() => Get(name));
|
||||
|
||||
public Stream GetStream(string name) => store.GetStream(name);
|
||||
|
||||
public IEnumerable<string> GetAvailableResources() => store.GetAvailableResources();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public List<ScoreInfo> Scores { get; set; }
|
||||
|
||||
public override string ToString() => $"{Metadata} [{Version}]";
|
||||
public override string ToString() => $"{Metadata} [{Version}]".Trim();
|
||||
|
||||
public bool Equals(BeatmapInfo other)
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using osu.Framework.Audio;
|
||||
@ -14,6 +15,7 @@ using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO.Archives;
|
||||
@ -72,6 +74,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>();
|
||||
|
||||
private readonly BeatmapUpdateQueue updateQueue;
|
||||
|
||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null,
|
||||
WorkingBeatmap defaultBeatmap = null)
|
||||
: base(storage, contextFactory, new BeatmapStore(contextFactory), host)
|
||||
@ -86,9 +90,11 @@ namespace osu.Game.Beatmaps
|
||||
beatmaps = (BeatmapStore)ModelStore;
|
||||
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
|
||||
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
|
||||
|
||||
updateQueue = new BeatmapUpdateQueue(api);
|
||||
}
|
||||
|
||||
protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive)
|
||||
protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (archive != null)
|
||||
beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
|
||||
@ -104,8 +110,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
validateOnlineIds(beatmapSet);
|
||||
|
||||
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
|
||||
fetchAndPopulateOnlineValues(b);
|
||||
return updateQueue.UpdateAsync(beatmapSet, cancellationToken);
|
||||
}
|
||||
|
||||
protected override void PreImport(BeatmapSetInfo beatmapSet)
|
||||
@ -122,7 +127,7 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
Delete(existingOnlineId);
|
||||
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
|
||||
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
|
||||
LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -181,10 +186,10 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
request.Success += filename =>
|
||||
{
|
||||
Task.Factory.StartNew(() =>
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
// This gets scheduled back to the update thread, but we want the import to run in the background.
|
||||
Import(downloadNotification, filename);
|
||||
await Import(downloadNotification, filename);
|
||||
currentDownloads.Remove(request);
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
};
|
||||
@ -322,6 +327,8 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>Results from the provided query.</returns>
|
||||
public IQueryable<BeatmapInfo> QueryBeatmaps(Expression<Func<BeatmapInfo, bool>> query) => beatmaps.Beatmaps.AsNoTracking().Where(query);
|
||||
|
||||
protected override string HumanisedModelName => "beatmap";
|
||||
|
||||
protected override BeatmapSetInfo CreateModel(ArchiveReader reader)
|
||||
{
|
||||
// let's make sure there are actually .osu files to import.
|
||||
@ -342,6 +349,7 @@ namespace osu.Game.Beatmaps
|
||||
OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID,
|
||||
Beatmaps = new List<BeatmapInfo>(),
|
||||
Metadata = beatmap.Metadata,
|
||||
DateAdded = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
@ -380,47 +388,6 @@ namespace osu.Game.Beatmaps
|
||||
return beatmapInfos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query the API to populate missing values like OnlineBeatmapID / OnlineBeatmapSetID or (Rank-)Status.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap to populate.</param>
|
||||
/// <param name="force">Whether to re-query if the provided beatmap already has populated values.</param>
|
||||
/// <returns>True if population was successful.</returns>
|
||||
private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, bool force = false)
|
||||
{
|
||||
if (api?.State != APIState.Online)
|
||||
return false;
|
||||
|
||||
if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null
|
||||
&& beatmap.Status != BeatmapSetOnlineStatus.None && beatmap.BeatmapSet.Status != BeatmapSetOnlineStatus.None)
|
||||
return true;
|
||||
|
||||
Logger.Log("Attempting online lookup for the missing values...", LoggingTarget.Database);
|
||||
|
||||
try
|
||||
{
|
||||
var req = new GetBeatmapRequest(beatmap);
|
||||
|
||||
req.Perform(api);
|
||||
|
||||
var res = req.Result;
|
||||
|
||||
Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database);
|
||||
|
||||
beatmap.Status = res.Status;
|
||||
beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
|
||||
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
|
||||
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"Failed ({e})", LoggingTarget.Database);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
||||
/// </summary>
|
||||
@ -429,7 +396,7 @@ namespace osu.Game.Beatmaps
|
||||
private readonly IBeatmap beatmap;
|
||||
|
||||
public DummyConversionBeatmap(IBeatmap beatmap)
|
||||
: base(beatmap.BeatmapInfo)
|
||||
: base(beatmap.BeatmapInfo, null)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
}
|
||||
@ -454,5 +421,55 @@ namespace osu.Game.Beatmaps
|
||||
public override bool IsImportant => false;
|
||||
}
|
||||
}
|
||||
|
||||
private class BeatmapUpdateQueue
|
||||
{
|
||||
private readonly IAPIProvider api;
|
||||
|
||||
private const int update_queue_request_concurrency = 4;
|
||||
|
||||
private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(update_queue_request_concurrency, nameof(BeatmapUpdateQueue));
|
||||
|
||||
public BeatmapUpdateQueue(IAPIProvider api)
|
||||
{
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken)
|
||||
{
|
||||
if (api?.State != APIState.Online)
|
||||
return Task.CompletedTask;
|
||||
|
||||
LogForModel(beatmapSet, "Performing online lookups...");
|
||||
return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray());
|
||||
}
|
||||
|
||||
// todo: expose this when we need to do individual difficulty lookups.
|
||||
protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken)
|
||||
=> Task.Factory.StartNew(() => update(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler, updateScheduler);
|
||||
|
||||
private void update(BeatmapSetInfo set, BeatmapInfo beatmap)
|
||||
{
|
||||
if (api?.State != APIState.Online)
|
||||
return;
|
||||
|
||||
var req = new GetBeatmapRequest(beatmap);
|
||||
|
||||
req.Success += res =>
|
||||
{
|
||||
LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
|
||||
|
||||
beatmap.Status = res.Status;
|
||||
beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
|
||||
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
|
||||
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
|
||||
};
|
||||
|
||||
req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap}", e); };
|
||||
|
||||
// intentionally blocking to limit web request concurrency
|
||||
req.Perform(api);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,14 +20,12 @@ namespace osu.Game.Beatmaps
|
||||
protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
|
||||
{
|
||||
private readonly IResourceStore<byte[]> store;
|
||||
private readonly AudioManager audioManager;
|
||||
|
||||
public BeatmapManagerWorkingBeatmap(IResourceStore<byte[]> store, TextureStore textureStore, BeatmapInfo beatmapInfo, AudioManager audioManager)
|
||||
: base(beatmapInfo)
|
||||
: base(beatmapInfo, audioManager)
|
||||
{
|
||||
this.store = store;
|
||||
this.textureStore = textureStore;
|
||||
this.audioManager = audioManager;
|
||||
}
|
||||
|
||||
protected override IBeatmap GetBeatmap()
|
||||
@ -47,6 +45,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private TextureStore textureStore;
|
||||
|
||||
private ITrackStore trackStore;
|
||||
|
||||
protected override bool BackgroundStillValid(Texture b) => false; // bypass lazy logic. we want to return a new background each time for refcounting purposes.
|
||||
|
||||
protected override Texture GetBackground()
|
||||
@ -68,8 +68,7 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
try
|
||||
{
|
||||
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
|
||||
return trackData == null ? null : new TrackBass(trackData);
|
||||
return (trackStore ?? (trackStore = AudioManager.GetTrackStore(store))).Get(getPathForFile(Metadata.AudioFile));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -77,6 +76,14 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
public override void RecycleTrack()
|
||||
{
|
||||
base.RecycleTrack();
|
||||
|
||||
trackStore?.Dispose();
|
||||
trackStore = null;
|
||||
}
|
||||
|
||||
public override void TransferTo(WorkingBeatmap other)
|
||||
{
|
||||
base.TransferTo(other);
|
||||
@ -135,7 +142,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
try
|
||||
{
|
||||
skin = new LegacyBeatmapSkin(BeatmapInfo, store, audioManager);
|
||||
skin = new LegacyBeatmapSkin(BeatmapInfo, store, AudioManager);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
@ -20,6 +21,8 @@ namespace osu.Game.Beatmaps
|
||||
set => onlineBeatmapSetID = value > 0 ? value : null;
|
||||
}
|
||||
|
||||
public DateTimeOffset DateAdded { get; set; }
|
||||
|
||||
public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None;
|
||||
|
||||
public BeatmapMetadata Metadata { get; set; }
|
||||
|
@ -1,11 +1,7 @@
|
||||
// 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.Diagnostics;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
@ -16,32 +12,15 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public abstract class BindableBeatmap : NonNullableBindable<WorkingBeatmap>
|
||||
{
|
||||
private AudioManager audioManager;
|
||||
private WorkingBeatmap lastBeatmap;
|
||||
|
||||
protected BindableBeatmap(WorkingBeatmap defaultValue)
|
||||
: base(defaultValue)
|
||||
{
|
||||
BindValueChanged(b => updateAudioTrack(b.NewValue), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an <see cref="AudioManager"/> for <see cref="Track"/>s to be added to.
|
||||
/// </summary>
|
||||
/// <param name="audioManager">The <see cref="AudioManager"/> to register.</param>
|
||||
protected void RegisterAudioManager([NotNull] AudioManager audioManager)
|
||||
{
|
||||
if (this.audioManager != null) throw new InvalidOperationException($"Cannot register multiple {nameof(AudioManager)}s.");
|
||||
|
||||
this.audioManager = audioManager;
|
||||
|
||||
ValueChanged += b => registerAudioTrack(b.NewValue);
|
||||
|
||||
// If the track has changed prior to this being called, let's register it
|
||||
if (Value != Default)
|
||||
registerAudioTrack(Value);
|
||||
}
|
||||
|
||||
private void registerAudioTrack(WorkingBeatmap beatmap)
|
||||
private void updateAudioTrack(WorkingBeatmap beatmap)
|
||||
{
|
||||
var trackLoaded = lastBeatmap?.TrackLoaded ?? false;
|
||||
|
||||
@ -55,18 +34,9 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
lastBeatmap.RecycleTrack();
|
||||
}
|
||||
|
||||
audioManager.Track.AddItem(beatmap.Track);
|
||||
}
|
||||
|
||||
lastBeatmap = beatmap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <see cref="BindableBeatmap"/> instance weakly bound to this <see cref="BindableBeatmap"/>.
|
||||
/// If you are further binding to events of the retrieved <see cref="BindableBeatmap"/>, ensure a local reference is held.
|
||||
/// </summary>
|
||||
[NotNull]
|
||||
public new abstract BindableBeatmap GetBoundCopy();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -31,24 +32,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// </summary>
|
||||
protected virtual double UnloadDelay => 10000;
|
||||
|
||||
private BeatmapInfo lastModel;
|
||||
private bool firstLoad = true;
|
||||
|
||||
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Drawable content, double timeBeforeLoad)
|
||||
{
|
||||
return new DelayedLoadUnloadWrapper(() =>
|
||||
{
|
||||
// If DelayedLoadUnloadWrapper is attempting to RELOAD the same content (Beatmap), that means that it was
|
||||
// previously UNLOADED and thus its children have been disposed of, so we need to recreate them here.
|
||||
if (!firstLoad && lastModel == Beatmap.Value)
|
||||
return CreateDrawable(Beatmap.Value);
|
||||
|
||||
// If the model has changed since the previous unload (or if there was no load), then we can safely use the given content
|
||||
lastModel = Beatmap.Value;
|
||||
firstLoad = false;
|
||||
return content;
|
||||
}, timeBeforeLoad, UnloadDelay);
|
||||
}
|
||||
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func<Drawable> createContentFunc, double timeBeforeLoad)
|
||||
=> new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay);
|
||||
|
||||
protected override Drawable CreateDrawable(BeatmapInfo model)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
@ -16,9 +17,9 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
public class DummyWorkingBeatmap : WorkingBeatmap
|
||||
{
|
||||
private readonly OsuGameBase game;
|
||||
private readonly TextureStore textures;
|
||||
|
||||
public DummyWorkingBeatmap(OsuGameBase game = null)
|
||||
public DummyWorkingBeatmap(AudioManager audio, TextureStore textures)
|
||||
: base(new BeatmapInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
@ -34,16 +35,16 @@ namespace osu.Game.Beatmaps
|
||||
OverallDifficulty = 0,
|
||||
},
|
||||
Ruleset = new DummyRulesetInfo()
|
||||
})
|
||||
}, audio)
|
||||
{
|
||||
this.game = game;
|
||||
this.textures = textures;
|
||||
}
|
||||
|
||||
protected override IBeatmap GetBeatmap() => new Beatmap();
|
||||
|
||||
protected override Texture GetBackground() => game?.Textures.Get(@"Backgrounds/bg4");
|
||||
protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4");
|
||||
|
||||
protected override Track GetTrack() => new TrackVirtual { Length = 1000 };
|
||||
protected override Track GetTrack() => GetVirtualTrack();
|
||||
|
||||
private class DummyRulesetInfo : RulesetInfo
|
||||
{
|
||||
|
@ -11,15 +11,17 @@ using osu.Framework.IO.File;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
public abstract partial class WorkingBeatmap : IDisposable
|
||||
public abstract class WorkingBeatmap : IDisposable
|
||||
{
|
||||
public readonly BeatmapInfo BeatmapInfo;
|
||||
|
||||
@ -27,8 +29,11 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public readonly BeatmapMetadata Metadata;
|
||||
|
||||
protected WorkingBeatmap(BeatmapInfo beatmapInfo)
|
||||
protected AudioManager AudioManager { get; }
|
||||
|
||||
protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager)
|
||||
{
|
||||
AudioManager = audioManager;
|
||||
BeatmapInfo = beatmapInfo;
|
||||
BeatmapSetInfo = beatmapInfo.BeatmapSet;
|
||||
Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
|
||||
@ -46,13 +51,39 @@ namespace osu.Game.Beatmaps
|
||||
return b;
|
||||
});
|
||||
|
||||
track = new RecyclableLazy<Track>(() => GetTrack() ?? new VirtualBeatmapTrack(Beatmap));
|
||||
track = new RecyclableLazy<Track>(() => GetTrack() ?? GetVirtualTrack());
|
||||
background = new RecyclableLazy<Texture>(GetBackground, BackgroundStillValid);
|
||||
waveform = new RecyclableLazy<Waveform>(GetWaveform);
|
||||
storyboard = new RecyclableLazy<Storyboard>(GetStoryboard);
|
||||
skin = new RecyclableLazy<Skin>(GetSkin);
|
||||
}
|
||||
|
||||
protected virtual Track GetVirtualTrack()
|
||||
{
|
||||
const double excess_length = 1000;
|
||||
|
||||
var lastObject = Beatmap.HitObjects.LastOrDefault();
|
||||
|
||||
double length;
|
||||
|
||||
switch (lastObject)
|
||||
{
|
||||
case null:
|
||||
length = excess_length;
|
||||
break;
|
||||
|
||||
case IHasEndTime endTime:
|
||||
length = endTime.EndTime + excess_length;
|
||||
break;
|
||||
|
||||
default:
|
||||
length = lastObject.StartTime + excess_length;
|
||||
break;
|
||||
}
|
||||
|
||||
return AudioManager.Tracks.GetVirtual(length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the <see cref="Beatmaps.Beatmap"/>.
|
||||
/// </summary>
|
||||
@ -150,6 +181,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public bool SkinLoaded => skin.IsResultAvailable;
|
||||
public Skin Skin => skin.Value;
|
||||
|
||||
protected virtual Skin GetSkin() => new DefaultSkin();
|
||||
private readonly RecyclableLazy<Skin> skin;
|
||||
|
||||
@ -175,7 +207,7 @@ namespace osu.Game.Beatmaps
|
||||
/// Eagerly dispose of the audio track associated with this <see cref="WorkingBeatmap"/> (if any).
|
||||
/// Accessing track again will load a fresh instance.
|
||||
/// </summary>
|
||||
public void RecycleTrack() => track.Recycle();
|
||||
public virtual void RecycleTrack() => track.Recycle();
|
||||
|
||||
public class RecyclableLazy<T>
|
||||
{
|
||||
|
@ -1,41 +0,0 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
public partial class WorkingBeatmap
|
||||
{
|
||||
/// <summary>
|
||||
/// A type of <see cref="TrackVirtual"/> which provides a valid length based on the <see cref="HitObject"/>s of an <see cref="IBeatmap"/>.
|
||||
/// </summary>
|
||||
protected class VirtualBeatmapTrack : TrackVirtual
|
||||
{
|
||||
private const double excess_length = 1000;
|
||||
|
||||
public VirtualBeatmapTrack(IBeatmap beatmap)
|
||||
{
|
||||
var lastObject = beatmap.HitObjects.LastOrDefault();
|
||||
|
||||
switch (lastObject)
|
||||
{
|
||||
case null:
|
||||
Length = excess_length;
|
||||
break;
|
||||
|
||||
case IHasEndTime endTime:
|
||||
Length = endTime.EndTime + excess_length;
|
||||
break;
|
||||
|
||||
default:
|
||||
Length = lastObject.StartTime + excess_length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
@ -19,6 +20,8 @@ namespace osu.Game.Configuration
|
||||
|
||||
private readonly RulesetInfo ruleset;
|
||||
|
||||
private readonly bool legacySettingsExist;
|
||||
|
||||
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null)
|
||||
{
|
||||
this.settings = settings;
|
||||
@ -26,6 +29,7 @@ namespace osu.Game.Configuration
|
||||
this.variant = variant;
|
||||
|
||||
databasedSettings = settings.Query(ruleset?.ID, variant);
|
||||
legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _));
|
||||
|
||||
InitialiseDefaults();
|
||||
}
|
||||
@ -43,7 +47,18 @@ namespace osu.Game.Configuration
|
||||
{
|
||||
base.AddBindable(lookup, bindable);
|
||||
|
||||
var setting = databasedSettings.Find(s => (int)s.Key == (int)(object)lookup);
|
||||
if (legacySettingsExist)
|
||||
{
|
||||
var legacySetting = databasedSettings.Find(s => s.Key == ((int)(object)lookup).ToString());
|
||||
|
||||
if (legacySetting != null)
|
||||
{
|
||||
bindable.Parse(legacySetting.Value);
|
||||
settings.Delete(legacySetting);
|
||||
}
|
||||
}
|
||||
|
||||
var setting = databasedSettings.Find(s => s.Key == lookup.ToString());
|
||||
|
||||
if (setting != null)
|
||||
{
|
||||
@ -53,7 +68,7 @@ namespace osu.Game.Configuration
|
||||
{
|
||||
settings.Update(setting = new DatabasedSetting
|
||||
{
|
||||
Key = lookup,
|
||||
Key = lookup.ToString(),
|
||||
Value = bindable.Value,
|
||||
RulesetID = ruleset?.ID,
|
||||
Variant = variant,
|
||||
|
@ -1,4 +1,4 @@
|
||||
// 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.
|
||||
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
@ -15,12 +15,10 @@ namespace osu.Game.Configuration
|
||||
|
||||
public int? Variant { get; set; }
|
||||
|
||||
public int? SkinInfoID { get; set; }
|
||||
|
||||
[Column("Key")]
|
||||
public int IntKey
|
||||
{
|
||||
get => (int)Key;
|
||||
private set => Key = value;
|
||||
}
|
||||
public string Key { get; set; }
|
||||
|
||||
[Column("Value")]
|
||||
public string StringValue
|
||||
@ -29,10 +27,9 @@ namespace osu.Game.Configuration
|
||||
set => Value = value;
|
||||
}
|
||||
|
||||
public object Key;
|
||||
public object Value;
|
||||
|
||||
public DatabasedSetting(object key, object value)
|
||||
public DatabasedSetting(string key, object value)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
|
@ -37,5 +37,11 @@ namespace osu.Game.Configuration
|
||||
|
||||
SettingChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void Delete(DatabasedSetting setting)
|
||||
{
|
||||
using (var usage = ContextFactory.GetForWrite())
|
||||
usage.Context.Remove(setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -13,6 +14,7 @@ using osu.Framework.Extensions;
|
||||
using osu.Framework.IO.File;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.IPC;
|
||||
@ -29,7 +31,7 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model type.</typeparam>
|
||||
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
|
||||
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles
|
||||
public abstract class ArchiveModelManager<TModel, TFileModel> : ArchiveModelManager, ICanAcceptFiles
|
||||
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
|
||||
where TFileModel : INamedFileInfo, new()
|
||||
{
|
||||
@ -130,56 +132,50 @@ namespace osu.Game.Database
|
||||
/// This will post notifications tracking progress.
|
||||
/// </summary>
|
||||
/// <param name="paths">One or more archive locations on disk.</param>
|
||||
public void Import(params string[] paths)
|
||||
public Task Import(params string[] paths)
|
||||
{
|
||||
var notification = new ProgressNotification { State = ProgressNotificationState.Active };
|
||||
|
||||
PostNotification?.Invoke(notification);
|
||||
Import(notification, paths);
|
||||
|
||||
return Import(notification, paths);
|
||||
}
|
||||
|
||||
protected void Import(ProgressNotification notification, params string[] paths)
|
||||
protected async Task Import(ProgressNotification notification, params string[] paths)
|
||||
{
|
||||
notification.Progress = 0;
|
||||
notification.Text = "Import is initialising...";
|
||||
|
||||
var term = $"{typeof(TModel).Name.Replace("Info", "").ToLower()}";
|
||||
|
||||
List<TModel> imported = new List<TModel>();
|
||||
|
||||
int current = 0;
|
||||
|
||||
foreach (string path in paths)
|
||||
var imported = new List<TModel>();
|
||||
|
||||
await Task.WhenAll(paths.Select(async path =>
|
||||
{
|
||||
if (notification.State == ProgressNotificationState.Cancelled)
|
||||
// user requested abort
|
||||
return;
|
||||
notification.CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
var text = "Importing ";
|
||||
var model = await Import(path, notification.CancellationToken);
|
||||
|
||||
if (path.Length > 1)
|
||||
text += $"{++current} of {paths.Length} {term}s..";
|
||||
else
|
||||
text += $"{term}..";
|
||||
lock (imported)
|
||||
{
|
||||
imported.Add(model);
|
||||
current++;
|
||||
|
||||
// only show the filename if it isn't a temporary one (as those look ugly).
|
||||
if (!path.Contains(Path.GetTempPath()))
|
||||
text += $"\n{Path.GetFileName(path)}";
|
||||
|
||||
notification.Text = text;
|
||||
|
||||
imported.Add(Import(path));
|
||||
|
||||
notification.Progress = (float)current / paths.Length;
|
||||
notification.Text = $"Imported {current} of {paths.Length} {HumanisedModelName}s";
|
||||
notification.Progress = (float)current / paths.Length;
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e = e.InnerException ?? e;
|
||||
Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})");
|
||||
Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})", LoggingTarget.Database);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
if (imported.Count == 0)
|
||||
{
|
||||
@ -190,7 +186,7 @@ namespace osu.Game.Database
|
||||
{
|
||||
notification.CompletionText = imported.Count == 1
|
||||
? $"Imported {imported.First()}!"
|
||||
: $"Imported {current} {term}s!";
|
||||
: $"Imported {current} {HumanisedModelName}s!";
|
||||
|
||||
if (imported.Count > 0 && PresentImport != null)
|
||||
{
|
||||
@ -210,12 +206,15 @@ namespace osu.Game.Database
|
||||
/// Import one <see cref="TModel"/> from the filesystem and delete the file on success.
|
||||
/// </summary>
|
||||
/// <param name="path">The archive location on disk.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
/// <returns>The imported model, if successful.</returns>
|
||||
public TModel Import(string path)
|
||||
public async Task<TModel> Import(string path, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
TModel import;
|
||||
using (ArchiveReader reader = getReaderFrom(path))
|
||||
import = Import(reader);
|
||||
import = await Import(reader, cancellationToken);
|
||||
|
||||
// We may or may not want to delete the file depending on where it is stored.
|
||||
// e.g. reconstructing/repairing database with items from default storage.
|
||||
@ -228,7 +227,7 @@ namespace osu.Game.Database
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})");
|
||||
LogForModel(import, $@"Could not delete original file after import ({Path.GetFileName(path)})", e);
|
||||
}
|
||||
|
||||
return import;
|
||||
@ -243,23 +242,32 @@ namespace osu.Game.Database
|
||||
/// Import an item from an <see cref="ArchiveReader"/>.
|
||||
/// </summary>
|
||||
/// <param name="archive">The archive to be imported.</param>
|
||||
public TModel Import(ArchiveReader archive)
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
public Task<TModel> Import(ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
TModel model = null;
|
||||
|
||||
try
|
||||
{
|
||||
var model = CreateModel(archive);
|
||||
model = CreateModel(archive);
|
||||
|
||||
if (model == null) return null;
|
||||
|
||||
model.Hash = computeHash(archive);
|
||||
|
||||
return Import(model, archive);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, $"Model creation of {archive.Name} failed.", LoggingTarget.Database);
|
||||
LogForModel(model, $"Model creation of {archive.Name} failed.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return Import(model, archive, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -269,6 +277,16 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
protected abstract string[] HashableFileTypes { get; }
|
||||
|
||||
protected static void LogForModel(TModel model, string message, Exception e = null)
|
||||
{
|
||||
string prefix = $"[{(model?.Hash ?? "?????").Substring(0, 5)}]";
|
||||
|
||||
if (e != null)
|
||||
Logger.Error(e, $"{prefix} {message}", LoggingTarget.Database);
|
||||
else
|
||||
Logger.Log($"{prefix} {message}", LoggingTarget.Database);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a SHA-2 hash from the provided archive based on file content of all files matching <see cref="HashableFileTypes"/>.
|
||||
/// </summary>
|
||||
@ -288,13 +306,30 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
/// <param name="item">The model to be imported.</param>
|
||||
/// <param name="archive">An optional archive to use for model population.</param>
|
||||
public TModel Import(TModel item, ArchiveReader archive = null)
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
public async Task<TModel> Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
delayEvents();
|
||||
|
||||
void rollback()
|
||||
{
|
||||
if (!Delete(item))
|
||||
{
|
||||
// We may have not yet added the model to the underlying table, but should still clean up files.
|
||||
LogForModel(item, "Dereferencing files for incomplete import.");
|
||||
Files.Dereference(item.Files.Select(f => f.FileInfo).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Log($"Importing {item}...", LoggingTarget.Database);
|
||||
LogForModel(item, "Beginning import...");
|
||||
|
||||
item.Files = archive != null ? createFileInfos(archive, Files) : new List<TFileModel>();
|
||||
|
||||
await Populate(item, archive, cancellationToken);
|
||||
|
||||
using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes.
|
||||
{
|
||||
@ -302,11 +337,6 @@ namespace osu.Game.Database
|
||||
{
|
||||
if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}");
|
||||
|
||||
if (archive != null)
|
||||
item.Files = createFileInfos(archive, Files);
|
||||
|
||||
Populate(item, archive);
|
||||
|
||||
var existing = CheckForExisting(item);
|
||||
|
||||
if (existing != null)
|
||||
@ -314,15 +344,17 @@ namespace osu.Game.Database
|
||||
if (CanUndelete(existing, item))
|
||||
{
|
||||
Undelete(existing);
|
||||
Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database);
|
||||
LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
|
||||
handleEvent(() => ItemAdded?.Invoke(existing, true));
|
||||
|
||||
// existing item will be used; rollback new import and exit early.
|
||||
rollback();
|
||||
flushEvents(true);
|
||||
return existing;
|
||||
}
|
||||
else
|
||||
{
|
||||
Delete(existing);
|
||||
ModelStore.PurgeDeletable(s => s.ID == existing.ID);
|
||||
}
|
||||
|
||||
Delete(existing);
|
||||
ModelStore.PurgeDeletable(s => s.ID == existing.ID);
|
||||
}
|
||||
|
||||
PreImport(item);
|
||||
@ -337,21 +369,21 @@ namespace osu.Game.Database
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Log($"Import of {item} successfully completed!", LoggingTarget.Database);
|
||||
LogForModel(item, "Import successfully completed!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, $"Import of {item} failed and has been rolled back.", LoggingTarget.Database);
|
||||
item = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// we only want to flush events after we've confirmed the write context didn't have any errors.
|
||||
flushEvents(item != null);
|
||||
if (!(e is TaskCanceledException))
|
||||
LogForModel(item, "Database import or population failed and has been rolled back.", e);
|
||||
|
||||
rollback();
|
||||
flushEvents(false);
|
||||
throw;
|
||||
}
|
||||
|
||||
flushEvents(true);
|
||||
return item;
|
||||
}
|
||||
}, cancellationToken, TaskCreationOptions.HideScheduler, IMPORT_SCHEDULER).Unwrap();
|
||||
|
||||
/// <summary>
|
||||
/// Perform an update of the specified item.
|
||||
@ -392,7 +424,8 @@ namespace osu.Game.Database
|
||||
var notification = new ProgressNotification
|
||||
{
|
||||
Progress = 0,
|
||||
CompletionText = $"Deleted all {typeof(TModel).Name.Replace("Info", "").ToLower()}s!",
|
||||
Text = $"Preparing to delete all {HumanisedModelName}s...",
|
||||
CompletionText = $"Deleted all {HumanisedModelName}s!",
|
||||
State = ProgressNotificationState.Active,
|
||||
};
|
||||
|
||||
@ -409,7 +442,7 @@ namespace osu.Game.Database
|
||||
// user requested abort
|
||||
return;
|
||||
|
||||
notification.Text = $"Deleting ({++i} of {items.Count})";
|
||||
notification.Text = $"Deleting {HumanisedModelName}s ({++i} of {items.Count})";
|
||||
|
||||
Delete(b);
|
||||
|
||||
@ -533,7 +566,7 @@ namespace osu.Game.Database
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return Task.Factory.StartNew(() => Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray()), TaskCreationOptions.LongRunning);
|
||||
return Task.Run(async () => await Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray()));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -552,9 +585,8 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
/// <param name="model">The model to populate.</param>
|
||||
/// <param name="archive">The archive to use as a reference for population. May be null.</param>
|
||||
protected virtual void Populate(TModel model, [CanBeNull] ArchiveReader archive)
|
||||
{
|
||||
}
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
protected virtual Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Perform any final actions before the import to database executes.
|
||||
@ -582,6 +614,8 @@ namespace osu.Game.Database
|
||||
|
||||
private DbSet<TModel> queryModel() => ContextFactory.Get().Set<TModel>();
|
||||
|
||||
protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace("Info", "").ToLower()}";
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="ArchiveReader"/> from a valid storage path.
|
||||
/// </summary>
|
||||
@ -599,4 +633,18 @@ namespace osu.Game.Database
|
||||
throw new InvalidFormatException($"{path} is not a valid archive");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ArchiveModelManager
|
||||
{
|
||||
private const int import_queue_request_concurrency = 1;
|
||||
|
||||
/// <summary>
|
||||
/// A singleton scheduler shared by all <see cref="ArchiveModelManager{TModel,TFileModel}"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly.
|
||||
/// It is mainly being used as a queue mechanism for large imports.
|
||||
/// </remarks>
|
||||
protected static readonly ThreadedTaskScheduler IMPORT_SCHEDULER = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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.Threading.Tasks;
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
/// <summary>
|
||||
@ -12,7 +14,7 @@ namespace osu.Game.Database
|
||||
/// Import the specified paths.
|
||||
/// </summary>
|
||||
/// <param name="paths">The files which should be imported.</param>
|
||||
void Import(params string[] paths);
|
||||
Task Import(params string[] paths);
|
||||
|
||||
/// <summary>
|
||||
/// An array of accepted file extensions (in the standard format of ".abc").
|
||||
|
@ -214,7 +214,6 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
base.Draw(vertexAction);
|
||||
|
||||
shader.Bind();
|
||||
texture.TextureGL.Bind();
|
||||
|
||||
Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy;
|
||||
|
||||
@ -231,7 +230,8 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
ColourInfo colourInfo = DrawColourInfo.Colour;
|
||||
colourInfo.ApplyChild(particle.Colour);
|
||||
|
||||
texture.DrawTriangle(
|
||||
DrawTriangle(
|
||||
texture,
|
||||
triangle,
|
||||
colourInfo,
|
||||
null,
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
protected void BeginConfirm()
|
||||
{
|
||||
if (confirming || !AllowMultipleFires && fired) return;
|
||||
if (confirming || (!AllowMultipleFires && fired)) return;
|
||||
|
||||
confirming = true;
|
||||
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
if (AutoSizeAxes != Axes.None)
|
||||
{
|
||||
content.RelativeSizeAxes = RelativeSizeAxes;
|
||||
content.RelativeSizeAxes = (Axes.Both & ~AutoSizeAxes);
|
||||
content.AutoSizeAxes = AutoSizeAxes;
|
||||
}
|
||||
|
||||
|
@ -51,10 +51,10 @@ namespace osu.Game.Graphics.Containers
|
||||
if (osuGame != null)
|
||||
OverlayActivationMode.BindTo(osuGame.OverlayActivationMode);
|
||||
|
||||
samplePopIn = audio.Sample.Get(@"UI/overlay-pop-in");
|
||||
samplePopOut = audio.Sample.Get(@"UI/overlay-pop-out");
|
||||
samplePopIn = audio.Samples.Get(@"UI/overlay-pop-in");
|
||||
samplePopOut = audio.Samples.Get(@"UI/overlay-pop-out");
|
||||
|
||||
StateChanged += onStateChanged;
|
||||
State.ValueChanged += onStateChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -70,7 +70,7 @@ namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
|
||||
{
|
||||
State = Visibility.Hidden;
|
||||
Hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ namespace osu.Game.Graphics.Containers
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.Back:
|
||||
State = Visibility.Hidden;
|
||||
Hide();
|
||||
return true;
|
||||
|
||||
case GlobalAction.Select:
|
||||
@ -94,9 +94,9 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
public bool OnReleased(GlobalAction action) => false;
|
||||
|
||||
private void onStateChanged(Visibility visibility)
|
||||
private void onStateChanged(ValueChangedEvent<Visibility> state)
|
||||
{
|
||||
switch (visibility)
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case Visibility.Visible:
|
||||
if (OverlayActivationMode.Value != OverlayActivation.Disabled)
|
||||
@ -105,7 +105,7 @@ namespace osu.Game.Graphics.Containers
|
||||
if (BlockScreenWideMouse && DimMainContent) osuGame?.AddBlockingOverlay(this);
|
||||
}
|
||||
else
|
||||
State = Visibility.Hidden;
|
||||
Hide();
|
||||
|
||||
break;
|
||||
|
||||
|
@ -24,7 +24,13 @@ namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
Enabled.ValueChanged += e =>
|
||||
{
|
||||
if (!e.NewValue) unhover();
|
||||
if (isHovered)
|
||||
{
|
||||
if (e.NewValue)
|
||||
fadeIn();
|
||||
else
|
||||
fadeOut();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -32,27 +38,28 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
if (isHovered)
|
||||
return false;
|
||||
|
||||
isHovered = true;
|
||||
|
||||
if (!Enabled.Value)
|
||||
return false;
|
||||
|
||||
EffectTargets.ForEach(d => d.FadeColour(HoverColour, FADE_DURATION, Easing.OutQuint));
|
||||
isHovered = true;
|
||||
fadeIn();
|
||||
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
unhover();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private void unhover()
|
||||
{
|
||||
if (!isHovered) return;
|
||||
if (!isHovered)
|
||||
return;
|
||||
|
||||
isHovered = false;
|
||||
EffectTargets.ForEach(d => d.FadeColour(IdleColour, FADE_DURATION, Easing.OutQuint));
|
||||
fadeOut();
|
||||
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -67,5 +74,9 @@ namespace osu.Game.Graphics.Containers
|
||||
base.LoadComplete();
|
||||
EffectTargets.ForEach(d => d.FadeColour(IdleColour));
|
||||
}
|
||||
|
||||
private void fadeIn() => EffectTargets.ForEach(d => d.FadeColour(HoverColour, FADE_DURATION, Easing.OutQuint));
|
||||
|
||||
private void fadeOut() => EffectTargets.ForEach(d => d.FadeColour(IdleColour, FADE_DURATION, Easing.OutQuint));
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ namespace osu.Game.Graphics.Containers
|
||||
protected override void PopIn()
|
||||
{
|
||||
foreach (var w in wavesContainer.Children)
|
||||
w.State = Visibility.Visible;
|
||||
w.Show();
|
||||
|
||||
this.FadeIn(100, Easing.OutQuint);
|
||||
contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint);
|
||||
@ -117,7 +117,7 @@ namespace osu.Game.Graphics.Containers
|
||||
contentContainer.MoveToY(DrawHeight * 2f, DISAPPEAR_DURATION, Easing.In);
|
||||
|
||||
foreach (var w in wavesContainer.Children)
|
||||
w.State = Visibility.Hidden;
|
||||
w.Hide();
|
||||
|
||||
this.FadeOut(DISAPPEAR_DURATION, Easing.InQuint);
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Graphics.Cursor
|
||||
{
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
Cursor = new MenuCursor { State = Visibility.Hidden },
|
||||
Cursor = new MenuCursor { State = { Value = Visibility.Hidden } },
|
||||
content = new Container { RelativeSizeAxes = Axes.Both }
|
||||
});
|
||||
}
|
||||
|
@ -40,8 +40,10 @@ namespace osu.Game.Graphics
|
||||
// See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less
|
||||
public readonly Color4 PurpleLighter = FromHex(@"eeeeff");
|
||||
public readonly Color4 PurpleLight = FromHex(@"aa88ff");
|
||||
public readonly Color4 PurpleLightAlternative = FromHex(@"cba4da");
|
||||
public readonly Color4 Purple = FromHex(@"8866ee");
|
||||
public readonly Color4 PurpleDark = FromHex(@"6644cc");
|
||||
public readonly Color4 PurpleDarkAlternative = FromHex(@"312436");
|
||||
public readonly Color4 PurpleDarker = FromHex(@"441188");
|
||||
|
||||
public readonly Color4 PinkLighter = FromHex(@"ffddee");
|
||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Graphics
|
||||
screenshotFormat = config.GetBindable<ScreenshotFormat>(OsuSetting.ScreenshotFormat);
|
||||
captureMenuCursor = config.GetBindable<bool>(OsuSetting.ScreenshotCaptureMenuCursor);
|
||||
|
||||
shutter = audio.Sample.Get("UI/shutter");
|
||||
shutter = audio.Samples.Get("UI/shutter");
|
||||
}
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
|
@ -82,6 +82,10 @@ namespace osu.Game.Graphics.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
public override void Hide() => State = Visibility.Hidden;
|
||||
|
||||
public override void Show() => State = Visibility.Visible;
|
||||
|
||||
public BreadcrumbTabItem(T value)
|
||||
: base(value)
|
||||
{
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleClick = audio.Sample.Get($@"UI/generic-select{SampleSet.GetDescription()}");
|
||||
sampleClick = audio.Samples.Get($@"UI/generic-select{SampleSet.GetDescription()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleHover = audio.Sample.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
|
||||
sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,6 +66,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
set
|
||||
{
|
||||
Content.RelativeSizeAxes = Axes.None;
|
||||
Content.AutoSizeAxes = Axes.None;
|
||||
Content.Size = value;
|
||||
}
|
||||
}
|
||||
|
@ -19,14 +19,14 @@ namespace osu.Game.Graphics.UserInterface
|
||||
public class OsuAnimatedButton : OsuClickableContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// The colour that should be flashed when the <see cref="IconButton"/> is clicked.
|
||||
/// The colour that should be flashed when the <see cref="OsuAnimatedButton"/> is clicked.
|
||||
/// </summary>
|
||||
protected Color4 FlashColour = Color4.White.Opacity(0.3f);
|
||||
|
||||
private Color4 hoverColour = Color4.White.Opacity(0.1f);
|
||||
|
||||
/// <summary>
|
||||
/// The background colour of the <see cref="IconButton"/> while it is hovered.
|
||||
/// The background colour of the <see cref="OsuAnimatedButton"/> while it is hovered.
|
||||
/// </summary>
|
||||
protected Color4 HoverColour
|
||||
{
|
||||
@ -74,6 +74,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
if (AutoSizeAxes != Axes.None)
|
||||
{
|
||||
content.RelativeSizeAxes = (Axes.Both & ~AutoSizeAxes);
|
||||
content.AutoSizeAxes = AutoSizeAxes;
|
||||
}
|
||||
|
||||
Enabled.BindValueChanged(enabled => this.FadeColour(enabled.NewValue ? Color4.White : colours.Gray9, 200, Easing.OutQuint), true);
|
||||
}
|
||||
|
||||
|
@ -17,11 +17,11 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// <summary>
|
||||
/// A button with added default sound effects.
|
||||
/// </summary>
|
||||
public class OsuButton : Button
|
||||
public abstract class OsuButton : Button
|
||||
{
|
||||
private Box hover;
|
||||
|
||||
public OsuButton()
|
||||
protected OsuButton()
|
||||
{
|
||||
Height = 40;
|
||||
|
||||
|
@ -6,10 +6,9 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
@ -33,27 +32,26 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
public string LabelText
|
||||
{
|
||||
get => labelSpriteText?.Text;
|
||||
set
|
||||
{
|
||||
if (labelSpriteText != null)
|
||||
labelSpriteText.Text = value;
|
||||
if (labelText != null)
|
||||
labelText.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
public MarginPadding LabelPadding
|
||||
{
|
||||
get => labelSpriteText?.Padding ?? new MarginPadding();
|
||||
get => labelText?.Padding ?? new MarginPadding();
|
||||
set
|
||||
{
|
||||
if (labelSpriteText != null)
|
||||
labelSpriteText.Padding = value;
|
||||
if (labelText != null)
|
||||
labelText.Padding = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly Nub Nub;
|
||||
|
||||
private readonly SpriteText labelSpriteText;
|
||||
private readonly OsuTextFlowContainer labelText;
|
||||
private SampleChannel sampleChecked;
|
||||
private SampleChannel sampleUnchecked;
|
||||
|
||||
@ -62,24 +60,28 @@ namespace osu.Game.Graphics.UserInterface
|
||||
AutoSizeAxes = Axes.Y;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
||||
const float nub_padding = 5;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
labelSpriteText = new OsuSpriteText(),
|
||||
labelText = new OsuTextFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding }
|
||||
},
|
||||
Nub = new Nub
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Margin = new MarginPadding { Right = 5 },
|
||||
Margin = new MarginPadding { Right = nub_padding },
|
||||
},
|
||||
new HoverClickSounds()
|
||||
};
|
||||
|
||||
Nub.Current.BindTo(Current);
|
||||
|
||||
Current.DisabledChanged += disabled =>
|
||||
{
|
||||
Alpha = disabled ? 0.3f : 1;
|
||||
};
|
||||
Current.DisabledChanged += disabled => labelText.Alpha = Nub.Alpha = disabled ? 0.3f : 1;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -112,8 +114,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleChecked = audio.Sample.Get(@"UI/check-on");
|
||||
sampleUnchecked = audio.Sample.Get(@"UI/check-off");
|
||||
sampleChecked = audio.Samples.Get(@"UI/check-on");
|
||||
sampleUnchecked = audio.Samples.Get(@"UI/check-off");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,8 +71,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleHover = audio.Sample.Get(@"UI/generic-hover");
|
||||
sampleClick = audio.Sample.Get(@"UI/generic-select");
|
||||
sampleHover = audio.Samples.Get(@"UI/generic-hover");
|
||||
sampleClick = audio.Samples.Get(@"UI/generic-select");
|
||||
|
||||
BackgroundColour = Color4.Transparent;
|
||||
BackgroundColourHover = OsuColour.FromHex(@"172023");
|
||||
|
@ -86,7 +86,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, OsuColour colours)
|
||||
{
|
||||
sample = audio.Sample.Get(@"UI/sliderbar-notch");
|
||||
sample = audio.Samples.Get(@"UI/sliderbar-notch");
|
||||
AccentColour = colours.Pink;
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.9f,
|
||||
},
|
||||
new LoadingAnimation { State = Visibility.Visible }
|
||||
new LoadingAnimation { State = { Value = Visibility.Visible } }
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -76,11 +76,11 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
titleText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 25),
|
||||
Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light),
|
||||
},
|
||||
pageText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 25),
|
||||
Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user