task-loop.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import { type ILogger, Logger } from './utils/logger';
  2. /**
  3. * @ignore
  4. * Sub-class specialization of EventHandler base class.
  5. *
  6. * TaskLoop allows to schedule a task function being called (optionnaly repeatedly) on the main loop,
  7. * scheduled asynchroneously, avoiding recursive calls in the same tick.
  8. *
  9. * The task itself is implemented in `doTick`. It can be requested and called for single execution
  10. * using the `tick` method.
  11. *
  12. * It will be assured that the task execution method (`tick`) only gets called once per main loop "tick",
  13. * no matter how often it gets requested for execution. Execution in further ticks will be scheduled accordingly.
  14. *
  15. * If further execution requests have already been scheduled on the next tick, it can be checked with `hasNextTick`,
  16. * and cancelled with `clearNextTick`.
  17. *
  18. * The task can be scheduled as an interval repeatedly with a period as parameter (see `setInterval`, `clearInterval`).
  19. *
  20. * Sub-classes need to implement the `doTick` method which will effectively have the task execution routine.
  21. *
  22. * Further explanations:
  23. *
  24. * The baseclass has a `tick` method that will schedule the doTick call. It may be called synchroneously
  25. * only for a stack-depth of one. On re-entrant calls, sub-sequent calls are scheduled for next main loop ticks.
  26. *
  27. * When the task execution (`tick` method) is called in re-entrant way this is detected and
  28. * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
  29. * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
  30. */
  31. export default class TaskLoop extends Logger {
  32. private readonly _boundTick: () => void;
  33. private _tickTimer: number | null = null;
  34. private _tickInterval: number | null = null;
  35. private _tickCallCount = 0;
  36. constructor(label: string, logger: ILogger) {
  37. super(label, logger);
  38. this._boundTick = this.tick.bind(this);
  39. }
  40. public destroy() {
  41. this.onHandlerDestroying();
  42. this.onHandlerDestroyed();
  43. }
  44. protected onHandlerDestroying() {
  45. // clear all timers before unregistering from event bus
  46. this.clearNextTick();
  47. this.clearInterval();
  48. }
  49. protected onHandlerDestroyed() {}
  50. public hasInterval(): boolean {
  51. return !!this._tickInterval;
  52. }
  53. public hasNextTick(): boolean {
  54. return !!this._tickTimer;
  55. }
  56. /**
  57. * @param millis - Interval time (ms)
  58. * @eturns True when interval has been scheduled, false when already scheduled (no effect)
  59. */
  60. public setInterval(millis: number): boolean {
  61. if (!this._tickInterval) {
  62. this._tickCallCount = 0;
  63. this._tickInterval = self.setInterval(this._boundTick, millis);
  64. return true;
  65. }
  66. return false;
  67. }
  68. /**
  69. * @returns True when interval was cleared, false when none was set (no effect)
  70. */
  71. public clearInterval(): boolean {
  72. if (this._tickInterval) {
  73. self.clearInterval(this._tickInterval);
  74. this._tickInterval = null;
  75. return true;
  76. }
  77. return false;
  78. }
  79. /**
  80. * @returns True when timeout was cleared, false when none was set (no effect)
  81. */
  82. public clearNextTick(): boolean {
  83. if (this._tickTimer) {
  84. self.clearTimeout(this._tickTimer);
  85. this._tickTimer = null;
  86. return true;
  87. }
  88. return false;
  89. }
  90. /**
  91. * Will call the subclass doTick implementation in this main loop tick
  92. * or in the next one (via setTimeout(,0)) in case it has already been called
  93. * in this tick (in case this is a re-entrant call).
  94. */
  95. public tick(): void {
  96. this._tickCallCount++;
  97. if (this._tickCallCount === 1) {
  98. this.doTick();
  99. // re-entrant call to tick from previous doTick call stack
  100. // -> schedule a call on the next main loop iteration to process this task processing request
  101. if (this._tickCallCount > 1) {
  102. // make sure only one timer exists at any time at max
  103. this.tickImmediate();
  104. }
  105. this._tickCallCount = 0;
  106. }
  107. }
  108. public tickImmediate(): void {
  109. this.clearNextTick();
  110. this._tickTimer = self.setTimeout(this._boundTick, 0);
  111. }
  112. /**
  113. * For subclass to implement task logic
  114. * @abstract
  115. */
  116. protected doTick(): void {}
  117. }