timer.tsx 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. /**
  2. * EventTarget has ~97% browser support
  3. */
  4. export class Timer extends EventTarget {
  5. private _id: number | null = null;
  6. private _active: boolean = false;
  7. private _start: number = 0;
  8. private _time: number = 0;
  9. private _additionalTime: number = 0;
  10. private _callbacks: Map<number, (() => void)[]> = new Map();
  11. constructor() {
  12. super();
  13. }
  14. step = () => {
  15. if (!this._active) {
  16. return;
  17. }
  18. this._time = window.performance.now() - this._start;
  19. // We don't expect _callbacks to be very large, so we can deal with a
  20. // linear search
  21. this._callbacks.forEach((value, key, callbacks) => {
  22. if (this._time >= key) {
  23. // Call every callback and then clear the callbacks at offset
  24. value.forEach(callback => callback());
  25. callbacks.set(key, []);
  26. }
  27. });
  28. this._id = window.requestAnimationFrame(this.step);
  29. };
  30. /**
  31. * @param seconds The number of seconds to start at
  32. */
  33. start(seconds?: number) {
  34. this._start = window.performance.now() - (seconds ?? 0);
  35. this._active = true;
  36. this._id = window.requestAnimationFrame(this.step);
  37. }
  38. /**
  39. * Stops timer and moves time to `seconds` if provided
  40. */
  41. stop(seconds?: number) {
  42. if (seconds !== undefined) {
  43. this._time = seconds;
  44. }
  45. if (this._id) {
  46. window.cancelAnimationFrame(this._id);
  47. }
  48. this._active = false;
  49. }
  50. reset() {
  51. this.stop();
  52. this._start = 0;
  53. this._time = 0;
  54. }
  55. getTime() {
  56. return this._time + this._additionalTime;
  57. }
  58. isActive() {
  59. return this._active;
  60. }
  61. addNotificationAtTime(offset: number, callback: () => void) {
  62. // Can't notify going backwards in time
  63. if (offset <= this._time) {
  64. return;
  65. }
  66. if (!this._callbacks.has(offset)) {
  67. this._callbacks.set(offset, []);
  68. }
  69. const callbacksAtOffset = this._callbacks.get(offset)!;
  70. callbacksAtOffset.push(callback);
  71. this._callbacks.set(offset, callbacksAtOffset);
  72. }
  73. }