2019-03-06 19:30:14 +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.
2021-04-15 18:07:25 +08:00
using System ;
2019-03-06 19:30:14 +08:00
using osu.Framework.Allocation ;
2022-09-07 16:38:00 +08:00
using osu.Framework.Audio ;
2019-03-06 19:30:14 +08:00
using osu.Framework.Bindables ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2022-06-28 14:23:29 +08:00
using osu.Framework.Logging ;
2019-03-06 19:30:14 +08:00
using osu.Framework.Timing ;
2022-08-18 13:52:47 +08:00
using osu.Game.Beatmaps ;
2019-03-06 19:30:14 +08:00
namespace osu.Game.Screens.Play
{
2021-04-16 19:33:29 +08:00
/// <summary>
2022-08-15 18:59:08 +08:00
/// Encapsulates gameplay timing logic and provides a <see cref="IGameplayClock"/> via DI for gameplay components to use.
2021-04-16 19:33:29 +08:00
/// </summary>
2022-08-18 13:52:47 +08:00
[Cached(typeof(IGameplayClock))]
2023-12-13 15:13:08 +08:00
[Cached(typeof(GameplayClockContainer))]
2022-11-24 13:32:20 +08:00
public partial class GameplayClockContainer : Container , IAdjustableClock , IGameplayClock
2019-03-06 19:30:14 +08:00
{
2022-08-15 16:36:18 +08:00
public IBindable < bool > IsPaused = > isPaused ;
2023-07-06 13:02:20 +08:00
public bool IsRewinding = > GameplayClock . IsRewinding ;
2021-09-20 22:28:58 +08:00
/// <summary>
/// Invoked when a seek has been performed via <see cref="Seek"/>
/// </summary>
2022-08-15 16:06:24 +08:00
public event Action ? OnSeek ;
2021-09-20 22:28:58 +08:00
2022-03-17 19:54:42 +08:00
/// <summary>
2022-03-17 22:39:45 +08:00
/// The time from which the clock should start. Will be seeked to on calling <see cref="Reset"/>.
2022-08-22 13:11:06 +08:00
/// Can be adjusted by calling <see cref="Reset"/> with a time value.
2022-03-17 19:54:42 +08:00
/// </summary>
/// <remarks>
2022-08-18 17:10:20 +08:00
/// By default, a value of zero will be used.
/// Importantly, the value will be inferred from the current beatmap in <see cref="MasterGameplayClockContainer"/> by default.
2022-03-17 19:54:42 +08:00
/// </remarks>
2022-08-23 17:32:56 +08:00
public double StartTime { get ; protected set ; }
2022-07-29 21:12:52 +08:00
2022-09-08 16:14:06 +08:00
public IAdjustableAudioComponent AdjustmentsFromMods { get ; } = new AudioAdjustments ( ) ;
2022-08-15 16:36:18 +08:00
2022-08-15 18:59:08 +08:00
private readonly BindableBool isPaused = new BindableBool ( true ) ;
/// <summary>
/// The adjustable source clock used for gameplay. Should be used for seeks and clock control.
2022-08-26 15:46:22 +08:00
/// This is the final source exposed to gameplay components <see cref="IGameplayClock"/> via delegation in this class.
2022-08-15 18:59:08 +08:00
/// </summary>
2022-08-18 13:52:47 +08:00
protected readonly FramedBeatmapClock GameplayClock ;
protected override Container < Drawable > Content { get ; } = new Container { RelativeSizeAxes = Axes . Both } ;
2022-08-15 16:06:24 +08:00
2021-04-16 19:33:29 +08:00
/// <summary>
/// Creates a new <see cref="GameplayClockContainer"/>.
/// </summary>
/// <param name="sourceClock">The source <see cref="IClock"/> used for timing.</param>
2022-08-18 13:52:47 +08:00
/// <param name="applyOffsets">Whether to apply platform, user and beatmap offsets to the mix.</param>
2023-09-22 13:26:33 +08:00
/// <param name="requireDecoupling">Whether decoupling logic should be applied on the source clock.</param>
public GameplayClockContainer ( IClock sourceClock , bool applyOffsets , bool requireDecoupling )
2019-03-06 19:30:14 +08:00
{
2021-04-14 16:47:11 +08:00
RelativeSizeAxes = Axes . Both ;
2019-03-06 19:30:14 +08:00
2022-08-18 13:52:47 +08:00
InternalChildren = new Drawable [ ]
{
2023-09-22 13:26:33 +08:00
GameplayClock = new FramedBeatmapClock ( applyOffsets , requireDecoupling , sourceClock ) ,
2022-08-18 13:52:47 +08:00
Content
} ;
2021-04-14 16:47:11 +08:00
}
2019-03-06 19:30:14 +08:00
2021-04-16 19:33:29 +08:00
/// <summary>
2022-08-19 00:40:12 +08:00
/// Starts gameplay and marks un-paused state.
2021-04-16 19:33:29 +08:00
/// </summary>
2022-08-22 18:43:18 +08:00
public void Start ( )
2021-04-14 16:47:11 +08:00
{
2022-08-22 18:43:18 +08:00
if ( ! isPaused . Value )
return ;
2021-04-20 12:56:13 +08:00
2022-08-19 00:40:12 +08:00
isPaused . Value = false ;
2022-08-26 14:20:09 +08:00
// The case which caused this to be added is FrameStabilityContainer, which manages its own current and elapsed time.
2022-08-22 18:43:18 +08:00
// Because we generally update our own current time quicker than children can query it (via Start/Seek/Update),
// this means that the first frame ever exposed to children may have a non-zero current time.
//
// If the child component is not aware of the parent ElapsedFrameTime (which is the case for FrameStabilityContainer)
// they will take on the new CurrentTime with a zero elapsed time. This can in turn cause components to behave incorrectly
// if they are intending to trigger events at the precise StartTime (ie. DrawableStoryboardSample).
//
// By scheduling the start call, children are guaranteed to receive one frame at the original start time, allowing
// then to progress with a correct locally calculated elapsed time.
SchedulerAfterChildren . Add ( ( ) = >
2021-04-14 16:47:11 +08:00
{
2022-08-22 18:43:18 +08:00
if ( isPaused . Value )
return ;
StartGameplayClock ( ) ;
} ) ;
2021-04-14 16:47:11 +08:00
}
2019-03-25 19:25:47 +08:00
2021-04-14 16:47:11 +08:00
/// <summary>
/// Seek to a specific time in gameplay.
/// </summary>
/// <param name="time">The destination time to seek to.</param>
2022-08-29 18:51:16 +08:00
public virtual void Seek ( double time )
2021-04-20 16:35:59 +08:00
{
2022-06-28 14:23:29 +08:00
Logger . Log ( $"{nameof(GameplayClockContainer)} seeking to {time}" ) ;
2022-08-18 13:52:47 +08:00
GameplayClock . Seek ( time ) ;
2021-04-20 16:35:59 +08:00
2021-09-20 22:28:58 +08:00
OnSeek ? . Invoke ( ) ;
2021-04-20 16:35:59 +08:00
}
2020-12-10 16:42:47 +08:00
2021-04-16 19:33:29 +08:00
/// <summary>
2022-08-19 00:40:12 +08:00
/// Stops gameplay and marks paused state.
2021-04-16 19:33:29 +08:00
/// </summary>
2022-08-22 18:43:18 +08:00
public void Stop ( )
{
if ( isPaused . Value )
return ;
isPaused . Value = true ;
StopGameplayClock ( ) ;
}
2024-03-06 04:17:15 +08:00
protected virtual void StartGameplayClock ( )
{
Logger . Log ( $"{nameof(GameplayClockContainer)} started via call to {nameof(StartGameplayClock)}" ) ;
GameplayClock . Start ( ) ;
}
protected virtual void StopGameplayClock ( )
{
Logger . Log ( $"{nameof(GameplayClockContainer)} stopped via call to {nameof(StopGameplayClock)}" ) ;
GameplayClock . Stop ( ) ;
}
2020-12-10 16:42:47 +08:00
2021-04-16 19:33:29 +08:00
/// <summary>
2021-04-19 18:53:55 +08:00
/// Resets this <see cref="GameplayClockContainer"/> and the source to an initial state ready for gameplay.
2021-04-16 19:33:29 +08:00
/// </summary>
2022-08-22 13:11:06 +08:00
/// <param name="time">The time to seek to on resetting. If <c>null</c>, the existing <see cref="StartTime"/> will be used.</param>
2023-10-10 21:54:19 +08:00
/// <param name="startClock">Whether to start the clock immediately. If <c>false</c> and the clock was already paused, the clock will remain paused after this call.
/// </param>
2022-08-22 13:11:06 +08:00
public void Reset ( double? time = null , bool startClock = false )
2020-12-10 16:42:47 +08:00
{
2022-08-22 18:43:18 +08:00
bool wasPaused = isPaused . Value ;
2023-10-10 17:16:12 +08:00
// The intention of the Reset method is to get things into a known sane state.
// As such, we intentionally stop the underlying clock directly here, bypassing Stop/StopGameplayClock.
// This is to avoid any kind of isPaused state checks and frequency ramping (as provided by MasterGameplayClockContainer).
GameplayClock . Stop ( ) ;
2021-04-14 16:47:11 +08:00
2022-08-22 13:11:06 +08:00
if ( time ! = null )
StartTime = time . Value ;
2022-08-18 17:10:20 +08:00
Seek ( StartTime ) ;
2022-08-19 00:40:12 +08:00
2022-08-22 18:43:18 +08:00
if ( ! wasPaused | | startClock )
2022-08-19 00:40:12 +08:00
Start ( ) ;
2019-03-06 19:30:14 +08:00
}
2021-04-19 18:55:59 +08:00
/// <summary>
/// Changes the source clock.
/// </summary>
/// <param name="sourceClock">The new source.</param>
2023-09-22 12:16:51 +08:00
protected void ChangeSource ( IClock sourceClock ) = > GameplayClock . ChangeSource ( sourceClock ) ;
2021-04-24 03:56:08 +08:00
2021-04-15 18:07:25 +08:00
#region IAdjustableClock
bool IAdjustableClock . Seek ( double position )
{
Seek ( position ) ;
return true ;
}
2021-04-21 16:13:01 +08:00
void IAdjustableClock . Reset ( ) = > Reset ( ) ;
2021-04-15 18:07:25 +08:00
2022-09-05 22:22:38 +08:00
public virtual void ResetSpeedAdjustments ( )
{
}
2021-04-15 18:07:25 +08:00
double IAdjustableClock . Rate
{
2022-08-18 13:52:47 +08:00
get = > GameplayClock . Rate ;
2021-04-15 18:07:25 +08:00
set = > throw new NotSupportedException ( ) ;
}
2022-08-18 13:52:47 +08:00
public double Rate = > GameplayClock . Rate ;
2021-04-15 18:07:25 +08:00
2022-08-18 13:52:47 +08:00
public double CurrentTime = > GameplayClock . CurrentTime ;
2021-04-15 18:07:25 +08:00
2022-08-18 13:52:47 +08:00
public bool IsRunning = > GameplayClock . IsRunning ;
2021-04-15 18:07:25 +08:00
#endregion
2022-08-15 16:06:24 +08:00
public void ProcessFrame ( )
{
// Handled via update. Don't process here to safeguard from external usages potentially processing frames additional times.
}
2022-08-18 13:52:47 +08:00
public double ElapsedFrameTime = > GameplayClock . ElapsedFrameTime ;
2022-08-15 16:06:24 +08:00
2022-08-18 13:52:47 +08:00
public double FramesPerSecond = > GameplayClock . FramesPerSecond ;
2021-04-14 16:47:11 +08:00
}
2019-03-06 19:30:14 +08:00
}