A game framework written with osu! in mind.
at master 3.3 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 osu.Framework.Extensions.TypeExtensions; 7using System; 8 9namespace osu.Framework.Timing 10{ 11 /// <summary> 12 /// Takes a clock source and separates time reading on a per-frame level. 13 /// The CurrentTime value will only change on initial construction and whenever ProcessFrame is run. 14 /// </summary> 15 public class FramedClock : IFrameBasedClock, ISourceChangeableClock 16 { 17 public IClock Source { get; private set; } 18 19 /// <summary> 20 /// Construct a new FramedClock with an optional source clock. 21 /// </summary> 22 /// <param name="source">A source clock which will be used as the backing time source. If null, a StopwatchClock will be created. When provided, the CurrentTime of <paramref name="source"/> will be transferred instantly.</param> 23 /// <param name="processSource">Whether the source clock's <see cref="ProcessFrame"/> method should be called during this clock's process call.</param> 24 public FramedClock(IClock? source = null, bool processSource = true) 25 { 26 this.processSource = processSource; 27 Source = source ?? new StopwatchClock(true); 28 29 ChangeSource(Source); 30 } 31 32 public FrameTimeInfo TimeInfo => new FrameTimeInfo { Elapsed = ElapsedFrameTime, Current = CurrentTime }; 33 34 public double FramesPerSecond { get; private set; } 35 36 public virtual double CurrentTime { get; protected set; } 37 38 protected virtual double LastFrameTime { get; set; } 39 40 public double Rate => Source.Rate; 41 42 protected double SourceTime => Source.CurrentTime; 43 44 public double ElapsedFrameTime => CurrentTime - LastFrameTime; 45 46 public bool IsRunning => Source.IsRunning; 47 48 private readonly bool processSource; 49 50 private double timeUntilNextCalculation; 51 private double timeSinceLastCalculation; 52 private int framesSinceLastCalculation; 53 54 private const int fps_calculation_interval = 250; 55 56 public void ChangeSource(IClock source) 57 { 58 CurrentTime = LastFrameTime = source.CurrentTime; 59 Source = source; 60 } 61 62 public virtual void ProcessFrame() 63 { 64 if (processSource && Source is IFrameBasedClock framedSource) 65 framedSource.ProcessFrame(); 66 67 if (timeUntilNextCalculation <= 0) 68 { 69 timeUntilNextCalculation += fps_calculation_interval; 70 71 if (framesSinceLastCalculation == 0) 72 FramesPerSecond = 0; 73 else 74 FramesPerSecond = (int)Math.Ceiling(framesSinceLastCalculation * 1000f / timeSinceLastCalculation); 75 timeSinceLastCalculation = framesSinceLastCalculation = 0; 76 } 77 78 framesSinceLastCalculation++; 79 timeUntilNextCalculation -= ElapsedFrameTime; 80 timeSinceLastCalculation += ElapsedFrameTime; 81 82 LastFrameTime = CurrentTime; 83 CurrentTime = SourceTime; 84 } 85 86 public override string ToString() => $@"{GetType().ReadableName()} ({Math.Truncate(CurrentTime)}ms, {FramesPerSecond} FPS)"; 87 } 88}