A game framework written with osu! in mind.
at master 2.7 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; 7using System.Diagnostics; 8using System.Threading; 9 10namespace osu.Framework.Timing 11{ 12 /// <summary> 13 /// A FrameClock which will limit the number of frames processed by adding Thread.Sleep calls on each ProcessFrame. 14 /// </summary> 15 public class ThrottledFrameClock : FramedClock 16 { 17 /// <summary> 18 /// The target number of updates per second. Only used when <see cref="Throttling"/> is <c>true</c>. 19 /// </summary> 20 /// <remarks> 21 /// A value of 0 is treated the same as "unlimited" or <see cref="double.MaxValue"/>. 22 /// </remarks> 23 public double MaximumUpdateHz = 1000.0; 24 25 /// <summary> 26 /// Whether throttling should be enabled. Defaults to <c>true</c>. 27 /// </summary> 28 public bool Throttling = true; 29 30 /// <summary> 31 /// The time spent in a Thread.Sleep state during the last frame. 32 /// </summary> 33 public double TimeSlept { get; private set; } 34 35 public override void ProcessFrame() 36 { 37 Debug.Assert(MaximumUpdateHz >= 0); 38 39 base.ProcessFrame(); 40 41 if (Throttling) 42 { 43 if (MaximumUpdateHz > 0 && MaximumUpdateHz < double.MaxValue) 44 { 45 throttle(); 46 } 47 else 48 { 49 // Even when running at unlimited frame-rate, we should call the scheduler 50 // to give lower-priority background processes a chance to do work. 51 TimeSlept = sleepAndUpdateCurrent(0); 52 } 53 } 54 else 55 { 56 TimeSlept = 0; 57 } 58 59 Debug.Assert(TimeSlept <= ElapsedFrameTime); 60 } 61 62 private double accumulatedSleepError; 63 64 private void throttle() 65 { 66 double excessFrameTime = 1000d / MaximumUpdateHz - ElapsedFrameTime; 67 68 TimeSlept = sleepAndUpdateCurrent((int)Math.Max(0, excessFrameTime + accumulatedSleepError)); 69 70 accumulatedSleepError += excessFrameTime - TimeSlept; 71 72 // Never allow the sleep error to become too negative and induce too many catch-up frames 73 accumulatedSleepError = Math.Max(-1000 / 30.0, accumulatedSleepError); 74 } 75 76 private double sleepAndUpdateCurrent(int milliseconds) 77 { 78 double before = CurrentTime; 79 80 Thread.Sleep(milliseconds); 81 82 return (CurrentTime = SourceTime) - before; 83 } 84 } 85}