Timer.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /**
  2. * This class is an alternative to {@link Clock} with a different API design and behavior.
  3. * The goal is to avoid the conceptual flaws that became apparent in `Clock` over time.
  4. *
  5. * - `Timer` has an `update()` method that updates its internal state. That makes it possible to
  6. * call `getDelta()` and `getElapsed()` multiple times per simulation step without getting different values.
  7. * - The class can make use of the Page Visibility API to avoid large time delta values when the app
  8. * is inactive (e.g. tab switched or browser hidden).
  9. *
  10. * ```js
  11. * const timer = new Timer();
  12. * timer.connect( document ); // use Page Visibility API
  13. * ```
  14. */
  15. class Timer {
  16. /**
  17. * Constructs a new timer.
  18. */
  19. constructor() {
  20. this._previousTime = 0;
  21. this._currentTime = 0;
  22. this._startTime = now();
  23. this._delta = 0;
  24. this._elapsed = 0;
  25. this._timescale = 1;
  26. this._document = null;
  27. this._pageVisibilityHandler = null;
  28. }
  29. /**
  30. * Connect the timer to the given document.Calling this method is not mandatory to
  31. * use the timer but enables the usage of the Page Visibility API to avoid large time
  32. * delta values.
  33. *
  34. * @param {Document} document - The document.
  35. */
  36. connect( document ) {
  37. this._document = document;
  38. // use Page Visibility API to avoid large time delta values
  39. if ( document.hidden !== undefined ) {
  40. this._pageVisibilityHandler = handleVisibilityChange.bind( this );
  41. document.addEventListener( 'visibilitychange', this._pageVisibilityHandler, false );
  42. }
  43. }
  44. /**
  45. * Disconnects the timer from the DOM and also disables the usage of the Page Visibility API.
  46. */
  47. disconnect() {
  48. if ( this._pageVisibilityHandler !== null ) {
  49. this._document.removeEventListener( 'visibilitychange', this._pageVisibilityHandler );
  50. this._pageVisibilityHandler = null;
  51. }
  52. this._document = null;
  53. }
  54. /**
  55. * Returns the time delta in seconds.
  56. *
  57. * @return {number} The time delta in second.
  58. */
  59. getDelta() {
  60. return this._delta / 1000;
  61. }
  62. /**
  63. * Returns the elapsed time in seconds.
  64. *
  65. * @return {number} The elapsed time in second.
  66. */
  67. getElapsed() {
  68. return this._elapsed / 1000;
  69. }
  70. /**
  71. * Returns the timescale.
  72. *
  73. * @return {number} The timescale.
  74. */
  75. getTimescale() {
  76. return this._timescale;
  77. }
  78. /**
  79. * Sets the given timescale which scale the time delta computation
  80. * in `update()`.
  81. *
  82. * @param {number} timescale - The timescale to set.
  83. * @return {Timer} A reference to this timer.
  84. */
  85. setTimescale( timescale ) {
  86. this._timescale = timescale;
  87. return this;
  88. }
  89. /**
  90. * Resets the time computation for the current simulation step.
  91. *
  92. * @return {Timer} A reference to this timer.
  93. */
  94. reset() {
  95. this._currentTime = now() - this._startTime;
  96. return this;
  97. }
  98. /**
  99. * Can be used to free all internal resources. Usually called when
  100. * the timer instance isn't required anymore.
  101. */
  102. dispose() {
  103. this.disconnect();
  104. }
  105. /**
  106. * Updates the internal state of the timer. This method should be called
  107. * once per simulation step and before you perform queries against the timer
  108. * (e.g. via `getDelta()`).
  109. *
  110. * @param {number} timestamp - The current time in milliseconds. Can be obtained
  111. * from the `requestAnimationFrame` callback argument. If not provided, the current
  112. * time will be determined with `performance.now`.
  113. * @return {Timer} A reference to this timer.
  114. */
  115. update( timestamp ) {
  116. if ( this._pageVisibilityHandler !== null && this._document.hidden === true ) {
  117. this._delta = 0;
  118. } else {
  119. this._previousTime = this._currentTime;
  120. this._currentTime = ( timestamp !== undefined ? timestamp : now() ) - this._startTime;
  121. this._delta = ( this._currentTime - this._previousTime ) * this._timescale;
  122. this._elapsed += this._delta; // _elapsed is the accumulation of all previous deltas
  123. }
  124. return this;
  125. }
  126. }
  127. /**
  128. * A special version of a timer with a fixed time delta value.
  129. * Can be useful for testing and debugging purposes.
  130. *
  131. * @augments Timer
  132. */
  133. class FixedTimer extends Timer {
  134. /**
  135. * Constructs a new timer.
  136. *
  137. * @param {number} [fps=60] - The fixed FPS of this timer.
  138. */
  139. constructor( fps = 60 ) {
  140. super();
  141. this._delta = ( 1 / fps ) * 1000;
  142. }
  143. update() {
  144. this._elapsed += ( this._delta * this._timescale ); // _elapsed is the accumulation of all previous deltas
  145. return this;
  146. }
  147. }
  148. function now() {
  149. return performance.now();
  150. }
  151. function handleVisibilityChange() {
  152. if ( this._document.hidden === false ) this.reset();
  153. }
  154. export { Timer, FixedTimer };
粤ICP备19079148号