UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,313 lines (1,115 loc) 38.8 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2006, 2007, 2011 Derrell Lipman License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Derrell Lipman (derrell) ************************************************************************ */ /** * A finite state machine. * * See {@link qx.util.fsm.State} for details on creating States, * and {@link qx.util.fsm.Transition} for details on creating * transitions between states. */ qx.Class.define("qx.util.fsm.FiniteStateMachine", { extend : qx.core.Object, /** * @param machineName {String} The name of this finite state machine */ construct : function(machineName) { // Call our superclass' constructor this.base(arguments); // Save the machine name this.setName(machineName); // Initialize the states object this.__states = {}; // The first state added will become the start state this.__startState = null; // Initialize the saved-states stack this.__savedStates = []; // Initialize the pending event queue this.__eventQueue = []; // Initialize the blocked events queue this.__blockedEvents = []; // Create the friendlyToObject" object. Each object has as its property // name, the friendly name of the object; and as its property value, the // object itself. this.__friendlyToObject = {}; // Create the "friendlyToHash" object. Each object has as its property // name, the friendly name of the object; and as its property value, the // hash code of the object. this.__friendlyToHash = {}; // Create the "hashToFriendly" object. Each object has as its property // name, the hash code of the object; and as its property value, the // friendly name of the object. this.__hashToFriendly = {}; // Friendly names can be added to groups, for easy manipulation of // enabling and disabling groups of widgets. Track which friendly names // are in which group. this.__groupToFriendly = {}; // We also need to be able to map back from friendly name to the groups it // is in. this.__friendlyToGroups = {}; }, statics : { /** * Constants which may be values of the nextState member in the * transitionInfo parameter of the Transition constructor. */ StateChange : { /** When used as a nextState value, means remain in current state */ CURRENT_STATE : 1, /** * When used as a nextState value, means go to most-recently pushed state */ POP_STATE_STACK : 2, /** When used as a nextState value, means terminate this state machine */ TERMINATE : 3 }, /** * Constants for use in the events member of the transitionInfo parameter * of the Transition constructor. */ EventHandling : { /** * This event is handled by this state, but the predicate of a transition * will determine whether to use that transition. */ PREDICATE : 1, /** Enqueue this event for possible use by the next state */ BLOCKED : 2 }, /** * Debug bitmask values. */ DebugFlags : { /** Show events */ EVENTS : 1, /** Show transitions */ TRANSITIONS : 2, /** Show individual function invocations during transitions */ FUNCTION_DETAIL : 4, /** * When object friendly names are referenced but not found, show message */ OBJECT_NOT_FOUND : 8 } }, events : { /** * Fired when the finite state machine terminates. Data is the last state * before termination. */ "terminated" : "qx.event.type.Data" }, properties : { /** * The name of this finite state machine (for debug messages) */ name : { check : "String", nullable : true }, /** * The current state of the finite state machine. */ state : { check : "String", nullable : true }, /** * The previous state of the finite state machine, i.e. the state from * which we most recently transitioned. Note that this could be the same * as the current state if a successful transition brought us back to the * same state. */ previousState : { check : "String", nullable : true }, /** * The state to which we will be transitioning. This property is valid * only during a Transition's ontransition function and a State's onexit * function. At all other times, it is null. */ nextState : { check : "String", nullable : true }, /** * The maximum number of states which may pushed onto the state-stack. It * is generally a poor idea to have very many states saved on a stack. * Following program logic becomes very difficult, and the code can be * highly unmaintainable. The default should be more than adequate. * You've been warned. */ maxSavedStates : { check : "Number", init : 2 }, /** * Debug flags, composed of the bitmask values in the DebugFlags constant. * * Set the debug flags from the application by or-ing together bits, akin * to this: * * <pre class='javascript'> * var FSM = qx.util.fsm.FiniteStateMachine; * fsm.setDebugFlags(FSM.DebugFlags.EVENTS | * FSM.DebugFlags.TRANSITIONS | * FSM.DebugFlags.FUNCTION_DETAIL | * FSM.DebugFlags.OBJECT_NOT_FOUND); * </pre> */ debugFlags : { check : "Number", // Default: // (qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS | // qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS | // qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND) init : 7 } }, members : { __states : null, __startState : null, __eventQueue : null, __blockedEvents : null, __savedStates : null, __friendlyToObject : null, __friendlyToHash : null, __hashToFriendly : null, __groupToFriendly : null, __friendlyToGroups : null, __bEventProcessingInProgress : false, __bTerminated : true, /** * Checks whether the finite state machine is terminated or not. * * @return {Boolean} If the finite state machine is terminated. */ isTerminated : function() { return this.__bTerminated; }, /** * Add a state to the finite state machine. * * * @param state {qx.util.fsm.State} * An object of class qx.util.fsm.State representing a state which is to * be a part of this finite state machine. * * * @throws {Error} If the given state is not an instanceof of qx.util.fsm.State. * @throws {Error} If the given state already exists. */ addState : function(state) { // Ensure that we got valid state info if (!state instanceof qx.util.fsm.State) { throw new Error("Invalid state: not an instance of " + "qx.util.fsm.State"); } // Retrieve the name of this state var stateName = state.getName(); // Ensure that the state name doesn't already exist if (stateName in this.__states) { throw new Error("State " + stateName + " already exists"); } // Is this the first state being added? if (this.__startState == null) { // Yup. Save this state as the start state. this.__startState = stateName; } // Add the new state object to the finite state machine this.__states[stateName] = state; }, /** * Replace a state in the finite state machine. This is useful if * initially "dummy" states are created which load the real state table * for a series of operations (and possibly also load the gui associated * with the new states at the same time). Having portions of the finite * state machine and their associated gui pages loaded at run time can * help prevent long delays at application start-up time. * * * @param state {qx.util.fsm.State} * An object of class qx.util.fsm.State representing a state which is to * be a part of this finite state machine. * * @param bDispose {Boolean} * If <i>true</i>, then dispose the old state object. If <i>false</i>, * the old state object is returned for disposing by the caller. * * @return {Object} * The old state object if it was not disposed; otherwise null. * * @throws {Error} If the given state is not an instanceof of qx.util.fsm.State. */ replaceState : function(state, bDispose) { // Ensure that we got valid state info if (!state instanceof qx.util.fsm.State) { throw new Error("Invalid state: not an instance of " + "qx.util.fsm.State"); } // Retrieve the name of this state var stateName = state.getName(); // Save the old state object, so we can return it to be disposed var oldState = this.__states[stateName]; // Replace the old state with the new state object. this.__states[stateName] = state; // Did they request that the old state be disposed? if (bDispose) { // Yup. Mark it to be disposed. oldState._bNeedDispose = true; } return oldState; }, /** * Add an object (typically a widget) that is to be accessed during state * transitions, to the finite state machine. * * * @param friendlyName {String} * The friendly name to used for access to the object being added. * * @param obj {Object} * The object to associate with the specified friendly name * * @param groupNames {Array} * An optional list of group names of which this object is a member. * */ addObject : function(friendlyName, obj, groupNames) { var hash = qx.core.ObjectRegistry.toHashCode(obj); this.__friendlyToHash[friendlyName] = hash; this.__hashToFriendly[hash] = friendlyName; this.__friendlyToObject[friendlyName] = obj; // If no groupNames are specified, we're done. if (!groupNames) { return; } // Allow either a single group name or an array of group names. If the // former, we convert it to the latter to make the subsequent code // simpler. if (typeof (groupNames) == "string") { groupNames = [ groupNames ]; } // For each group that this friendly name is to be a member of... for (var i=0; i<groupNames.length; i++) { var groupName = groupNames[i]; // If the group name doesn't yet exist... if (!this.__groupToFriendly[groupName]) { // ... then create it. this.__groupToFriendly[groupName] = {}; } // Add the friendly name to the list of names in this group this.__groupToFriendly[groupName][friendlyName] = true; // If the friendly name group mapping doesn't yet exist... if (!this.__friendlyToGroups[friendlyName]) { // ... then create it. this.__friendlyToGroups[friendlyName] = []; } // Append this group name to the list of groups this friendly name is // in this.__friendlyToGroups[friendlyName].push(groupName); } }, /** * Remove an object which had previously been added by {@link #addObject}. * * * @param friendlyName {String} * The friendly name associated with an object, specifying which object * is to be removed. * */ removeObject : function(friendlyName) { var hash; var groupName; var objName; var bGroupEmpty; hash = this.__friendlyToHash[friendlyName]; // Delete references to any groups this friendly name was in if (this.__friendlyToGroups[friendlyName]) { for (var i = 0; i < this.__friendlyToGroups[friendlyName].length; i++) { groupName = this.__friendlyToGroups[friendlyName][i]; delete this.__groupToFriendly[groupName][friendlyName]; // Is the group empty now? bGroupEmpty = true; for (objName in this.__groupToFriendly[groupName]) { // The group is not empty. That's all we wanted to know. bGroupEmpty = false; break; } // If the group is empty... if (bGroupEmpty) { // ... then we can delete the entire entry delete this.__groupToFriendly[groupName]; } } delete this.__friendlyToGroups[friendlyName]; } // Delete the friendly name delete this.__hashToFriendly[hash]; delete this.__friendlyToHash[friendlyName]; delete this.__friendlyToObject[friendlyName]; }, /** * Retrieve an object previously saved via {@link #addObject}, using its * Friendly Name. * * * @param friendlyName {String} * The friendly name of the object to be retrieved. * * @return {Object} * The object which has the specified friendly name, or undefined if no * object has been associated with that name. */ getObject : function(friendlyName) { return this.__friendlyToObject[friendlyName]; }, /** * Get the friendly name of an object. * * * @param obj {Object} * The object for which the friendly name is desired * * @return {String} * If the object has been previously registered via {@link #addObject}, * then the friendly name of the object is returned; otherwise, null. */ getFriendlyName : function(obj) { var hash = obj ? qx.core.ObjectRegistry.toHashCode(obj) : null; return hash ? this.__hashToFriendly[hash] : null; }, /** * Retrieve the list of objects which have registered, via {@link #addObject} * as being members of the specified group. * * * @param groupName {String} * The name of the group for which the member list is desired. * * @return {Array} * An array containing the friendly names of any objects which are * members of the specified group. The resultant array may be empty. */ getGroupObjects : function(groupName) { var a = []; for (var name in this.__groupToFriendly[groupName]) { a.push(name); } return a; }, /** * Display all of the saved objects and their reverse mappings. * */ displayAllObjects : function() { for (var friendlyName in this.__friendlyToHash) { var hash = this.__friendlyToHash[friendlyName]; var obj = this.getObject(friendlyName); this.debug(friendlyName + " => " + hash); this.debug(" " + hash + " => " + this.__hashToFriendly[hash]); this.debug(" " + friendlyName + " => " + this.getObject(friendlyName)); this.debug(" " + this.getObject(friendlyName) + " => " + this.getFriendlyName(obj)); } }, /** * Get internal data for debugging * * @return {Map} * A map containing the following: * __states * __startState * __eventQueue * __blockedEvents * __savedStates * __friendlyToObject * __friendlyToHash * __hashToFriendly * __groupToFriendly * __friendlyToGroups * __bEventProcessingInProgress */ _getInternalData : function() { return ( { "states" : this.__states, "startState" : this.__startState, "eventQueue" : this.__eventQueue, "blockedEvents" : this.__blockedEvents, "savedStates" : this.__savedStates, "friendlyToObject" : this.__friendlyToObject, "friendlyToHash" : this.__friendlyToHash, "hashToFriendly" : this.__hashToFriendly, "groupToFriendly" : this.__groupToFriendly, "friendlyToGroups" : this.__friendlyToGroups }); }, /** * Start (or restart, after it has terminated) the finite state machine * from the starting state. The starting state is defined as the first * state added to the finite state machine. * * @throws {Error} If the machine stared with not available state. */ start : function() { this.__bTerminated = false; var stateName = this.__startState; if (stateName == null) { throw new Error("Machine started with no available states"); } // Set the start state to be the first state which was added to the // machine this.setState(stateName); this.setPreviousState(null); this.setNextState(null); var debugFunctions = (this.getDebugFlags() & qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL); // Run the actionsBeforeOnentry actions for the initial state if (debugFunctions) { this.debug(this.getName() + "#" + stateName + "#actionsBeforeOnentry"); } this.__states[stateName].getAutoActionsBeforeOnentry()(this); // Run the entry function for the new state, if one is specified if (debugFunctions) { this.debug(this.getName() + "#" + stateName + "#entry"); } this.__states[stateName].getOnentry()(this, null); // Run the actionsAfterOnentry actions for the initial state if (debugFunctions) { this.debug(this.getName() + "#" + stateName + "#actionsAfterOnentry"); } this.__states[stateName].getAutoActionsAfterOnentry()(this); }, /** * Save the current or previous state on the saved-state stack. A future * transition can then provide, as its nextState value, the class * constant: * * <code> * qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK * </code> * * which will cause the next state to be whatever is at the top of the * saved-state stack, and remove that top element from the saved-state * stack. * * * @param state {Boolean|String} * When <i>true</i>, then push the current state onto the stack. This * might be used in a transition, before the state has changed. When * <i>false</i>, then push the previous state onto the stack. This * might be used in an on entry function to save the previous state to * return to. If this parameter is a string, it is taken to be the * name of the state to transition to. * * * @throws {Error} If the saved-state stack is full. */ pushState : function(state) { // See if there's room on the state stack for a new state if (this.__savedStates.length >= this.getMaxSavedStates()) { // Nope. Programmer error. throw new Error("Saved-state stack is full"); } if (state === true) { // Push the current state onto the saved-state stack this.__savedStates.push(this.getState()); } else if (state) { this.__savedStates.push(state); } else { // Push the previous state onto the saved-state stack this.__savedStates.push(this.getPreviousState()); } }, /** * Pop the saved state stack. * * @return {String|Boolean} * The name of a state or a boolean flag that had most recently been * pushed onto the saved-state stack. */ popState : function() { // Is there anything on the saved-state stack? if (this.__savedStates.length == 0) { // Nope. Programmer error. throw new Error("Saved-state stack is empty"); } return this.__savedStates.pop(); }, /** * Add the specified event to a list of events to be passed to the next * state following state transition. * * * @param event {qx.event.type.Event} * The event to add to the event queue for processing after state change. * */ postponeEvent : function(event) { // Add this event to the blocked event queue, so it will be passed to the // next state upon transition. this.__blockedEvents.unshift(event); }, /** * Enqueue an event for processing * * * @param event {qx.event.type.Event} * The event to be enqueued * * @param bAddAtHead {Boolean} * If <i>true</i>, put the event at the head of the queue for immediate * processing. If <i>false</i>, place the event at the tail of the * queue so that it receives in-order processing. * */ enqueueEvent : function(event, bAddAtHead) { // Add the event to the event queue if (bAddAtHead) { // Put event at the head of the queue this.__eventQueue.push(event); } else { // Put event at the tail of the queue this.__eventQueue.unshift(event); } if (this.getDebugFlags() & qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS) { // Individual objects are listed. Ensure target is a saved object var friendly = this.getFriendlyName(event.getTarget()); if (bAddAtHead) { this.debug(this.getName() + ": Pushed event: " + event.getType() + (friendly ? " on " + friendly : "")); } else { this.debug(this.getName() + ": Queued event: " + event.getType() + (friendly ? " on " + friendly : "")); } } }, /** * Event listener for all event types in the finite state machine * * @param event {qx.event.type.Event} The event that was dispatched. */ eventListener : function(event) { if (this.__bTerminated) { this.debug(this.getName() + ": Cannot listen to event '" + event.getType() + "', because the finite state machine is not running."); return; } // Events are enqueued upon receipt. Some events are then processed // immediately; other events get processed later. We need to allow the // event dispatcher to free the source event upon our return, so we'll // clone it and enqueue our clone. The source event can then be // disposed upon our return. var e = event.clone(); // Enqueue the new event on the tail of the queue this.enqueueEvent(e, false); // Process events this.__processEvents(); }, /** * Create an event and send it immediately to the finite state machine. * * @param type {String} * The type of event, e.g. "execute" * * @param target {qx.core.Object} * The target of the event * * @param data {Object|null} * The data, if any, to issue in the event. If this parameter is null * then a qx.event.type.Event is instantiated. Otherwise, an event of * type qx.event.type.Data is instantiated and this data is applied to * it. * */ fireImmediateEvent : function(type, target, data) { if (this.__bTerminated) { this.debug(this.getName() + ": Cannot listen to event '" + type + "', because the finite state machine is not running."); return; } if (data) { var event = qx.event.Registration.createEvent(type, qx.event.type.Data, [ data, null, false ]); } else { var event = qx.event.Registration.createEvent(type, qx.event.type.Event, [ false, false ]); } event.setTarget(target); this.eventListener(event); }, /** * Create and schedule an event to be sent to the finite state machine * "shortly". This allows such things as letting a progress cursor * display prior to handling the event. * * @param type {String} * The type of event, e.g. "execute" * * @param target {qx.core.Object} * The target of the event * * @param data {Object|null} * See {@link #fireImmediateEvent} for details. * * @param timeout {Integer|null} * If provided, this is the number of milliseconds to wait before firing * the event. If not provided, a default short interval (on the order * of 20 milliseconds) is used. * */ scheduleEvent : function(type, target, data, timeout) { qx.event.Timer.once( function() { this.fireImmediateEvent(type, target, data); }, this, timeout || 20); }, /** * Process all of the events on the event queue. * */ __processEvents : function() { // eventListener() can potentially be called while we're processing // events if (this.__bEventProcessingInProgress) { // We were processing already, so don't process concurrently. return ; } // Track that we're processing events this.__bEventProcessingInProgress = true; // Process each of the events on the event queue while (this.__eventQueue.length > 0) { // Pull the next event from the pending event queue var event = this.__eventQueue.pop(); // Run the finite state machine with this event var bDispose = this.__run(event); // If we didn't block (and re-queue) the event, dispose it. if (bDispose) { event.dispose(); } } // We're no longer processing events this.__bEventProcessingInProgress = false; }, /** * Run the finite state machine to process a single event. * * * @param event {qx.event.type.Event} * An event that has been dispatched. The event may be handled (if the * current state handles this event type), queued (if the current state * blocks this event type), or discarded (if the current state neither * handles nor blocks this event type). * * @return {Boolean} * Whether the event should be disposed. If it was blocked, we've * pushed it back onto the event queue, and it should not be disposed. * * @throws {Error} If the explicit transitions does not exist. * @throws {Error} If the transition returns an invalid value. * @throws {Error} If the next step will transit to an nonexistent state. * @throws {Error} If the state stack is empty and the next state is POP_STATE_STACK * @throws {Error} If the next state is invalid. */ __run : function(event) { // For use in generated functions... // State name variables var thisState; var nextState; var prevState; // The current State object var currentState; // The transitions available in the current State var transitions; // Events handled by the current State var e; // The action to take place upon receipt of a particular event var action; // Get the debug flags var debugFlags = this.getDebugFlags(); // Allow slightly faster access to determine if debug is enabled var debugEvents = debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.EVENTS; var debugTransitions = debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.TRANSITIONS; var debugFunctions = debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.FUNCTION_DETAIL; var debugObjectNotFound = debugFlags & qx.util.fsm.FiniteStateMachine.DebugFlags.OBJECT_NOT_FOUND; // Individual objects are listed. Ensure target is a saved object var friendly = this.getFriendlyName(event.getTarget()); if (debugEvents) { this.debug(this.getName() + ": Process event: " + event.getType() + (friendly ? " on " + friendly : "")); } // Get the current state name thisState = this.getState(); // Get the current State object currentState = this.__states[thisState]; // Get a list of the transitions available from this state transitions = currentState.transitions; // Determine how to handle this event e = currentState.getEvents()[event.getType()]; // See if we actually found this event type if (!e) { if (debugEvents) { this.debug(this.getName() + ": Event '" + event.getType() + "'" + " not handled. Ignoring."); } return true; } // We might have found a constant (PREDICATE or BLOCKED) or an object // with each property name being the friendly name of a saved object, // and the property value being one of the constants (PREDICATE or // BLOCKED). if (typeof (e) == "object") { if (!friendly) { // Nope, it doesn't seem so. Just discard it. if (debugObjectNotFound) { this.debug(this.getName() + ": Could not find friendly name for '" + event.getType() + "' on '" + event.getTarget() + "'"); } return true; } action = e[friendly]; // Do we handle this event type for the widget from which it // originated? if (! action) { // Nope. if (debugEvents) { this.debug(this.getName() + ": Event '" + event.getType() + "'" + " not handled for target " + friendly + ". Ignoring."); } return true; } } else { action = e; } switch(action) { case qx.util.fsm.FiniteStateMachine.EventHandling.PREDICATE: // Process this event. One of the transitions should handle it. break; case qx.util.fsm.FiniteStateMachine.EventHandling.BLOCKED: // This event is blocked. Enqueue it for later, and get outta here. if (debugEvents) { this.debug(this.getName() + ": Event '" + event.getType() + "'" + " blocked. Re-queuing."); } this.__blockedEvents.unshift(event); return false; default: // See if we've been given an explicit transition name if (typeof (action) == "string") { // Yup! Ensure that it exists if (transitions[action]) { // Yup. Create a transitions object containing only this // transition. var trans = transitions[action]; transitions = {}; transitions[action] = trans; } else { throw new Error("Explicit transition " + action + " does not exist"); } break; } } // We handle the event. Try each transition in turn until we find one // that is acceptable. for (var t in transitions) { var trans = transitions[t]; // Does the predicate allow use of this transition? switch(trans.getPredicate()(this, event)) { case true: // Transition is allowed. Proceed. break; case false: // Transition is not allowed. Try next transition. continue; case null: // Transition indicates not to try further transitions return true; default: throw new Error("Transition " + thisState + ":" + t + " returned a value other than " + "true, false, or null."); } // We think we can transition to the next state. Set next state. nextState = trans.getNextState(); if (typeof (nextState) == "string") { // We found a literal state name. Ensure it exists. if (!nextState in this.__states) { throw new Error("Attempt to transition to nonexistent state " + nextState); } // It exists. Track it being the next state. this.setNextState(nextState); } else { // If it's not a string, nextState must be a StateChange constant switch(nextState) { case qx.util.fsm.FiniteStateMachine.StateChange.CURRENT_STATE: // They want to remain in the same state. nextState = thisState; this.setNextState(nextState); break; case qx.util.fsm.FiniteStateMachine.StateChange.POP_STATE_STACK: // Switch to the state at the top of the state stack. if (this.__savedStates.length == 0) { throw new Error("Attempt to transition to POP_STATE_STACK " + "while state stack is empty."); } // Pop the state stack to retrieve the state to transition to nextState = this.__savedStates.pop(); this.setNextState(nextState); break; case qx.util.fsm.FiniteStateMachine.StateChange.TERMINATE: // Terminate fsm this.__bTerminated = true; this.setNextState(null); break; default: throw new Error("Internal error: invalid nextState"); } } // Run the actionsBeforeOntransition actions for this transition if (debugFunctions) { this.debug(this.getName() + "#" + thisState + "#" + t + "#autoActionsBeforeOntransition"); } trans.getAutoActionsBeforeOntransition()(this); // Run the 'ontransition' function if (debugFunctions) { this.debug(this.getName() + "#" + thisState + "#" + t + "#ontransition"); } trans.getOntransition()(this, event); // Run the autoActionsAfterOntransition actions for this transition if (debugFunctions) { this.debug(this.getName() + "#" + thisState + "#" + t + "#autoActionsAfterOntransition"); } trans.getAutoActionsAfterOntransition()(this); // Run the autoActionsBeforeOnexit actions for the old state if (debugFunctions) { this.debug(this.getName() + "#" + thisState + "#autoActionsBeforeOnexit"); } currentState.getAutoActionsBeforeOnexit()(this); // Run the exit function for the old state if (debugFunctions) { this.debug(this.getName() + "#" + thisState + "#exit"); } currentState.getOnexit()(this, event); // Run the autoActionsAfterOnexit actions for the old state if (debugFunctions) { this.debug(this.getName() + "#" + thisState + "#autoActionsAfterOnexit"); } currentState.getAutoActionsAfterOnexit()(this); // If this state has been replaced and we're supposed to dispose it... if (currentState._bNeedDispose) { // ... then dispose it now that it's no longer in use currentState.dispose(); } // It the fsm has terminated, stop right here if (this.__bTerminated) { if (debugFunctions) { this.debug(this.getName() + "#" + "TERMINATED"); } this.fireDataEvent("terminated", thisState); return true; } // Reset currentState to the new state object currentState = this.__states[this.getNextState()]; // set previousState and state, and clear nextState, for transition this.setPreviousState(thisState); this.setState(this.getNextState()); this.setNextState(null); prevState = thisState; thisState = nextState; nextState = undefined; // Run the autoActionsBeforeOnentry actions for the new state if (debugFunctions) { this.debug(this.getName() + "#" + thisState + "#autoActionsBeforeOnentry"); } currentState.getAutoActionsBeforeOnentry()(this); // Run the entry function for the new state, if one is specified if (debugFunctions) { this.debug(this.getName() + "#" + thisState + "#entry"); } currentState.getOnentry()(this, event); // Run the autoActionsAfterOnentry actions for the new state if (debugFunctions) { this.debug(this.getName() + "#" + thisState + "#autoActionsAfterOnentry"); } currentState.getAutoActionsAfterOnentry()(this); // Add any blocked events back onto the pending event queue for (var i=0; i<this.__blockedEvents.length; i++) { e = this.__blockedEvents.pop(); this.__eventQueue.unshift(e); } if (debugTransitions) { this.debug(this.getName() + "#" + prevState + " => " + this.getName() + "#" + thisState); } // See ya! return true; } if (debugTransitions) { this.debug(this.getName() + "#" + thisState + ": event '" + event.getType() + "'" + ": no transition found. No state change."); } return true; } }, destruct : function() { this._disposeArray("__eventQueue"); this._disposeArray("__blockedEvents"); this.__savedStates = this.__states = null; } });