2020-10-26 10:47:39 +00:00
// 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.
2022-06-17 07:37:17 +00:00
#nullable disable
2020-10-26 12:22:01 +00:00
using System.Linq ;
2020-10-26 10:47:39 +00:00
using NUnit.Framework ;
2020-10-26 12:17:12 +00:00
using osu.Framework.Allocation ;
2022-01-03 08:31:12 +00:00
using osu.Framework.Extensions ;
2020-10-28 07:29:06 +00:00
using osu.Framework.Graphics ;
2020-10-26 12:45:37 +00:00
using osu.Framework.Screens ;
using osu.Framework.Testing ;
2020-10-26 12:22:01 +00:00
using osu.Game.Beatmaps ;
2021-04-01 14:48:26 +00:00
using osu.Game.Database ;
2021-11-04 09:02:44 +00:00
using osu.Game.Online.API.Requests.Responses ;
2020-10-26 12:17:12 +00:00
using osu.Game.Online.Spectator ;
2020-10-27 05:47:15 +00:00
using osu.Game.Rulesets.Osu ;
using osu.Game.Rulesets.Osu.Replays ;
2020-10-27 07:28:11 +00:00
using osu.Game.Rulesets.UI ;
2022-01-28 13:26:05 +00:00
using osu.Game.Scoring ;
2021-08-03 09:28:08 +00:00
using osu.Game.Screens ;
2020-10-26 10:47:39 +00:00
using osu.Game.Screens.Play ;
2020-10-26 12:45:37 +00:00
using osu.Game.Tests.Beatmaps.IO ;
2022-05-28 12:55:57 +00:00
using osu.Game.Tests.Gameplay ;
2021-04-26 08:22:16 +00:00
using osu.Game.Tests.Visual.Multiplayer ;
2021-05-12 03:17:42 +00:00
using osu.Game.Tests.Visual.Spectator ;
2022-01-28 13:26:05 +00:00
using osuTK ;
2020-10-26 10:47:39 +00:00
namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneSpectator : ScreenTestScene
{
2021-11-04 09:02:44 +00:00
private readonly APIUser streamingUser = new APIUser { Id = MultiplayerTestScene . PLAYER_1_ID , Username = "Test user" } ;
2021-05-12 03:17:42 +00:00
2021-04-01 14:48:26 +00:00
[Cached(typeof(UserLookupCache))]
private UserLookupCache lookupCache = new TestUserLookupCache ( ) ;
2020-10-27 10:23:35 +00:00
// used just to show beatmap card for the time being.
protected override bool UseOnlineAPI = > true ;
2020-10-26 12:22:01 +00:00
[Resolved]
private OsuGameBase game { get ; set ; }
2022-02-16 00:43:28 +00:00
private TestSpectatorClient spectatorClient = > dependenciesScreen . SpectatorClient ;
2022-02-15 12:08:27 +00:00
private DependenciesScreen dependenciesScreen ;
2023-11-22 06:19:04 +00:00
private SoloSpectatorScreen spectatorScreen ;
2020-10-28 13:50:57 +00:00
2021-08-03 09:28:08 +00:00
private BeatmapSetInfo importedBeatmap ;
2020-10-28 13:50:57 +00:00
private int importedBeatmapId ;
2021-08-03 09:28:08 +00:00
[SetUpSteps]
public void SetupSteps ( )
2020-10-26 12:45:37 +00:00
{
2021-08-03 09:28:08 +00:00
AddStep ( "load dependencies" , ( ) = >
2020-10-28 13:50:57 +00:00
{
2022-02-15 12:08:27 +00:00
LoadScreen ( dependenciesScreen = new DependenciesScreen ( ) ) ;
// The dependencies screen gets suspended so it stops receiving updates. So its children are manually added to the test scene instead.
Children = new Drawable [ ]
{
dependenciesScreen . UserLookupCache ,
2022-02-16 00:43:28 +00:00
dependenciesScreen . SpectatorClient ,
2022-02-15 12:08:27 +00:00
} ;
2020-10-28 13:50:57 +00:00
} ) ;
2020-10-26 12:45:37 +00:00
2021-08-03 09:28:08 +00:00
AddUntilStep ( "wait for dependencies to load" , ( ) = > dependenciesScreen . IsLoaded ) ;
AddStep ( "import beatmap" , ( ) = >
2020-10-26 12:45:37 +00:00
{
2021-12-17 09:26:12 +00:00
importedBeatmap = BeatmapImportHelper . LoadOszIntoOsu ( game , virtualTrack : true ) . GetResultSafely ( ) ;
2022-01-27 06:19:48 +00:00
importedBeatmapId = importedBeatmap . Beatmaps . First ( b = > b . Ruleset . OnlineID = = 0 ) . OnlineID ;
2020-10-26 12:45:37 +00:00
} ) ;
}
2022-03-17 10:23:49 +00:00
[Test]
public void TestSeekToGameplayStartFramesArriveAfterPlayerLoad ( )
{
const double gameplay_start = 10000 ;
loadSpectatingScreen ( ) ;
start ( ) ;
waitForPlayer ( ) ;
sendFrames ( startTime : gameplay_start ) ;
2022-08-18 16:19:24 +00:00
AddAssert ( "time is greater than seek target" , ( ) = > currentFrameStableTime , ( ) = > Is . GreaterThan ( gameplay_start ) ) ;
2022-03-17 10:23:49 +00:00
}
/// <summary>
/// Tests the same as <see cref="TestSeekToGameplayStartFramesArriveAfterPlayerLoad"/> but with the frames arriving just as <see cref="Player"/> is transitioning into existence.
/// </summary>
[Test]
public void TestSeekToGameplayStartFramesArriveAsPlayerLoaded ( )
{
const double gameplay_start = 10000 ;
loadSpectatingScreen ( ) ;
start ( ) ;
2023-11-22 07:40:22 +00:00
AddUntilStep ( "wait for player loader" , ( ) = > this . ChildrenOfType < PlayerLoader > ( ) . SingleOrDefault ( ) ? . IsLoaded = = true ) ;
2022-03-17 10:23:49 +00:00
AddUntilStep ( "queue send frames on player load" , ( ) = >
{
2023-11-22 07:40:22 +00:00
var loadingPlayer = this . ChildrenOfType < PlayerLoader > ( ) . SingleOrDefault ( ) ? . CurrentPlayer ;
2022-03-17 10:23:49 +00:00
if ( loadingPlayer = = null )
return false ;
loadingPlayer . OnLoadComplete + = _ = >
spectatorClient . SendFramesFromUser ( streamingUser . Id , 10 , gameplay_start ) ;
2023-11-22 07:40:22 +00:00
2022-03-17 10:23:49 +00:00
return true ;
} ) ;
waitForPlayer ( ) ;
AddUntilStep ( "state is playing" , ( ) = > spectatorClient . WatchedUserStates [ streamingUser . Id ] . State = = SpectatedUserState . Playing ) ;
2022-08-18 16:19:24 +00:00
AddAssert ( "time is greater than seek target" , ( ) = > currentFrameStableTime , ( ) = > Is . GreaterThan ( gameplay_start ) ) ;
2022-03-17 10:23:49 +00:00
}
2020-10-26 10:47:39 +00:00
[Test]
2020-10-29 06:10:11 +00:00
public void TestFrameStarvationAndResume ( )
2020-10-26 10:47:39 +00:00
{
2020-10-27 08:10:48 +00:00
loadSpectatingScreen ( ) ;
2020-10-27 09:28:49 +00:00
2023-11-22 06:19:04 +00:00
AddAssert ( "screen hasn't changed" , ( ) = > Stack . CurrentScreen is SoloSpectatorScreen ) ;
2020-10-27 09:28:49 +00:00
2020-10-27 09:32:05 +00:00
start ( ) ;
waitForPlayer ( ) ;
2021-06-03 08:27:24 +00:00
sendFrames ( ) ;
2020-10-27 05:47:15 +00:00
AddAssert ( "ensure frames arrived" , ( ) = > replayHandler . HasFrames ) ;
2020-10-27 05:57:23 +00:00
2021-04-12 09:50:25 +00:00
AddUntilStep ( "wait for frame starvation" , ( ) = > replayHandler . WaitingForFrame ) ;
2020-10-27 09:32:05 +00:00
checkPaused ( true ) ;
2020-10-29 05:57:36 +00:00
double? pausedTime = null ;
AddStep ( "store time" , ( ) = > pausedTime = currentFrameStableTime ) ;
2020-10-27 09:13:58 +00:00
sendFrames ( ) ;
2020-10-27 07:28:11 +00:00
2021-04-12 09:50:25 +00:00
AddUntilStep ( "wait for frame starvation" , ( ) = > replayHandler . WaitingForFrame ) ;
2020-10-27 09:32:05 +00:00
checkPaused ( true ) ;
2020-10-29 05:57:36 +00:00
2022-08-18 16:19:24 +00:00
AddAssert ( "time advanced" , ( ) = > currentFrameStableTime , ( ) = > Is . GreaterThan ( pausedTime ) ) ;
2020-10-26 12:17:12 +00:00
}
2020-10-27 09:28:49 +00:00
[Test]
public void TestPlayStartsWithNoFrames ( )
{
loadSpectatingScreen ( ) ;
2020-10-27 09:32:05 +00:00
start ( ) ;
waitForPlayer ( ) ;
2020-10-28 13:59:54 +00:00
checkPaused ( true ) ;
2020-10-27 09:28:49 +00:00
2021-06-10 13:41:38 +00:00
// send enough frames to ensure play won't be paused
sendFrames ( 100 ) ;
2020-10-27 09:28:49 +00:00
2020-10-27 09:32:05 +00:00
checkPaused ( false ) ;
2020-10-27 09:28:49 +00:00
}
2020-10-26 12:27:05 +00:00
[Test]
public void TestSpectatingDuringGameplay ( )
{
2020-10-27 09:32:05 +00:00
start ( ) ;
2021-06-10 13:41:38 +00:00
sendFrames ( 300 ) ;
2020-10-27 09:56:28 +00:00
2020-10-27 08:10:48 +00:00
loadSpectatingScreen ( ) ;
2021-06-03 08:27:24 +00:00
waitForPlayer ( ) ;
2020-10-27 09:56:28 +00:00
2021-06-10 13:41:38 +00:00
sendFrames ( 300 ) ;
2020-10-27 09:56:28 +00:00
2022-08-18 16:19:24 +00:00
AddUntilStep ( "playing from correct point in time" , ( ) = > player . ChildrenOfType < DrawableRuleset > ( ) . First ( ) . FrameStableClock . CurrentTime , ( ) = > Is . GreaterThan ( 30000 ) ) ;
2020-10-26 12:27:05 +00:00
}
[Test]
2020-10-29 06:08:06 +00:00
public void TestHostRetriesWhileWatching ( )
2020-10-26 12:27:05 +00:00
{
2020-10-27 08:10:48 +00:00
loadSpectatingScreen ( ) ;
2020-10-26 12:45:37 +00:00
2020-10-27 09:32:05 +00:00
start ( ) ;
2020-10-27 07:28:11 +00:00
sendFrames ( ) ;
2020-10-28 13:50:57 +00:00
2020-10-29 06:03:38 +00:00
waitForPlayer ( ) ;
Player lastPlayer = null ;
AddStep ( "store first player" , ( ) = > lastPlayer = player ) ;
2020-10-27 09:32:05 +00:00
start ( ) ;
2020-10-27 07:28:11 +00:00
sendFrames ( ) ;
2020-10-29 06:03:38 +00:00
waitForPlayer ( ) ;
AddAssert ( "player is different" , ( ) = > lastPlayer ! = player ) ;
2020-10-26 12:27:05 +00:00
}
[Test]
public void TestHostFails ( )
{
2020-10-27 08:10:48 +00:00
loadSpectatingScreen ( ) ;
2020-10-26 12:45:37 +00:00
2020-10-27 09:32:05 +00:00
start ( ) ;
2020-10-26 12:45:37 +00:00
2020-10-28 13:59:54 +00:00
waitForPlayer ( ) ;
checkPaused ( true ) ;
2022-02-02 14:09:38 +00:00
sendFrames ( ) ;
2020-10-28 13:59:54 +00:00
2022-02-09 03:09:04 +00:00
finish ( SpectatedUserState . Failed ) ;
2020-10-28 13:59:54 +00:00
2022-02-02 14:09:38 +00:00
checkPaused ( false ) ; // Should continue playing until out of frames
2022-02-08 12:20:33 +00:00
checkPaused ( true ) ; // And eventually stop after running out of frames and fail.
// Todo: Should check for + display a failed message.
2020-10-26 12:27:05 +00:00
}
[Test]
public void TestStopWatchingDuringPlay ( )
{
2020-10-27 08:10:48 +00:00
loadSpectatingScreen ( ) ;
2020-10-26 12:45:37 +00:00
2020-10-27 09:32:05 +00:00
start ( ) ;
2020-10-27 07:28:11 +00:00
sendFrames ( ) ;
2020-10-27 09:32:05 +00:00
waitForPlayer ( ) ;
2020-10-27 10:23:35 +00:00
2020-10-26 12:45:37 +00:00
AddStep ( "stop spectating" , ( ) = > ( Stack . CurrentScreen as Player ) ? . Exit ( ) ) ;
2020-10-29 06:08:06 +00:00
AddUntilStep ( "spectating stopped" , ( ) = > spectatorScreen . GetChildScreen ( ) = = null ) ;
2020-10-26 12:45:37 +00:00
}
2020-10-29 06:09:12 +00:00
[Test]
public void TestStopWatchingThenHostRetries ( )
{
loadSpectatingScreen ( ) ;
start ( ) ;
sendFrames ( ) ;
waitForPlayer ( ) ;
AddStep ( "stop spectating" , ( ) = > ( Stack . CurrentScreen as Player ) ? . Exit ( ) ) ;
AddUntilStep ( "spectating stopped" , ( ) = > spectatorScreen . GetChildScreen ( ) = = null ) ;
// host starts playing a new session
start ( ) ;
waitForPlayer ( ) ;
}
2020-10-26 12:45:37 +00:00
[Test]
public void TestWatchingBeatmapThatDoesntExistLocally ( )
{
2020-10-27 08:10:48 +00:00
loadSpectatingScreen ( ) ;
2020-10-26 12:45:37 +00:00
2020-10-28 13:51:35 +00:00
start ( - 1234 ) ;
2020-10-27 07:28:11 +00:00
sendFrames ( ) ;
2020-10-27 10:23:35 +00:00
2023-11-22 06:19:04 +00:00
AddAssert ( "screen didn't change" , ( ) = > Stack . CurrentScreen is SoloSpectatorScreen ) ;
2020-10-26 12:27:05 +00:00
}
2022-01-28 13:26:05 +00:00
[Test]
public void TestFinalFramesPurgedBeforeEndingPlay ( )
{
2022-12-12 04:59:27 +00:00
AddStep ( "begin playing" , ( ) = > spectatorClient . BeginPlaying ( 0 , TestGameplayState . Create ( new OsuRuleset ( ) ) , new Score ( ) ) ) ;
2022-01-28 13:26:05 +00:00
AddStep ( "send frames and finish play" , ( ) = >
{
spectatorClient . HandleFrame ( new OsuReplayFrame ( 1000 , Vector2 . Zero ) ) ;
2022-05-28 12:55:57 +00:00
var completedGameplayState = TestGameplayState . Create ( new OsuRuleset ( ) ) ;
completedGameplayState . HasPassed = true ;
spectatorClient . EndPlaying ( completedGameplayState ) ;
2022-01-28 13:26:05 +00:00
} ) ;
// We can't access API because we're an "online" test.
2022-01-31 06:12:08 +00:00
AddAssert ( "last received frame has time = 1000" , ( ) = > spectatorClient . LastReceivedUserFrames . First ( ) . Value . Time = = 1000 ) ;
2022-01-28 13:26:05 +00:00
}
2022-01-31 09:32:17 +00:00
[Test]
public void TestFinalFrameInBundleHasHeader ( )
{
FrameDataBundle lastBundle = null ;
AddStep ( "bind to client" , ( ) = > spectatorClient . OnNewFrames + = ( _ , bundle ) = > lastBundle = bundle ) ;
start ( - 1234 ) ;
sendFrames ( ) ;
finish ( ) ;
AddUntilStep ( "bundle received" , ( ) = > lastBundle ! = null ) ;
AddAssert ( "first frame does not have header" , ( ) = > lastBundle . Frames [ 0 ] . Header = = null ) ;
AddAssert ( "last frame has header" , ( ) = > lastBundle . Frames [ ^ 1 ] . Header ! = null ) ;
}
2022-02-08 11:27:08 +00:00
[Test]
public void TestPlayingState ( )
{
loadSpectatingScreen ( ) ;
start ( ) ;
sendFrames ( ) ;
waitForPlayer ( ) ;
2022-02-09 03:09:04 +00:00
AddUntilStep ( "state is playing" , ( ) = > spectatorClient . WatchedUserStates [ streamingUser . Id ] . State = = SpectatedUserState . Playing ) ;
2022-02-08 11:27:08 +00:00
}
[Test]
2022-02-08 11:29:49 +00:00
public void TestPassedState ( )
2022-02-08 11:27:08 +00:00
{
loadSpectatingScreen ( ) ;
start ( ) ;
sendFrames ( ) ;
waitForPlayer ( ) ;
2022-02-22 08:56:07 +00:00
AddStep ( "send passed" , ( ) = > spectatorClient . SendEndPlay ( streamingUser . Id , SpectatedUserState . Passed ) ) ;
2022-02-09 03:09:04 +00:00
AddUntilStep ( "state is passed" , ( ) = > spectatorClient . WatchedUserStates [ streamingUser . Id ] . State = = SpectatedUserState . Passed ) ;
2022-02-08 11:27:08 +00:00
start ( ) ;
sendFrames ( ) ;
waitForPlayer ( ) ;
2022-02-09 03:09:04 +00:00
AddUntilStep ( "state is playing" , ( ) = > spectatorClient . WatchedUserStates [ streamingUser . Id ] . State = = SpectatedUserState . Playing ) ;
2022-02-08 11:27:08 +00:00
}
[Test]
public void TestQuitState ( )
{
loadSpectatingScreen ( ) ;
start ( ) ;
sendFrames ( ) ;
waitForPlayer ( ) ;
2022-02-22 08:56:07 +00:00
AddStep ( "send quit" , ( ) = > spectatorClient . SendEndPlay ( streamingUser . Id ) ) ;
2022-02-09 03:09:04 +00:00
AddUntilStep ( "state is quit" , ( ) = > spectatorClient . WatchedUserStates [ streamingUser . Id ] . State = = SpectatedUserState . Quit ) ;
2022-02-08 11:27:08 +00:00
2023-11-23 03:07:01 +00:00
AddAssert ( "wait for player exit" , ( ) = > Stack . CurrentScreen is SoloSpectatorScreen ) ;
2022-02-08 11:27:08 +00:00
start ( ) ;
sendFrames ( ) ;
waitForPlayer ( ) ;
2022-02-09 03:09:04 +00:00
AddUntilStep ( "state is playing" , ( ) = > spectatorClient . WatchedUserStates [ streamingUser . Id ] . State = = SpectatedUserState . Playing ) ;
2022-02-08 11:27:08 +00:00
}
[Test]
public void TestFailedState ( )
{
loadSpectatingScreen ( ) ;
start ( ) ;
sendFrames ( ) ;
waitForPlayer ( ) ;
2022-02-22 08:56:07 +00:00
AddStep ( "send failed" , ( ) = > spectatorClient . SendEndPlay ( streamingUser . Id , SpectatedUserState . Failed ) ) ;
2022-02-09 03:09:04 +00:00
AddUntilStep ( "state is failed" , ( ) = > spectatorClient . WatchedUserStates [ streamingUser . Id ] . State = = SpectatedUserState . Failed ) ;
2022-02-08 11:27:08 +00:00
2023-11-23 03:45:03 +00:00
AddUntilStep ( "wait for player to fail" , ( ) = > player . GameplayState . HasFailed ) ;
2022-02-08 11:27:08 +00:00
start ( ) ;
sendFrames ( ) ;
waitForPlayer ( ) ;
2022-02-09 03:09:04 +00:00
AddUntilStep ( "state is playing" , ( ) = > spectatorClient . WatchedUserStates [ streamingUser . Id ] . State = = SpectatedUserState . Playing ) ;
2022-02-08 11:27:08 +00:00
}
2020-10-29 06:10:42 +00:00
private OsuFramedReplayInputHandler replayHandler = >
( OsuFramedReplayInputHandler ) Stack . ChildrenOfType < OsuInputManager > ( ) . First ( ) . ReplayInputHandler ;
2023-11-22 07:40:22 +00:00
private Player player = > this . ChildrenOfType < Player > ( ) . Single ( ) ;
2020-10-29 06:10:42 +00:00
private double currentFrameStableTime
2022-08-15 09:53:10 +00:00
= > player . ChildrenOfType < FrameStabilityContainer > ( ) . First ( ) . CurrentTime ;
2020-10-29 06:10:42 +00:00
2023-11-22 07:40:22 +00:00
private void waitForPlayer ( ) = > AddUntilStep ( "wait for player" , ( ) = > this . ChildrenOfType < Player > ( ) . SingleOrDefault ( ) ? . IsLoaded = = true ) ;
2020-10-27 09:32:05 +00:00
2022-02-22 08:56:07 +00:00
private void start ( int? beatmapId = null ) = > AddStep ( "start play" , ( ) = > spectatorClient . SendStartPlay ( streamingUser . Id , beatmapId ? ? importedBeatmapId ) ) ;
2020-10-27 09:32:05 +00:00
2022-02-22 08:56:07 +00:00
private void finish ( SpectatedUserState state = SpectatedUserState . Quit ) = > AddStep ( "end play" , ( ) = > spectatorClient . SendEndPlay ( streamingUser . Id , state ) ) ;
2020-10-28 13:59:54 +00:00
2020-10-27 09:32:05 +00:00
private void checkPaused ( bool state ) = >
2020-10-28 13:59:54 +00:00
AddUntilStep ( $"game is {(state ? " paused " : " playing ")}" , ( ) = > player . ChildrenOfType < DrawableRuleset > ( ) . First ( ) . IsPaused . Value = = state ) ;
2020-10-27 09:32:05 +00:00
2022-03-17 10:23:49 +00:00
private void sendFrames ( int count = 10 , double startTime = 0 )
2020-10-27 09:13:58 +00:00
{
2022-03-17 10:23:49 +00:00
AddStep ( "send frames" , ( ) = > spectatorClient . SendFramesFromUser ( streamingUser . Id , count , startTime ) ) ;
2020-10-27 09:13:58 +00:00
}
2020-10-28 07:29:06 +00:00
private void loadSpectatingScreen ( )
{
2023-11-22 06:19:04 +00:00
AddStep ( "load spectator" , ( ) = > LoadScreen ( spectatorScreen = new SoloSpectatorScreen ( streamingUser ) ) ) ;
2020-10-28 07:29:06 +00:00
AddUntilStep ( "wait for screen load" , ( ) = > spectatorScreen . LoadState = = LoadState . Loaded ) ;
}
2021-08-03 09:28:08 +00:00
/// <summary>
/// Used for the sole purpose of adding <see cref="TestSpectatorClient"/> as a resolvable dependency.
/// </summary>
private partial class DependenciesScreen : OsuScreen
{
[Cached(typeof(SpectatorClient))]
2022-02-16 00:43:28 +00:00
public readonly TestSpectatorClient SpectatorClient = new TestSpectatorClient ( ) ;
2021-08-03 09:28:08 +00:00
2022-02-15 12:08:27 +00:00
[Cached(typeof(UserLookupCache))]
public readonly TestUserLookupCache UserLookupCache = new TestUserLookupCache ( ) ;
2021-08-03 09:28:08 +00:00
}
2020-10-26 10:47:39 +00:00
}
}