A game framework written with osu! in mind.
at master 4.4 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4#nullable enable 5 6using System; 7 8namespace osu.Framework.Timing 9{ 10 /// <summary> 11 /// A clock which uses an internal stopwatch to interpolate (smooth out) a source. 12 /// Note that this will NOT function unless a source has been set. 13 /// </summary> 14 public class InterpolatingFramedClock : IFrameBasedClock, ISourceChangeableClock 15 { 16 private readonly FramedClock clock = new FramedClock(new StopwatchClock(true)); 17 18 public IClock? Source { get; private set; } 19 20 protected IFrameBasedClock? FramedSourceClock; 21 protected double LastInterpolatedTime; 22 protected double CurrentInterpolatedTime; 23 24 public FrameTimeInfo TimeInfo => new FrameTimeInfo { Elapsed = ElapsedFrameTime, Current = CurrentTime }; 25 26 public double FramesPerSecond => 0; 27 28 public virtual void ChangeSource(IClock? source) 29 { 30 if (source != null) 31 { 32 Source = source; 33 FramedSourceClock = Source as IFrameBasedClock ?? new FramedClock(Source); 34 } 35 36 LastInterpolatedTime = 0; 37 CurrentInterpolatedTime = 0; 38 } 39 40 public InterpolatingFramedClock(IClock? source = null) 41 { 42 ChangeSource(source); 43 } 44 45 public virtual double CurrentTime => currentTime; 46 47 private double currentTime; 48 49 /// <summary> 50 /// The amount of error that is allowed between the source and interpolated time before the interpolated time is ignored and the source time is used. 51 /// </summary> 52 public virtual double AllowableErrorMilliseconds => 1000.0 / 60 * 2 * Rate; 53 54 private bool sourceIsRunning; 55 56 public virtual double Rate 57 { 58 get => FramedSourceClock?.Rate ?? 1; 59 set => throw new NotSupportedException(); 60 } 61 62 public virtual bool IsRunning => sourceIsRunning; 63 64 public virtual double Drift => CurrentTime - (FramedSourceClock?.CurrentTime ?? 0); 65 66 public virtual double ElapsedFrameTime => CurrentInterpolatedTime - LastInterpolatedTime; 67 68 /// <summary> 69 /// Whether time is being interpolated for the frame currently being processed. 70 /// </summary> 71 public bool IsInterpolating { get; private set; } 72 73 public virtual void ProcessFrame() 74 { 75 if (FramedSourceClock == null) return; 76 77 clock.ProcessFrame(); 78 FramedSourceClock.ProcessFrame(); 79 80 sourceIsRunning = FramedSourceClock.IsRunning; 81 82 LastInterpolatedTime = currentTime; 83 84 if (FramedSourceClock.IsRunning) 85 { 86 if (FramedSourceClock.ElapsedFrameTime != 0) 87 IsInterpolating = true; 88 89 CurrentInterpolatedTime += clock.ElapsedFrameTime * Rate; 90 91 if (!IsInterpolating || Math.Abs(FramedSourceClock.CurrentTime - CurrentInterpolatedTime) > AllowableErrorMilliseconds) 92 { 93 // if we've exceeded the allowable error, we should use the source clock's time value. 94 // seeking backwards should only be allowed if the source is explicitly doing that. 95 CurrentInterpolatedTime = FramedSourceClock.ElapsedFrameTime < 0 ? FramedSourceClock.CurrentTime : Math.Max(LastInterpolatedTime, FramedSourceClock.CurrentTime); 96 97 // once interpolation fails, we don't want to resume interpolating until the source clock starts to move again. 98 IsInterpolating = false; 99 } 100 else 101 { 102 //if we differ from the elapsed time of the source, let's adjust for the difference. 103 CurrentInterpolatedTime += (FramedSourceClock.CurrentTime - CurrentInterpolatedTime) / 8; 104 105 // limit the direction of travel to avoid seeking against the flow. 106 CurrentInterpolatedTime = Rate >= 0 ? Math.Max(LastInterpolatedTime, CurrentInterpolatedTime) : Math.Min(LastInterpolatedTime, CurrentInterpolatedTime); 107 } 108 } 109 110 currentTime = sourceIsRunning ? CurrentInterpolatedTime : FramedSourceClock.CurrentTime; 111 } 112 } 113}