SimpleTimer.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. using System;
  3. using System.Diagnostics;
  4. using System.Runtime.CompilerServices;
  5. using System.Text;
  6. namespace Animancer
  7. {
  8. /// <summary>A very simple timer system based on a <see cref="System.Diagnostics.Stopwatch"/>.</summary>
  9. public struct SimpleTimer : IDisposable
  10. {
  11. /************************************************************************************************************************/
  12. /// <summary>The default <see cref="format"/> contains 3 decimal places.</summary>
  13. const string Format3DP = "0.000";
  14. /// <summary>A default timer that hasn't been started.</summary>
  15. public static SimpleTimer Default = new(null);
  16. /************************************************************************************************************************/
  17. /// <summary>The system used to track time.</summary>
  18. public static readonly Stopwatch
  19. Stopwatch = Stopwatch.StartNew();
  20. /************************************************************************************************************************/
  21. /// <summary>An optional prefix for <see cref="ToString"/>.</summary>
  22. public string name;
  23. /// <summary>The string format to use for <see cref="ToString"/>.</summary>
  24. /// <remarks>
  25. /// If <c>null</c>, ticks will be used directly.
  26. /// Otherwise, the ticks will be converted to seconds and this format will be used.
  27. /// </remarks>
  28. public string format;
  29. /// <summary>The <see cref="Stopwatch.ElapsedTicks"/> from when this timer was started.</summary>
  30. /// <remarks>If not started, this value will be <c>-1</c>.</remarks>
  31. public long startTicks;
  32. /// <summary>The total number of ticks that have elapsed since the <see cref="startTicks"/>.</summary>
  33. /// <remarks>This value is updated by <see cref="Count"/>.</remarks>
  34. public long totalTicks;
  35. /************************************************************************************************************************/
  36. /// <summary>Converts the <see cref="startTicks"/> to seconds.</summary>
  37. public readonly double StartTimeSeconds
  38. => startTicks / (double)Stopwatch.Frequency;
  39. /// <summary>Converts the <see cref="totalTicks"/> to seconds.</summary>
  40. public readonly double TotalTimeSeconds
  41. => totalTicks / (double)Stopwatch.Frequency;
  42. /************************************************************************************************************************/
  43. /// <summary>Has <see cref="Start()"/> been called and <see cref="Count"/> not?</summary>
  44. public readonly bool IsStarted
  45. => startTicks != -1;
  46. /************************************************************************************************************************/
  47. /// <summary>Creates a new <see cref="SimpleTimer"/> with the specified `name`.</summary>
  48. /// <remarks>
  49. /// You will need to call <see cref="Start()"/> to start the timer.
  50. /// Or use the static <see cref="Start(string, string)"/>.
  51. /// <para></para>
  52. /// Use <c>null</c> as the `format` to have <see cref="Format"/> return the ticks instead of seconds.
  53. /// </remarks>
  54. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  55. public SimpleTimer(string name, string format = Format3DP)
  56. {
  57. this.name = name;
  58. this.format = format;
  59. startTicks = -1;
  60. totalTicks = 0;
  61. }
  62. /************************************************************************************************************************/
  63. /// <summary>Creates a new <see cref="SimpleTimer"/> with the specified `name` and starts it.</summary>
  64. /// <remarks>Use <c>null</c> as the `format` to have <see cref="Format"/> return the ticks instead of seconds.</remarks>
  65. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  66. public static SimpleTimer Start(string name = null, string format = Format3DP)
  67. => new()
  68. {
  69. name = name,
  70. format = format,
  71. startTicks = Stopwatch.ElapsedTicks,
  72. };
  73. /************************************************************************************************************************/
  74. /// <summary>
  75. /// Stores the <see cref="Stopwatch.ElapsedTicks"/> in <see cref="startTicks"/>
  76. /// so that <see cref="Count"/> will be able to calculate how much time has passed.
  77. /// </summary>
  78. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  79. public void Start()
  80. => startTicks = Stopwatch.ElapsedTicks;
  81. /// <summary>Clears the <see cref="startTicks"/>.</summary>
  82. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  83. public void Cancel()
  84. => startTicks = -1;
  85. /************************************************************************************************************************/
  86. /// <summary>
  87. /// Calculates the amount of time that has passed since the <see cref="startTicks"/>
  88. /// and returns it after adding it to the <see cref="totalTicks"/>.
  89. /// Also resumes this timer.
  90. /// </summary>
  91. /// <remarks>Returns -1 if this timer wasn't started.</remarks>
  92. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  93. public long Count()
  94. {
  95. var endTicks = Stopwatch.ElapsedTicks;
  96. long count;
  97. if (startTicks >= 0)
  98. {
  99. count = endTicks - startTicks;
  100. totalTicks += count;
  101. }
  102. else
  103. {
  104. count = -1;
  105. }
  106. startTicks = endTicks;
  107. return count;
  108. }
  109. /************************************************************************************************************************/
  110. private static StringBuilder _StringBuilder;
  111. /// <summary>Calls <see cref="Count"/> and returns a string describing the current values of this timer.</summary>
  112. public override string ToString()
  113. {
  114. var count = Count();
  115. if (_StringBuilder == null)
  116. _StringBuilder = new();
  117. else
  118. _StringBuilder.Length = 0;
  119. if (!string.IsNullOrEmpty(name))
  120. _StringBuilder.Append(name)
  121. .Append(": ");
  122. if (count != totalTicks && count >= 0)
  123. {
  124. _StringBuilder
  125. .Append("Count ")
  126. .Append(Format(count))
  127. .Append(", Total ");
  128. }
  129. _StringBuilder.Append(Format(totalTicks));
  130. return _StringBuilder.ToString();
  131. }
  132. /************************************************************************************************************************/
  133. /// <summary>Converts the given `ticks` to a string using the <see cref="format"/>.</summary>
  134. public readonly string Format(long ticks)
  135. => format is null
  136. ? $"{ticks} Ticks"
  137. : $"{(ticks / (double)Stopwatch.Frequency).ToString(format)}s";
  138. /************************************************************************************************************************/
  139. /// <summary>Logs <see cref="ToString"/> and calls <see cref="Cancel"/>.</summary>
  140. public void Dispose()
  141. {
  142. UnityEngine.Debug.Log(ToString());
  143. Cancel();
  144. totalTicks = 0;
  145. }
  146. /************************************************************************************************************************/
  147. }
  148. }