core/finite-state-machine.js

  1. /**
  2. * Creates finite state machines.
  3. * @template {string} TypeGeneric The generic type of the names of a state.
  4. *
  5. * @example
  6. *
  7. * const toggle = new FiniteStateMachine([
  8. *
  9. * {
  10. * $state: 'ON',
  11. * $transitions: [{
  12. *
  13. * $state: 'OFF',
  14. * $condition: ({$timer}) => ($timer >= 1000)
  15. * }]
  16. * },
  17. * {
  18. * $state: 'OFF',
  19. * $transitions: [{
  20. *
  21. * $state: 'ON',
  22. * $condition: ({$timer}) => ($timer >= 1000)
  23. * }]
  24. * }
  25. * ]);
  26. *
  27. * toggle.tick(timetick);
  28. */
  29. class FiniteStateMachine {
  30. /**
  31. * @callback TypeStateHandlerEnter A state entering handler.
  32. * @param {Object} $parameters The given parameters.
  33. * @param {TypeGeneric} $parameters.$previous The previous state.
  34. * @returns {void}
  35. * @protected
  36. *
  37. * @memberof FiniteStateMachine
  38. */
  39. /**
  40. * @callback TypeStateHandlerLeave A state leaving handler.
  41. * @param {Object} $parameters The given parameters.
  42. * @param {number} $parameters.$timer The timer of the current state.
  43. * @param {TypeGeneric} $parameters.$next The next state.
  44. * @returns {void}
  45. * @protected
  46. *
  47. * @memberof FiniteStateMachine
  48. */
  49. /**
  50. * @callback TypeStateHandlerTick A state updating handler.
  51. * @param {Object} $parameters The given parameters.
  52. * @param {number} $parameters.$timetick The tick duration (in ms).
  53. * @param {number} $parameters.$timer The timer of the current state.
  54. * @returns {void}
  55. * @protected
  56. *
  57. * @memberof FiniteStateMachine
  58. */
  59. /**
  60. * @callback TypeStateTransitionCondition A state transition condition.
  61. * @param {Object} $parameters The given parameters.
  62. * @param {TypeGeneric} $parameters.$previous The previous state.
  63. * @param {number} $parameters.$timer The timer of the current state.
  64. * @returns {boolean}
  65. * @protected
  66. *
  67. * @memberof FiniteStateMachine
  68. */
  69. /**
  70. * @typedef {Object} TypeStateTransition A transition to a state.
  71. * @property {TypeStateTransitionCondition} TypeStateTransition.$condition The condition to transition to given state.
  72. * @property {TypeGeneric} TypeStateTransition.$state The given state to transition to.
  73. * @protected
  74. *
  75. * @memberof FiniteStateMachine
  76. */
  77. /**
  78. * @typedef {Object} TypeState A state.
  79. * @property {TypeGeneric} TypeState.$state The name of the state.
  80. * @property {TypeStateHandlerEnter} [TypeState.$onEnter] The handler to execute when entering the state.
  81. * @property {TypeStateHandlerLeave} [TypeState.$onLeave] The handler to execute when leaving the state.
  82. * @property {TypeStateHandlerTick} [TypeState.$onTick] The handler to execute when updating the state.
  83. * @property {Array<TypeStateTransition>} [TypeState.$transitions] The transitions to given states.
  84. * @protected
  85. *
  86. * @memberof FiniteStateMachine
  87. */
  88. /**
  89. * Stores the initiated status.
  90. * @type {boolean}
  91. * @private
  92. */
  93. $initiated;
  94. /**
  95. * Stores the previous state.
  96. * @type {TypeState}
  97. * @private
  98. */
  99. $previous;
  100. /**
  101. * Stores the current state.
  102. * @type {TypeState}
  103. * @private
  104. */
  105. $state;
  106. /**
  107. * Stores the states.
  108. * @type {Map<TypeGeneric, TypeState>}
  109. * @private
  110. */
  111. $states;
  112. /**
  113. * Stores the timer of the current state.
  114. * @type {number}
  115. * @private
  116. */
  117. $timer;
  118. /**
  119. * Gets the name of the current state.
  120. * @type {TypeGeneric}
  121. * @public
  122. */
  123. get state() {
  124. return this.$state.$state;
  125. }
  126. /**
  127. * Gets the initiated status.
  128. * @type {boolean}
  129. * @public
  130. */
  131. get initiated() {
  132. return this.$initiated;
  133. }
  134. /**
  135. * Creates a new finite state machine.
  136. * @param {Array<TypeState>} $data The representation of the finite state machine.
  137. */
  138. constructor($data) {
  139. this.$initiated = false;
  140. this.$states = new Map();
  141. this.$timer = 0;
  142. $data.forEach(($state) => {
  143. this.$states.set($state.$state, $state);
  144. });
  145. }
  146. /**
  147. * Initiates the finite state machine.
  148. * @param {TypeGeneric} $state The name of the state to initiate.
  149. * @public
  150. */
  151. initiate($state) {
  152. if (this.$initiated === true) {
  153. return;
  154. }
  155. this.$previous = this.$state
  156. this.$state = this.$states.get($state);
  157. if (typeof this.$state.$onEnter === 'function') {
  158. this.$state.$onEnter({$previous: undefined});
  159. }
  160. this.$initiated = true;
  161. }
  162. /**
  163. * Updates the finite state machine by one tick update.
  164. * @param {number} $timetick The tick duration (in ms).
  165. * @public
  166. */
  167. tick($timetick) {
  168. if (this.$initiated === false) {
  169. return;
  170. }
  171. this.$timer += $timetick;
  172. if (typeof this.$state.$onTick === 'function') {
  173. this.$state.$onTick({$timetick: $timetick, $timer: this.$timer});
  174. }
  175. const transitions = this.$state.$transitions;
  176. if (Array.isArray(transitions) === false) {
  177. return;
  178. }
  179. for (let $transition of transitions) {
  180. let previous;
  181. if (typeof this.$previous !== 'undefined') {
  182. previous = this.$previous.$state;
  183. }
  184. const current = this.$state.$state;
  185. const next = $transition.$state;
  186. if ($transition.$condition({$previous: previous, $timer: this.$timer}) === true) {
  187. if (typeof this.$state.$onLeave === 'function') {
  188. this.$state.$onLeave({$timer: this.$timer, $next: next});
  189. }
  190. this.$timer = 0;
  191. this.$previous = this.$state;
  192. this.$state = this.$states.get(next);
  193. if (typeof this.$state.$onEnter === 'function') {
  194. this.$state.$onEnter({$previous: current});
  195. }
  196. this.tick(0);
  197. break;
  198. }
  199. }
  200. }
  201. }
  202. export {
  203. FiniteStateMachine
  204. };
  205. export default FiniteStateMachine;