2021-04-08 21:14:30 +08: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.
using System.Collections.Generic ;
using System.Linq ;
using NUnit.Framework ;
using osu.Framework.Allocation ;
using osu.Framework.Graphics ;
using osu.Framework.Testing ;
using osu.Game.Beatmaps ;
2021-06-11 17:13:54 +08:00
using osu.Game.Rulesets.UI ;
2021-04-08 21:14:30 +08:00
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate ;
using osu.Game.Screens.Play ;
using osu.Game.Tests.Beatmaps.IO ;
namespace osu.Game.Tests.Visual.Multiplayer
{
2021-04-23 18:23:52 +08:00
public class TestSceneMultiSpectatorScreen : MultiplayerTestScene
2021-04-08 21:14:30 +08:00
{
[Resolved]
private OsuGameBase game { get ; set ; }
[Resolved]
private BeatmapManager beatmapManager { get ; set ; }
2021-04-22 22:39:02 +08:00
private MultiSpectatorScreen spectatorScreen ;
2021-04-08 21:14:30 +08:00
private readonly List < int > playingUserIds = new List < int > ( ) ;
private BeatmapSetInfo importedSet ;
private BeatmapInfo importedBeatmap ;
private int importedBeatmapId ;
[BackgroundDependencyLoader]
private void load ( )
{
importedSet = ImportBeatmapTest . LoadOszIntoOsu ( game , virtualTrack : true ) . Result ;
importedBeatmap = importedSet . Beatmaps . First ( b = > b . RulesetID = = 0 ) ;
importedBeatmapId = importedBeatmap . OnlineBeatmapID ? ? - 1 ;
}
2021-06-25 14:00:10 +08:00
[SetUp]
2021-06-29 16:06:57 +08:00
public new void Setup ( ) = > Schedule ( ( ) = > playingUserIds . Clear ( ) ) ;
2021-04-08 21:14:30 +08:00
2021-04-16 11:25:29 +08:00
[Test]
public void TestDelayedStart ( )
{
AddStep ( "start players silently" , ( ) = >
{
2021-04-26 16:22:16 +08:00
Client . CurrentMatchPlayingUserIds . Add ( PLAYER_1_ID ) ;
Client . CurrentMatchPlayingUserIds . Add ( PLAYER_2_ID ) ;
playingUserIds . Add ( PLAYER_1_ID ) ;
playingUserIds . Add ( PLAYER_2_ID ) ;
2021-04-16 11:25:29 +08:00
} ) ;
loadSpectateScreen ( false ) ;
AddWaitStep ( "wait a bit" , 10 ) ;
2021-06-25 14:00:10 +08:00
AddStep ( "load player first_player_id" , ( ) = > SpectatorClient . StartPlay ( PLAYER_1_ID , importedBeatmapId ) ) ;
2021-04-22 22:39:02 +08:00
AddUntilStep ( "one player added" , ( ) = > spectatorScreen . ChildrenOfType < Player > ( ) . Count ( ) = = 1 ) ;
2021-04-16 11:25:29 +08:00
AddWaitStep ( "wait a bit" , 10 ) ;
2021-06-25 14:00:10 +08:00
AddStep ( "load player second_player_id" , ( ) = > SpectatorClient . StartPlay ( PLAYER_2_ID , importedBeatmapId ) ) ;
2021-04-22 22:39:02 +08:00
AddUntilStep ( "two players added" , ( ) = > spectatorScreen . ChildrenOfType < Player > ( ) . Count ( ) = = 2 ) ;
2021-04-16 11:25:29 +08:00
}
2021-04-08 21:14:30 +08:00
[Test]
public void TestGeneral ( )
{
2021-04-26 16:22:16 +08:00
int [ ] userIds = Enumerable . Range ( 0 , 4 ) . Select ( i = > PLAYER_1_ID + i ) . ToArray ( ) ;
2021-04-08 21:14:30 +08:00
start ( userIds ) ;
loadSpectateScreen ( ) ;
sendFrames ( userIds , 1000 ) ;
AddWaitStep ( "wait a bit" , 20 ) ;
}
2021-06-11 18:15:53 +08:00
[Test]
public void TestTimeDoesNotProgressWhileAllPlayersPaused ( )
{
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
loadSpectateScreen ( ) ;
2021-06-29 21:45:18 +08:00
sendFrames ( PLAYER_1_ID , 40 ) ;
sendFrames ( PLAYER_2_ID , 20 ) ;
2021-06-11 18:15:53 +08:00
checkPaused ( PLAYER_2_ID , true ) ;
checkPausedInstant ( PLAYER_1_ID , false ) ;
AddAssert ( "master clock still running" , ( ) = > this . ChildrenOfType < MasterGameplayClockContainer > ( ) . Single ( ) . IsRunning ) ;
checkPaused ( PLAYER_1_ID , true ) ;
AddUntilStep ( "master clock paused" , ( ) = > ! this . ChildrenOfType < MasterGameplayClockContainer > ( ) . Single ( ) . IsRunning ) ;
}
2021-04-08 21:14:30 +08:00
[Test]
public void TestPlayersMustStartSimultaneously ( )
{
2021-04-26 16:22:16 +08:00
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
2021-04-08 21:14:30 +08:00
loadSpectateScreen ( ) ;
// Send frames for one player only, both should remain paused.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 20 ) ;
checkPausedInstant ( PLAYER_1_ID , true ) ;
checkPausedInstant ( PLAYER_2_ID , true ) ;
2021-04-08 21:14:30 +08:00
// Send frames for the other player, both should now start playing.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_2_ID , 20 ) ;
checkPausedInstant ( PLAYER_1_ID , false ) ;
checkPausedInstant ( PLAYER_2_ID , false ) ;
2021-04-08 21:14:30 +08:00
}
[Test]
2021-04-26 15:45:20 +08:00
public void TestPlayersDoNotStartSimultaneouslyIfBufferingForMaximumStartDelay ( )
2021-04-08 21:14:30 +08:00
{
2021-04-26 16:22:16 +08:00
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
2021-04-08 21:14:30 +08:00
loadSpectateScreen ( ) ;
// Send frames for one player only, both should remain paused.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 1000 ) ;
checkPausedInstant ( PLAYER_1_ID , true ) ;
checkPausedInstant ( PLAYER_2_ID , true ) ;
2021-04-08 21:14:30 +08:00
2021-04-26 15:45:20 +08:00
// Wait for the start delay seconds...
AddWaitStep ( "wait maximum start delay seconds" , ( int ) ( CatchUpSyncManager . MAXIMUM_START_DELAY / TimePerAction ) ) ;
2021-04-08 21:14:30 +08:00
// Player 1 should start playing by itself, player 2 should remain paused.
2021-04-26 16:22:16 +08:00
checkPausedInstant ( PLAYER_1_ID , false ) ;
checkPausedInstant ( PLAYER_2_ID , true ) ;
2021-04-08 21:14:30 +08:00
}
[Test]
public void TestPlayersContinueWhileOthersBuffer ( )
{
2021-04-26 16:22:16 +08:00
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
2021-04-08 21:14:30 +08:00
loadSpectateScreen ( ) ;
// Send initial frames for both players. A few more for player 1.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 20 ) ;
sendFrames ( PLAYER_2_ID , 10 ) ;
checkPausedInstant ( PLAYER_1_ID , false ) ;
checkPausedInstant ( PLAYER_2_ID , false ) ;
2021-04-08 21:14:30 +08:00
// Eventually player 2 will pause, player 1 must remain running.
2021-04-26 16:22:16 +08:00
checkPaused ( PLAYER_2_ID , true ) ;
checkPausedInstant ( PLAYER_1_ID , false ) ;
2021-04-08 21:14:30 +08:00
// Eventually both players will run out of frames and should pause.
2021-04-26 16:22:16 +08:00
checkPaused ( PLAYER_1_ID , true ) ;
checkPausedInstant ( PLAYER_2_ID , true ) ;
2021-04-08 21:14:30 +08:00
// Send more frames for the first player only. Player 1 should start playing with player 2 remaining paused.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 20 ) ;
checkPausedInstant ( PLAYER_2_ID , true ) ;
checkPausedInstant ( PLAYER_1_ID , false ) ;
2021-04-08 21:14:30 +08:00
// Send more frames for the second player. Both should be playing
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_2_ID , 20 ) ;
checkPausedInstant ( PLAYER_2_ID , false ) ;
checkPausedInstant ( PLAYER_1_ID , false ) ;
2021-04-08 21:14:30 +08:00
}
[Test]
public void TestPlayersCatchUpAfterFallingBehind ( )
{
2021-04-26 16:22:16 +08:00
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
2021-04-08 21:14:30 +08:00
loadSpectateScreen ( ) ;
// Send initial frames for both players. A few more for player 1.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 1000 ) ;
2021-06-30 19:16:57 +08:00
sendFrames ( PLAYER_2_ID , 30 ) ;
2021-04-26 16:22:16 +08:00
checkPausedInstant ( PLAYER_1_ID , false ) ;
checkPausedInstant ( PLAYER_2_ID , false ) ;
2021-04-08 21:14:30 +08:00
// Eventually player 2 will run out of frames and should pause.
2021-04-26 16:22:16 +08:00
checkPaused ( PLAYER_2_ID , true ) ;
2021-04-08 21:14:30 +08:00
AddWaitStep ( "wait a few more frames" , 10 ) ;
// Send more frames for player 2. It should unpause.
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_2_ID , 1000 ) ;
checkPausedInstant ( PLAYER_2_ID , false ) ;
2021-04-08 21:14:30 +08:00
// Player 2 should catch up to player 1 after unpausing.
2021-04-26 16:22:16 +08:00
waitForCatchup ( PLAYER_2_ID ) ;
2021-04-13 21:40:10 +08:00
AddWaitStep ( "wait a bit" , 10 ) ;
2021-04-08 21:14:30 +08:00
}
2021-04-22 21:59:47 +08:00
[Test]
public void TestMostInSyncUserIsAudioSource ( )
{
2021-04-26 16:22:16 +08:00
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
2021-04-22 21:59:47 +08:00
loadSpectateScreen ( ) ;
2021-04-26 18:01:30 +08:00
assertMuted ( PLAYER_1_ID , true ) ;
assertMuted ( PLAYER_2_ID , true ) ;
2021-04-22 21:59:47 +08:00
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 10 ) ;
sendFrames ( PLAYER_2_ID , 20 ) ;
2021-06-29 21:45:18 +08:00
checkPaused ( PLAYER_1_ID , false ) ;
2021-06-29 21:53:31 +08:00
assertOneNotMuted ( ) ;
2021-04-22 21:59:47 +08:00
2021-04-26 16:22:16 +08:00
checkPaused ( PLAYER_1_ID , true ) ;
2021-04-26 18:01:30 +08:00
assertMuted ( PLAYER_1_ID , true ) ;
assertMuted ( PLAYER_2_ID , false ) ;
2021-04-22 21:59:47 +08:00
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_1_ID , 100 ) ;
waitForCatchup ( PLAYER_1_ID ) ;
checkPaused ( PLAYER_2_ID , true ) ;
2021-04-26 18:01:30 +08:00
assertMuted ( PLAYER_1_ID , false ) ;
assertMuted ( PLAYER_2_ID , true ) ;
2021-04-22 21:59:47 +08:00
2021-04-26 16:22:16 +08:00
sendFrames ( PLAYER_2_ID , 100 ) ;
waitForCatchup ( PLAYER_2_ID ) ;
2021-04-26 18:01:30 +08:00
assertMuted ( PLAYER_1_ID , false ) ;
assertMuted ( PLAYER_2_ID , true ) ;
2021-04-22 21:59:47 +08:00
}
2021-06-11 17:13:54 +08:00
[Test]
public void TestSpectatingDuringGameplay ( )
{
var players = new [ ] { PLAYER_1_ID , PLAYER_2_ID } ;
start ( players ) ;
sendFrames ( players , 300 ) ;
loadSpectateScreen ( ) ;
sendFrames ( players , 300 ) ;
AddUntilStep ( "playing from correct point in time" , ( ) = > this . ChildrenOfType < DrawableRuleset > ( ) . All ( r = > r . FrameStableClock . CurrentTime > 30000 ) ) ;
}
[Test]
public void TestSpectatingDuringGameplayWithLateFrames ( )
{
start ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } ) ;
sendFrames ( new [ ] { PLAYER_1_ID , PLAYER_2_ID } , 300 ) ;
loadSpectateScreen ( ) ;
sendFrames ( PLAYER_1_ID , 300 ) ;
AddWaitStep ( "wait maximum start delay seconds" , ( int ) ( CatchUpSyncManager . MAXIMUM_START_DELAY / TimePerAction ) ) ;
checkPaused ( PLAYER_1_ID , false ) ;
sendFrames ( PLAYER_2_ID , 300 ) ;
AddUntilStep ( "player 2 playing from correct point in time" , ( ) = > getPlayer ( PLAYER_2_ID ) . ChildrenOfType < DrawableRuleset > ( ) . Single ( ) . FrameStableClock . CurrentTime > 30000 ) ;
}
2021-04-16 11:25:29 +08:00
private void loadSpectateScreen ( bool waitForPlayerLoad = true )
2021-04-08 21:14:30 +08:00
{
AddStep ( "load screen" , ( ) = >
{
Beatmap . Value = beatmapManager . GetWorkingBeatmap ( importedBeatmap ) ;
Ruleset . Value = importedBeatmap . Ruleset ;
2021-04-22 22:39:02 +08:00
LoadScreen ( spectatorScreen = new MultiSpectatorScreen ( playingUserIds . ToArray ( ) ) ) ;
2021-04-08 21:14:30 +08:00
} ) ;
2021-04-22 22:39:02 +08:00
AddUntilStep ( "wait for screen load" , ( ) = > spectatorScreen . LoadState = = LoadState . Loaded & & ( ! waitForPlayerLoad | | spectatorScreen . AllPlayersLoaded ) ) ;
2021-04-08 21:14:30 +08:00
}
private void start ( int [ ] userIds , int? beatmapId = null )
{
AddStep ( "start play" , ( ) = >
{
foreach ( int id in userIds )
{
Client . CurrentMatchPlayingUserIds . Add ( id ) ;
2021-06-25 14:00:10 +08:00
SpectatorClient . StartPlay ( id , beatmapId ? ? importedBeatmapId ) ;
2021-04-08 21:14:30 +08:00
playingUserIds . Add ( id ) ;
}
} ) ;
}
private void sendFrames ( int userId , int count = 10 ) = > sendFrames ( new [ ] { userId } , count ) ;
private void sendFrames ( int [ ] userIds , int count = 10 )
{
AddStep ( "send frames" , ( ) = >
{
foreach ( int id in userIds )
2021-06-29 16:06:57 +08:00
SpectatorClient . SendFrames ( id , count ) ;
2021-04-08 21:14:30 +08:00
} ) ;
}
2021-04-22 21:59:47 +08:00
private void checkPaused ( int userId , bool state )
= > AddUntilStep ( $"{userId} is {(state ? " paused " : " playing ")}" , ( ) = > getPlayer ( userId ) . ChildrenOfType < GameplayClockContainer > ( ) . First ( ) . GameplayClock . IsRunning ! = state ) ;
private void checkPausedInstant ( int userId , bool state )
2021-06-29 21:45:18 +08:00
{
checkPaused ( userId , state ) ;
// Todo: The following should work, but is broken because SpectatorScreen retrieves the WorkingBeatmap via the BeatmapManager, bypassing the test scene clock and running real-time.
// AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().GameplayClock.IsRunning != state);
}
2021-04-22 21:59:47 +08:00
2021-06-29 21:53:31 +08:00
private void assertOneNotMuted ( ) = > AddAssert ( "one player not muted" , ( ) = > spectatorScreen . ChildrenOfType < PlayerArea > ( ) . Count ( p = > ! p . Mute ) = = 1 ) ;
2021-04-26 18:01:30 +08:00
private void assertMuted ( int userId , bool muted )
= > AddAssert ( $"{userId} {(muted ? " is " : " is not ")} muted" , ( ) = > getInstance ( userId ) . Mute = = muted ) ;
2021-04-08 21:14:30 +08:00
2021-04-22 21:59:47 +08:00
private void waitForCatchup ( int userId )
= > AddUntilStep ( $"{userId} not catching up" , ( ) = > ! getInstance ( userId ) . GameplayClock . IsCatchingUp ) ;
2021-04-08 21:14:30 +08:00
private Player getPlayer ( int userId ) = > getInstance ( userId ) . ChildrenOfType < Player > ( ) . Single ( ) ;
2021-04-22 22:52:22 +08:00
private PlayerArea getInstance ( int userId ) = > spectatorScreen . ChildrenOfType < PlayerArea > ( ) . Single ( p = > p . UserId = = userId ) ;
2021-04-08 21:14:30 +08:00
}
}