A game framework written with osu! in mind.
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
4using osu.Framework.Extensions.TypeExtensions;
5using System;
6using System.Diagnostics;
7
8namespace osu.Framework.Timing
9{
10 public class StopwatchClock : Stopwatch, IAdjustableClock
11 {
12 private double seekOffset;
13
14 /// <summary>
15 /// Keep track of how much stopwatch time we have used at previous rates.
16 /// </summary>
17 private double rateChangeUsed;
18
19 /// <summary>
20 /// Keep track of the resultant time that was accumulated at previous rates.
21 /// </summary>
22 private double rateChangeAccumulated;
23
24 public StopwatchClock(bool start = false)
25 {
26 if (start)
27 Start();
28 }
29
30 public double CurrentTime => stopwatchCurrentTime + seekOffset;
31
32 /// <summary>
33 /// The current time, represented solely by the accumulated <see cref="Stopwatch"/> time.
34 /// </summary>
35 private double stopwatchCurrentTime => (stopwatchMilliseconds - rateChangeUsed) * rate + rateChangeAccumulated;
36
37 private double stopwatchMilliseconds => (double)ElapsedTicks / Frequency * 1000;
38
39 private double rate = 1;
40
41 public double Rate
42 {
43 get => rate;
44 set
45 {
46 if (rate == value) return;
47
48 rateChangeAccumulated += (stopwatchMilliseconds - rateChangeUsed) * rate;
49 rateChangeUsed = stopwatchMilliseconds;
50
51 rate = value;
52 }
53 }
54
55 public new void Reset()
56 {
57 resetAccumulatedRate();
58 base.Reset();
59 }
60
61 public new void Restart()
62 {
63 resetAccumulatedRate();
64 base.Restart();
65 }
66
67 public void ResetSpeedAdjustments() => Rate = 1;
68
69 public bool Seek(double position)
70 {
71 // Determine the offset that when added to stopwatchCurrentTime; results in the requested time value
72 seekOffset = position - stopwatchCurrentTime;
73 return true;
74 }
75
76 public override string ToString() => $@"{GetType().ReadableName()} ({Math.Truncate(CurrentTime)}ms)";
77
78 private void resetAccumulatedRate()
79 {
80 rateChangeAccumulated = 0;
81 rateChangeUsed = 0;
82 }
83 }
84}