2019-03-29 03:38:47 +00:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-01-24 08:43:03 +00:00
// See the LICENCE file in the repository root for full licence text.
2018-04-13 09:19:50 +00:00
2021-04-16 03:31:32 +00:00
#nullable enable
2018-04-13 09:19:50 +00:00
using System ;
2021-05-02 23:02:14 +00:00
using System.Collections.Generic ;
2021-05-02 22:56:32 +00:00
using System.Linq ;
2019-03-30 16:33:56 +00:00
using JetBrains.Annotations ;
2018-04-13 09:19:50 +00:00
using osu.Game.Input.Handlers ;
2018-11-28 08:20:37 +00:00
using osu.Game.Replays ;
2018-04-13 09:19:50 +00:00
namespace osu.Game.Rulesets.Replays
{
/// <summary>
/// The ReplayHandler will take a replay and handle the propagation of updates to the input stack.
/// It handles logic of any frames which *must* be executed.
/// </summary>
public abstract class FramedReplayInputHandler < TFrame > : ReplayInputHandler
where TFrame : ReplayFrame
{
2021-04-12 06:52:43 +00:00
/// <summary>
/// Whether we have at least one replay frame.
/// </summary>
public bool HasFrames = > Frames . Count ! = 0 ;
2018-04-13 09:19:50 +00:00
2021-04-12 06:52:43 +00:00
/// <summary>
/// Whether we are waiting for new frames to be received.
/// </summary>
2021-04-12 09:50:25 +00:00
public bool WaitingForFrame = > ! replay . HasReceivedAllFrames & & currentFrameIndex = = Frames . Count - 1 ;
2018-04-13 09:19:50 +00:00
2021-04-12 06:52:43 +00:00
/// <summary>
/// The current frame of the replay.
/// The current time is always between the start and the end time of the current frame.
/// </summary>
2021-04-13 06:55:23 +00:00
/// <remarks>Returns null if the current time is strictly before the first frame.</remarks>
2021-04-16 03:53:58 +00:00
public TFrame ? CurrentFrame = > currentFrameIndex = = - 1 ? null : ( TFrame ) Frames [ currentFrameIndex ] ;
/// <summary>
/// The next frame of the replay.
/// The start time of <see cref="NextFrame"/> is always greater or equal to the start time of <see cref="CurrentFrame"/> regardless of the seeking direction.
/// </summary>
/// <remarks>Returns null if the current frame is the last frame.</remarks>
public TFrame ? NextFrame = > currentFrameIndex = = Frames . Count - 1 ? null : ( TFrame ) Frames [ currentFrameIndex + 1 ] ;
/// <summary>
/// The frame for the start value of the interpolation of the replay movement.
/// </summary>
2021-04-12 06:52:43 +00:00
/// <exception cref="InvalidOperationException">The replay is empty.</exception>
2021-04-16 03:53:58 +00:00
public TFrame StartFrame
2019-03-29 03:38:40 +00:00
{
get
{
2021-04-12 02:17:56 +00:00
if ( ! HasFrames )
2021-04-16 03:53:58 +00:00
throw new InvalidOperationException ( $"Attempted to get {nameof(StartFrame)} of an empty replay" ) ;
2019-03-29 03:38:40 +00:00
2021-04-16 03:53:58 +00:00
return ( TFrame ) Frames [ Math . Max ( 0 , currentFrameIndex ) ] ;
2019-03-29 03:38:40 +00:00
}
}
2021-04-12 06:52:43 +00:00
/// <summary>
2021-04-16 03:53:58 +00:00
/// The frame for the end value of the interpolation of the replay movement.
2021-04-12 06:52:43 +00:00
/// </summary>
/// <exception cref="InvalidOperationException">The replay is empty.</exception>
2021-04-16 03:53:58 +00:00
public TFrame EndFrame
2019-03-29 03:38:40 +00:00
{
get
{
if ( ! HasFrames )
2021-04-16 03:53:58 +00:00
throw new InvalidOperationException ( $"Attempted to get {nameof(EndFrame)} of an empty replay" ) ;
2019-03-29 03:38:40 +00:00
2021-04-16 03:53:58 +00:00
return ( TFrame ) Frames [ Math . Min ( currentFrameIndex + 1 , Frames . Count - 1 ) ] ;
2019-03-29 03:38:40 +00:00
}
}
2018-04-13 09:19:50 +00:00
2021-04-12 06:52:43 +00:00
/// <summary>
/// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data.
/// Disabling this can make replay playback smoother (useful for autoplay, currently).
/// </summary>
public bool FrameAccuratePlayback ;
2018-04-13 09:19:50 +00:00
2021-04-12 06:52:43 +00:00
// This input handler should be enabled only if there is at least one replay frame.
public override bool IsActive = > HasFrames ;
2018-04-13 09:19:50 +00:00
2021-04-16 03:31:32 +00:00
protected double CurrentTime { get ; private set ; }
2018-04-13 09:19:50 +00:00
2019-03-28 10:28:13 +00:00
protected virtual double AllowedImportantTimeSpan = > sixty_frame_time * 1.2 ;
2021-04-12 06:52:43 +00:00
protected List < ReplayFrame > Frames = > replay . Frames ;
2019-03-28 10:28:13 +00:00
2021-04-12 06:52:43 +00:00
private readonly Replay replay ;
2018-04-13 09:19:50 +00:00
2021-04-12 06:52:43 +00:00
private int currentFrameIndex ;
2018-04-13 09:19:50 +00:00
2021-04-12 06:52:43 +00:00
private const double sixty_frame_time = 1000.0 / 60 ;
protected FramedReplayInputHandler ( Replay replay )
{
2021-04-12 09:50:54 +00:00
// TODO: This replay frame ordering should be enforced on the Replay type.
// Currently, the ordering can be broken if the frames are added after this construction.
2021-05-02 22:56:32 +00:00
replay . Frames = replay . Frames . OrderBy ( f = > f . Time ) . ToList ( ) ;
2021-04-12 06:52:43 +00:00
this . replay = replay ;
currentFrameIndex = - 1 ;
CurrentTime = double . NegativeInfinity ;
}
2018-04-13 09:19:50 +00:00
2019-03-30 16:33:56 +00:00
private bool inImportantSection
{
get
{
2021-04-16 03:53:58 +00:00
if ( ! HasFrames | | ! FrameAccuratePlayback | | currentFrameIndex = = - 1 )
2019-03-30 16:33:56 +00:00
return false ;
2021-04-16 03:53:58 +00:00
return IsImportant ( StartFrame ) & & // a button is in a pressed state
Math . Abs ( CurrentTime - EndFrame . Time ) < = AllowedImportantTimeSpan ; // the next frame is within an allowable time span
2019-03-30 16:33:56 +00:00
}
}
2018-04-13 09:19:50 +00:00
2019-03-30 16:33:56 +00:00
protected virtual bool IsImportant ( [ NotNull ] TFrame frame ) = > false ;
2018-04-13 09:19:50 +00:00
/// <summary>
/// Update the current frame based on an incoming time value.
/// There are cases where we return a "must-use" time value that is different from the input.
/// This is to ensure accurate playback of replay data.
/// </summary>
/// <param name="time">The time which we should use for finding the current frame.</param>
/// <returns>The usable time value. If null, we should not advance time as we do not have enough data.</returns>
public override double? SetFrameFromTime ( double time )
{
2020-10-30 07:09:01 +00:00
if ( ! HasFrames )
{
2021-04-12 06:52:43 +00:00
// In the case all frames are received, allow time to progress regardless.
2020-10-30 07:09:01 +00:00
if ( replay . HasReceivedAllFrames )
return CurrentTime = time ;
return null ;
}
2020-10-28 06:54:58 +00:00
2021-04-12 06:52:43 +00:00
double frameStart = getFrameTime ( currentFrameIndex ) ;
double frameEnd = getFrameTime ( currentFrameIndex + 1 ) ;
2020-10-28 06:54:58 +00:00
2021-04-15 05:19:06 +00:00
// If the proposed time is after the current frame end time, we progress forwards to precisely the new frame's time (regardless of incoming time).
2021-04-12 06:52:43 +00:00
if ( frameEnd < = time )
2020-10-27 09:28:49 +00:00
{
2021-04-12 06:52:43 +00:00
time = frameEnd ;
currentFrameIndex + + ;
2020-10-27 09:28:49 +00:00
}
2021-04-15 05:19:06 +00:00
// If the proposed time is before the current frame start time, and we are at the frame boundary, we progress backwards.
2021-04-12 06:52:43 +00:00
else if ( time < frameStart & & CurrentTime = = frameStart )
currentFrameIndex - - ;
2018-04-13 09:19:50 +00:00
2021-04-12 06:52:43 +00:00
frameStart = getFrameTime ( currentFrameIndex ) ;
frameEnd = getFrameTime ( currentFrameIndex + 1 ) ;
2020-10-28 06:54:58 +00:00
2021-04-12 06:52:43 +00:00
// Pause until more frames are arrived.
2021-04-12 09:50:25 +00:00
if ( WaitingForFrame & & frameStart < time )
2020-10-30 07:09:01 +00:00
{
2021-04-12 06:52:43 +00:00
CurrentTime = frameStart ;
2020-10-28 06:54:58 +00:00
return null ;
2020-10-30 07:09:01 +00:00
}
2021-04-12 06:52:43 +00:00
CurrentTime = Math . Clamp ( time , frameStart , frameEnd ) ;
2020-10-30 07:09:01 +00:00
2021-04-12 06:52:43 +00:00
// In an important section, a mid-frame time cannot be used and a null is returned instead.
2021-04-16 03:31:32 +00:00
return inImportantSection & & frameStart < time & & time < frameEnd ? null : ( double? ) CurrentTime ;
2018-04-13 09:19:50 +00:00
}
2020-10-28 05:35:42 +00:00
2021-04-12 06:52:43 +00:00
private double getFrameTime ( int index )
2020-10-28 05:35:42 +00:00
{
2021-04-12 06:52:43 +00:00
if ( index < 0 )
return double . NegativeInfinity ;
if ( index > = Frames . Count )
return double . PositiveInfinity ;
return Frames [ index ] . Time ;
2020-10-28 05:35:42 +00:00
}
2018-04-13 09:19:50 +00:00
}
}