Source: StateMachine/StateMachine.js

  1. // Copyright 2011 Tart. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview This class is an implementation of a Moore state machine that is common in digital electronics, etc.
  16. *
  17. * The state machine is by it's nature an event-driven system. Events that come at the right state will put the state
  18. * machine in another expected state.
  19. *
  20. * To keep the implementation simple, many details are omitted. The state machine doesn't keep a record of its
  21. * states, new states cannot be added after a state machine is started, etc. These rules do not prevent
  22. * the functioning of the machine though.
  23. *
  24. * This class is a base class and not intended to be used on its own. To use a state machine, the developer has to
  25. * subclass this base class and override the <code>createStates_</code> method. This method should hold the
  26. * initialization of states in the state machine and should return the first state the machine should begin with.
  27. *
  28. * This implementation features lazy loading in the sense that the <code>createStates_</code> method is not called
  29. * until the first call to <code>startMachine</code> method.
  30. *
  31. * Example usage:
  32. *
  33. * Foo.MooreMachine = function(){
  34. * goog.base(this);
  35. * }
  36. * goog.inherits(Foo.MooreMachine, tart.StateMachine);
  37. *
  38. * // Having an enumerated type for events helps readability
  39. * Foo.MooreMachine.Events = {
  40. * CLICK: '0',
  41. * DOUBLECLICK: '1'
  42. * }
  43. *
  44. * Foo.MooreMachine.prototype.createStates_ = function(){
  45. * var STATE1 = new tart.State(function(){
  46. * console.log("state 1");
  47. * });
  48. *
  49. * var STATE2 = new tart.State(function(){
  50. * console.log("state 2");
  51. * });
  52. *
  53. * STATE1.transitions[Foo.MooreMachine.Events.CLICK] = STATE2;
  54. * STATE2.transitions[Foo.MooreMachine.Events.DOUBLECLICK] = STATE1;
  55. *
  56. * this.addState_(STATE1);
  57. * this.addState_(STATE2);
  58. *
  59. * return STATE1;
  60. * }
  61. *
  62. * var myMachine = new Foo.MooreMachine();
  63. * myMachine.startMachine();
  64. *
  65. */
  66. goog.require('goog.array');
  67. goog.require('goog.pubsub.PubSub');
  68. goog.require('tart.State');
  69. goog.provide('tart.StateMachine');
  70. /**
  71. * Tart State Machine Class
  72. * @constructor
  73. * @extends {goog.pubsub.PubSub}
  74. */
  75. tart.StateMachine = function() {
  76. goog.base(this);
  77. /**
  78. * @type {Array.<string>}
  79. * @private
  80. */
  81. this.events_ = [];
  82. this.currentState = undefined;
  83. };
  84. goog.inherits(tart.StateMachine, goog.pubsub.PubSub);
  85. /**
  86. * Adds a state to the state machine.
  87. * @param {tart.State} state State to be added.
  88. * @protected
  89. */
  90. tart.StateMachine.prototype.addState = function(state) {
  91. for (var event in state.transitions) {
  92. goog.array.insert(this.events_, event);
  93. }
  94. };
  95. /**
  96. * Sets the current state disregarding the previous one, and executes it's function.
  97. * @param {tart.State} state State to be set as the current state.
  98. * @param {Array.<*>=} opt_args Arguments to pass to the new state.
  99. * @protected
  100. */
  101. tart.StateMachine.prototype.setCurrentState = function(state, opt_args) {
  102. opt_args = opt_args || [];
  103. this.currentState = state;
  104. this.currentState.fn.apply(this, opt_args);
  105. };
  106. /**
  107. * Starts the state machine. If it's the first time this function is called, it also lazy loads the states via
  108. * <code>createStates</code> method.
  109. */
  110. tart.StateMachine.prototype.startMachine = function() {
  111. if (this.currentState == undefined) {
  112. this.firstState = this.createStates();
  113. this.bindEvents_();
  114. this.setCurrentState(this.firstState);
  115. }
  116. };
  117. tart.StateMachine.prototype.reset = function() {
  118. this.firstState && this.setCurrentState(this.firstState);
  119. };
  120. /**
  121. * This should be overridden by child classes. It holds the initialization of states and is called when the
  122. * <code>startMachine</code> method is called for the first time.
  123. * @return {tart.State} The first state that the machine will start with.
  124. */
  125. tart.StateMachine.prototype.createStates = goog.abstractMethod;
  126. /**
  127. * Subscribes its <code>handleEvent_</code> function to its every event
  128. * @private
  129. */
  130. tart.StateMachine.prototype.bindEvents_ = function() {
  131. var self = this;
  132. for (var i = 0, l = self.events_.length; i < l; i++) {
  133. var eventName = self.events_[i];
  134. self.subscribe(eventName, self.handleEvent_(self, eventName));
  135. }
  136. };
  137. /**
  138. * Handles incoming events. If any of the events are in the transitions list for the current state, that transition's
  139. * target state becomes the current state.
  140. * @param {tart.StateMachine} self The State Machine instance (due to the nature of goog.pubsub.PubSub.subscribe,
  141. * this function loses its scope. This variable corrects it).
  142. * @param {string} event The event to handle.
  143. * @return {function()} Returns a closure to use as an eventHandler.
  144. * @private
  145. */
  146. tart.StateMachine.prototype.handleEvent_ = function(self, event) {
  147. return function() {
  148. var nextState = self.currentState.transitions[event];
  149. if (nextState) {
  150. self.setCurrentState(nextState, Array.prototype.slice.call(arguments));
  151. }
  152. }
  153. };