PageRenderTime 51ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/com/flashsim/FStEng/Three/StateEngine.as

http://flash-state-engine.googlecode.com/
ActionScript | 503 lines | 179 code | 59 blank | 265 comment | 38 complexity | 617826397363a3bb116a29293b631492 MD5 | raw file
  1. /**
  2. ***********************************************
  3. EXAMPLE: Hierarchical State Machine Engine
  4. AUTHOR: Jonathan Kaye
  5. RELEASE: August, 2009
  6. Implementation originally conceived and developed DECEMBER 2001
  7. FILE: com.flashsim.FStEng.Three.StateEngine.as
  8. This is the main code for the Flash State Engine, created
  9. by Jonathan Kaye to accompany the book, "Flash for Interactive Simulation"
  10. (October, 2002), by Jonathan Kaye and David Castillo
  11. (Delmar Thomson Learning).
  12. Special thanks to Zjnue Brzavi for helping to tighten up the code.
  13. Copyright (c) 2009, Jonathan Kaye, All rights reserved.
  14. Redistribution and use in source and binary forms, with or without modification, are permitted
  15. provided that the following conditions are met:
  16. - Redistributions of source code must retain the above copyright notice, this list of conditions
  17. and the following disclaimer.
  18. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions
  19. and the following disclaimer in the documentation and/or other materials provided with the distribution.
  20. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
  21. WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  22. PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  23. ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  24. TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  25. HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  26. NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  27. POSSIBILITY OF SUCH DAMAGE.
  28. [New BSD License, http://www.opensource.org/licenses/bsd-license.php]
  29. For questions or comments, please refer to http://code.google.com/p/flashsim-state-engines/.
  30. *********************************************** ***
  31. */
  32. package com.flashsim.FStEng.Three {
  33. import flash.display.Sprite;
  34. import flash.events.*;
  35. import flash.utils.Timer;
  36. import flash.utils.Dictionary;
  37. /**
  38. * The StateEngine is the main data structure for the hierarchical state machine.
  39. * <p>The StateEngine encapsulates the functionality for a state machine (hierarchical or finite states).
  40. * Once you create an instance of a StateEngine, you then instantiate the state network as a series of
  41. * State's (simple states), HState's (hierarchical states), and HStateC's (Hierarchical, concurrent states).
  42. * States are connected with one another via a system of State Managers (<code>StateManager</code>). Each hierarchical state
  43. * is required to have a State Manager. Concurrent hierarchical states can have more than one manager, one to manage each
  44. * concurrent sub-network. The State Manager keeps track info needed for the immediate sub-network, such as which state is active, which was
  45. * the last state visited, etc.</p>
  46. * <p>To activate the state network, use <code>active = true;</code>, or <code>activate()</code>. To send events
  47. * into the StateEngine, use <code>PostEvent()</code>. Events are processed in a FIFO queue. The engine guarantees
  48. * that the network runs-to-completion before processing the next event in the queue.</p>
  49. * <p>If you only want a finite state machine, you can create a top state as a hierarchical
  50. * state (HState) and then just add simple states (State) to the network.</p>
  51. *
  52. *
  53. * @class StateEngine
  54. * @package com.flashsim.FStEng.Three
  55. * @author Jonathan Kaye
  56. * ***********************************************************************************/
  57. public class StateEngine extends EventDispatcher {
  58. /**
  59. * Event sent when the state engine gets activated.
  60. * @eventType ACTIVATE
  61. */
  62. public static const ACTIVATE:String = "ACTIVATE";
  63. /**
  64. * Event sent when the state engine gets deactivated.
  65. * @eventType DEACTIVATE
  66. */
  67. public static const DEACTIVATE:String = "DEACTIVATE";
  68. /**
  69. * Event sent when no state has handled an injected event.
  70. * <p>If an event is posted but no state has an event handler to handle it. To help the handler recognize which event went
  71. * unhandled, the StateEngine has an <code>event</code> property that hangs off of the EventWithData's data object. For example,
  72. * <code>myEventWithDataPtr.data.event</code> gives the Event object that went unhandled. The default handler can examine
  73. * that event and decide what the default behavior needs to be.</p>
  74. * @eventType UNHANDLED_EVENT
  75. */
  76. public static const UNHANDLED_EVENT:String = "UNHANDLED";
  77. /**
  78. * User-defined (String) name of the state engine.
  79. * @default "State Engine"
  80. */
  81. public var name:String; // state engine name
  82. /**
  83. * A Dictionary (flash.utils.Dictionary) of the currently active states.
  84. * <p>The State objects are keys, with values being <code>true</code></p>.
  85. */
  86. public var activeStates:Dictionary;
  87. /**
  88. * Count used to know when to pass events up to concurrent (hier) states. When we pass an event
  89. * to an HStateC, we have to wait until all the sub-networks have had a chance to handle the event
  90. * before we give the opportunity to the ancestors.
  91. * @private
  92. */
  93. internal var _concEvCt:Dictionary;
  94. /**
  95. * An instance of a Sprite to use for frame-based pulse activities.
  96. * You can make pulse activities (PulseActivity) trigger based on an approximate time value (using Flash's
  97. * Timer class), or based on number of frames. We create a Sprite during initialization in case the
  98. * user wants to have frame-based timings (for which we need to listen for an ENTER_FRAME event).
  99. * @private
  100. */
  101. internal var spriteForFramePulse:Sprite;
  102. /**
  103. * Hash table of ids (keys) to their state. All state ID's must be unique.
  104. * @private
  105. */
  106. internal var statesByID:Object;
  107. private var stNet:HStateC; // topmost state in this engine's state network
  108. private var handlingEvent:Boolean; // true while an event is being processed, false otherwise
  109. private var eventsWaiting:Array; // Array of events waiting to be handled (new for version 3)
  110. private var unhandledEvent:EventWithData; // event object passed when there were no handlers for an event
  111. private var eventTimerDelay:Timer;
  112. private var topLevelStateNetworkMgr:StateManager; // state network's top manager
  113. private var networkPrepared:Boolean; // true when network has already had state engine pointers set in states, and transitions resolved.
  114. /**
  115. * Flag indicating if state engine is active.
  116. * <p>Setting this flag to true will activate the engine. Setting it to false will deactive the engine. Retrieving
  117. * the value will tell you if the state engine is active or not.
  118. * @default false
  119. */
  120. public function set active (f:Boolean) : void {
  121. if (f) {
  122. if (!_active) {
  123. activate();
  124. }
  125. } else {
  126. if (active) {
  127. deactivate();
  128. }
  129. }
  130. }
  131. /**
  132. * @private
  133. */
  134. public function get active () : Boolean {
  135. return _active;
  136. }
  137. /**
  138. * Actual flag saying if engine active or not.
  139. * @private
  140. */
  141. private var _active:Boolean;
  142. /**
  143. * Creates a new instance of a state engine data structure.
  144. * <p>This is the constructor. Make sure to hold onto the pointer to this somewhere safe (i.e., away from
  145. * the possibility that it will be garbage-collected). For example, you typically will set the return value
  146. * to a property of an class instance in your application. If you do not hold onto this, Flash may mark
  147. * the instance for garbage collection and your network could start acting strange.</p>
  148. *
  149. * @method new StateEngine
  150. * @param n Optional name (String) for the state engine. Default is "State Engine".
  151. * @param stateNetwork Optional top state (HState, HStateC, or State [under rare circumstances]) of the network.
  152. * A top state is mandatory, but supplying it in the constructor is optional. If you don't
  153. * supply it here, you must use <code>setStateNetwork()</code> and pass in the top state.
  154. * @example <listing> // this includes top state in instantiation<br>
  155. * se = new StateEngine("engine1", new HState("TOP CONTAINER STATE", 0));</listing>
  156. * @example <listing>// this assumes top state pointer will be supplied via setStateNetwork()<br>
  157. * se = new StateEngine("engine1");</listing>
  158. */
  159. public function StateEngine(n:String = "State Engine", stateNetwork:HStateC = null) {
  160. name = n;
  161. stNet = null;
  162. _active = false;
  163. eventsWaiting = [];
  164. handlingEvent = false;
  165. unhandledEvent = new EventWithData(StateEngine.UNHANDLED_EVENT);
  166. activeStates = new Dictionary(true);
  167. spriteForFramePulse = new Sprite();
  168. networkPrepared = false;
  169. statesByID = new Object();
  170. eventTimerDelay = new Timer(0); // used by default for delaying event handling
  171. eventTimerDelay.addEventListener(TimerEvent.TIMER_COMPLETE, timeDelayElapsed, false, 0, true);
  172. // Create the first state manager, by default. This is done as a convenience.
  173. if (stateNetwork != null) {
  174. setStateNetwork(stateNetwork);
  175. topLevelStateNetworkMgr = new StateManager("top level State Network Manager", null, stateNetwork.id);
  176. stateNetwork.myStMgr = topLevelStateNetworkMgr;
  177. }
  178. }
  179. /**
  180. * Attaches the state network (referred to by topmost state) to this state engine.
  181. * You can use this method as an alternative to supplying the top state's pointer in the StateEngine constructor.
  182. * @method setStateNetwork
  183. * @param sn Pointer to topmost state (virtually always HState or HStateC, if you need it to be State, cast your State into an HStateC) for the state network.
  184. * @param suppressReassign Optional parameter that locks out possibility to change the top state, once it has been assigned. If an
  185. * attempt is made to change the name, the routine throws an Error.
  186. * @example <listing>myTopStatePtr = new HState("State Network", 0);<br>se.setStateNetwork(myTopStatePtr);</listing>
  187. */
  188. public function setStateNetwork (sn:HStateC, suppressReassign:Boolean = false) : void {
  189. if (topLevelStateNetworkMgr == null) {
  190. stNet = sn;
  191. topLevelStateNetworkMgr = sn.myStMgr;
  192. } else if (suppressReassign != true) {
  193. throw new Error("ERROR: Trying to set " + sn.name + " as top state of state engine, but " + stNet.name + " has been set already.");
  194. }
  195. }
  196. /**
  197. * Topmost state in the state network.
  198. * Set and retrieve the topmost state (HState, HStateC) in the state network. The top state can be a State, but that would
  199. * be a pretty boring state machine. If you want the top state to be a State, you can cast your State into an HStateC and pass
  200. * it in, since HStateC is a sub-state of State.
  201. *
  202. * You can set this parameter as an alternative to supplying the top state's pointer in the StateEngine constructor.
  203. * @default null
  204. */
  205. public function set topState (s:HStateC) : void {
  206. stNet = s;
  207. }
  208. /**
  209. *
  210. * @private
  211. */
  212. public function get topState () : HStateC {
  213. return stNet;
  214. }
  215. /**
  216. * Removes memory associated with the state engine passed in.
  217. * @method removeStateEngine
  218. */
  219. public function removeStateEngine () : void {
  220. stNet.removeState();
  221. eventTimerDelay = null;
  222. }
  223. /**
  224. Activates the state engine.
  225. <p>Typically you will use the <code>active</code> property to activate <code>active = true;</code> your state engine. However,
  226. you can use this method all the same. When an engine is activated, it combs through the state network and
  227. clears all the history markers by default. If you don't want this to happen, you cannot use <code>active</code>, you
  228. must use this routine and set the 'resetHistory' parameter to <code>false</code>.</p>
  229. @method activate
  230. @param resetHistory (optional) Set this to 'false' if you do not want to reset the history mechanism throughout the system. Default is true.
  231. */
  232. public function activate(resetHistory:Boolean = true) : void {
  233. var i:int, j:int;
  234. // Clear all history variables to restart anew
  235. if (resetHistory) {
  236. stNet.resetHistory();
  237. }
  238. if (!networkPrepared) {
  239. prepareNetwork();
  240. }
  241. _active = true;
  242. if (this.hasEventListener(StateEngine.ACTIVATE)) {
  243. dispatchEvent(new EventWithData(StateEngine.ACTIVATE));
  244. }
  245. // Setup the outermost manager to active.
  246. stNet.myStMgr.cs = stNet.id;
  247. stNet.myStMgr.active = true;
  248. // Enter the state network.
  249. stNet.enter();
  250. }
  251. /**
  252. * Initializes the states and transitions before the engine activates the first time.
  253. * <p>The State Engine must do two things before activating:</p><ol>
  254. * <li>Place a state engine pointer in each state (since State's are not related to the State Engine, but States need access)
  255. * <li>Pre-compute the paths of transitions, to make their execution as efficient as possible.
  256. * </ol>
  257. * <p>This routine accomplishes both tasks. If the developer wants to avoid the time it takes to perform these
  258. * when the engine activates the first time, he should call this routine. Once complete, the developer would activate the engine.
  259. * If the developer does not call this routine, the engine activation will call it automatically.</p>
  260. * @method prepareNetwork.
  261. */
  262. public function prepareNetwork () : void {
  263. // Go through all the substates to set their state engine ptr to us, and add their states to the master table
  264. setSubState2SE(stNet);
  265. computeTransitionPaths();
  266. networkPrepared = true;
  267. }
  268. /**
  269. Set the _se property of all states to the controlling state engine. This is necessary because state engine
  270. keeps track of the active states.
  271. @method setSubState2SE
  272. @param stPtr State being currently registered.
  273. @private
  274. */
  275. internal function setSubState2SE (stPtr:State) : void {
  276. // If this step has been done already, skip it!
  277. if (stPtr._se != null) {
  278. return;
  279. } else {
  280. stPtr.registerSubStates(this);
  281. }
  282. }
  283. /**
  284. * When developers specify transitions, the engine does not compute the path to the transition. The engine
  285. * knows that when it gets activated, all the states should be present. Therefore, at that time, it can
  286. * compute and store the path from the source to the target. This means that there is a brief delay when
  287. * the engine is activated -- to run through and resolve all transitions. If a developer wants to speed things
  288. * up, he can call this routine before activation, thereby removing the need to compute the paths at activation.
  289. * @method computeTransitionPaths
  290. */
  291. private function computeTransitionPaths () : void {
  292. var sp:State, ta:Array, tpi:int;
  293. // Go through all the states, looking for transitions. When we find them, compute the transition paths.
  294. for (var s:String in statesByID) {
  295. sp = statesByID[s];
  296. if (sp.transitions != null) {
  297. for (var tid:String in sp.transitions) {
  298. ta = sp.transitions[tid];
  299. for (tpi = 0; tpi < ta.length; tpi++) {
  300. Transition(ta[tpi]).determineTransition();
  301. }
  302. }
  303. }
  304. }
  305. }
  306. /**
  307. Deactivate the state engine.
  308. <p>Typically you will use the <code>active</code> property to deactivate <code>active = false;</code> your state engine.
  309. @method deactivate
  310. @access public
  311. */
  312. public function deactivate () : void {
  313. stNet.leave();
  314. // Setup the outermost manager to deactive.
  315. stNet.myStMgr.cs = null;
  316. stNet.myStMgr.active = false;
  317. // Clear the event timer, if active
  318. eventTimerDelay.stop();
  319. //
  320. if (this.hasEventListener(StateEngine.DEACTIVATE)) {
  321. dispatchEvent(new EventWithData(StateEngine.DEACTIVATE));
  322. }
  323. _active = false;
  324. }
  325. /**
  326. * Main routine for injecting an event into the state network.
  327. * <p>The state engine sits in the current state(s) and basically waits until some event comes in. This method
  328. * is how you inject an event into the network. When you pass in an event, this routine sends the event to
  329. * the active state(s) in the network. If that/those state(s) don't handle it, the event bubbles up to the
  330. * parent to take a shot at handling the event, and so on, up to the topmost state. If no state handles the event,
  331. * the state engine generates a special event, <code>StateEngine.UNHANDLED_EVENT</code>, which you can subscribe to. In
  332. * that way, the caller can tell if any state handled the event or not.</p>
  333. * <p>Events are Priority FIFO-queued. The engine guarantees run-to-completion by verifying no event is currently being handled
  334. * when the engine injects the event. If a new event comes in while one is already being handled, the event gets queued.</p>
  335. * <p>Callers may indicate to postpone an event for a set amount of time (in milliseconds). This might be useful, for example, if you need to space
  336. * out the time a bit due to Flash needing a moment to get to the next frame.</p>
  337. * <p>Events can be assigned priorities, so that higher-priority (lower number) events can jump in line in front of lower-priority
  338. * events. However, because run-to-completion must be observed, there is no way to interrupt an event being handled currently
  339. * even with a higher-priority event.</p>
  340. @method postEvent
  341. @param ev Event object (pointer)
  342. @param timeDelay Amount of time (in milliseconds) to wait after finishing handling the prior event, and when this event gets processed. Default value is 0 (immediately).
  343. @param pty Integer indicating the priority of the event. By default, all events are given a priority of 1000.
  344. @acccess public
  345. */
  346. public function postEvent (ev:Event, timeDelay:Number = 0, pty:int = 1000) : void {
  347. var i:int = 0;
  348. var newEvent:Object = { event: ev, priority: pty, delay: timeDelay };
  349. // Add new event in order of its priority.
  350. for (i = 0; i < eventsWaiting.length; i++) {
  351. if (eventsWaiting[i].priority > pty) {
  352. eventsWaiting.splice(i, 0, newEvent);
  353. trace(">>>>>>>>>>>>>>>>>>>>>> FOUND CUTTER");
  354. break;
  355. }
  356. }
  357. if (i == eventsWaiting.length) {
  358. eventsWaiting.push(newEvent);
  359. }
  360. if (!handlingEvent) {
  361. handlingEvent = true;
  362. processEvents();
  363. }
  364. }
  365. /**
  366. This is the main event handler for the state engine. It used to be called "ieh".
  367. In version 1 of FStEng, developers were supposed to override this method with their
  368. own event handler that found the active state(s) and executed actions, transitions, and
  369. activities. In version 1.5, however, the event handling was modified to alleviate
  370. this meticulous burden from the developer, at the expense of some small time expense.
  371. In version 1.5, the default ieh() method sends the event to the active
  372. state(s) onEvent method. The state, by default, runs through its transition
  373. and internal action triggers. If any are true, then it fires those actions. See the
  374. description of onEvent in the state documentation.
  375. The developer does not override the ieh() method, unless the developer
  376. needs to optimize the event handling at a global level.
  377. @method processEvent
  378. @private
  379. */
  380. private function processEvents () : void {
  381. while (eventsWaiting.length > 0) {
  382. if (eventsWaiting[0].delay != 0) {
  383. // if this event should be fired after some delay, setup the timer to come back to processEvents.
  384. eventTimerDelay.delay = eventsWaiting[0].delay;
  385. eventTimerDelay.repeatCount = 1;
  386. // when the timer returns to this routine, the delay will be zero and the event will be fired.
  387. eventsWaiting[0].delay = 0;
  388. // start the delay
  389. eventTimerDelay.start();
  390. break;
  391. } else {
  392. // Handle the first event from the queue
  393. var ev:Event = eventsWaiting[0].event;
  394. var astates:Dictionary = new Dictionary(true);
  395. var handled:Boolean = false;
  396. for (var sp:* in this.activeStates) {
  397. astates[sp] = activeStates[sp];
  398. }
  399. eventsWaiting.splice(0, 1);
  400. // clear our storage space for knowing when to pass up events to concurrent states
  401. _concEvCt = new Dictionary();
  402. // dispatch the event to the current active state(s). We copy the activeStates before
  403. // dispatching the event because it is possible that during processing, a transition
  404. // is triggered and the real activeStates gets updated, then we would send the same event
  405. // erroneously to the new active state.
  406. for (var activeSt:* in astates) {
  407. handled = State(activeSt).onEvent(ev, false) || handled;
  408. }
  409. if (!handled) {
  410. // Pass along the unhandled event in the event property of data
  411. unhandledEvent.data.event = ev;
  412. dispatchEvent(unhandledEvent);
  413. }
  414. _concEvCt = null;
  415. }
  416. }
  417. // Clearing the handlingEvent flag, so future event postings invoke processEvent. We clear the
  418. // flag here rather than in postEvent because an event could have been postponed and when the handling resumes, we get called.
  419. if (eventsWaiting.length == 0) {
  420. handlingEvent = false;
  421. }
  422. }
  423. /**
  424. * Called when the event delay timer has elapsed, telling us it is time to resume event processing.
  425. * @method timeDelayElapsed
  426. * @private
  427. */
  428. private function timeDelayElapsed (ev:Event) : void {
  429. processEvents();
  430. }
  431. }
  432. }