diff --git a/automata.js b/automata.js index 37f4e6f..1e28d8f 100644 --- a/automata.js +++ b/automata.js @@ -5,13 +5,40 @@ * */ -/** - * requireJS available ??? - */ -var module = module || {}; (function (root) { + var TIMER_CHECK_RESOLUTION= 200; + + /** + * requireJS available ??? + */ + var module = {}; + + + /** + * @name TransitionCallback + * @type function + * @param state {FSM.State} + * @param transition {FSM.Transition} + * @param message {object} + */ + + /** + * @name StateCallback + * @type function + * @param state {FSM.State} + * @param transition {FSM.Transition} + * @param message {object} + */ + + /** + * @name StateTimeTransitionInfo + * @type object + * @param event {object} + * @param timeout {number} + */ + /** * Regular extension mechanism. @@ -81,42 +108,103 @@ var module = module || {}; var fsmContext= null; /** + * @name FSM + * @namespace + * * Local module definition. */ var FSM= {}; /** - * FSMTimerTask + * @memberOf FSM + * + * @class FSMTimerTask + * @classdesc * * This object encapsulates a task timer. * They are automatically defined by setting an onTimer block in a state definition. * * @constructor * @param session a session object - * @param event a message object. + * @param event a message object. * @param time an integer specifying milliseconds. - * @param id a unique integer value. */ - FSM.TimerTask= function( session, event, time, id ) { + FSM.TimerTask= function( session, event, time ) { + + /** + * Session to forward the event to on timeout. + * @name session + * @memberOf FSM.TimerTask + * @type {FSM.Session} + */ this.session= session; + + /** + * This event will be forwarded to the task session owner when timeout. + * This is an event to be sent to a transition. + * @name event + * @memberOf FSM.TimerTask + * @type {Object} + */ this.event= event; + + /** + * Milliseconds to consider this task expired. + * @name triggerTime + * @memberOf FSM.TimerTask + * @type {number} + */ this.triggerTime= time; - this.id= id; + /** + * TimerTask id. + * This id is returned whenever a timed-transition is set. Thus, timed events can be cancelled. + * @name id + * @memberOf FSM.TimerTask + * @type {number} + */ + this.id= __TimerIndex++; + + /** + * Cache session's current state. When the task times-out, it is checked whether the session is still in the + * same state. If so, the timeout event is sent. + * @name contextState + * @memberOf FSM.TimerTask + * @type {FSM.State} + */ this.contextState= session.getCurrentState(); + + /** + * Time when the timer task was created. More or less at scheduleTime + triggerTime the task times out. + * @mame scheduleTime + * @memberOf FSM.TimerTask + * @type {number} + */ this.scheduleTime= new Date().getTime(); + /** + * Internal flag of timer task validity. + * @name consumed + * @memberOf FSM.consumed + * @type {boolean} + */ + this.consumed = false; + return this; }; + /** + * @lend FSM.TimerTask.prototype + */ FSM.TimerTask.prototype= { - session : null, - contextState : null, - event : null, - triggerTime : 0, - scheduleTime : 0, - consumed : false, - id : 0, + + /** + * Get this task id. + * @returns {number} + */ + getId : function() { + return this.id; + }, /** * Has this timer task already been fired ? @@ -127,7 +215,7 @@ var module = module || {}; /** * Is this timer task on time so that it must be triggered ? - * @param t + * @param t {number} current time. */ isExpired : function( t ) { return this.scheduleTime + this.triggerTime < t; @@ -135,17 +223,20 @@ var module = module || {}; /** * This is the timer task control function. - * @param t + * @param t {number} current time. */ consume : function( t ) { + + // should never happen. how a consumed task could be resubmitted ? if ( this.isConsumed() ) { return true; } if ( this.isExpired( t ) ) { if ( this.contextState === this.session.getCurrentState() ) { - this.session.dispatch( this.event ); + this.session.consume( this.event ); } + // else, the session has already changed state and needs to dispatch the timeout message. this.consumed= true; return true; @@ -156,31 +247,54 @@ var module = module || {}; }; /** + * @memberOf FSM + * + * @class FSMContext + * @classdesc * - * FSMContext * FSMContext is the core of the Automata engine. It server as Finite State Machines registry, timer task * manager, FSM session creation, etc. - * It is intended to be a unique object. + * It is intended to be a unique object of this type. * * @constructor * */ FSM.FSMContext= function() { + /** + * Array of pending timer tasks. + * @name timerTask + * @memberOf FSM.FSMContext + * @type {Array} + */ this.timerTasks= []; + + /** + * Registry of State machines. + * From each entry a FSM session object can be built. + * + * @name registry + * @memberOf FSM.FSMContext + * @type {map} + */ this.registry= {}; + /** + * This timer is used to check all the TimerTask timeouts. + * @name timerId + * @memberOf FSM.FSMContext + * @type {number} + */ + this.timerId= root.setInterval( this.__checkTimers.bind(this), TIMER_CHECK_RESOLUTION ); + return this; }; + /** + * @lend FSM.FSMContext.prototype + */ FSM.FSMContext.prototype= { - timerId : null, - timerTasks : null, - registry : null, - - initialized : false, - /** * Check every FSM running session pending timer tasks. * @private @@ -199,14 +313,9 @@ var module = module || {}; /** * Initialize Automata's engine. + * @deprecated */ initialize : function() { - if ( this.initialized ) { - throw "Automata already initialized."; - } - - this.timerId= root.setInterval( this.__checkTimers.bind(this), 200 ); - this.initialized= true; return this; }, @@ -222,8 +331,8 @@ var module = module || {}; * Register a new FSM. * This is the first step to have a running FSM session in Automata engine. * - * @param name a FSM name. - * @param fsm an FSM object instance. + * @param name {string} a FSM name. + * @param fsm {FSM.FSM} an FSM object instance. */ registerFSM : function( name, fsm ) { if ( this.registry[name] ) { @@ -236,7 +345,7 @@ var module = module || {}; /** * Get a FSM.FSM registered instance. * - * @param name + * @param name {string} get a FSM.FSM previously registered object. * @private */ getFSM : function( name ) { @@ -245,9 +354,9 @@ var module = module || {}; /** * Create a given FSM session. - * @param fromFSM a FSM name. Must be previously registered by calling registerFSM function. - * @param args an array of parameters passed from context.createSession() - * @return an initialized session object. + * @param fromFSM {string} a FSM name. Must be previously registered by calling registerFSM function. + * @param args {Array.<*>} an array of parameters passed from context.createSession() + * @return {FSM.Session} an initialized session object. */ createSession : function( fromFSM, args ) { @@ -266,16 +375,16 @@ var module = module || {}; * * Should not be called directly. * - * @param session a session object - * @param event a message object - * @param time an integer indicating milliseconds. + * @param session {FSM.Session} a session object + * @param event {object} a message object + * @param time {number} an integer indicating milliseconds. * - * @return a unique timertask id. + * @return {number} a unique timertask id. */ addTimerTask : function( session, event, time ) { - var id= __TimerIndex++; - this.timerTasks.push( new FSM.TimerTask( session, event, time, id ) ); - return id; + var ttask= new FSM.TimerTask( session, event, time ); + this.timerTasks.push( ttask ); + return ttask.getId(); }, /** @@ -284,7 +393,7 @@ var module = module || {}; * * Should not be called directly. * - * @param id + * @param id {number} removes a timertask created by calling addTimerTask. */ removeTimerTask : function( id ) { for( var i=0; i + * @param m {TransitionCallback|string} */ setOnPreGuard : function( m ) { this.onPreGuard= m; return this; }, + /** + * Create a GuardException. + * @param msg {object} + */ createThrowable : function( msg ) { throw new FSM.GuardException(msg); }, @@ -364,7 +542,7 @@ var module = module || {}; /** * Set this transition's post guard function or function name form the logic object. * - * @param m + * @param m {TransitionCallback|string} */ setOnPostGuard : function( m ) { this.onPostGuard= m; @@ -373,7 +551,7 @@ var module = module || {}; /** * Set this transition's callback function executed when the transition is fired. - * @param m + * @param m {TransitionCallback|string} */ setOnTransition : function( m ) { this.onTransition= m; @@ -382,21 +560,23 @@ var module = module || {}; /** * Do this transition's pre-transition code - * @param msg - * @param session + * @param msg {object} + * @param session {FSM.Session} */ firePreTransition : function( msg, session) { if ( this.initialState!=null ) { this.initialState.callOnExit( session, this, msg ); } - session.callMethod( this.onTransition, this.initialState, this, msg ); + if ( this.onTransition ) { + session.callMethod(this.onTransition, this.initialState, this, msg); + } }, /** * Do this transition's post-transition code - * @param msg - * @param session + * @param msg {object} + * @param session {FSM.Session} */ firePostTransition : function( msg, session) { this.finalState.callOnEnter( session, this, msg ); @@ -406,23 +586,25 @@ var module = module || {}; * Do this transition's pre-transition code. Though it may seem equal to firePreTransition it is handled * in another function because an exception could be throws. In such case a pre-guard is assumed to have * been fired. - * @param msg - * @param session + * @param msg {object} + * @param session {FSM.Session} */ firePreTransitionGuardedByPostCondition : function( msg, session ) { if ( this.initialState!=null ) { this.initialState.callOnExit( session, this, msg ); } - session.callMethod( this.onTransition, this.initialState, this, msg); + if ( this.onTransition ) { + session.callMethod(this.onTransition, this.initialState, this, msg); + } }, /** * Do this transition's post-transition code. Though it may seem equal to firePreTransition it is handled * in another function because an exception could be throws. In such case a pre-guard is assumed to have * been fired. - * @param msg - * @param session + * @param msg {object} + * @param session {FSM.Session} */ firePostTransitionGuardedByPostCondition : function( msg, session ) { if ( this.initialState!=null ) { @@ -433,8 +615,8 @@ var module = module || {}; /** * Fire pre-Guard code. * If the method throws an exception, this transition is aborted as if it hadn't been fired. - * @param msg - * @param session + * @param msg {object} + * @param session {FSM.Session} */ checkGuardPreCondition : function( msg, session ) { session.callMethod( this.onPreGuard, this.initialState, this, msg ); @@ -444,8 +626,8 @@ var module = module || {}; * Fire post-Guard code. * If the method throws an exception, this transition is vetoed, and it will issue an auto-transition instead * of a state-to-state transition. - * @param msg - * @param session + * @param msg {object} + * @param session {FSM.Session} */ checkGuardPostCondition : function( msg, session ) { session.callMethod( this.onPostGuard, this.initialState, this, msg ); @@ -453,8 +635,8 @@ var module = module || {}; /** * Notify observers about this transition fire event. - * @param msg the message which fired this transition - * @param session + * @param msg {object} the message which fired this transition + * @param session {FSM.Session} * * @private */ @@ -463,7 +645,10 @@ var module = module || {}; this.initialState.callOnExit( session, this, msg ); } - session.callMethod( this.onTransition, this.initialState,this,msg ); + if ( this.onTransition ) { + session.callMethod(this.onTransition, this.initialState, this, msg); + } + this.finalState.callOnEnter( session, this, msg ); }, @@ -473,37 +658,98 @@ var module = module || {}; }; /** - * FSMState - * This object defines a FSM state. + * @memberOf FSM + * @class FSMState + * @classdesc + * + * This object defines a FSM state. There's a finite number of states, and each session can only be in one such + * State at the same time. + * + * @param name {string} state's name. * * @constructor */ FSM.State= function( name ) { - this.exitTransitions= { - count: 0 - }; - this.name= name || ( "state"+__StateIndex++ ); + /** + * Exiting transitions from this State. + * + * @type {map} + * @memberOf FSM.State + * @name exitTransitions + */ + this.exitTransitions= {}; + + /** + * Number of exit transitions. Needed to know which State is a final state (no exit transitions). + * @name FSM.State.exitTransitionsCount + * @type {number} + * @memberOf FSM.State + */ + this.exitTransitionsCount= 0; + + /** + * State name. + * + * @type {string} + * @memberOf FSM.State + * @name name + */ + this.name= name || ( "state"+__StateIndex++ ); - this.onEnter= this.name+"_enter"; - this.onExit= this.name+"_exit"; + /** + * On State Enter action. + * @type {string|StateCallback} + * @name onEnter + * @memberOf FSM.State + */ + this.onEnter= null; + + /** + * On State Exit action. + * @type {string|StateCallback} + * @name onEnter + * @memberOf FSM.State + */ + this.onExit= null; + + /** + * Described a timed transition to this State. + * @type {StateTimeTransitionInfo} + * @name onTimer + * @memberOf FSM.State + */ + this.onTimer= null; + + /** + * Whether this State is a whole FSM substate. (Nested FSM.FSM objects) + * + * @type {FSM.FSM} + * @name subState + * @memberOf FSM.State + */ + this.subState= null; return this; }; + /** + * @lend FSM.State.prototype + */ FSM.State.prototype= { - exitTransitions : null, - name : "", - onEnter : null, - onExit : null, - onTimer : null, - subState : null, + /** + * Get this state name. + * @returns {string} + */ + getName : function() { + return this.name; + }, /** * Add an exit transition to this State instance. * This transition must be uniquely added. - * @param tr + * @param tr {FSM.Transition} */ addTransition : function( tr ) { var event= tr.getEvent(); @@ -513,7 +759,7 @@ var module = module || {}; } this.exitTransitions[event]= tr; - this.exitTransitions.count++; + this.exitTransitionsCount++; return this; }, @@ -521,14 +767,16 @@ var module = module || {}; /** * Check whether this state has exiting transitions. * If not, will be defined as final. + * + * @return bool */ isFinalState : function() { - return this.exitTransitions.count===0; + return this.exitTransitionsCount===0; }, /** * Set this state's onEnter callback function. - * @param c + * @param c {string|StateCallback} */ setOnEnter : function( c ) { this.onEnter= c; @@ -537,7 +785,7 @@ var module = module || {}; /** * Set this state's onExit callback function. - * @param c + * @param c {string|StateCallback} */ setOnExit : function( c ) { this.onExit= c; @@ -546,7 +794,7 @@ var module = module || {}; /** * Add a timed transition to this state. - * @param c , event: > + * @param c {StateTimeTransitionInfo} */ setOnTimer : function( c ) { this.onTimer= c; @@ -554,7 +802,7 @@ var module = module || {}; /** * Get a transition for the defined typeof message. - * @param msg + * @param msg {string} */ getTransitionFor : function( msg ) { return this.exitTransitions[ msg.msgId ]; @@ -570,9 +818,9 @@ var module = module || {}; /** * Execute the procedure on entering this State. * It may seem to set a timer, and calling the optional onEnter callback function. - * @param session - * @param transition - * @param msg + * @param session {FSM.Session} + * @param transition {FSM.Transition} + * @param msg {object} */ callOnEnter : function( session, transition, msg ) { if ( this.onTimer ) { @@ -588,9 +836,9 @@ var module = module || {}; * Execute the procedure on exiting this State. * It may seem to reset a timer, and calling the optional onEnter callback function. * - * @param session - * @param transition - * @param msg + * @param session {FSM.Session} + * @param transition {FSM.Transition} + * @param msg {object} */ callOnExit : function( session, transition, msg ) { if( this.onTimer ) { @@ -608,37 +856,67 @@ var module = module || {}; }; /** - * FSM + * @memberOf FSM + * @class FSM + * @classdesc + * * FSM defines a complete finite state machine. * A FSM.FSM object extends a FSM.State object, so polymorphically a complete FSM is an State. This way, we can * supply with sub-states to Automata's engine. * + * There's just one instance for each FSM object. From FSM, sessions are created, which keep track of current + * state and session context data. The session context data is created by invoking the constructor function + * supplied as parameter. + * * @constructor + * @param sessionObjectFactory {Function} object factory + * @param name {string} FSM name * */ FSM.FSM= function( sessionObjectFactory, name ) { FSM.FSM.superclass.constructor.call(this, name); - this.sessionObjectFactory= sessionObjectFactory; + /** + * Session factory. + * + * @name sessionObjectFactory + * @memberOf FSM.FSM + * @type {Function} + */ + this.sessionObjectFactory= sessionObjectFactory; - this._onEnter= this.name+"_enter"; + /** + * @type {string} + * @private + */ + this._onEnter= this.name+"_enter"; + /** + * FSM initial transition. + * + * @name initialTransition + * @type {FSM.Transition} + * @memberOf FSM.FSM + */ + this.initialTransition= null; return this; }; + /** + * @lend FSM.FSM.prototype + */ FSM.FSM.prototype= { - sessionObjectFactory : null, - - initialTransition : null, - initialState : null, /** * Initialize a Finite State Machine. + * Create the initial transition to the supplied state. + * A FSM is a single State reached by calling the initial transition. This state drills down to the + * FSM definition. * - * @param initialState + * @param initialState {FSM.State} */ initialize : function( initialState ) { @@ -654,15 +932,27 @@ var module = module || {}; this.initialState= initialState; this.initialTransition= new FSM.Transition(__InitialTransitionId, null, initialState ); this.initialTransition.setOnTransition( function( session, state, transition, msg ) { - session.push( me.initialState ); + session.push( initialState ); }); }, + /** + * Set FSM on enter callback. + * @param m {string|StateCallback} + */ setOnEnter : function( m ) { this._onEnter= m; return this; }, + /** + * Override State callOnEnter. + * When a substate is entered, its onEnter action is called and then, substate's initial state's onEnter action. + * + * @param session {FSM.Session} + * @param transition {FSM.Transition} + * @param msg {object} + */ callOnEnter : function( session, transition, msg ) { session.callMethod( this._onEnter, this, transition, msg ); FSM.FSM.superclass.callOnEnter.call( this, session, transition, msg ); @@ -683,6 +973,8 @@ var module = module || {}; * I'm not happy with the semantics of manually calling a (supposed to be) initial transition. Will keep it * this way for the sake of simplicity, but will probably change this semantics in the future, * (by adding an Automata with just one substate) which could cause backward incompatibilities. + * + * @param args {object} session factory initialization parameters. */ createSession : function(args) { @@ -690,9 +982,7 @@ var module = module || {}; return null; } - var session= new FSM.Session( ); - var logic= new this.sessionObjectFactory(session, args); - session.setLogic( logic ); + var session= new FSM.Session( new this.sessionObjectFactory(session, args) ); session.push( this ); this.callOnEnter( session, null, null ); @@ -704,27 +994,42 @@ var module = module || {}; /** - * SessionContext - * A session context is just a holder for a current state across the different nesting levels of a given FSM. + * @memberOf FSM + * + * @class SessionContext + * @classdesc + * + * A session context is just a holder for a current state across the different nesting levels of an FSM. * This class is some sugar to deal with an State. * A FSM.Session is an stack of different contexts. * + * @param state {FSM.State} + * * @constructor */ FSM.SessionContext= function( state ) { + + /** + * Current context state. + * + * @name currentState + * @type {FSM.State} + * @memberOf FSM.SessionContext + */ this.currentState= state; return this; }; + /** + * @lend FSM.SessionContext.prototype + */ FSM.SessionContext.prototype= { - currentState : null, - /** * Set this context current state. * This method will be called by Automata's engine when a state change is fired. - * @param s + * @param s {FSM.State} */ setCurrentState : function( s ) { this.currentState= s; @@ -732,7 +1037,7 @@ var module = module || {}; /** * Get this context's current state. - * @return + * @return {FSM.State} */ getState : function() { return this.currentState; @@ -740,7 +1045,7 @@ var module = module || {}; /** * Get an exiting transition defined by this message for the current State. - * @param msg + * @param msg {object} */ getTransitionFor : function( msg ) { return this.currentState.getTransitionFor( msg ); @@ -748,9 +1053,9 @@ var module = module || {}; /** * Call this current State onExit callback function. - * @param session - * @param transition - * @param msg + * @param session {FSM.Session} + * @param transition {FSM.Transition} + * @param msg {object} */ exit : function( session, transition, msg) { this.currentState.callOnExit(session, transition, msg); @@ -760,43 +1065,164 @@ var module = module || {}; * Print this context current state info. */ printStackTrace : function() { - root.console.log(" "+this.currentState.name); + FSM.Log.d(" "+this.currentState.name); } }; /** - * FSM.Session - * A Session is the real artifact to deal with in Automata engine. - * A session must be created and will the core object to send messages to. Automata framework will take care - * of choreograph the calls, context push/pop, session observer notification, etc. + * @class Log + * @memberOf FSM + * + * logging facilities. + * There are 3 log levels: DEBUG, INFO, ERROR. + * Errors are hierarchically solved. DEBUG level will print all three types of log messages, + * INFO level only INFO and ERROR messages while ERROR just ERROR messages. + * + */ + FSM.Log = { + + __logLevel : 0, + + /** + * A constant to define DEBUG log level. + * + * @name DEBUG + * @memberOf Log + * @type {number} + */ + DEBUG : 0, + + /** + * A constant to define INFO log level. + * + * @name INFO + * @memberOf Log + * @type {number} + */ + INFO : 1, + + /** + * A constant to define ERROR log level. + * + * @name ERROR + * @memberOf Log + * @type {number} + */ + ERROR : 2, + + /** + * Set execution log level. + * @param l {FSM.Log.DEBUG | FSM.Log.INFO | FSM.Log.ERROR} + */ + setLogLevel : function( l ) { + this.__logLevel= l; + }, + + /** + * Print a debug message if the current log level allows for it. + * @param str {string} + */ + d : function( str ) { + if ( this.__logLevel<=this.DEBUG ) { + console.log("DEBUG: " + str); + } + }, + + /** + * Print a info message if the current log level allows for it. + * @param str {string} + */ + i : function( str ) { + if ( this.__logLevel<=this.INFO ) { + console.log("INFO: " + str); + } + }, + + /** + * Print an error message if the current log level allows for it. + * @param str {string} + */ + e : function( str ) { + if ( this.__logLevel<=this.ERROR ) { + console.log("ERROR: " + str); + } + } + }; + + /** + * @memberOf FSM + * + * @class Session + * @classdesc + * + * A Session is the real artifact to deal with the Automata engine. + * A session must be created for an FSM and will the core object to send messages to. + * Automata framework will take care choreographing calls, context push/pop, session observer notification, etc. * * @constructor + * + * @param logic {object} an object coming from the FSM session factory object. */ - FSM.Session= function( ) { + FSM.Session= function( logic ) { + /** + * Each sub-state accessed during the FSM execution will generated a new context object. + * This is the stack-trace of the different sub-states a FSM currently is in. + * @type {Array.} + * @name sessionContextList + * @memberOf FSM.Session + */ this.sessionContextList= []; + + /** + * A collection of session listener objects. + * A session listener exposes all information for activity, from creating context objects to setting properties, + * etc. + * + * @name sessionListeners + * @memberOf FSM.Session + * @type {Array.} + */ this.sessionListener= []; + + /** + * A map of key/value pairs. + * The only imposed property from the engine will be the FSM name itself, and will store the timed + * auto-transition timer ids. + * This is a general purpose map holder, use wisely. + * + * @name properties + * @memberOf FSM.Session + * @type {map} + */ this.properties= {}; + /** + * Session data. An object created form the FSM factory constructor function. + * + * @name logic + * @memberOf FSM.Session + * @type {object} an object returned from the FSM factory constructor. + */ + this.logic= logic; + + /** + * When a message is sent to a session, that message consumtion may fire new messages sent to the session. + * These messages are not consumed immediately. + * + * @name messages + * @memberOf FSM.Session + * @type {Array.} + */ + this.messages = []; + return this; }; FSM.Session.prototype= { - id : null, - sessionContextList : null, - sessionListener : null, - properties : null, - - transitioning : false, - - logic : null, - - setLogic : function( logic ) { - this.logic= logic; - }, /** * Never call this method directly. @@ -819,13 +1245,15 @@ var module = module || {}; } else { if ( typeof this.logic[method]!=="undefined" ) { this.logic[ method ].apply( this.logic, args ); + } else { + // no method with given name on session object data. } } }, /** * Add an observer to this session. - * @param sl + * @param sl {FSM.SessionListener} */ addListener : function( sl ) { this.sessionListener.push( sl ); @@ -833,7 +1261,7 @@ var module = module || {}; /** * Remove an observer from this session. - * @param sl + * @param sl {FSM.SessionListener} */ removeListener : function( sl ) { var pos= this.sessionListener.indexOf( sl ); @@ -844,7 +1272,9 @@ var module = module || {}; /** * Push and set up a new FSM.Context level. - * @param state + * The state must be an state from the FSM object this session belongs to. + * + * @param state {FSM.State} * * @private */ @@ -858,8 +1288,9 @@ var module = module || {}; /** * Pop and reset the last FSM.Context object level. - * @param transition the firing transition - * @param msg the message that triggered the transition + * + * @param transition {FSM.Transition} the firing transition + * @param msg {object} the message that triggered the transition * * @private */ @@ -876,32 +1307,32 @@ var module = module || {}; /** * Asynchronously consume a message. - * @param msg + * @param msg {object} + * @param endCallback {function} */ - dispatch : function( msg ) { - var me= this; - setTimeout( function() { - try { - me.processMessage( msg ); - } catch(e) { - throw e; - }; - }, 0 ); + consume : function( msg, endCallback ) { + this.messages.push( msg ); + if ( !this.transitioning ) { + this.__processMessages(endCallback); + } }, /** - * Synchronoulsy consume a message. - * @param msg + * Consume a message. + * A message consumption may imply more messages to be consumed. The callback will be invoked + * when no more messages are available to be processed. + * + * @param endCallback {function} a callback function fired when there're no pending messages to be processed. */ - processMessage : function( msg ) { - if ( this.transitioning ) { - throw "Processing message during transition"; - } + __processMessages : function( endCallback ) { if ( this.sessionContextList.length===0 ) { throw "Empty Session"; } + // remove first message + var msg= this.messages.shift(); + var firingTransition= null; // FSM.Transition var target= null; // FSM.SessionContext var i; @@ -925,7 +1356,8 @@ var module = module || {}; this.fireGuardPreCondition(firingTransition, msg, e); return; // fails on pre-guard. simply return. } else { - console.error("An error ocurred: "+ e.message, e.stack); + FSM.Log.e("An error ocurred: "+ e.message); + this.printStackTrace(); } } @@ -954,7 +1386,8 @@ var module = module || {}; this.pop( null, msg ); } } catch( ex ) { - console.error("An error ocurred: "+ ex.message, ex.stack); + FSM.Log.e("An error ocurred: "+ ex.message); + this.printStackTrace(); } } catch( guardException ) { if ( guardException instanceof FSM.GuardException ) { @@ -963,22 +1396,37 @@ var module = module || {}; this.fireStateChanged( target, firingTransition.initialState, msg ); firingTransition.firePostTransitionGuardedByPostCondition( msg, this ); } else { - console.error("An error ocurred: "+ guardException.message, guardException.stack); + FSM.Log.e("An error ocurred: "+ guardException.toString()); + this.printStackTrace(); + } + } + + + if ( this.messages.length===0 ) { + this.transitioning = false; + if ( endCallback ) { + endCallback(); } + } else { + // differ to next tick execution + setTimeout( this.consume.bind( this, endCallback ), 0 ); } - this.transitioning= false; }, /** * Get the current execution context. + * + * @return FSM.SessionContext current session context. */ getCurrentSessionContext : function() { return this.sessionContextList[ this.sessionContextList.length-1 ]; }, /** - * Get current's context state. + * Get current context's state. + * + * @return {FSM.State} current state. */ getCurrentState : function() { try { @@ -990,12 +1438,13 @@ var module = module || {}; /** * Print information about the context stack state. + * For each stacked context, its current state and information will be printed. */ printStackTrace : function() { if ( this.sessionContextList.length===0 ) { - root.console.log("session empty"); + FSM.Log.d("session empty"); } else { - root.console.log("session stack trace:"); + FSM.Log.d("session stack trace:"); for( var i=0; i - * @param value + * Add a property. Used as a holder for onTimer information, as well as any user-defined per-session information. + * + * @param key {string} + * @param value {object} */ addProperty : function( key, value ) { this.properties[key]= value; }, /** - * Remove a property. - * @param key + * Remove a property. The property will be nulled in the properties collection. + * + * @param key {string} */ removeProperty : function( key ) { - delete this.properties[ key ]; + this.addProperty( key, null ); }, + /** + * Get a property value. if it does not exist, undefined will be returned. + * Properties can exist with null values. + * + * @param key {string} property to get value. + * + * @return {object} + */ getProperty : function( key ) { return this.properties[key]; }, @@ -1095,7 +1554,11 @@ var module = module || {}; }; /** - * SessionListener + * @memberOf FSM + * + * @class SessionListener + * @classdesc + * * A template object to set a session object observer. * * @constructor @@ -1104,6 +1567,9 @@ var module = module || {}; return this; }; + /** + * @lend FSM.SessionListener.prototype + */ FSM.SessionListener.prototype= { contextCreated : function( obj ) {}, contextDestroyed : function( obj ) {}, @@ -1118,11 +1584,11 @@ var module = module || {}; * Create and initalize a fsmContext object. * This is the initial source of interaction with Automata engine. */ - fsmContext= new FSM.FSMContext().initialize(); + fsmContext= new FSM.FSMContext(); /** * Register a FSM in Automata engine. - * @param fsmd A FSM object definition. + * @param fsmd {object} A FSM object definition. */ function registerFSM( fsmd ) { var fsm= new FSM.FSM( fsmd.logic, fsmd.name ); @@ -1237,16 +1703,31 @@ var module = module || {}; return fsmContext.createSession( fsm, args ); } + function guardException( str ) { + return new FSM.GuardException(str); + } + + function newSessionListener( obj ) { + var s= new FSM.SessionListener(); + for( var method in obj ) { + if ( obj.hasOwnProperty(method) && "function"===typeof obj[method] ) { + s[method]= obj[method]; + } + } + return s; + } + /** * node module definition. */ var _export= { - registerFSM : registerFSM, - registerFDA : registerFSM, - createSession : createSession + registerFSM : registerFSM, + registerFDA : registerFSM, + createSession : createSession, + newGuardException : guardException, + newSessionListener : newSessionListener }; - if (typeof define!=='undefined' && define.amd) { // AMD / RequireJS define('async', [], function () { return _export; @@ -1260,4 +1741,6 @@ var module = module || {}; } + return _export; + })( typeof window!=='undefined' ? window : global ); diff --git a/changelog b/changelog index ad7b5f9..4ac6fff 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,17 @@ +07-02-2015 *1.1.0* +------------------ + +Major changes. + +* Session.dispatch and Session.processMessage dissapear in favor of Session.consume +* Session.consume accepts a callback function triggered when the FDA ends executing its message queue. +* All examples are working. +* Added methods newGuardException and newSessionListener to the module object. +* Added documentation for all object variables. +* Added doclet annotations. +* Checked readme.md +* Guards must throw a GuardException and not any exception. This exception can be created by calling module.newGuardException. + 04-02-2013 *1.0.7* ------------------ @@ -26,7 +40,7 @@ transition postGuard: transition.event+"_postGuard" * Top level FSM now have correct name. The one they're registered with. -* Fixed a bug that didn't passed properly the state on FSM enter to callbacks. +* Fixed a bug that didn't pass properly the state on FSM enter to callbacks. 04-15-2012 *1.0.4* ------------------ diff --git a/package.json b/package.json index 73fa3d2..28f5d1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "automata", - "version": "1.0.7", + "version": "1.1.0", "main": "automata", "keywords": [ "DFA", @@ -8,7 +8,7 @@ "automata" ], "author": "hyperandroid (http://labs.hyperandroid.com)", - "description": "Automata is a Deterministic Finite State Machine automata framework featuring: a JSON based automata creation, timed transitions, sub-states, guards, FSM registry, etc.", + "description": "Automata is a Deterministic Finite State Machine automata framework featuring: a JSON based automata definition, timed transitions, sub-states, guards, FSM registry, etc.", "repository": { "type": "git", "url": "git://github.com/hyperandroid/Automata.git" diff --git a/readme.md b/readme.md index 3dd97c3..a51bfa0 100644 --- a/readme.md +++ b/readme.md @@ -16,23 +16,38 @@ finite state machine framework like ##How to -Automata is valid to be used directly as a node js module or directly on browsers. +Automata works on browsers or Node. + To get it: * npm install automata * include automata.js script file -Automata exposes an object with two functions: +Automata will then expose an object with some functions: ```javascript module.exports= { - registerFSM - createSession + registerFSM, // register a FSM object. + registerFDA, // same as registerFSM + createSession, // create a session for an FSM + guardException, // create a guard exception + newSessionListener // create a session listener overriding methods with the parameter object. } ``` -First of all, one or more FSM must be registered in the system. One State must be labeled as initial, -which will be the entry point. A minimal state machine could be: +##How it works + +In Automata, there will be a single instance of every FSM. Think of the FSM as the class or template to build an +automata. From this unique FSM, you can create an undefined amount of sessions. Each session will track the current + State, and the session data. +The Session and its data is created by supplying the FSM definition with a factory constructor function. + +First of all, one or more FSM must be registered in the system by calling either registerFSM ( +register finite state machine) or registerFDA (register finite deterministic automaton). Both methods do the same, but + i prefer calling registerFDA. +In the FDA definition one State must be labeled as initial. This will be the entry point. + +A minimal state machine could be: ```javascript fsmContext.registerFSM( { @@ -71,44 +86,46 @@ fsmContext.registerFSM( { } ); ``` -To start using this machine, a FSM session must created out of a registered FSM. For example: +To start using this machine, a FSM session must be created from a registered FSM. For example: ```javascript var session= fsmContext.createSession("Test"); ``` -To send notification events to a session object, call either: +To send notification events to a session object, call consume method: ```javascript +session.consume( { msgId: "12" } ); +``` -// asynchronous call ( setTimeout with 0 ) -session.dispatch( { msgId: "12" } ); +By consuming a message in the FDA, new messages being dispatched to the session can be created. Each successive +message will be consumed in the next execution tick. This is why you can call: -// synchronous call -session.processMessage( {msgId: "12"} ); +```javascript +session.processMessage( {msgId: "12"}, function consumeEndCallback() { + // the session has no more pending messages to be consumed. + }); ``` -These methods accept as a valid message any object which contains a field called **msgId**. To trigger a transition, - any message object's msgId value must be the value defined in the **event** attribute present in the transition - FSM definition block. +One important thing to note is that the FDA does not really know whether the queued-for-consumption messages come +from consuming a message or from external events. In either case, the **consumeEndCallback** won't be invoked until +the session's message queue is empty. + +This method accept as a valid message any object which contains a field called **msgId**. To trigger a transition, + any message object's msgId value must be the value defined in the **event** attribute present in the Transition + FDA definition block. A session accepts messages until it has reached a final State at its top level. From then and beyond, the session will -toss exceptions if it has a message sent. +toss exceptions if it has a message sent for consumption. ##Logic object -The FSM logic and state are isolated. The developer supplies a custom object to the FSM via the **logic** value. -This must be a constructor function and will create a new object per **session**. -Methods on this object can be automatically invoked by the framework by assigning them to the activity hook values -available on State and Transition objects. -The hooks points can be either an string, identifying a logic object function or a callback function. In either case, the - function is of the form: - -```javascript -function( session, state, transition, msg ); -``` +The FSM logic and state are isolated. The developer supplies a custom object to the FSM via the **logic** value in the +FDA definition object. It must be a constructor function and will create a new object per **Session**. +The logic object will contain per session data, like for example the cards dealt in game, the authorization credentials, +or any other Session specific information. -In either case, the calling **this** scope will be the logic object itself. +For both, State and Transitions, the calling **this** scope will be the logic object itself. ##Activy hooks @@ -208,46 +225,52 @@ function constructor_func() { // Enter state B ``` -Function hooks will be addressed in two ways: -* **By convention**. The FSM engine will look for an unspecified method with the following rules: +The logic object can be notified automatically about Session changes in two different ways: - state/fsm enter action: state.name+"_enter" - state/fsm exit action: state.name+"_exit" +* Configuration: supply callback functions in the FDA definition object. +* Convention: the framework will automatically try to find methods in the logic object as follows: - transition action: transition.event+"_transition" - transition preGuard: transition.event+"_preGuard" - transition postGuard: transition.event+"_postGuard" +* * State enter: state.getName() + "_enter" +* * State exit: state.getName() + "_exit" +* * Transition fire: transition.getEvent() + "_transition" +* * Transition pre-guard: transition.getEvent() + "preGuard" +* * Transition post-guard: transition.getEvent() + "postGuard" -* **By configuration**. Defining onEnter, onExit or onTransition in the FSM JSON file. +State and Transition activity callbacks are of the form: + +```javascript +function( session, state, transition, msg ); +``` In any case, those functions will be automatically called if they exist in the logic object. ##Guards -Guard prevent a transition from being fired. In Automata there're two available guard points out of the box. +Guards prevent a transition from being fired. In Automata there are two available guard points out of the box. One on preTransitionFire and the other on postTransitionFire. The difference is straight: - * The **pre-transition guard**, if fired, aborts the transition firing procedure as if it had never ocurred. - That means, that neither the onExit function, nor a sefl transition event will be fired by the engine. - A good usage of this situation is for counting states. For example, in a multiplayer game where 3 players - must be present to start the game, an transition from state WaitPlayers to StartGame will be defined. + * The **pre-transition guard**, if fired, aborts the transition firing procedure as if it had never occurred. + That means, that neither the onExit function, nor a self transition event will be fired by the engine. + A good usage of this situation is for counting states. For example, in a multi-player game where 3 players + must be present to start the game, a transition from state WaitPlayers to state StartGame will be defined. The pre-transition guard will allow to set a count up, so that whenever a new player enters the game, the count increments, and will fail until the desired amount is reached. This procedure won't affect the state machine, nor its observers. - * The **post-transition guard**, if fired, maked the transition behave as a self-transition trigger, and the following - action sequence will be fired: Exit_State_A, Transition Fire, Enter_State_A. + * The **post-transition guard**, if fired, makes the transition behave as a self-transition trigger. + For a Transition form State A to State B, a post-transition-guard would fire the following + action sequence: Exit_State_A, Transition Fire, Enter_State_A. As opposed to Exit_State_A, Transition Fire, Enter_State_B. A natural transition flow of executed actions for a transition from StateA to StateB with preGuard and postGuard actions will be: ``` -if preGuard throws exception +if preGuard throws guard-exception // nothing will happen nil; else - if postGuard throws exception + if postGuard throws guard-exception // auto-transition. State change to StateA will be notified to observers. StateA.onExit -> transition.onTransition -> StateA.onEnter else @@ -258,9 +281,9 @@ else endif ``` - The way to instrument the engine that a guard veto has been launched, will be by throwing an exception from the + The way to instrument the engine that a guard veto has been fired, will be by throwing an exception from the pre/post-transition functions. A Guard is expected to throw a GuardException object by calling - `transition.createThrowable`method. + `transition.createThrowable` method or `module.newGuardException`. Those functions are optional, and must be set in the "transition" block of the FSM definition as follows: @@ -284,7 +307,7 @@ endif } ``` - If no onPreGuard/onPostGuard attributes are specified, Automata DFA engine will assume a call to a convention method + If no onPreGuard/onPostGuard attributes are specified, Automata FDA engine will assume a call to a convention method of the form: ``` @@ -298,7 +321,7 @@ endif ##Timed transitions -Automata offers out of the box timed transitions by defining an **onTimer** block in a state definition. For example: +Automata offers out of the box timed transitions by defining an **onTimer** block in a FDA definition. For example: ```javascript fsmContext.registerFSM( { @@ -321,9 +344,10 @@ Automata offers out of the box timed transitions by defining an **onTimer** bloc } ); ``` -This instruments the engine that after 2 seconds of entering this state, a transition by a transition with an -event id like "12" will be sent to the FSM session. The timer is handled automatically, and set/canceled on state -enter/exit respectively. +This instruments the engine that after 2 seconds of entering this state, an event {msgId: "12"} will be sent to the +FSM session. The timer is handled automatically, and set/canceled on state enter/exit respectively. +The timers are checked every 200 milliseconds by the unique instance of FSMContext object. Thus, if you need to have +less than 200ms timers, you may want to change TIMER_CHECK_RESOLUTION in the automata.js file. ##SubStates @@ -354,10 +378,12 @@ register more than one FSM in the registry, and then reference one of them as a ``` Then, the transition section will identify this FSM as a substate by its name, STest. A "subState" can't have a - regular name, nor onEnter/onExit functions. + regular name, nor onEnter/onExit functions. The name is the one of the FDA itself, and the activity hooks are + overridden to do the stacking. The stacking of different subStates is done transparently, and they are handled by the "session" object. For each - stacked level, a FSM.Context object is created. A context object is just a holder for the current state for each nesting level. + stacked level, a FSM.Context object is created. A context object is just a holder for the current state for each + nesting level. ##Transition from Substates @@ -405,23 +431,32 @@ Any FSM session activity can be monitored by adding a listener. For example: ```javascript -session.addListener( { - contextCreated : function( obj ) { - console.log("SessionListener contextCreated"); - }, - contextDestroyed : function( obj ) { - console.log("SessionListener contextDestroyed"); - }, - finalStateReached : function( obj ) { - console.log("SessionListener finalStateReached"); - }, - stateChanged : function( obj ) { - console.log("SessionListener stateChanged"); - }, - customEvent : function( obj ) { - console.log("SessionListener customEvent"); - } -} ); +session.addListener( new FSM.SessionListener() ); +``` + +or + +```javascript + +// create a SessionListener and override the methods with the one in the parameter supplied. +session.addListener( module.newSessionListener( { + contextCreated : function( obj ) { + console.log("SessionListener contextCreated"); + }, + contextDestroyed : function( obj ) { + console.log("SessionListener contextDestroyed"); + }, + finalStateReached : function( obj ) { + console.log("SessionListener finalStateReached"); + }, + stateChanged : function( obj ) { + console.log("SessionListener stateChanged"); + }, + customEvent : function( obj ) { + console.log("SessionListener customEvent"); + } + } +) ); ``` The obj parameter for each listener object function contains the following parameters: @@ -441,7 +476,7 @@ In all cases: ##Custom events -The preferred way for sending custom events will by calling: +The preferred way for sending custom events will be by calling: ```javascript session.fireCustomEvent( a_json_object ); ``` @@ -453,9 +488,6 @@ This method will be notified on the method customEvent : function( { session: session, customEvent: a_json_object } ) { ``` - - - #Samples ##Sample 1 - Simple FSM @@ -527,10 +559,10 @@ context.registerFSM( { } ); var session= context.createSession("Test1"); -session.dispatch( { msgId: "ab" } ); +session.consume( { msgId: "ab" } ); var session2= context.createSession("Test1"); -session2.dispatch( { msgId: "ab" } ); +session2.consume( { msgId: "ab" } ); ``` @@ -589,7 +621,7 @@ context.registerFSM( { var session1= context.createSession("Test2"); var session2= context.createSession("Test2"); -session2.dispatch( {msgId : "ab"} ); +session2.consume( {msgId : "ab"} ); /* will print: @@ -653,7 +685,7 @@ var Logic= function() { this.count++; console.log("count= "+this.count); if ( this.count<3 ) { - throw "PreGuard_tr_BC"; + throw context.newGuardException("PreGuard_tr_BC"); } else { console.log("Ok, go."); } @@ -663,7 +695,7 @@ var Logic= function() { this.count++; console.log("count= "+this.count); if ( this.count<5 ) { - throw "PostGuard_tr_BC"; + throw context.newGuardException("PostGuard_tr_BC"); } }; @@ -725,31 +757,33 @@ context.registerFSM( { var session= context.createSession("Test3"); -session.addListener( { - contextCreated : function( obj ) { }, - contextDestroyed : function( obj ) { }, - finalStateReached : function( obj ) { - console.log("SessionListener finalStateReached"); - }, - stateChanged : function( obj ) { - console.log("SessionListener stateChanged"); - }, - customEvent : function( obj ) { } -} ); +session.addListener( + context.newSessionListener( { + contextCreated : function( obj ) { }, + contextDestroyed : function( obj ) { }, + finalStateReached : function( obj ) { + console.log("SessionListener finalStateReached"); + }, + stateChanged : function( obj ) { + console.log("SessionListener stateChanged"); + }, + customEvent : function( obj ) { } + } ) +); console.log(""); console.log("Sent 'ab'"); -session.processMessage( { msgId: "ab" } ); +session.consume( { msgId: "ab" } ); // fail on pre-guard. count=1, but no notification of state change sent. console.log(""); console.log("Sent 'bc'"); -session.processMessage( { msgId: "bc" } ); +session.consume( { msgId: "bc" } ); // fail on pre-guard. count=2, but no notification of state change sent. console.log(""); console.log("Sent 'bc'"); -session.processMessage( { msgId: "bc" } ); +session.consume( { msgId: "bc" } ); // on pre-guard. count=3. // Ok go transition. @@ -758,11 +792,11 @@ session.processMessage( { msgId: "bc" } ); // notification of 'stateChanged' on the observer. console.log(""); console.log("Sent 'bc'"); -session.processMessage( { msgId: "bc" } ); +session.consume( { msgId: "bc" } ); console.log(""); console.log("Sent 'bc'"); -session.processMessage( { msgId: "bc" } ); +session.consume( { msgId: "bc" } ); ``` @@ -890,27 +924,28 @@ context.registerFSM( { } ); var session= context.createSession("Test4"); -session.processMessage( { msgId : "ab" } ); -session.processMessage( { msgId : "bc" } ); - -// The session is now in State-1 on STest FSM. -session.printStackTrace(); - -// The stack trace is: -// Test4 -// SubStateTest -// 1 - -session.processMessage( { msgId : "cd" } ); - -// Although neither State-1 on SubStateTest, nor SubStateTest have a transition to "cd", Automata's engine traverses -// current Session's stack trace upwards trying to find a suitable State with an exit transition to "cd". In this case, -// SubStateTest itself consumes the transition, meaning the last Session's context will be poped out and the control flow -// will be transitioning from SubStateTest to State-c. - -// After that call, the session will be empty, since State-c is final, and every context is poped out the session. -session.printStackTrace(); - -// prints: session empty. +session.consume( { msgId : "ab" } ); +session.consume( { msgId : "bc" }, function() { + + // The session is now in State-1 on STest FSM. + session.printStackTrace(); + + // The stack trace is: + // Test4 + // SubStateTest + // 1 +} ); +session.consume( { msgId : "cd" }, function() { + + // Although neither State-1 on SubStateTest, nor SubStateTest have a transition to "cd", Automata's engine traverses + // current Session's stack trace upwards trying to find a suitable State with an exit transition to "cd". In this case, + // SubStateTest itself consumes the transition, meaning the last Session's context will be poped out and the control flow + // will be transitioning from SubStateTest to State-c. + + // After that call, the session will be empty, since State-c is final, and every context is poped out the session. + session.printStackTrace(); + + // prints: session empty. +} ); ``` diff --git a/test/test.html b/test/test.html index c6e1893..29f2818 100644 --- a/test/test.html +++ b/test/test.html @@ -8,7 +8,7 @@ - + diff --git a/test/test1.js b/test/test1.js index 79e23e8..7e6804b 100644 --- a/test/test1.js +++ b/test/test1.js @@ -90,6 +90,6 @@ context.registerFSM( { } ); var session= context.createSession("Test1"); -session.dispatch( { msgId: "ab" } ); -session.dispatch( { msgId: "bc" } ); +session.consume( { msgId: "ab" } ); +session.consume( { msgId: "bc" } ); diff --git a/test/test2.js b/test/test2.js index 14db594..e5b4fe0 100644 --- a/test/test2.js +++ b/test/test2.js @@ -59,7 +59,7 @@ context.registerFSM( { var session1= context.createSession("Test2"); var session2= context.createSession("Test2"); -session2.dispatch( {msgId : "ab"} ); +session2.consume( {msgId : "ab"} ); /* will print: diff --git a/test/test3.js b/test/test3.js index 08c2433..e8bd56f 100644 --- a/test/test3.js +++ b/test/test3.js @@ -50,7 +50,7 @@ var Logic= function() { this.count++; console.log("count= "+this.count); if ( this.count<3 ) { - throw "PreGuard_tr_BC"; + throw context.newGuardException("PreGuard_tr_BC"); } else { console.log("Ok, go."); } @@ -60,7 +60,7 @@ var Logic= function() { this.count++; console.log("count= "+this.count); if ( this.count<5 ) { - throw "PostGuard_tr_BC"; + throw context.newGuardException("PostGuard_tr_BC"); } }; @@ -122,7 +122,7 @@ context.registerFSM( { var session= context.createSession("Test3"); -session.addListener( { +session.addListener( context.newSessionListener( { contextCreated : function( obj ) { }, contextDestroyed : function( obj ) { }, finalStateReached : function( obj ) { @@ -132,21 +132,21 @@ session.addListener( { console.log("SessionListener stateChanged"); }, customEvent : function( obj ) { } -} ); +} ) ); console.log(""); console.log("Sent 'ab'"); -session.processMessage( { msgId: "ab" } ); +session.consume( { msgId: "ab" } ); // fail on pre-guard. count=1, but no notification of state change sent. console.log(""); console.log("Sent 'bc'"); -session.processMessage( { msgId: "bc" } ); +session.consume( { msgId: "bc" } ); // fail on pre-guard. count=2, but no notification of state change sent. console.log(""); console.log("Sent 'bc'"); -session.processMessage( { msgId: "bc" } ); +session.consume( { msgId: "bc" } ); // on pre-guard. count=3. // Ok go transition. @@ -155,8 +155,8 @@ session.processMessage( { msgId: "bc" } ); // notification of 'stateChanged' on the observer. console.log(""); console.log("Sent 'bc'"); -session.processMessage( { msgId: "bc" } ); +session.consume( { msgId: "bc" } ); console.log(""); console.log("Sent 'bc'"); -session.processMessage( { msgId: "bc" } ); +session.consume( { msgId: "bc" } ); diff --git a/test/test4.js b/test/test4.js index 4c44ee8..d9e47f9 100644 --- a/test/test4.js +++ b/test/test4.js @@ -143,25 +143,28 @@ context.registerFSM( { } ); var session= context.createSession("Test4"); -session.processMessage( { msgId : "ab" } ); -session.processMessage( { msgId : "bc" } ); +session.consume( { msgId : "ab" } ); +session.consume( { msgId : "bc" }, function() { -// The session is now in State-1 on STest FSM. -session.printStackTrace(); + // The session is now in State-1 on STest FSM. + session.printStackTrace(); -// The stack trace is: -// Test4 -// SubStateTest -// 1 + // The stack trace is: + // Test4 + // SubStateTest + // 1 -session.processMessage( { msgId : "cd" } ); +} ); + +session.consume( { msgId : "cd" }, function() { -// Although neither State-1 on SubStateTest, nor SubStateTest have a transition to "cd", Automata's engine traverses -// current Session's stack trace upwards trying to find a suitable State with an exit transition to "cd". In this case, -// SubStateTest itself consumes the transition, meaning the last Session's context will be poped out and the control flow -// will be transitioning from SubStateTest to State-c. + // Although neither State-1 on SubStateTest, nor SubStateTest have a transition to "cd", Automata's engine traverses + // current Session's stack trace upwards trying to find a suitable State with an exit transition to "cd". In this case, + // SubStateTest itself consumes the transition, meaning the last Session's context will be poped out and the control flow + // will be transitioning from SubStateTest to State-c. -// After that call, the session will be empty, since State-c is final, and every context is poped out the session. -session.printStackTrace(); + // After that call, the session will be empty, since State-c is final, and every context is poped out the session. + session.printStackTrace(); -// prints: session empty. \ No newline at end of file + // prints: session empty. +} ); \ No newline at end of file