diff --git a/automata.d.ts b/automata.d.ts deleted file mode 100644 index 63343f7..0000000 --- a/automata.d.ts +++ /dev/null @@ -1,149 +0,0 @@ -declare module FSM { - - export interface FSMDefinitionStateTimer { - timeout : number; - event : FSM.TransitionMessage; - } - - export interface FSMDefinitionState { - name : string; - initial? : boolean; - onTimer? : FSMDefinitionStateTimer; - onEnter? : string|FSM.StateCallback; - onExit? : string|StateCallback; - } - - export interface FSMDefinitionSubState { - name : string; - } - - export interface FSMDefinitionTransition { - event :string; - from : string; - to : string; - onTransition? : string|TransitionCallback; - onPreGuard? : string|TransitionCallback; - onPostGuard? : string|TransitionCallback; - } - - export interface FSMDefinition { - name : string; - state : (FSMDefinitionSubState|FSMDefinitionState)[]; - transition : FSMDefinitionTransition[]; - onEnter? : string | StateCallback; - onExit? : string | StateCallback; - } - - export interface SessionCreationData { - fda : string; - controller? : any; - } - - export interface TransitionMessage { - msgId : string; - data? : any; - } - - export interface ConsumeCallback { - (session:Session):void - } - - export interface TransitionCallback { - (state:State, transition:Transition, message:TransitionMessage):void; - } - - export interface StateCallback { - (state:State, transition:Transition, message:TransitionMessage):void; - } - - - export interface SessionFinalStateReachedEvent { - session : FSM.Session; - } - - export interface SessionContextEvent { - session : FSM.Session; - context : FSM.SessionContext; - } - - export interface SessionStateChangeEvent { - session : FSM.Session; - context : FSM.SessionContext; - prevState : FSM.State; - state : FSM.State; - message : FSM.TransitionMessage; - isUserMessage : boolean; - } - - export interface TransitionGuardEvent { - session : FSM.Session; - transition : FSM.Transition; - message : FSM.TransitionMessage; - exception : string; - } - - export interface SessionCustomEvent { - session : FSM.Session; - data : any; - } - - class SessionContext { - getState() : FSM.State; - printStackTrace() : void; - } - - class Session { - - consume( message : TransitionMessage, consumeCallback? : FSM.ConsumeCallback ); - addListener( sl : SessionListener ); - removeListener( sl : SessionListener ); - printStackTrace(); - addProperty( key:string, value:any ); - removeProperty( key:string ); - getProperty( key:string ) : any; - start( callback:ConsumeCallback ) : void; - fireCustomEvent( e:any ) : void; - serialize() : any; - } - - class GuardException { - - msg : string; - toString() : string; - } - - class SessionListener { - contextCreated( e:SessionContextEvent ); - contextDestroyed( e:SessionContextEvent ); - finalStateReached( e:SessionFinalStateReachedEvent ); - stateChanged( e:SessionStateChangeEvent ); - customEvent( e:SessionCustomEvent ); - guardPreCondition( e:TransitionGuardEvent ); - guardPostCondition( e:TransitionGuardEvent ); - } - - class State { - getName() : string; - isFinal() : boolean; - } - - class Transition { - getEvent() : string; - getStartState() : FSM.State; - } - - export interface StateTransitionCallback { - ( session:Session, state:State, transition:Transition, message:TransitionMessage ) : void; - } -} - -declare module "automata" { - - export function registerFSM( object:FSM.FSMDefinition ); - export function registerFDA( object:FSM.FSMDefinition ); - export function createSession( data:FSM.SessionCreationData ) : FSM.Session; - export function newGuardException( message : string ) : FSM.GuardException; - export function newSessionListener( obj : any ) : FSM.SessionListener; - export function deserializeSession( obj : any, controllerDeserializerFunction ) : FSM.Session; - -} \ No newline at end of file diff --git a/automata.js b/automata.js deleted file mode 100644 index c0f17f3..0000000 --- a/automata.js +++ /dev/null @@ -1,2266 +0,0 @@ -/** - * @author Ibon Tolosana, @hyperandroid - * - * See LICENSE file. - * - */ - - -(function (root) { - - var TIMER_CHECK_RESOLUTION= 200; - - - /** - * requireJS available ??? - */ - root.module = {}; - - /** - * @callback ConsumeCallback - * @param session {FSM.Session} - */ - - /** - * @callback TransitionCallback - * @param state {FSM.State} - * @param transition {FSM.Transition} - * @param message {FSM.TransitionMessage} - */ - - /** - * @callback StateCallback - * @param state {FSM.State} - * @param transition {FSM.Transition} - * @param message {FSM.TransitionMessage} - */ - - - /** - * @name FSM - * @namespace - * - * Local module definition. - */ - var FSM= {}; - - /** - * @typedef {{ message :FSM.TransitionMessage, callback : ConsumeCallback}} - */ - FSM.MessageCallbackTuple; - - /** - * @typedef {{ event : FSM.TransitionMessage, timeout : number}} - */ - FSM.StateTimeTransitionInfo; - - /** - * @typedef {{ msgId : string, data? : object }} - */ - FSM.TransitionMessage; - - /** - * @typedef {{ fda : string, controller? : Object }} - */ - FSM.SessionCreationData; - - /** - * @typedef {{ session : FSM.Session }} - */ - FSM.SessionFinalStateReachedEvent; - - /** - * @typedef {{ session : FSM.Session, context : FSM.SessionContext }} - */ - FSM.SessionContextEvent; - - /** - * @typedef {{ - * session : FSM.Session, - * context : FSM.SessionContext, - * prevState : FSM.State, - * state : FSM.State, - * message : FSM.TransitionMessage, - * isUserMessage : boolean - * }} - */ - FSM.SessionStateChangeEvent; - - /** - * @typedef {{ - * session : FSM.Session, - * transition : FSM.Transition, - * message : FSM.TransitionMessage, - * exception : string, - * }} - */ - FSM.TransitionGuardEvent; - - /** - * @typedef {{ - * session : FSM.Session, - * data : Object, - * }} - */ - FSM.SessionCustomEvent; - - /** - * @typedef {{ - * timeout : number, - * event : FSM.TransitionMessage - * }} - */ - var FSMDefinitionStateTimer; - - /** - * @typedef {{ - * name : string, - * initial : boolean=, - * onTimer : FSMDefinitionStateTimer=, - * onEnter : (string|StateCallback), - * onExit : (string|StateCallback) - * }} - */ - var FSMDefinitionState; - - /** - * @typedef {{ - * name : string, - * }} - */ - var FSMDefinitionSubState; - - /** - * @typedef {{ - * event : string, - * from : string, - * to : string, - * onTransition : (string|TransitionCallback), - * onPreGuard : (string|TransitionCallback), - * onPostGuard : (string|TransitionCallback) - * }} - */ - var FSMDefinitionTransition; - - /** - * @typedef {{ - * name : string, - * state : Array, - * transition : Array, - * onEnter : (string|StateCallback), - * onExit : (string|StateCallback) - * }} - */ - var FSMDefinition; - - /** - * Regular extension mechanism. - * - * @param subc object to subclass - * @param superc object to subclass from - */ - function extend(subc, superc) { - var subcp = subc.prototype; - var method; - - // Class pattern. - var F = function() { - }; - F.prototype = superc.prototype; - - subc.prototype = new F(); // chain prototypes. - subc.superclass = superc.prototype; - subc.prototype.constructor = subc; - - // Reset constructor. See Object Oriented Javascript for an in-depth explanation of this. - if (superc.prototype.constructor === Object.prototype.constructor) { - superc.prototype.constructor = superc; - } - - // los metodos de superc, que no esten en esta clase, crear un metodo que - // llama al metodo de superc. - for ( method in subcp ) { - if (subcp.hasOwnProperty(method)) { - subc.prototype[method] = subcp[method]; - } - } - } - - /** - * Bind mechanism. Honors already existing bind functions. - */ - Function.prototype.bind = Function.prototype.bind || function( /* this */ ) { - - var fn= this; // the function - var args= Array.prototype.slice.call(arguments); // copy the arguments. - var obj= args.shift(); // first parameter will be context 'this' - - return function() { - fn.apply( obj, args.concat( Array.prototype.slice(arguments) ) ); - }; - }; - - /** - * TimerTask sequence. - */ - var __TimerIndex= 0; - - /** - * State creation sequence - */ - var __StateIndex= 0; - - /** - * Initial transition msgId identification. - */ - var __InitialTransitionId= "__initial_transition_id"; - - /** - * Automata system context object. Supposed to be unique. - */ - var fsmContext= null; - - /** - * @memberOf FSM - * - * @class TimerTask - * @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 time an integer specifying milliseconds. - */ - FSM.TimerTask= function( session, event, time ) { - - /** - * Session to forward the event to on timeout. - * @name session - * @memberOf TimerTask.prototype - * @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 TimerTask.prototype - * @type {FSM.TransitionMessage} - */ - this.event= event; - - /** - * Milliseconds to consider this task expired. - * @name triggerTime - * @memberOf TimerTask.prototype - * @type {number} - */ - this.triggerTime= time; - - /** - * TimerTask id. - * This id is returned whenever a timed-transition is set. Thus, timed events can be cancelled. - * @name id - * @memberOf TimerTask.prototype - * @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 TimerTask.prototype - * @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 TimerTask.prototype - * @type {number} - */ - this.scheduleTime= new Date().getTime(); - - /** - * Internal flag of timer task validity. - * @name consumed - * @memberOf TimerTask.prototype - * @type {boolean} - */ - this.consumed = false; - - return this; - }; - - /** - * @lend FSM.TimerTask.prototype - */ - FSM.TimerTask.prototype= { - - /** - * Get this task id. - * @returns {number} - */ - getId : function() { - return this.id; - }, - - /** - * Has this timer task already been fired ? - */ - isConsumed : function() { - return this.consumed; - }, - - /** - * Is this timer task on time so that it must be triggered ? - * @param t {number} current time. - */ - isExpired : function( t ) { - return this.scheduleTime + this.triggerTime < t; - }, - - /** - * This is the timer task control function. - * @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.consume( this.event ); - } - // else, the session has already changed state and needs to dispatch the timeout message. - - this.consumed= true; - return true; - } - - return false; - } - }; - - /** - * @memberOf FSM - * - * @class FSMContext - * @classdesc - * - * 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 of this type. - * - * @constructor - * - */ - FSM.FSMContext= function() { - - /** - * Array of pending timer tasks. - * @name timerTask - * @memberOf FSMContext.prototype - * @type {Array} - */ - this.timerTasks= []; - - /** - * Registry of State machines. - * From each entry a FSM session object can be built. - * - * @name registry - * @memberOf FSMContext.prototype - * @type {map} - */ - this.registry= {}; - - /** - * This timer is used to check all the TimerTask timeouts. - * @name timerId - * @memberOf FSMContext.prototype - * @type {number} - */ - this.timerId= root.setInterval( this.__checkTimers.bind(this), TIMER_CHECK_RESOLUTION ); - - return this; - }; - - /** - * @lend FSM.FSMContext.prototype - */ - FSM.FSMContext.prototype= { - - /** - * Check every FSM running session pending timer tasks. - * @private - */ - __checkTimers : function() { - - var time= new Date().getTime(); - - for( var i=0; i < this.timerTasks.length; i++ ) { - var timerTask= this.timerTasks[i]; - if ( timerTask.consume( time ) ) { - this.timerTasks.splice( i,1 ); - } - } - }, - - /** - * Initialize Automata's engine. - * @deprecated - */ - initialize : function() { - return this; - }, - - /** - * Shutdown Automata's engine. - * Pending timer tasks won't be notified. - */ - destroy : function() { - root.clearInterval( this.timerId ); - }, - - /** - * Register a new FSM. - * This is the first step to have a running FSM session in Automata engine. - * - * @param name {string} a FSM name. - * @param fsm {FSM.FSM} an FSM object instance. - */ - registerFSM : function( name, fsm ) { - if ( this.registry[name] ) { - throw "'"+name+"' FSM already registered."; - } - - this.registry[ name ]= fsm; - }, - - /** - * Get a FSM.FSM registered instance. - * - * @param name {string} get a FSM.FSM previously registered object. - */ - getFSM : function( name ) { - return this.registry[ name ]; - }, - - /** - * Create a given FSM session. - * - * @param sessionData {FSM.SessionCreationData} - * - * @return {FSM.Session} an initialized session object. - */ - createSession : function( sessionData ) { - - var automata= sessionData.fda; - var fsm= this.registry[ automata ]; - if ( typeof fsm==="undefined" ) { - throw "FSM "+automata+" does not exist."; - } - - return fsm.createSession(sessionData.controller); - }, - - /** - * Add a new Timer Task. - * A timer task means sending a message to a given FSM session after elapsing some time. - * It is automatically managed by onTimer block definition. - * - * Should not be called directly. - * - * @param session {FSM.Session} a session object - * @param event {FSM.TransitionMessage} a message object - * @param time {number} an integer indicating milliseconds. - * - * @return {number} a unique timertask id. - */ - addTimerTask : function( session, event, time ) { - var ttask= new FSM.TimerTask( session, event, time ); - this.timerTasks.push( ttask ); - return ttask.getId(); - }, - - /** - * Remove a previously set timer task. - * It is automatically managed by onTimer block definition. - * - * Should not be called directly. - * - * @param id {number} removes a timertask created by calling addTimerTask. - */ - removeTimerTask : function( id ) { - for( var i=0; i} - * @memberOf State.prototype - * @name exitTransitions - */ - this.exitTransitions= {}; - - /** - * Number of exit transitions. Needed to know which State is a final state (no exit transitions). - * @name exitTransitionsCount - * @type {number} - * @memberOf State.prototype - */ - this.exitTransitionsCount= 0; - - /** - * State name. - * - * @type {string} - * @memberOf State.prototype - * @name name - */ - this.name= name || ( "state"+__StateIndex++ ); - - /** - * On State Enter action. - * @type {string|StateCallback} - * @name onEnter - * @memberOf State.prototype - */ - this.onEnter= null; - - /** - * On State Exit action. - * @type {string|StateCallback} - * @name onEnter - * @memberOf State.prototype - */ - this.onExit= null; - - /** - * Described a timed transition to this State. - * @type {FSM.StateTimeTransitionInfo} - * @name onTimer - * @memberOf FSM.State.prototype - */ - this.onTimer= null; - - /** - * Whether this State is a whole FSM substate. (Nested FSM.FSM objects) - * - * @type {FSM.FSM} - * @name subState - * @memberOf State.prototype - */ - this.subState= null; - - return this; - }; - - FSM.State.deserialize = function( obj, parentState ) { - - if ( null==parentState ) { - return fsmContext.getFSM( obj.name ); - } - - return parentState.getStateByName( obj.name ); - }; - - /** - * @lend FSM.State.prototype - */ - FSM.State.prototype= { - - serialize : function() { - return { - "class" : "State", - "name" : this.name - }; - }, - - /** - * 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 {FSM.Transition} - */ - addTransition : function( tr ) { - var event= tr.getEvent(); - - if ( this.exitTransitions[event] ) { - throw "Already set transition for event "+event; - } - - this.exitTransitions[event]= tr; - this.exitTransitionsCount++; - - return this; - }, - - /** - * Check whether this state has exiting transitions. - * If not, will be defined as final. - * - * @return bool - */ - isFinalState : function() { - return this.exitTransitionsCount===0; - }, - - /** - * Set this state's onEnter callback function. - * @param c {string|StateCallback} - */ - setOnEnter : function( c ) { - this.onEnter= c; - return this; - }, - - /** - * Set this state's onExit callback function. - * @param c {string|StateCallback} - */ - setOnExit : function( c ) { - this.onExit= c; - return this; - }, - - /** - * Add a timed transition to this state. - * @param c {FSM.StateTimeTransitionInfo} - */ - setOnTimer : function( c ) { - this.onTimer= c; - }, - - /** - * Get a transition for the defined typeof message. - * @param msg {FSM.TransitionMessage} - */ - getTransitionFor : function( msg ) { - if (!msg || !msg.msgId ) { - // WTF ?? - return null; - } - return this.exitTransitions[ msg.msgId ]; - }, - - /** - * @private - */ - __getTimerKey : function( ) { - return this.name; // + "#" + this.onTimer.event.msgId; - }, - - /** - * Execute the procedure on entering this State. - * It may seem to set a timer, and calling the optional onEnter callback function. - * @param session {FSM.Session} - * @param transition {FSM.Transition} - * @param msg {FSM.TransitionMessage} - */ - callOnEnter : function( session, transition, msg ) { - if ( this.onTimer ) { - session.addProperty( - this.__getTimerKey( ), - fsmContext.addTimerTask( session, this.onTimer.event, this.onTimer.timeout ) - ); - } - session.callMethod( this.onEnter, this, transition, msg ); - }, - - /** - * Execute the procedure on exiting this State. - * It may seem to reset a timer, and calling the optional onEnter callback function. - * - * @param session {FSM.Session} - * @param transition {FSM.Transition} - * @param msg {FSM.TransitionMessage} - */ - callOnExit : function( session, transition, msg ) { - if( this.onTimer ) { - var pr= session.getProperty( this.__getTimerKey() ); - fsmContext.removeTimerTask( pr ); - session.removeProperty(pr); - } - session.callMethod( this.onExit, this, transition, msg ); - }, - - toString : function() { - return ""+this.name; - } - - }; - - /** - * @memberOf FSM - * @class FSM - * @extends FSM.State - * - * @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 name {string} FSM name - * - */ - FSM.FSM= function( name ) { - - FSM.FSM.superclass.constructor.call(this, name); - - /** - * @name onEnter - * @type {string|StateCallback} - * @memberOf FSM.prototype - */ - this.onEnter= this.getName()+"_enter"; - - /** - * @name onExit - * @type {string|StateCallback} - * @memberOf FSM.prototype - */ - this.onExit= this.getName()+"_exit"; - - /** - * Defines the FDA's initial state. - * - * @memberOf FSM.prototype - * @name initialState - * @type {FSM.State} - */ - this.initialState= null; - - /** - * FSM declarative description. - * @type {FSM.FSMState[]} - */ - this.states = null; - - return this; - }; - - /** - * @lend FSM.FSM.prototype - */ - FSM.FSM.prototype= { - - getStateByName : function( n ) { - - var s= this.states[n]; - return s ? s : 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 {FSM.State} - */ - initialize : function( initialState ) { - this.initialState= initialState; - }, - - /** - * 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 {FSM.TransitionMessage} - */ - callOnEnter : function( session, transition, msg ) { - FSM.FSM.superclass.callOnEnter.call( this, session, transition, msg ); - session.consume( { - msgId : __InitialTransitionId - }); - - }, - - /** - * Build a Session for this FSM object. - * A session is (of course) initially empty. - * This method is called only once, and from this on, sub-state automata go on with the normal lifecycle - * calling their custom onEnter method which launcher the initialTransition. - * Strictly talking, automata object should be constructed from a building block where just an FSM defined - * just one state being a substate of the target FSM. - * - * To avoid such automata definition inefficiencies, here I'm calling the block manually: - * + pushing a top level FSM context - * + calling its onEnter method as if an initialTransition was fired. - * - * 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 sessionController {object} session factory initialization parameters. - */ - createSession : function(sessionController ) { - return new FSM.Session(this, sessionController ); - }, - - /** - * - * @param session {FSM.Session} - * @param callback {ConsumeCallback} - * @returns {FSM.Session} - */ - startSession : function( session, callback ) { - session.push(this); - FSM.FSM.superclass.callOnEnter.call( this, session, null, null ); - session.consume( { - msgId : __InitialTransitionId - }, callback); - - return session; - } - }; - - extend( FSM.FSM, FSM.State ); - - - /** - * @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 SessionContext.prototype - */ - this.currentState= state; - - return this; - }; - - /** - * - * @param obj {object} - * @param parentFDA {FSM.State} - */ - FSM.SessionContext.deserialize= function( obj, parentFDA ) { - - if ( obj && obj["class"] && obj["class"]==="SessionContext" ) { - var s= FSM.State.deserialize( obj.state, parentFDA ); - if ( s ) { - return new FSM.SessionContext(s); - } - - throw "Unknown state '"+obj.state.name+"' in FDA "+parentFDA.getName(); - } - - throw "SessionContext invalid data."; - }; - - /** - * @lend FSM.SessionContext.prototype - */ - FSM.SessionContext.prototype= { - - /** - * Set this context current state. - * This method will be called by Automata's engine when a state change is fired. - * @param s {FSM.State} - */ - setCurrentState : function( s ) { - this.currentState= s; - }, - - /** - * Get this context's current state. - * @return {FSM.State} - */ - getState : function() { - return this.currentState; - }, - - /** - * Get an exiting transition defined by this message for the current State. - * @param msg {FSM.TransitionMessage} - */ - getTransitionFor : function( msg ) { - return this.currentState.getTransitionFor( msg ); - }, - - /** - * Call this current State onExit callback function. - * @param session {FSM.Session} - * @param transition {FSM.Transition} - * @param msg {FSM.TransitionMessage} - */ - exit : function( session, transition, msg) { - this.currentState.callOnExit(session, transition, msg); - }, - - /** - * Print this context current state info. - */ - printStackTrace : function() { - FSM.Log.d(" "+this.currentState.getName()); - }, - - serialize : function() { - return { - "class" : "SessionContext", - "state" : this.currentState.serialize() - } - } - }; - - /** - * @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 fsm {FSM.FSM} a FDA. - * @param controller {object} an object coming from the FSM session factory object. - */ - FSM.Session= function( fsm, controller ) { - - /** - * FSM.FSM instance this sessio belongs to. - * @name _fda - * @memberOf FSM.Session.prototype - * @type {FSM.FSM} - * @private - */ - this._fda= fsm; - - /** - * 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 Session.prototype - */ - 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 Session.prototype - * @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 Session.prototype - * @type {map} - */ - this.properties= {}; - - /** - * Session data. An object created form the FSM factory constructor function. - * - * @name controller - * @memberOf Session.prototype - * @type {object} an object returned from the FSM factory constructor. - */ - this.controller= controller; - - /** - * 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 Session.prototype - * @type {Array.} - */ - this.messageQueues = []; - - /** - * Internal flag used to signal that 'consume' calls are in the context of a 'callMethod'. - * - * @name _inCallMethod - * @memberOf Session.prototype - * @type {Array.} - */ - this._inCallMethod = false; - - /** - * Internal flag for session state. - * @name _started - * @memberOf FSM.Session.prototype - * @type {boolean} - * @private - */ - this._started = false; - - return this; - }; - - /** - * @class SessionMessageQueue - * @memberOf FSM - * @classdesc - * - * This function creates objects to hold messages for a unit of work. - * The unit of work is a user-made 'consume' method, and all the transitions generated from this call. - * - * This is a FIFO queue. - * - * @param msg {FSM.TransitionMessage} - * @param callback {ConsumeCallback} - * @returns {FSM.SessionMessageQueue} - * - * @constructor - */ - FSM.SessionMessageQueue = function( msg, callback ) { - - /** - * @name _callback - * @type {ConsumeCallback} - * @memberOf SessionMessageQueue.prototype - * @private - */ - this._callback = callback; - - /** - * @name _messageQueue; - * @type {Array} - * @memberOf SessionMessageQueue.prototype - * @private - */ - this._messages = []; - - this._userMessage = msg; - - this.push( msg ); - - return this; - }; - - /** - * @lend FSM.SessionMessageQueue.prototype - */ - FSM.SessionMessageQueue.prototype = { - - isUserMessage : function( msg ) { - return msg===this._userMessage; - }, - - /** - * - * @param message {FSM.TransitionMessage} a valid FSM message. can be null if called from the initial state context. - * @param callback {ConsumeCallback?} - */ - push : function( message, callback ) { - if ( message ) { - this._messages.push( { - message: message, - callback: callback - }); - } - }, - - /** - * Get the head of messages. - * @returns {FSM.MessageCallbackTuple} - */ - shift : function() { - return this._messages.shift(); - }, - - /** - * Get number of pending messages. - * @returns {Number} - */ - getNumMessages : function() { - return this._messages.length; - }, - - /** - * Notify this messages queue callback. - * This happens when the unit of work ends, ie the queue gets empty. - * - * @param session {FSM.Session} - */ - notify : function( session ) { - if ( this._callback ) { - this._callback( session ); - } - } - }; - - /** - * @param obj - */ - FSM.Session.deserialize= function( obj, controllerDeserializer ) { - - if ( obj && obj["class"] && obj["class"]==="Session" ) { - - var fsm = fsmContext.getFSM(obj.fda); - if (!fsm) { - throw "Unknown FSM '" + obj.fda + "'"; - } - var s = new FSM.Session(fsm); - s._started= obj.started; - - s.sessionContextList= []; - var prevState= null; - obj.sessionContextList.forEach( function(scdef) { - var ns= FSM.SessionContext.deserialize( scdef, prevState ); - s.sessionContextList.push( ns ); - prevState= ns.getState(); - }); - - s.controller= controllerDeserializer( obj.controller ); - - return s; - } - - throw "Invalid session object definition."; - }; - - /** - * @lend FSM.Session.prototype - */ - FSM.Session.prototype= { - - serialize : function() { - var ret= { - "class" : "Session", - "fda" : this._fda.getName(), - "sessionContextList" : [], - "started" : this._started, - "controller" : this.controller.serialize ? this.controller.serialize() : "" - }; - - this.sessionContextList.forEach( function( sc ) { - ret["sessionContextList"].push( sc.serialize() ); - }); - - return ret; - }, - - /** - * Start a Session object. - * The session can be started only once. - * The reason to have a create and start functions, is that you can attach session listeners just after - * creation, and before it is started. Starting a session may imply state transitions. It is not reasonable - * to be able to attach observers after the inital transition executes and not before. - * - * @param callback {ConsumeCallback=} - */ - start : function( callback ) { - if ( this._started ) { - throw "Session is already started."; - } - - this._started= true; - - this._fda.startSession( this, callback ); - }, - - /** - * Never call this method directly. - * For a given Automata event triggering function (state.onEnter, state.onExit, transition.onPre/PostGuard, - * transition.onTransition), this method makes the appropriate call, either to the controller object, or to - * the supplied callback function instead. - * This method also sets an internal flag (_inCallMethod) which indicates that `session.consume` calls happening - * inside a called method must not creat a message bucket, but queue messages in the current message bucket. - */ - callMethod : function( /* method, argument1, ... */ ) { - - - var args = Array.prototype.slice.call(arguments); - var method = args.shift(); - - if (null === method) { // just in case. - return; - } - - args.splice(0, 0, this); - - this._inCallMethod = true; - - if (typeof method === "function") { - method.apply(this.controller, args); - } else { - if ( this.controller ) { - - if (this.controller && typeof this.controller[method] !== "undefined") { - this.controller[method].apply(this.controller, args); - } else { - // no method with given name on session object data. - } - - } - } - - this._inCallMethod= false; - }, - - /** - * Add an observer to this session. - * @param sl {FSM.SessionListener} - */ - addListener : function( sl ) { - this.sessionListener.push( sl ); - }, - - /** - * Remove an observer from this session. - * @param sl {FSM.SessionListener} - */ - removeListener : function( sl ) { - var pos= this.sessionListener.indexOf( sl ); - if ( -1!==pos ) { - this.sessionListener.splice( pos, 1 ); - } - }, - - /** - * Push and set up a new FSM.Context level. - * The state must be an state from the FSM object this session belongs to. - * - * @param state {FSM.State} - * - */ - push : function( state ) { - var sc= new FSM.SessionContext( state ); - - this.sessionContextList.push( sc ); - this.fireContextCreated( sc ); - this.fireStateChanged( sc, null, state, {msgId : __InitialTransitionId} ); - }, - - /** - * Pop and reset the last FSM.Context object level. - * - * @param transition {FSM.Transition} the firing transition - * @param msg {FSM.TransitionMessage} the message that triggered the transition - * - * @private - */ - pop : function( transition, msg ) { - var sc= this.sessionContextList.pop(); - sc.exit( this, transition, msg ); - - this.fireContextRemoved( sc ); - - if ( this.sessionContextList.length===0 ) { - this.fireSessionEmpty(); - } - }, - - /** - * Asynchronously consume a message. - * @param msg {FSM.TransitionMessage} - * @param endCallback {ConsumeCallback?} - */ - consume : function( msg, endCallback ) { - - if ( msg.msgId===__InitialTransitionId ) { - this.push( this.getCurrentState().initialState ); - this.messageQueues.push( new FSM.SessionMessageQueue(null, endCallback) ); - this.getCurrentState().callOnEnter( this, null, msg ); - } else { - - // calling consume from a method call, not a user generated consume call. - if ( this._inCallMethod ) { - this.messageQueues[0].push( msg, endCallback ); - } else { - this.messageQueues.push( new FSM.SessionMessageQueue( msg, endCallback ) ); - } - } - - this.__doConsume(); - }, - - __doConsume : function() { - setTimeout(this.__processMessages.bind(this), 0); - }, - - isEmpty : function() { - return this.sessionContextList.length===0 - }, - - /** - * 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. - */ - __processMessages : function( ) { - - if ( this.messageQueues.length===0 ) { - return; - } - - if ( this.isEmpty() ) { - throw "Empty Session"; - } - - var queue= this.messageQueues[0]; - // trivial exit - if ( queue.getNumMessages()===0 ) { - - this.messageQueues.shift(); - queue.notify( this ); - if ( this.messageQueues.length>0 ) { - this.__doConsume(); - } - // sanity clear - this._inCallMethod= false; - return; - } - - /** - * remove first message - * @type FSM.MessageCallbackTuple - */ - var pair= queue.shift(); - - /** - * @type {FSM.TransitionMessage} - */ - var msg= pair.message; - - /** - * @type {ConsumeCallback} - */ - var callback= pair.callback; - - var firingTransition= null; // FSM.Transition - var target= null; // FSM.SessionContext - var i; - for( i= this.sessionContextList.length - 1; i>=0; i-- ) { - target= this.sessionContextList[i]; - firingTransition= target.getTransitionFor( msg ); - if ( null!=firingTransition ) { - break; - } - } - - if ( !firingTransition ) { - FSM.Log.e( "No transition on state "+this.getCurrentState().name+" for message "+msg.msgId ); - if ( callback ) { - callback(this); - } - this.__doConsume(); - return; - } - - // check guard pre condition. - try { - firingTransition.checkGuardPreCondition( msg, this ); - } catch( e ) { - if ( e instanceof FSM.GuardException ) { - FSM.Log.i(e.toString()); - this.fireGuardPreCondition(firingTransition, msg, e); - if ( callback ) { - callback(this); - } - this.__doConsume(); - return; // fails on pre-guard. simply return. - } else { - FSM.Log.e("An error ocurred: "+ e.message); - this.printStackTrace(); - } - } - - try { - firingTransition.checkGuardPostCondition( msg, this ); - - try { - for( var j= this.sessionContextList.length-1; j>i; j-- ) { - this.pop( firingTransition, msg ); - } - - firingTransition.firePreTransition( msg, this ); - - var currentState= this.getCurrentState(); - var newState= firingTransition.finalState; - target.setCurrentState( newState ); - this.fireStateChanged( target, currentState, newState, msg ); - - firingTransition.firePostTransition( msg, this ); - - while( - this.sessionContextList.length!==0 && - this.getCurrentSessionContext().getState().isFinalState() ) { - - this.pop( null, msg ); - } - } catch( ex ) { - FSM.Log.e("An error ocurred: "+ ex.message); - this.printStackTrace(); - } - } catch( guardException ) { - if ( guardException instanceof FSM.GuardException ) { - FSM.Log.i(guardException.toString()); - this.fireGuardPostCondition(firingTransition, msg, guardException); - firingTransition.firePreTransitionGuardedByPostCondition( msg, this ); - this.fireStateChanged( target, this.getCurrentState(), firingTransition.initialState, msg ); - firingTransition.firePostTransitionGuardedByPostCondition( msg, this ); - } else { - FSM.Log.e("An error ocurred: "+ guardException.toString()); - this.printStackTrace(); - } - } - - if ( callback ) { - callback(this); - } - - if ( this.isEmpty() ) { - var sess= this; - // the session is empty. - // notify main callback only. - this.messageQueues.forEach( function(mq) { - mq.notify(sess); - }); - this.messageQueues= []; - } - - if ( this.messageQueues.length>0 ) { - this.__doConsume(); - } - - }, - - /** - * Get the current execution context. - * - * @return FSM.SessionContext current session context. - */ - getCurrentSessionContext : function() { - return this.sessionContextList[ this.sessionContextList.length-1 ]; - }, - - /** - * Get current context's state. - * - * @return {FSM.State} current state. - */ - getCurrentState : function() { - try { - return this.getCurrentSessionContext().getState(); - } catch( e ) { - return null; - } - }, - - /** - * 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 ) { - FSM.Log.d("session empty"); - } else { - FSM.Log.d("session stack trace:"); - for( var i=0; i} - */ - var states_a= fsmd.state; - var states= {}; - var initial_state= null; - for( i=0; i} - */ - var transitions_a= fsmd.transition; - for( i=0; i = { + [name: string]: T; +}; +export declare type Action = (session: Session, message: Message) => T; +export interface AutoTransitionJson { + millis: number; + data?: any; +} +export interface TransitionJson { + from: string; + to: string; + event: string; + timeout?: AutoTransitionJson; +} +export interface FSMJson { + name: string; + state: string[]; + initial_state: string; + transition: TransitionJson[]; +} +export declare type SessionMessageCallback = (session: Session, message?: Message) => void; +export declare type SessionMessageCallbackError = (session: Session, message?: string) => void; +export declare class SessionConsumeMessagePromise { + _success: SessionMessageCallback; + _error: SessionMessageCallbackError; + constructor(); + then(ok: SessionMessageCallback, error?: SessionMessageCallbackError): this; + __success(s: Session, m: Message): void; + __error(s: Session, message?: string): void; +} +export declare class FSMRegistry { + static _fsm: GenericMap; + static FSMFromId(id: string): FSM; + static register(fsm_json: FSMJson): void; + static createSession(session_controller: T, fsm_id: string, o?: SessionObserver): SessionConsumeMessagePromise; +} +export interface StateAutoTransitionElement { + millis: number; + message?: Message; + timer_id?: number; +} +export declare class State { + _name: string; + _exit_transitions: GenericMap; + _exit_transitions_count: number; + _enter_action: Action; + _exit_action: Action; + _auto_transition: StateAutoTransitionElement[]; + constructor(name: string); + transitionForMessage(m: Message): Transition; + addExitTransition(t: Transition): void; + name: string; + __onExit(s: Session, m: Message): boolean; + __onEnter(s: Session, m: Message): boolean; + __startTimeoutTransitionElements(s: Session): void; + __stopTimeoutTransitionElements(): void; + __notifyTimeoutEvent(s: Session, m: Message): void; + __setTimeoutTransitionInfo(millis: number, message: Message): void; + isFinal(): boolean; + toString(): string; +} +export declare class FSM extends State { + _states: State[]; + _transitions: Transition[]; + _initial_state: State; + constructor(fsm: FSMJson); + initial_state: State; + serialize(): FSMJson; + __createStates(states: string[], initial: string): void; + __setInitialState(st: State): void; + __createInitialTransition(): void; + __createEnterAction(): void; + __findStateByName(n: string): State; + __createTransitions(transitions: TransitionJson[]): void; +} +export declare class Transition { + static __InitialTransitionEvent: string; + static __InitialTransitionMessage: Message; + _event: string; + _initial_state: State; + _final_state: State; + constructor(from: State, to: State, event: string); + event: string; + final_state: State; + toString(): string; +} +export interface SerializedSessionContext { + current_state: string; + prev_state: string; +} +export declare class SessionContext { + _current_state: State; + _prev_state: State; + constructor(c: State, p: State); + serialize(): SerializedSessionContext; + current_state: State; + prev_state: State; + currentStateName(): string; + prevStateName(): string; + printStackTrace(): void; +} +export interface SessionObserverEvent { + session: Session; + message: Message; + custom_message?: Message; + current_state_name: string; + prev_state_name: string; +} +export interface SessionObserver { + contextCreated(e: SessionObserverEvent): void; + contextDestroyed(e: SessionObserverEvent): void; + sessionEnded(e: SessionObserverEvent): void; + customEvent(e: SessionObserverEvent): void; + stateChanged(e: SessionObserverEvent): void; +} +export interface SerializedSession { + ended: boolean; + controller: any; + states: SerializedSessionContext[]; + fsm: FSMJson; +} +export declare class Session { + _fsm: FSM; + _session_controller: T; + _states: SessionContext[]; + _ended: boolean; + _messages_controller: SessionMessagesController; + _observers: SessionObserver[]; + _sessionEndPromise: SessionConsumeMessagePromise; + constructor(session_controller: T); + __initialize(fsm: FSM): SessionConsumeMessagePromise; + __serializeController(): any; + serialize(): SerializedSession; + static deserialize(s: SerializedSession, deserializer: (sg: U) => T): Session; + __deserialize(s: SerializedSession): void; + addObserver(o: SessionObserver): void; + /** + * User side message. + */ + dispatchMessage(m: U): SessionConsumeMessagePromise; + /** + * From SessionController internals. + */ + postMessage(m: Message): void; + __messageImpl(m: Message): void; + current_state: State; + prev_state: State; + __onEnter(m: Message): void; + __onExit(m: Message): void; + __invoke(method: string, m: Message): any; + __consumeMessageForFSM(m: Message): void; + __findStateWithTransitionForMessage(m: Message): State; + __exitAllStatesUpToStateWithTransitionForMessage(stateWitTransition: State, m: Message): void; + __popAllStates(m: Message): void; + __setCurrentState(s: State, m: Message): void; + __endSession(m: Message): void; + current_state_name: string; + prev_state_name: string; + __consumeMessageForState(m: Message): void; + __processMessage(state_for_message: State, m: Message): void; + fireCustomEvent(message: any): void; + __notifySessionEnded(m: Message): void; + __notifyContextCreated(m: Message): void; + __notifyContextDestroyed(m: Message): void; + __notifyStateChange(m: Message): void; + __notify(m: Message, method: string): void; + controller: T; + printStackTrace(): void; +} +export declare class SessionMessageControllerMessageQueue { + _session: Session; + _triggering_message: Message; + _messages_queue: Message[]; + _callback: SessionConsumeMessagePromise; + constructor(session: Session, m: Message, callback?: SessionConsumeMessagePromise); + postMessage(m: Message): void; + __consumeMessage(): boolean; +} +export declare class SessionMessagesController { + _session: Session; + _message_queues: SessionMessageControllerMessageQueue[]; + _consuming: boolean; + constructor(session: Session); + dispatchMessage(m: Message, callback?: SessionConsumeMessagePromise): void; + postMessage(m: Message): void; + __consumeMessage(): void; + __consumeOne(): void; +} +export declare class Automata { + static RegisterFSM(file: string | FSMJson): void; + static CreateSession(controller: T, fsm_name: string, o?: SessionObserver): SessionConsumeMessagePromise; +} diff --git a/build/src/automata.js b/build/src/automata.js new file mode 100644 index 0000000..e886654 --- /dev/null +++ b/build/src/automata.js @@ -0,0 +1,669 @@ +/** + * Created by ibon on 2/8/16. + */ +"use strict"; +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var SessionConsumeMessagePromise = (function () { + function SessionConsumeMessagePromise() { + } + SessionConsumeMessagePromise.prototype.then = function (ok, error) { + this._success = ok; + this._error = error; + return this; + }; + SessionConsumeMessagePromise.prototype.__success = function (s, m) { + this._success && this._success(s, m); + }; + SessionConsumeMessagePromise.prototype.__error = function (s, message) { + this._error && this._error(s, message); + }; + return SessionConsumeMessagePromise; +}()); +exports.SessionConsumeMessagePromise = SessionConsumeMessagePromise; +var FSMRegistry = (function () { + function FSMRegistry() { + } + FSMRegistry.FSMFromId = function (id) { + return FSMRegistry._fsm[id]; + }; + FSMRegistry.register = function (fsm_json) { + try { + var fsm = new FSM(fsm_json); + FSMRegistry._fsm[fsm.name] = fsm; + console.log("Registered Automata '" + fsm.name + "'"); + } + catch (e) { + console.error(e); + } + }; + FSMRegistry.createSession = function (session_controller, fsm_id, o) { + var promise = new SessionConsumeMessagePromise(); + var fsm = FSMRegistry._fsm[fsm_id]; + if (fsm) { + var session = new Session(session_controller); + if (o) { + session.addObserver(o); + } + session.__initialize(fsm).then(function (session, m) { + promise.__success(session, m); + }, function (session, m) { + promise.__error(session, m); + }); + } + else { + setImmediate(function () { + promise.__error(null, "Unkonwn automata: '" + fsm_id + "'"); + }); + } + return promise; + }; + FSMRegistry._fsm = {}; + return FSMRegistry; +}()); +exports.FSMRegistry = FSMRegistry; +var State = (function () { + function State(name) { + this._name = name; + this._exit_transitions = {}; + this._exit_transitions_count = 0; + this._enter_action = null; + this._exit_action = null; + this._auto_transition = []; + } + State.prototype.transitionForMessage = function (m) { + var tr = this._exit_transitions[m.msgId]; + return tr || null; + }; + State.prototype.addExitTransition = function (t) { + this._exit_transitions[t.event] = t; + this._exit_transitions_count += 1; + }; + Object.defineProperty(State.prototype, "name", { + get: function () { + return this._name; + }, + enumerable: true, + configurable: true + }); + State.prototype.__onExit = function (s, m) { + if (this._exit_action !== null) { + this._exit_action(s, m); + } + this.__stopTimeoutTransitionElements(); + return this._exit_action !== null; + }; + State.prototype.__onEnter = function (s, m) { + if (this._enter_action !== null) { + this._enter_action(s, m); + } + this.__startTimeoutTransitionElements(s); + return this._enter_action !== null; + }; + State.prototype.__startTimeoutTransitionElements = function (s) { + var _this = this; + this._auto_transition.forEach(function (sate) { + sate.timer_id = setTimeout(_this.__notifyTimeoutEvent.bind(_this, s, sate.message), sate.millis); + }); + }; + State.prototype.__stopTimeoutTransitionElements = function () { + this._auto_transition.forEach(function (sate) { + if (sate.timer_id !== -1) { + clearTimeout(sate.timer_id); + sate.timer_id = -1; + } + }); + }; + State.prototype.__notifyTimeoutEvent = function (s, m) { + this.__stopTimeoutTransitionElements(); + s.dispatchMessage(m); + }; + State.prototype.__setTimeoutTransitionInfo = function (millis, message) { + this._auto_transition.push({ + millis: millis, + message: message, + timer_id: -1 + }); + }; + State.prototype.isFinal = function () { + return this._exit_transitions_count === 0; + }; + State.prototype.toString = function () { + return this._name; + }; + return State; +}()); +exports.State = State; +var FSM = (function (_super) { + __extends(FSM, _super); + function FSM(fsm) { + _super.call(this, fsm.name); + this._states = []; + this._transitions = []; + this._initial_state = null; + this.__createStates(fsm.state, fsm.initial_state); + this.__createTransitions(fsm.transition); + } + Object.defineProperty(FSM.prototype, "initial_state", { + get: function () { + return this._initial_state; + }, + enumerable: true, + configurable: true + }); + FSM.prototype.serialize = function () { + return { + name: this._name, + state: this._states.map(function (st) { return st._name; }), + initial_state: this._initial_state._name, + transition: this._transitions.map(function (tr) { + return { + event: tr._event, + from: tr._initial_state._name, + to: tr._final_state._name + }; + }) + }; + }; + FSM.prototype.__createStates = function (states, initial) { + for (var _i = 0, states_1 = states; _i < states_1.length; _i++) { + var name_1 = states_1[_i]; + var st = void 0; + if (name_1.lastIndexOf("FSM:") === -1) { + st = new State(name_1); + } + else { + var fsmname = name_1.substring(4); + st = FSMRegistry._fsm[fsmname]; + if (!st) { + throw "Automata '" + this._name + "' referencing other non existent automata: '" + name_1 + "'"; + } + } + this._states.push(st); + if (st.name === initial) { + this.__setInitialState(st); + } + } + }; + FSM.prototype.__setInitialState = function (st) { + this._initial_state = st; + this.__createInitialTransition(); + this.__createEnterAction(); + }; + FSM.prototype.__createInitialTransition = function () { + this.addExitTransition(new Transition(this, this._initial_state, Transition.__InitialTransitionEvent)); + }; + FSM.prototype.__createEnterAction = function () { + this._enter_action = function (session, message) { + session.postMessage(Transition.__InitialTransitionMessage); + }; + }; + FSM.prototype.__findStateByName = function (n) { + for (var _i = 0, _a = this._states; _i < _a.length; _i++) { + var s = _a[_i]; + if (s.name === n) { + return s; + } + } + return null; + }; + FSM.prototype.__createTransitions = function (transitions) { + var _this = this; + transitions.forEach(function (v /*, index:number, arr:TransitionJson[] */) { + var f = _this.__findStateByName(v.from); + var t = _this.__findStateByName(v.to); + var e = v.event; + if (!f || !t) { + throw "Wrongly defined Automata '" + _this.name + "'. Transition '" + v.event + "' refers unknown state:'" + (!f ? v.from : v.to) + "'"; + } + _this._transitions.push(new Transition(f, t, e)); + // auto transition behavior. + if (typeof v.timeout !== "undefined") { + f.__setTimeoutTransitionInfo(v.timeout.millis, { + msgId: e, + data: v.timeout.data + }); + } + }); + }; + return FSM; +}(State)); +exports.FSM = FSM; +var Transition = (function () { + function Transition(from, to, event) { + this._event = event; + this._initial_state = from; + this._final_state = to; + if (from) { + from.addExitTransition(this); + } + } + Object.defineProperty(Transition.prototype, "event", { + get: function () { + return this._event; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Transition.prototype, "final_state", { + get: function () { + return this._final_state; + }, + enumerable: true, + configurable: true + }); + Transition.prototype.toString = function () { + return this._event; + }; + Transition.__InitialTransitionEvent = "__INITIAL_EVENT"; + Transition.__InitialTransitionMessage = { msgId: Transition.__InitialTransitionEvent }; + return Transition; +}()); +exports.Transition = Transition; +var SessionContext = (function () { + function SessionContext(c, p) { + this._current_state = c; + this._prev_state = p; + } + SessionContext.prototype.serialize = function () { + return { + current_state: this._current_state._name, + prev_state: this._prev_state ? this._prev_state._name : "", + }; + }; + Object.defineProperty(SessionContext.prototype, "current_state", { + get: function () { + return this._current_state; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(SessionContext.prototype, "prev_state", { + get: function () { + return this._prev_state; + }, + enumerable: true, + configurable: true + }); + SessionContext.prototype.currentStateName = function () { + return this._current_state && this._current_state.name; + }; + SessionContext.prototype.prevStateName = function () { + return this._prev_state && this._prev_state.name; + }; + SessionContext.prototype.printStackTrace = function () { + console.log(" " + this._current_state.name); + }; + return SessionContext; +}()); +exports.SessionContext = SessionContext; +var Session = (function () { + function Session(session_controller) { + this._states = []; + this._session_controller = session_controller; + this._messages_controller = new SessionMessagesController(this); + this._observers = []; + this._fsm = null; + this._ended = false; + this._sessionEndPromise = null; + } + Session.prototype.__initialize = function (fsm) { + this._fsm = fsm; + this._states.push(new SessionContext(fsm, null)); + this.__invoke(fsm.name + "_enter", Transition.__InitialTransitionMessage); + var promise = this.dispatchMessage(Transition.__InitialTransitionMessage); + this._sessionEndPromise = promise; + return promise; + }; + Session.prototype.__serializeController = function () { + var sc = this._session_controller; + if (sc.serialize && typeof sc.serialize === "function") { + return sc.serialize(); + } + return {}; + }; + Session.prototype.serialize = function () { + var serializedController = this.__serializeController(); + return { + ended: this._ended, + fsm: this._fsm.serialize(), + states: this._states.map(function (st) { return st.serialize(); }), + controller: serializedController + }; + }; + Session.deserialize = function (s, deserializer) { + var controller = deserializer(s.controller); + var session = new Session(controller); + session.__deserialize(s); + return session; + }; + Session.prototype.__deserialize = function (s) { + var _this = this; + FSMRegistry.register(s.fsm); + this._fsm = FSMRegistry.FSMFromId(s.fsm.name); + this._ended = s.ended; + this._states = s.states.map(function (e) { + var c = e.current_state === s.fsm.name ? + _this._fsm : + _this._fsm._states.filter(function (s) { return s._name === e.current_state; })[0]; + var p = e.prev_state === "" ? + null : + _this._fsm._states.filter(function (s) { return s._name === e.prev_state; })[0]; + return new SessionContext(c, p); + }); + }; + Session.prototype.addObserver = function (o) { + this._observers.push(o); + }; + /** + * User side message. + */ + Session.prototype.dispatchMessage = function (m) { + if (this._ended) { + throw "Session is ended."; + } + var c = new SessionConsumeMessagePromise(); + this._messages_controller.dispatchMessage(m, c); + return c; + }; + /** + * From SessionController internals. + */ + Session.prototype.postMessage = function (m) { + this._messages_controller.postMessage(m); + }; + Session.prototype.__messageImpl = function (m) { + if (m === Transition.__InitialTransitionMessage) { + this.__consumeMessageForFSM(m); + } + else { + this.__consumeMessageForState(m); + } + }; + Object.defineProperty(Session.prototype, "current_state", { + get: function () { + return this._states.length ? + this._states[this._states.length - 1].current_state : + null; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Session.prototype, "prev_state", { + get: function () { + return this._states.length ? + this._states[this._states.length - 1].prev_state : + null; + }, + enumerable: true, + configurable: true + }); + Session.prototype.__onEnter = function (m) { + var cs = this.current_state; + if (cs !== null && !cs.__onEnter(this, m)) { + this.__invoke(cs.name + "_enter", m); + } + }; + Session.prototype.__onExit = function (m) { + var cs = this.current_state; + if (cs !== null && !cs.__onExit(this, m)) { + this.__invoke(cs.name + "_exit", m); + } + }; + Session.prototype.__invoke = function (method, m) { + return this._session_controller[method] && this._session_controller[method](this, this.current_state_name, m); + }; + Session.prototype.__consumeMessageForFSM = function (m) { + var cs = this.current_state; + var fsm = cs; + var new_current_state = fsm.initial_state; + this._states.push(new SessionContext(new_current_state, this.current_state)); + this.__notifyContextCreated(m); + this.__onEnter(m); + }; + Session.prototype.__findStateWithTransitionForMessage = function (m) { + var sc = this._states; + var state = null; + for (var i = sc.length - 1; i >= 0; i--) { + var current_state = sc[i].current_state; + var tr = current_state.transitionForMessage(m); + if (tr !== null) { + state = current_state; + break; + } + } + return state; + }; + Session.prototype.__exitAllStatesUpToStateWithTransitionForMessage = function (stateWitTransition, m) { + while (this._states.length) { + var cs = this._states[this._states.length - 1]; + this.__onExit(m); + if (cs.current_state !== stateWitTransition) { + this._states.pop(); + this.__notifyContextDestroyed(m); + } + else { + break; + } + } + }; + Session.prototype.__popAllStates = function (m) { + while (this._states.length) { + this.__onExit(m); + this._states.pop(); + this.__notifyContextDestroyed(m); + } + }; + Session.prototype.__setCurrentState = function (s, m) { + var prev = null; + if (this._states.length) { + prev = this._states.pop().current_state; + } + this._states.push(new SessionContext(s, prev)); + this.__notifyStateChange(m); + this.__onEnter(m); + }; + Session.prototype.__endSession = function (m) { + this._ended = true; + this.__notifySessionEnded(m); + }; + Object.defineProperty(Session.prototype, "current_state_name", { + get: function () { + return this._states.length ? + this._states[this._states.length - 1].currentStateName() : + ""; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Session.prototype, "prev_state_name", { + get: function () { + return this._states.length ? + this._states[this._states.length - 1].prevStateName() : + ""; + }, + enumerable: true, + configurable: true + }); + Session.prototype.__consumeMessageForState = function (m) { + if (!this._ended) { + var state_for_message = this.__findStateWithTransitionForMessage(m); + if (null !== state_for_message) { + this.__processMessage(state_for_message, m); + } + else { + throw new Error("No message: '" + m.msgId + "' for state: '" + this.current_state_name + "'"); + } + } + else { + throw new Error("Session is ended. Message " + m.msgId + " is discarded."); + } + }; + Session.prototype.__processMessage = function (state_for_message, m) { + var tr = state_for_message.transitionForMessage(m); + var transition_event = tr.event; + if (!this.__invoke(transition_event + "_preGuard", m)) { + this.__exitAllStatesUpToStateWithTransitionForMessage(state_for_message, m); + this.__invoke(transition_event + "_transition", m); + var next = void 0; + if (!this.__invoke(transition_event + "_postGuard", m)) { + next = tr.final_state; + } + else { + next = state_for_message; + } + this.__setCurrentState(next, m); + if (next.isFinal()) { + this.__popAllStates(m); + this.__endSession(m); + } + } + }; + Session.prototype.fireCustomEvent = function (message) { + for (var _i = 0, _a = this._observers; _i < _a.length; _i++) { + var o = _a[_i]; + o.customEvent({ + session: this, + message: null, + current_state_name: this.current_state_name, + prev_state_name: this.prev_state.name, + custom_message: message + }); + } + }; + Session.prototype.__notifySessionEnded = function (m) { + this.__notify(m, "sessionEnded"); + }; + Session.prototype.__notifyContextCreated = function (m) { + this.__notify(m, "contextCreated"); + }; + Session.prototype.__notifyContextDestroyed = function (m) { + this.__notify(m, "contextDestroyed"); + }; + Session.prototype.__notifyStateChange = function (m) { + this.__notify(m, "stateChanged"); + }; + Session.prototype.__notify = function (m, method) { + for (var _i = 0, _a = this._observers; _i < _a.length; _i++) { + var o = _a[_i]; + o[method] && o[method]({ + session: this, + message: m, + current_state_name: this.current_state_name, + prev_state_name: this.prev_state_name + }); + } + }; + Object.defineProperty(Session.prototype, "controller", { + get: function () { + return this._session_controller; + }, + enumerable: true, + configurable: true + }); + Session.prototype.printStackTrace = function () { + if (this._states.length === 0) { + console.log("session empty"); + } + else { + console.log("session stack trace:"); + this._states.forEach(function (s) { + s.printStackTrace(); + }); + } + }; + return Session; +}()); +exports.Session = Session; +var SessionMessageControllerMessageQueue = (function () { + function SessionMessageControllerMessageQueue(session, m, callback) { + this._session = session; + this._callback = typeof callback !== "undefined" ? callback : null; + this._triggering_message = m; + this._messages_queue = [m]; + } + SessionMessageControllerMessageQueue.prototype.postMessage = function (m) { + this._messages_queue.push(m); + }; + SessionMessageControllerMessageQueue.prototype.__consumeMessage = function () { + var ret; + if (this._messages_queue.length) { + var m = this._messages_queue.shift(); + try { + this._session.__messageImpl(m); + ret = false; + } + catch (e) { + console.error("consume for message '" + m.msgId + "' got exception: ", e); + this._messages_queue = []; + this._callback.__error(this._session, e); + ret = true; + } + } + else { + ret = true; + if (this._callback) { + this._callback.__success(this._session, this._triggering_message); + } + } + return ret; + }; + return SessionMessageControllerMessageQueue; +}()); +exports.SessionMessageControllerMessageQueue = SessionMessageControllerMessageQueue; +var SessionMessagesController = (function () { + function SessionMessagesController(session) { + this._message_queues = []; + this._session = session; + this._consuming = false; + } + SessionMessagesController.prototype.dispatchMessage = function (m, callback) { + this._message_queues.push(new SessionMessageControllerMessageQueue(this._session, m, callback)); + this.__consumeMessage(); + }; + SessionMessagesController.prototype.postMessage = function (m) { + this._message_queues[0].postMessage(m); + this.__consumeMessage(); + }; + SessionMessagesController.prototype.__consumeMessage = function () { + if (!this._consuming) { + this._consuming = true; + setImmediate(this.__consumeOne.bind(this)); + } + }; + SessionMessagesController.prototype.__consumeOne = function () { + if (this._message_queues.length) { + if (this._message_queues[0].__consumeMessage()) { + this._message_queues.shift(); + } + } + if (this._message_queues.length) { + setImmediate(this.__consumeOne.bind(this)); + } + else { + this._consuming = false; + } + }; + return SessionMessagesController; +}()); +exports.SessionMessagesController = SessionMessagesController; +var Automata = (function () { + function Automata() { + } + Automata.RegisterFSM = function (file) { + if (typeof file === "string") { + } + else { + FSMRegistry.register(file); + } + }; + Automata.CreateSession = function (controller, fsm_name, o) { + return FSMRegistry.createSession(controller, fsm_name, o); + }; + return Automata; +}()); +exports.Automata = Automata; +//# sourceMappingURL=automata.js.map \ No newline at end of file diff --git a/build/src/automata.js.map b/build/src/automata.js.map new file mode 100644 index 0000000..aa3440a --- /dev/null +++ b/build/src/automata.js.map @@ -0,0 +1 @@ +{"version":3,"file":"automata.js","sourceRoot":"","sources":["../../src/automata.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/test/test1.d.ts b/build/test/test1.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/build/test/test1.js b/build/test/test1.js new file mode 100644 index 0000000..e0addaa --- /dev/null +++ b/build/test/test1.js @@ -0,0 +1,76 @@ +"use strict"; +var automata_1 = require("../src/automata"); +var Controller = (function () { + function Controller() { + } + Controller.prototype.a_enter = function (session, state, msg) { + console.log(state + " enter "); + }; + ; + Controller.prototype.a_exit = function (session, state, msg) { + console.log(state + " exit "); + }; + ; + Controller.prototype.b_enter = function (session, state, msg) { + console.log(state + " enter "); + }; + ; + Controller.prototype.b_exit = function (session, state, msg) { + console.log(state + " exit "); + }; + ; + Controller.prototype.c_exit = function (session, state, msg) { + console.log(state + " exit"); + }; + ; + Controller.prototype.c_enter = function (session, state, msg) { + console.log(state + " enter"); + }; + ; + Controller.prototype.ab_transition = function (session, state, msg) { + console.log("transition: " + msg.msgId); + }; + ; + Controller.prototype.bc_transition = function (session, state, msg) { + console.log("transition: " + msg.msgId); + }; + ; + Controller.prototype.Test1_enter = function (session, state, msg) { + console.log(state + " enter "); + }; + ; + Controller.prototype.Test1_exit = function (session, state, msg) { + console.log(state + " exit "); + }; + ; + return Controller; +}()); +automata_1.Automata.RegisterFSM({ + name: "Test1", + state: ["a", "b", "c"], + initial_state: "a", + transition: [ + { + event: "ab", + from: "a", + to: "b" + }, + { + event: "bc", + from: "b", + to: "c" + } + ] +}); +automata_1.Automata.CreateSession(new Controller(), "Test1").then(function success(s, m) { + console.log("-------------- by message " + m.msgId); + s.dispatchMessage({ msgId: "ab" }).then(function success(s, m) { + console.log("-------------- by message " + m.msgId); + }); + s.dispatchMessage({ msgId: "bc" }).then(function success(s, m) { + console.log("-------------- by message " + m.msgId); + }); +}, function error(s, m) { + console.log("Error creating Session of type Test1, reason: '" + m + "'"); +}); +//# sourceMappingURL=test1.js.map \ No newline at end of file diff --git a/build/test/test1.js.map b/build/test/test1.js.map new file mode 100644 index 0000000..2deb734 --- /dev/null +++ b/build/test/test1.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test1.js","sourceRoot":"","sources":["../../test/test1.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/test/test2.d.ts b/build/test/test2.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/build/test/test2.js b/build/test/test2.js new file mode 100644 index 0000000..58e3ed9 --- /dev/null +++ b/build/test/test2.js @@ -0,0 +1,99 @@ +/** + * @author Ibon Tolosana, @hyperandroid + * + * See LICENSE file. + * + * + * Sample 2 - FSM with timed events + * + * This sample show how to define a timed transition. + * + */ +"use strict"; +var automata_1 = require("../src/automata"); +automata_1.Automata.RegisterFSM({ + name: "Test2", + state: ["a", "b", "c"], + initial_state: "a", + transition: [ + { + event: "ab", + from: "a", + to: "b", + timeout: { + millis: 4000, + data: {} + } + }, + { + event: "bc", + from: "b", + to: "c" + } + ] +}); +var __index = 0; +var Controller = (function () { + function Controller(n) { + this.name = n || "controller_" + __index++; + } + Controller.prototype.a_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + ; + Controller.prototype.a_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + ; + Controller.prototype.b_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + ; + Controller.prototype.b_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + ; + Controller.prototype.c_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit"); + }; + ; + Controller.prototype.c_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter"); + }; + ; + Controller.prototype.ab_transition = function (session, state, msg) { + console.log(this.name + " " + "transition: " + msg.msgId); + }; + ; + Controller.prototype.bc_transition = function (session, state, msg) { + console.log(this.name + " " + "transition: " + msg.msgId); + }; + ; + Controller.prototype.Test2_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + ; + Controller.prototype.Test2_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + ; + return Controller; +}()); +automata_1.Automata.CreateSession(new Controller("c1"), "Test2"); +automata_1.Automata.CreateSession(new Controller("c2"), "Test2").then(function success(s, m) { + s.dispatchMessage({ msgId: "ab" }); +}); +/* +will print: + +immediately +Exit a +Enter b +from session2 which has triggered a transition change + +and +Exit a +Enter b +after 4 seconds from session1. +*/ +//# sourceMappingURL=test2.js.map \ No newline at end of file diff --git a/build/test/test2.js.map b/build/test/test2.js.map new file mode 100644 index 0000000..47f51f9 --- /dev/null +++ b/build/test/test2.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test2.js","sourceRoot":"","sources":["../../test/test2.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/test/test3.d.ts b/build/test/test3.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/build/test/test3.js b/build/test/test3.js new file mode 100644 index 0000000..7a69c36 --- /dev/null +++ b/build/test/test3.js @@ -0,0 +1,143 @@ +/** + * @author Ibon Tolosana, @hyperandroid + * + * See LICENSE file. + * + * + * + * Sample 3 - Guards + * + * This sample shows how transition guards work on Automata. To fire a transition, first of all an optional **pre-guard** + * function is tested. If this function throws an exception, Automata interprets a veto on this transition fire. During + * pre-guard stage, a veto means transition disposal, so no auto-transition is performed. This is useful for example, in + * a multiplayer game where while playing, a user abbadons the game and the game can continue playing. So instead of + * transitioning from State-playing to State-EndGame, a guard can decide to veto the transition. + * + * By definition, a guard **should not** modify the model, in this case, a Logic object. + * + * In the example, the guard will fail two times until the count reaches 3. + * At this moment, the transition is fired (its onTransition method is executed if exists), and after that, + * the **post-guard** condition is checked. PostGuard semantics are completely different. + * After firing the transition, the postGuard is checked. If this function **throws an exception** the transition + * turns into auto-transition, that means firing state change to current-state, and entering again current state. + * If not, the transition continues its natural flow and transition's next state is set as current state. + */ +"use strict"; +var automata_1 = require("../src/automata"); +var Controller = (function () { + function Controller(n) { + this.count = 0; + this.count = 0; + this.name = n; + } + Controller.prototype.a_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + ; + Controller.prototype.a_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + ; + Controller.prototype.b_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + ; + Controller.prototype.b_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + ; + Controller.prototype.c_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit"); + }; + ; + Controller.prototype.c_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter"); + }; + Controller.prototype.ab_transition = function (session, state, msg) { + console.log(this.name + " " + "transition: " + msg.msgId); + }; + Controller.prototype.bc_transition = function (session, state, msg) { + console.log(this.name + " " + "transition: " + msg.msgId); + }; + Controller.prototype.Test2_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + Controller.prototype.Test2_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + Controller.prototype.bc_preGuard = function (session, state, msg) { + this.count++; + console.log("count= " + this.count); + if (this.count < 3) { + return true; + } + console.log("Ok, go."); + return false; + }; + Controller.prototype.bc_postGuard = function (session, state, msg) { + this.count++; + console.log("count= " + this.count); + return this.count >= 5; + }; + return Controller; +}()); +automata_1.Automata.RegisterFSM({ + name: "Test3", + state: ["a", "b", "c", "d"], + initial_state: "a", + transition: [ + { + event: "ab", + from: "a", + to: "b" + }, + { + event: "bc", + from: "b", + to: "c" + }, + { + event: "cd", + from: "b", + to: "c" + } + ] +}); +automata_1.Automata.CreateSession(new Controller("c1"), "Test3", { + contextCreated: function (e) { + }, + contextDestroyed: function (e) { + }, + sessionEnded: function (e) { + console.log("SessionListener finalStateReached ", e.message); + }, + customEvent: function (e) { + }, + stateChanged: function (e) { + console.log("SessionListener stateChanged " + e.prev_state_name + " --> " + e.current_state_name); + } +}).then(function success(session, m) { + console.log(""); + console.log("Sent 'ab'"); + session.dispatchMessage({ msgId: "ab" }); + // fail on pre-guard. count=1, but no notification of state change sent. + console.log(""); + console.log("Sent 'bc'"); + session.dispatchMessage({ msgId: "bc" }); + // fail on pre-guard. count=2, but no notification of state change sent. + console.log(""); + console.log("Sent 'bc'"); + session.dispatchMessage({ msgId: "bc" }); + // on pre-guard. count=3. + // Ok go transition. + // Fail on post-guard + // so onExit State-b and onEnter State-b ( auto-transition ). Vetoed transition from State-b to State-c. + // notification of 'stateChanged' on the observer. + console.log(""); + console.log("Sent 'bc'"); + session.dispatchMessage({ msgId: "bc" }); + console.log(""); + console.log("Sent 'bc'"); + session.dispatchMessage({ msgId: "bc" }); +}); +//# sourceMappingURL=test3.js.map \ No newline at end of file diff --git a/build/test/test3.js.map b/build/test/test3.js.map new file mode 100644 index 0000000..8bc7ccc --- /dev/null +++ b/build/test/test3.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test3.js","sourceRoot":"","sources":["../../test/test3.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/build/test/test4.d.ts b/build/test/test4.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/build/test/test4.js b/build/test/test4.js new file mode 100644 index 0000000..fe12662 --- /dev/null +++ b/build/test/test4.js @@ -0,0 +1,116 @@ +/** + * @author Ibon Tolosana, @hyperandroid + * + * See LICENSE file. + * + * Sample 4 - SubStates + * + * Sub States is an Automata feature which allows to nest different registered FSM as states of other FSM. + * The mechanism is straightforward, just define a **substate** block in an FSM **state** definition block. + * Automata will handle automatically all the nesting procedure, call the FSM action hooks and set the system's new + * current state. + * + * A substate, or a FSM does not define neither onEnter nor onExit function callbacks. + * + */ +"use strict"; +var automata_1 = require("../src/automata"); +var Controller = (function () { + function Controller(n) { + this.name = n; + } + Controller.prototype._a_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + Controller.prototype._a_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + Controller.prototype._b_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + Controller.prototype._b_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + Controller.prototype._c_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + Controller.prototype._c_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + Controller.prototype._1_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + Controller.prototype._1_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + Controller.prototype._2_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + Controller.prototype._2_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + Controller.prototype._3_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + Controller.prototype._3_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + Controller.prototype.SubStateTest_enter = function (session, state, msg) { + console.log(this.name + " " + state + " enter "); + }; + Controller.prototype.SubStateTest_exit = function (session, state, msg) { + console.log(this.name + " " + state + " exit "); + }; + return Controller; +}()); +// Register one FSM model. +automata_1.Automata.RegisterFSM({ + name: "SubStateTest", + state: ["_1", "_2", "_3"], + initial_state: "_1", + transition: [ + { + event: "12", + from: "_1", + to: "_2" + }, + { + event: "23", + from: "_2", + to: "_3" + } + ] +}); +// register another FSM model +automata_1.Automata.RegisterFSM({ + name: "Test4", + state: ["a", "b", "FSM:SubStateTest", "c"], + initial_state: "a", + transition: [ + { + event: "ab", + from: "a", + to: "b", + }, + { + event: "bc", + from: "b", + to: "SubStateTest", + }, + { + event: "cd", + from: "SubStateTest", + to: "c", + } + ] +}); +var session = automata_1.Automata.CreateSession(new Controller("c1"), "Test4").then(function success(session, m) { + session.dispatchMessage({ msgId: "ab" }); + session.dispatchMessage({ msgId: "bc" }).then(function success(s, m) { + s.printStackTrace(); + }); + session.dispatchMessage({ msgId: "cd" }).then(function success(s, m) { + s.printStackTrace(); + }); +}); +//# sourceMappingURL=test4.js.map \ No newline at end of file diff --git a/build/test/test4.js.map b/build/test/test4.js.map new file mode 100644 index 0000000..23adb40 --- /dev/null +++ b/build/test/test4.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test4.js","sourceRoot":"","sources":["../../test/test4.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/changelog b/changelog index 1be5af8..4fa3228 100644 --- a/changelog +++ b/changelog @@ -1,4 +1,19 @@ -07-07-2016 *2.0.1* +04-15/2016 *3.0.0* +------------------ + +This version is incompatible with V 2.x.x. Before upgrade, please check the following changes: + +* Full typscript rewrite. Now the .d.ts file is created from source, not manually maintained. +* Automata definition format changed. Now States are defined in a string array, and initial state is explicitly set by +defining `initial_state`. +* Session Controller methods have changed signature: `session : Session, state : string, msg : Message`. Transition + parameter has been removed because it is redundant (`msg.msgId` is the transition id). +* `dispatchMessage` vs `postMessage`. Now, multiple sessions can create internal state changes in the correct way. External + message passing must be done by calling `dispatchMessage`. Controller methods that want to trigger an internal state + change must call `postMessge`. +* Session and their controllers can be serialized/deserialized. + +07-07-2015 *2.0.1* ------------------ * Fixed typescript definitions. diff --git a/package.json b/package.json index df5f34e..d32d96a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "automata", - "version": "2.0.3", - "main": "automata", + "version": "3.0.0", + "main": "./build/src/automata.js", "keywords": [ "DFA", "state machine", @@ -19,6 +19,7 @@ "engines": { "node": "*" }, + "typings": "./build/src/automata.d.ts", "license": "MIT", "homepage": "http://github.com/hyperandroid/automata" } diff --git a/readme.md b/readme.md index 93a4f38..73aa934 100644 --- a/readme.md +++ b/readme.md @@ -5,7 +5,7 @@ Current state of automata is version 2.x.x, which is not backward compatible wit ## Description Automata is a formal finite state machine (FDA) framework. -It aims at offering a totally decoupled management of logic and data storage. +It aims at offering a totally decoupled management of logic and data. It features all the needed elements to have a modern and flexible finite state machine framework like @@ -29,141 +29,171 @@ or Automata will then expose an object with some functions: -```javascript -module.exports= { - registerFSM, // register a FDA object. - registerFDA, // same as registerFSM - createSession, // create a session for an FDA - guardException, // create a guard exception - newSessionListener // create a session listener overriding methods with the parameter object. +#### Typescript +```typescript +export class Automata { + static RegisterFSM( file : string|FSMJson ); + static CreateSession( controller : T, fsm_name : string, o? : SessionObserver ) : SessionConsumeMessagePromise; } ``` -Or typescript definition: - -```typescript -declare module Automata { - - export function registerFSM( object:FSM.FSMDefinition ); - export function registerFDA( object:FSM.FSMDefinition ); - export function createSession( fda_name : string, controller : any ) : FSM.Session; - export function newGuardException( message : string ) : FSM.GuardException; - export function newSessionListener( obj : any ) : FSM.SessionListener; - +#### Javascript +```javascript +{ + Automata : { + CreateSession(controller, name, session_observer?), + RegisterFSM( automata_def ) + } } ``` ## How it works -In Automata, FDA (finite deterministic automaton) are declaratively defined. It is contstrained to `FSMDefinition` -object. -The Automata definition will be unique, and different execution `Session` objects will be created from there. -Think of the FDA as the class, and the `Session` as the object. +In Automata, FDA (finite deterministic automaton) are declaratively defined. Their definition can be found in `FSMDefinition` object. +The Automata definition will be unique, and properly registered in an internal registry. +`Session` objects will be created from the automata. Think of the FDA as the class, and the `Session` as the object. + For example, an FDA defines a Scrabble game. The sessions will be specific Scrabble games. Sessions keep track of the current State as well as per-session Data associated with a session controller object. This controller is an arbitrary object you supply at session creation time. -So, first of all, one or more FDA 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'd rather call `registerFDA`. -In the FDA definition one State must be labeled as initial. This will be the entry point. - -An example minimal state machine could be: +A minimal example state machine could be: ```javascript -fsmContext.registerFSM( { - - // FDA registry name - name : "Test", - - // States - state : [ +Automata.RegisterFSM({ + name : "Test1", + state : ["a","b","c"], + initial_state : "a", + transition : [ { - name : "1", - initial : true, - }, - { - name : "2", - }, - { - name : "3" - } - ], - - // transitions - transition : [ - { - event : "12", - from : "1", - to : "2" + event : "ab", + from : "a", + to : "b" }, { - event : "23", - from : "2", - to : "3" + event : "bc", + from : "b", + to : "c" } ] -} ); +}); ``` -Only one State must be labeled as `initial`. To start using this machine, a `Session` must be created from a registered FDA. For example: -```javascript +```typescript -// ControllerObject is an object that holds per-session data and FDA's activity function callbacks. -// Will come to it later. -var session= fsmContext.createSession("Test", new SessionController() ); +const Controller = (function () { + function Controller() { + } + Controller.prototype.a_enter = function (session, state, msg) { + console.log(state + " enter "); + }; + ; + Controller.prototype.a_exit = function (session, state, msg) { + console.log(state + " exit "); + }; + ; + Controller.prototype.b_enter = function (session, state, msg) { + console.log(state + " enter "); + }; + ; + Controller.prototype.b_exit = function (session, state, msg) { + console.log(state + " exit "); + }; + ; + Controller.prototype.c_exit = function (session, state, msg) { + console.log(state + " exit"); + }; + ; + Controller.prototype.c_enter = function (session, state, msg) { + console.log(state + " enter"); + }; + ; + Controller.prototype.ab_transition = function (session, state, msg) { + console.log("transition: " + msg.msgId); + }; + ; + Controller.prototype.bc_transition = function (session, state, msg) { + console.log("transition: " + msg.msgId); + }; + ; + Controller.prototype.Test1_enter = function (session, state, msg) { + console.log(state + " enter "); + }; + ; + Controller.prototype.Test1_exit = function (session, state, msg) { + console.log(state + " exit "); + }; + ; + return Controller; +}()); -// The session must ultimately be started in order to track FDA's activity: -session.start(); +Automata.CreateSession( + new Controller(), + "Test1" +).then( + function success( s : Session, m : Message ) { + // session has been created and the controller object correctly attached. + }, + function error( s : Session, m : Error ) { + // something went wrong. + } +); ``` -To send notification events to a session object, call consume method: +To send notification events to a session object, call dispatchMessage method: -```javascript -session.consume( { msgId: "12" } ); +```typescript +session.dispatchMessage( { msgId: "12" } ); ``` This is the most basic workflow, but some things must be taken into account: -### Why create a session then start ? +### Why create a session then success. Can't it be a synchronous call ? -Session creation may internally trigger state changes. -If you want to have a `SessionListener` object registered to track all these state changes, the `Session` lifecycle must -be spanned in two different stages. +Session creation may internally trigger state changes, so you never may be sure what Transition or State code defined +in the Controller object will do. Also, you can dispatch a message to a session that is currently executing other dispatched + messages, or there may be a few other messages queued for the session. +By nature, dispatching a message is totally asynchronous. -```javascript +### How can i be notified of all FSM activity. -var session= fsmContext.createSession( ... ); -session.addListener( { - ... -} ); +You can be notified of all the FSM activity bound to a `Session` by attaching an observer to the `Session` object. +Since starting a `Session` may trigger internal state changes, you may want to pass the listener as the third optional +parameter of type `SessionObserver` to `Automata.CreateSession` like: -session.start( function(session) { - // session started -}); +```typescript +Automata.CreateSession( + new Controller(), + "Test1", + { + contextCreated : ( e : SessionObserverEvent ) => {}, + contextDestroyed : ( e : SessionObserverEvent ) => {}, + sessionEnded : ( e : SessionObserverEvent ) => {}, + customEvent : ( e : SessionObserverEvent ) => {}, + stateChanged : ( e : SessionObserverEvent ) => {} + } +).then( + function success( s : Session, m : Message ) { + // session has been created and the controller object correctly attached. + }, + function error( s : Session, m : Error ) { + // something went wrong. + } +); ``` -### Why a callback to `start` session or `consume` ? - -As we said before, a `Session` creation may internally trigger state changes. -For example, an FDA definition states that when entering its initial state, a message will be consumed which will fire -a transition from state 'initial' to another one. -Since Automata's execution is fully asynchronous, by the time the call to `start` or `consume` ends, you definitely -can't be sure whether the session ended starting or not. -The callback is guaranteed to be notified when `start` or `consume` methods and all internally triggered state changes -end. +or Attach a listener at a later time by calling -Another thing to note is that is will be fully safe to call `consume` right after ending a previous `consume` or -`start` method call. Automata treates user issued `consume` calls differently than `consume` calls triggered by a -state or transition action execution. +```typescript +session.addObserver( ... ) +``` ### FDA messages -The `consume` method accepts as a valid message any object which conforms to the typedef `FSM.TransitionMessage` -which has the following form: +The `session.dispatchMessage` and `session.postMessage` methods accepts as a valid message any object which conforms to ```json { @@ -173,31 +203,43 @@ which has the following form: ``` msgId's values must be the value defined in the **event** attribute present in the Transition - FDA definition block. + FDA definition block. So, for a given `State`, a call to `session.dispatchMessage` will match an exit transition with + the passed-in message. + +* If found, it will start the process of State transition. +* If not found, the message will be discarded by notifying an Error of `unknown exit transition for State 's'` A session accepts messages until it has reached a final State. From then and beyond, the session will -toss exceptions if it has a message sent for consumption. +toss Errors any message is dispatched or posted to it. + +### Dispatch vs Post message. + +A session exposes two well defined method to interact with it. + +`session.postMessage`, is expected to by the internal submission point of messages for a given Session. Controller methods + that wanted to trigger a State change request must call this method. -### Session execution +`session.dispatchMessage`, is expected to be the external submission point of messages for a given Session. Each call to +`dispatchMessage` will create an internal messages queue. This queue will be fed with all the internal State change +requests, which means `postMessage` will add messages to the currently executing Session's messages queue. -Until Automata V2, all session messages where synchronously consumed. -From V2, all messages are **asynchronously** consumed, which renders Automata V2 incompatible with Automata V1.X. -The synchronous consumption led to some unexpected problems like deep execution stack traces that could led to stackoverflow -errors. -In order to avoid execution callback errors, Automata V2 creates internal message queues. They work as follows: +The signature for `postMessage` is: -* for each user called `session.consume(callback)` method, a new message queue will be created. This queue will not be - executed until all the previous message queues (user issued session.consume calls) end processing their messages. -* for each framework called `session.consume(callback)` method, a new message will be added to the current message queue. -Framework consume calls happen in the controller object, when the FDA callbacks get executed. +`postMessage( m : Message )` -When a message queue gets empty, the callback gets called. +while the signature for `dispatchMessage` is: + +`dispatchMessage( m : U ) : SessionConsumeMessagePromise` or in javascript> `dispatchMessage( message ) : SessionConsumeMessagePromise` +The promise-like object returned by dispatchMessage will be invoked when the associated messages queue gets empty. +This brings a level of execution isolation where once a message has been dispatched, all activity derived from it will +be treated as an asynchronous atomic operation. It is thus guaranteed that this object will be notified only after all +internal transitions have ended. ## Controller object The FDA logic and state are isolated. The developer supplies a custom FDA controller object when the `Session` is created. -The `controller` object contains per session data, like for example the cards dealt in game, the authorization credentials, +The `controller` object contains per session data, like for example the cards dealt in a game, the authorization credentials, or any other Session specific information. It also has callback functions for FDA specific hook points like entering/exiting a `State` or executing a `Transition`. @@ -205,123 +247,64 @@ For both, State and Transitions, the calling **this** scope will be the logic ob ## Activy hooks -Automata offers many activy hooks on its activity. The following hooks are available: +Automata offers many activity hooks: State and FDA: - * **onEnter**. Code fired on state enter. - * **onExit**. Code fired on state exit. + * **_enter**. Code fired on state enter. + * **_exit**. Code fired on state exit. Transition: - * **onTransition**. Code fired when the transition fires. - * **onPreGuard**. Code fired on transition fire but previously to onTransition. It can veto transition fire. - * **onPostGuard**. Code fired after onTransition execution. Could veto transition fire by issuing an auto-transition. + * **_transition**. Code fired when the transition fires. + * **_preGuard**. Code fired on transition fire but previously to onTransition. It can veto transition fire. + * **_postGuard**. Code fired after onTransition execution. Could veto transition fire by issuing an auto-transition. A natural transition flow of executed actions for a transition from StateA to StateB will be: ``` -StateA.onExit() -> Transition.onTransition() -> StateB.onEnter() -``` - -Those hooks are defined in the **FDA JSON** definition as in the example: - - -```javascript -/** - * Define a session controller object. - * @constructor - */ -function controller() { - - this.count= 0; - - this.B_onEnter= function() { - console.log("Enter state B"); - this.count++; - }; - - this.A_onExit= function() { - console.log("Exit state A"); - }; +// For an automata defined as follows: + +... +state : ['a', 'b', 'c'], +initial_state : 'a', +transition : [ + { + from : 'a', + to : 'b', + event : 'ab' + } +] +... - this.TR_AB= function() { - console.log("Transition fire code"); - } +StateA.onExit() -> Transition.onTransition() -> StateB.onEnter() - return this; -} +// which translate into the following Controller methods (should they exist) -/** - * Define a FDA - */ - fsmContext.registerFSM( { +a_exit( ... ); +ab_transition( ... ); +b_enter( ... ); - // FDA registry name - name : "Test", - - // States - state : [ - { - name : "A", - initial : true, - onExit : "A_onExit" - }, - { - name : "B", - onEnter : "B_onEnter" - }, - { - name : "C" - } - ], - - transition : [ - { - event : "AB", - from : "A", - to : "B", - onTransition: "TR_AB", - }, - { - event : "BC", - from : "B", - to : "C" - } - ] - } ); - - var session= fsmContext.createSession("Test", new controller()); - - session.start( function(session) { - session.dispatch( { msgId: "AB" } ); - // this will print: - // Exit state A - // Transition fire code - // Enter state B - }); - ``` -The controller object can be notified automatically about Session changes in two different ways: - -* Configuration: supply callback functions in the FDA definition object. -* Convention: the framework will automatically try to find methods in the controller object as follows: +The controller object can only be notified automatically about Session changes by Convention: +the framework will automatically try to find methods in the controller object as follows: * * 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" +* * Transition pre-guard: transition.getEvent() + "_preGuard" +* * Transition post-guard: transition.getEvent() + "_postGuard" State and Transition activity callbacks are of the form: ```javascript -function( session, state, transition, msg ); +( session : Session, + state : string, + msg : Message ) => void; ``` - -In any case, those functions will be automatically called if they exist in the logic object. +Those functions will be automatically called **only if** they exist in the logic object. ## Guards @@ -360,72 +343,43 @@ endif ``` 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 or `module.newGuardException`. - Those functions are optional, and must be set in the "transition" block of the - FDA definition as follows: - -```javascript - fsmContext.registerFSM( { - ..., - - transition : [ - { - event : "AB", - from : "A", - to : "B", - onTransition : "TR_AB", - onPreGuard : "pre_guard_function", - onPostGuard : "post_guard_function", - }, - ... - ], - - ... - } -``` - - If no onPreGuard/onPostGuard attributes are specified, Automata FDA engine will assume a call to a convention method - of the form: + pre/post-transition functions. A Guard is expected to throw an Error object by calling. + + Guards are optional, and will be invoked only if they exist in the Controller object. The method names must be of the form: ``` _preGuard / _postGuard. ``` - In this case: - * AB_preGuard - * AB_postGuard - +Event is the 'event' defined in the FSM definition transition block. ## Timed transitions -Automata offers out of the box timed transitions by defining an **onTimer** block in a FDA definition. For example: +Automata offers out of the box timed transitions by defining an **timeout** block in a transition definition block. For example: ```javascript fsmContext.registerFSM( { ..., - state : [ - { - name : "1", - initial : true, - onTimer : { - timeout : 2000, - event : "12" - }, - } - ], - + transition : [ + { + event : "ab", + from : "a", + to : "b", + timeout : { + millis : 4000, + data : {} + } + }, ... } ); ``` -This instruments the engine that after 2 seconds of entering this state, an event {msgId: "12"} will be sent to the -FDA 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. +This instruments the engine that after 4 seconds of entering `state a`, an event `{msgId: "ab"}` will be dispatched to the +`Session`. The timer is handled automatically, and set/canceled on state enter/exit respectively. +Timers are set by calling `setTimeout`, and automatically handled by the javascript engine. ## SubStates @@ -433,40 +387,60 @@ Automata allows to nest as much as needed substates. In fact, by defining a sing one for the FDA, and the other, initially for the FDA's initial state. To define different levels, you must register more than one FDA in the registry, and then reference one of them as a substate in the "state" section: -```javascript - fsmContext.registerFSM( { - ... - state : [ +```typescript +// Register one FSM model. +Automata.RegisterFSM( { + name : "SubStateTest", + state : ["_1","_2","_3"], + initial_state : "_1", + transition : [ { - name : "a", - initial : true, - onEnter : "enter_a", - onExit : "exit_a" + event : "12", + from : "_1", + to : "_2" }, { - subState: "STest" - }, - - ... + event : "23", + from : "_2", + to : "_3" + } + ] +} ); +``` - ], +To reference another Automata as substate, use the prefix `FSM:` for the state name: - ... - } ); -``` +```typescript -Then, the transition section will identify this FDA as a substate by its name, STest. A "subState" can't have a - 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. +Automata.RegisterFSM( { - The stacking of different subStates is done transparently, and they are handled by the "session" object. For each - stacked level, a FDA.Context object is created. A context object is just a holder for the current state for each - nesting level. + name : "Test4", + state : ["a","b","FSM:SubStateTest","c"], + initial_state : "a", + transition : [ + { + event : "ab", + from : "a", + to : "b", + }, + { + event : "bc", + from : "b", + to : "SubStateTest", + }, + { + event : "cd", + from : "SubStateTest", + to : "c", + } + ] +} ); +``` ## Transition from Substates The way in which Automata manages state changes is made hierarchycally. That means, the engine will try to find a -suitable transition for a given incoming message regardless of its nesting level. +suitable transition for a given incoming message regardless of its depth level. So for any given FDA stacktrace, the engine will traverse upwards trying to find a suitable state to fire a transition for the dispatched event. @@ -482,19 +456,19 @@ transition for the dispatched event. -For example, given the previous example, +For example, to a `Session` which is currently in `State S1`, -```javascript -session.consume( {msgId : "T_S1_S2" } ); +```typescript +session.dispatchMessage( {msgId : "T_S1_S2" } ); ``` -means the session is on state SS1, and the stackTrace will be the following: +will make the session change state to `SS1`, and the stackTrace will be the following: -ROOT, SUB_STATE, SS1 +`ROOT, SUB_STATE, SS1` By calling ```javascript -session.consume( {msgId : "T_SS_S3" } ); +session.dispatchMessage( {msgId : "T_SS_S3" } ); ``` on the session at state SS1, SS1 will be removed from the stack (since SS2 is a final state), and the session will @@ -502,51 +476,36 @@ transize to S3 state. Additionally, this session will be finished since S3 is a final State (this nesting level will be removed from the stack too), and so it is ROOT, which causes the session to be emptied. - ## FDA listeners -Any FDA session activity can be monitored by adding a listener. +Any FDA `Session` activity can be monitored by adding one or more observers to it. For example: ```javascript -session.addListener( new FDA.SessionListener() ); +session.addObserver( { + contextCreated( e : SessionObserverEvent ) : void; // fired when the Session creates a new depth level + contextDestroyed( e : SessionObserverEvent ) : void; // fired when the Session destroys a depth level + sessionEnded( e : SessionObserverEvent ) : void; // session reached one of its final states. + customEvent( e : SessionObserverEvent ) : void; // fire something by calling session.fireCustomEvent. + // for example, from the Controller object + stateChanged( e : SessionObserverEvent ) : void; // Session changed state (new and previous are available). + + }); ``` -or +The event parameter for the observer methods is: -```javascript +```typescript +export interface SessionObserverEvent { + session : Session; + message : Message; + custom_message? : Message; + current_state_name : string; + prev_state_name : string; +} -// 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: - -* **contextCreated**: FSM.SessionContextEvent -* **contextDestroyed**: FSM.SessionContextEvent -* **finalStateReached**: FSM.SessionFinalStateReachedEvent -* **stateChanged**: FSM.SessionStateChangeEvent -* **preGuard**: FSM.TransitionGuardEvent -* **postGuard**: FSM.TransitionGuardEvent -* **customEvent**: FSM.CustomEvent - ## Custom events The preferred way for sending custom events will be by calling: @@ -568,177 +527,9 @@ customEvent : function( ev : FSM.CustomEvent ) { This sample shows how to define common FDA session callback points. Either on logic object, or by defining a callback. In either case, **this** is defined to be the session's logic object. -```javascript - -context= module.exports; - -var Controller= function() { - - this.a_enter= function( session, state, transition, msg ) { - console.log("a enter "+state.toString()); - }; - - this.a_exit= function( session, state, transition, msg ) { - console.log("a exit "+state.toString()); - }; - - this.b_enter= function( session, state, transition, msg ) { - console.log("b enter "+state.toString()); - }; - - this.b_exit= function( session, state, transition, msg ) { - console.log("b exit "+state.toString()); - }; - - this.c_exit= function( session, state, transition, msg ) { - console.log("c exit "+state.toString()); - }; - - this.ab_transition= function( session, state, transition, msg ) { - console.log("transition: "+transition.toString()); - }; - - this.bc_transition= function( session, state, transition, msg ) { - console.log("transition: "+transition.toString()); - }; - - this.Test1_enter= function( session, state, transition, msg ) { - console.log("test1 enter "+state.toString()); - }; - - this.Test1_exit= function( session, state, transition, msg ) { - console.log("test1 exit "+state.toString()); - }; -}; - -context.registerFSM( { - - name : "Test1", - - state : [ - { - name : "a", - initial : true - }, - { - name : "b" - }, - { - name : "c", - onEnter : function( session, state, transition, msg ) { - console.log("Enter c"); - } - } - ], - - transition : [ - { - event : "ab", - from : "a", - to : "b" - }, - { - event : "bc", - from : "b", - to : "c" - } - ] -} ); - -var session= context.createSession({ - fda: "Test1", - controller: new Controller() -} ); -session.start( function onStartProcessEnds(session) { - session.consume( { msgId: "ab" } ); - session.consume( { msgId: "bc" } ); - } -); - - -``` - ## Sample 2 - FDA with timed events -This sample show how to define a timed transition. Note this example has no FDA Controller. - -```javascript - -context= module.exports; - - -context.registerFSM( { - - name : "Test2", - - state : [ - { - name : "a", - initial : true, - onExit : function( session, state, transition, msg ) { - console.log("Exit a"); - }, - onTimer : { // <-- Timed transition. - timeout: 4000, // after 4 seconds - event: { - msgId: "ab" // fire transition identified by "ab" if exists. - } - } - }, - { - name : "b", - onEnter : function( session, state, transition, msg ) { - console.log("Enter b"); - } - }, - { - name : "c" - } - ], - - transition : [ - { - event : "ab", - from : "a", - to : "b" - }, - { - event : "bc", - from : "b", - to : "c" - } - ] -} ); - -var session1= context.createSession({ - fda: "Test2" -}); -session1.start(); - -var session2= context.createSession({ - fda : "Test2" -} ); - -session2.start(); -session2.consume( {msgId : "ab"} ); - -/* -will print: - -immediately -Exit a -Enter b -from session2 which has triggered a transition change - -and -Exit a -Enter b -after 4 seconds from session1. -*/ - -*/ - -``` +This sample show how to define a timed transition. ## Sample 3 - Guards @@ -757,157 +548,6 @@ After firing the transition, the postGuard is checked. If this function **throws turns into auto-transition, that means firing state change to current-state, and entering again current state. If not, the transition continues its natural flow and transition's next state is set as current state. -```javascript - -context= module.exports; - - -var Controller= function() { - - this.count= 0; - - this.enter_b= function() { - console.log("enter b"); - }; - - this.enter= function( session, state, transition, msg ) { - console.log("enter "+state.toString()); - }; - - this.exit= function( session, state, transition, msg ) { - console.log("exit "+state.toString()); - }; - - this.action= function( session, state, transition, msg ) { - console.log("transition: "+transition.toString()); - }; - - this.pre_guard_tr_bc= function() { - this.count++; - console.log("count= "+this.count); - if ( this.count<3 ) { - throw context.newGuardException("PreGuard_tr_BC"); - } else { - console.log("Ok, go."); - } - }; - - this.post_guard_tr_bc= function() { - this.count++; - console.log("count= "+this.count); - if ( this.count<5 ) { - throw context.newGuardException("PostGuard_tr_BC"); - } - }; - - return this; -}; - -context.registerFSM( { - - name : "Test3", - - state : [ - { - name : "a", - initial : true, - onEnter : "enter", - onExit : "exit" - }, - { - name : "b", - onEnter : "enter_b", - onExit : "exit" - }, - { - name : "c", - onEnter : "enter", - onExit : "exit" - }, - { - name : "d", - onEnter : "enter", - onExit : "exit" - } - ], - - transition : [ - { - event : "ab", - from : "a", - to : "b", - onTransition: "action" - }, - { - event : "bc", - from : "b", - to : "c", - onTransition: "action", - onPreGuard : "pre_guard_tr_bc", - onPostGuard : "post_guard_tr_bc" - }, - { - event : "cd", - from : "b", - to : "c", - onTransition: "action" - } - ] -} ); - -var session= context.createSession({ - fda: "Test3", - controller: new Controller() -}); - -session.addListener( context.newSessionListener( { - finalStateReached : function( obj ) { - console.log("SessionListener finalStateReached " ); - }, - - /** - * - * @param obj {FSM.SessionStateChangeEvent} - */ - stateChanged : function( obj ) { - var ps= obj.prevState ? obj.prevState.getName() : "none"; - console.log("SessionListener stateChanged "+ps+" --> "+obj.state.getName() ); - } -} ) ); - -// start session. -session.start(); - -console.log(""); -console.log("Sent '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.consume( { msgId: "bc" } ); - -// fail on pre-guard. count=2, but no notification of state change sent. -console.log(""); -console.log("Sent 'bc'"); -session.consume( { msgId: "bc" } ); - -// on pre-guard. count=3. -// Ok go transition. -// Fail on post-guard -// so onExit State-b and onEnter State-b ( auto-transition ). Vetoed transition from State-b to State-c. -// notification of 'stateChanged' on the observer. -console.log(""); -console.log("Sent 'bc'"); -session.consume( { msgId: "bc" } ); - -console.log(""); -console.log("Sent 'bc'"); -session.consume( { msgId: "bc" } ); - -``` - - ## Sample 4 - SubStates Sub States is an Automata feature which allows to nest different registered FDA as states of other FDA. @@ -916,169 +556,3 @@ Automata will handle automatically all the nesting procedure, call the FDA actio current state. A substate, or a FDA does not define neither onEnter nor onExit function callbacks. - -It is done as follows: - -```javascript - -var context= module.exports; - - -var Controller= function() { - - this.enter= function( session, state, transition, msg ) { - console.log("Enter "+state.toString()); - }; - - this.exit= function( session, state, transition, msg ) { - console.log("Exit "+state.toString()); - }; - - this.transition= function(session, state, transition, msg ) { - console.log("transition "+transition.toString()); - }; - - return this; -}; - -// Register one FSM model. -context.registerFSM( { - name : "SubStateTest", - - state : [ - { - name : "1", - initial : true, - onEnter : "enter", - onExit : "exit" - }, - { - name : "2", - onEnter : "enter", - onExit : "exit" - }, - { - name : "3", - onEnter : "enter", - onExit : "exit" - } - ], - - transition : [ - { - event : "12", - from : "1", - to : "2" - }, - { - event : "23", - from : "2", - to : "3" - } - ], - - onExit : function() { - console.log(" --> Exit sub-automata SubStateTest"); - }, - - onEnter : function() { - console.log(" --> Enter sub-automata SubStateTest"); - } - -} ); - -// register another FSM model - -context.registerFSM( { - - name : "Test4", - - state : [ - { - name : "a", - initial : true, - onEnter : "enter", - onExit : "exit" - }, - { - name : "b", - onEnter : "enter", - onExit : "exit" - }, - { - subState: "SubStateTest" - }, - { - name : "c", - onEnter : "enter", - onExit : "exit" - } - ], - - transition : [ - { - event : "ab", - from : "a", - to : "b", - onTransition: "transition" - }, - { - event : "bc", - from : "b", - to : "SubStateTest", - onTransition: "transition" - }, - { - event : "cd", - from : "SubStateTest", - to : "c", - onTransition: "transition" - } - ], - - onExit : function() { - console.log(" --> Exit automata Test4"); - }, - - onEnter : function() { - console.log(" --> Enter automata Test4"); - } - -} ); - -var session= context.createSession({ - fda : "Test4", - controller : new Controller() -}); - -session.start( function(session) { - - 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/src/automata.ts b/src/automata.ts new file mode 100644 index 0000000..baaf1a2 --- /dev/null +++ b/src/automata.ts @@ -0,0 +1,844 @@ +/** + * Created by ibon on 2/8/16. + */ + +export interface Message { + msgId : string; + data? : any; +} + +export type GenericMap = { [name : string] : T }; +export type Action = (session: Session, message : Message) => T; + +export interface AutoTransitionJson { + millis : number, + data? : any; +} + +export interface TransitionJson { + from : string; + to : string; + event : string; + timeout? : AutoTransitionJson +} + +export interface FSMJson { + name : string; + state : string[]; + initial_state : string; + transition : TransitionJson[]; +} + +export type SessionMessageCallback = (session : Session, message? : Message) => void; +export type SessionMessageCallbackError = (session : Session, message? : string) => void; + +export class SessionConsumeMessagePromise { + + _success : SessionMessageCallback; + _error : SessionMessageCallbackError; + + constructor() { + } + + then( ok : SessionMessageCallback, error? : SessionMessageCallbackError ) { + this._success = ok; + this._error = error; + return this; + } + + __success( s : Session, m : Message ) { + this._success && this._success( s, m ); + } + + __error( s : Session, message? : string ) { + this._error && this._error( s, message ); + } +} + +export class FSMRegistry { + + static _fsm : GenericMap = {}; + + static FSMFromId( id : string ) : FSM { + return FSMRegistry._fsm[id]; + } + + static register( fsm_json : FSMJson ) { + try { + let fsm = new FSM(fsm_json); + FSMRegistry._fsm[fsm.name] = fsm; + console.log("Registered Automata '"+fsm.name+"'"); + } catch( e ) { + console.error(e); + } + } + + static createSession( session_controller : T, fsm_id : string, o? : SessionObserver ) : SessionConsumeMessagePromise { + + const promise : SessionConsumeMessagePromise = new SessionConsumeMessagePromise(); + + const fsm = FSMRegistry._fsm[ fsm_id ]; + if ( fsm ) { + const session = new Session( session_controller ); + if ( o ) { + session.addObserver(o); + } + session.__initialize( fsm ).then( + (session : Session, m: Message) : void => { + promise.__success( session, m ); + }, + (session : Session, m : string) : void => { + promise.__error( session, m ); + } + ); + } else { + setImmediate( function() { + promise.__error( null, "Unkonwn automata: '"+fsm_id+"'"); + } ); + } + + return promise; + } +} + +export interface StateAutoTransitionElement { + + millis : number; + message?: Message; + timer_id? : number; +} + +export class State { + + _name : string; + _exit_transitions : GenericMap; + _exit_transitions_count : number; + _enter_action : Action; + _exit_action : Action; + _auto_transition : StateAutoTransitionElement[]; + + constructor( name : string ) { + this._name = name; + this._exit_transitions = {}; + this._exit_transitions_count = 0; + this._enter_action = null; + this._exit_action = null; + this._auto_transition = []; + } + + transitionForMessage( m : Message ) { + const tr : Transition = this._exit_transitions[ m.msgId ]; + return tr||null; + } + + addExitTransition( t : Transition ) { + this._exit_transitions[t.event] = t; + this._exit_transitions_count += 1; + } + + get name() { + return this._name; + } + + __onExit( s : Session, m : Message ) : boolean { + if ( this._exit_action!==null ) { + this._exit_action( s, m ); + } + + this.__stopTimeoutTransitionElements(); + + return this._exit_action!==null; + } + + __onEnter( s : Session, m : Message ) { + if ( this._enter_action!==null ) { + this._enter_action( s, m); + } + + this.__startTimeoutTransitionElements(s); + + return this._enter_action!==null; + } + + __startTimeoutTransitionElements( s : Session ) { + this._auto_transition.forEach( (sate) : void => { + sate.timer_id = setTimeout( + this.__notifyTimeoutEvent.bind(this,s, sate.message), + sate.millis + ); + }); + } + + __stopTimeoutTransitionElements() { + this._auto_transition.forEach( (sate) : void => { + if ( sate.timer_id !==-1 ) { + clearTimeout(sate.timer_id); + sate.timer_id = -1; + } + }); + } + + __notifyTimeoutEvent( s : Session, m : Message ) { + this.__stopTimeoutTransitionElements(); + s.dispatchMessage( m ); + } + + __setTimeoutTransitionInfo( millis : number, message : Message ) { + this._auto_transition.push( { + millis : millis, + message : message, + timer_id : -1 + } ); + } + + isFinal() : boolean { + return this._exit_transitions_count === 0; + } + + toString() : string { + return this._name; + } +} + +export class FSM extends State { + + _states : State[]; + _transitions : Transition[]; + _initial_state : State; + + constructor( fsm : FSMJson ) { + super( fsm.name ); + + this._states = []; + this._transitions = []; + this._initial_state = null; + + this.__createStates( fsm.state, fsm.initial_state ); + this.__createTransitions( fsm.transition ); + } + + get initial_state() { + return this._initial_state; + } + + serialize() : FSMJson { + return { + name : this._name, + state : this._states.map( st => st._name ), + initial_state : this._initial_state._name, + transition : this._transitions.map( tr => { + return { + event: tr._event, + from: tr._initial_state._name, + to: tr._final_state._name + } + }) + }; + } + + __createStates( states : string[], initial : string ) { + + for( let name of states ) { + + let st: State; + if ( name.lastIndexOf("FSM:")===-1 ) { + st= new State(name); + } else { + const fsmname = name.substring(4); + st= FSMRegistry._fsm[fsmname]; + if ( !st ) { + throw "Automata '"+this._name+"' referencing other non existent automata: '"+name+"'"; + } + } + + this._states.push( st ); + + if ( st.name === initial ) { + this.__setInitialState( st ); + } + } + } + + __setInitialState( st : State ) { + this._initial_state = st; + this.__createInitialTransition(); + this.__createEnterAction(); + } + + __createInitialTransition() { + this.addExitTransition( + new Transition( + this, + this._initial_state, + Transition.__InitialTransitionEvent ) ); + } + + __createEnterAction() { + this._enter_action = (session : Session, message : Message ) : void => { + session.postMessage( Transition.__InitialTransitionMessage ); + } + } + + __findStateByName( n : string ) : State { + for( let s of this._states ) { + if ( s.name===n ) { + return s; + } + } + + return null; + } + + __createTransitions( transitions : TransitionJson[] ) { + transitions.forEach( (v:TransitionJson /*, index:number, arr:TransitionJson[] */ ) : void => { + + const f : State = this.__findStateByName( v.from ); + const t : State = this.__findStateByName( v.to ); + const e : string = v.event; + + if ( !f || !t ) { + throw `Wrongly defined Automata '${this.name}'. Transition '${v.event}' refers unknown state:'${(!f ? v.from : v.to)}'`; + } + + this._transitions.push( new Transition( f, t, e ) ); + + // auto transition behavior. + if ( typeof v.timeout!=="undefined" ) { + f.__setTimeoutTransitionInfo( v.timeout.millis, { + msgId : e, + data : v.timeout.data + } ); + } + }); + } +} + +export class Transition { + + static __InitialTransitionEvent : string = "__INITIAL_EVENT"; + static __InitialTransitionMessage : Message = { msgId : Transition.__InitialTransitionEvent }; + + _event : string; + _initial_state : State; + _final_state : State; + + constructor( from : State, to : State, event : string ) { + this._event= event; + this._initial_state = from; + this._final_state = to; + + if ( from ) { + from.addExitTransition( this ); + } + } + + get event() { + return this._event; + } + + get final_state() { + return this._final_state; + } + + toString() : string { + return this._event; + } +} + +export interface SerializedSessionContext { + current_state : string; + prev_state : string; +} + +export class SessionContext { + + _current_state : State; + _prev_state : State; + + constructor( c:State, p:State ) { + this._current_state = c; + this._prev_state = p; + } + + serialize() : SerializedSessionContext { + return { + current_state : this._current_state._name, + prev_state : this._prev_state ? this._prev_state._name : "", + }; + } + + get current_state() : State { + return this._current_state; + } + + get prev_state() : State { + return this._prev_state; + } + + currentStateName() : string { + return this._current_state && this._current_state.name; + } + + prevStateName() : string { + return this._prev_state && this._prev_state.name; + } + + printStackTrace() { + console.log(" "+this._current_state.name ); + } +} + +export interface SessionObserverEvent { + session : Session; + message : Message; + custom_message? : Message; + current_state_name : string; + prev_state_name : string; +} + +export interface SessionObserver { + contextCreated( e : SessionObserverEvent ) : void; + contextDestroyed( e : SessionObserverEvent ) : void; + sessionEnded( e : SessionObserverEvent ) : void; + customEvent( e : SessionObserverEvent ) : void; + stateChanged( e : SessionObserverEvent ) : void; +} + +export interface SerializedSession { + ended : boolean, + controller : any, + states : SerializedSessionContext[], + fsm : FSMJson +} + +export class Session { + _fsm : FSM; + _session_controller : T; + _states : SessionContext[]; + _ended : boolean; + _messages_controller : SessionMessagesController; + + _observers : SessionObserver[]; + _sessionEndPromise : SessionConsumeMessagePromise; + + constructor( session_controller : T ) { + this._states = []; + this._session_controller = session_controller; + this._messages_controller = new SessionMessagesController(this); + this._observers = []; + this._fsm = null; + this._ended = false; + this._sessionEndPromise = null; + } + + __initialize( fsm : FSM ) : SessionConsumeMessagePromise { + + this._fsm = fsm; + this._states.push( new SessionContext(fsm, null) ); + this.__invoke( fsm.name + "_enter", Transition.__InitialTransitionMessage ); + const promise =this.dispatchMessage( Transition.__InitialTransitionMessage ); + + this._sessionEndPromise = promise; + + return promise; + } + + __serializeController() : any { + var sc = this._session_controller; + if ( sc.serialize && typeof sc.serialize==="function" ) { + return sc.serialize(); + } + + return {}; + } + + serialize() : SerializedSession { + + const serializedController = this.__serializeController(); + + return { + ended : this._ended, + fsm : this._fsm.serialize(), + states : this._states.map( st => st.serialize() ), + controller: serializedController + }; + } + + static deserialize( s : SerializedSession, deserializer : (sg : U) => T ) : Session { + + const controller : T = deserializer( s.controller ); + const session : Session = new Session( controller ); + session.__deserialize( s ); + + return session; + } + + __deserialize( s : SerializedSession ) { + + FSMRegistry.register( s.fsm ); + this._fsm= FSMRegistry.FSMFromId( s.fsm.name ); + this._ended = s.ended; + this._states = s.states.map( e => { + const c : State = e.current_state === s.fsm.name ? + this._fsm : + this._fsm._states.filter( s => s._name===e.current_state )[0]; + const p : State = e.prev_state === "" ? + null : + this._fsm._states.filter( s => s._name===e.prev_state )[0]; + + return new SessionContext(c, p) + } ); + } + + addObserver( o : SessionObserver ) { + this._observers.push( o ); + } + + /** + * User side message. + */ + dispatchMessage( m : U ) : SessionConsumeMessagePromise { + if ( this._ended ) { + throw "Session is ended."; + } + + const c : SessionConsumeMessagePromise = new SessionConsumeMessagePromise(); + this._messages_controller.dispatchMessage( m, c ); + return c; + } + + /** + * From SessionController internals. + */ + postMessage( m : Message ) { + this._messages_controller.postMessage( m ); + } + + __messageImpl( m : Message ) { + if ( m === Transition.__InitialTransitionMessage ) { + this.__consumeMessageForFSM( m ); + } else { + this.__consumeMessageForState( m ); + } + } + + get current_state() : State { + return this._states.length ? + this._states[ this._states.length-1 ].current_state : + null; + } + + get prev_state() : State { + return this._states.length ? + this._states[ this._states.length-1 ].prev_state : + null; + } + + + __onEnter( m : Message ) { + + const cs : State = this.current_state; + if ( cs!==null && !cs.__onEnter(this, m) ) { + this.__invoke( cs.name+"_enter", m ); + } + } + + __onExit( m : Message ) { + const cs = this.current_state; + if ( cs!==null && !cs.__onExit( this, m ) ) { + this.__invoke( cs.name+"_exit", m ); + } + } + + __invoke( method : string, m : Message ) : any { + return (this._session_controller)[method] && (this._session_controller)[method]( this, this.current_state_name, m ); + } + + __consumeMessageForFSM( m : Message ) { + + const cs = this.current_state; + const fsm = cs; + const new_current_state = fsm.initial_state; + + this._states.push( new SessionContext( new_current_state, this.current_state ) ); + this.__notifyContextCreated( m ); + this.__onEnter( m ); + } + + + __findStateWithTransitionForMessage( m : Message ) : State { + + const sc = this._states; + let state : State = null; + + for( let i= sc.length-1; i>=0; i-- ) { + const current_state : State = sc[i].current_state; + const tr : Transition= current_state.transitionForMessage( m ); + if ( tr!==null ) { + state= current_state; + break; + } + } + + return state; + } + + + __exitAllStatesUpToStateWithTransitionForMessage( stateWitTransition : State, m : Message ) { + + while( this._states.length ) { + + let cs : SessionContext= this._states[ this._states.length - 1 ]; + this.__onExit( m ); + + if ( cs.current_state!==stateWitTransition ) { + this._states.pop(); + this.__notifyContextDestroyed( m ); + } else { + break; + } + } + } + + __popAllStates( m : Message ) { + while( this._states.length ) { + this.__onExit(m); + this._states.pop(); + this.__notifyContextDestroyed( m ); + } + } + + __setCurrentState( s :State, m : Message ) { + + let prev : State = null; + if ( this._states.length ) { + prev= this._states.pop().current_state; + } + + this._states.push( new SessionContext( s, prev ) ); + this.__notifyStateChange( m ); + + this.__onEnter( m ); + } + + __endSession( m : Message ) { + + this._ended= true; + this.__notifySessionEnded( m ); + } + + get current_state_name() { + return this._states.length ? + this._states[ this._states.length - 1].currentStateName() : + ""; + } + + get prev_state_name() { + return this._states.length ? + this._states[ this._states.length - 1].prevStateName() : + ""; + } + + __consumeMessageForState( m : Message ) { + + if ( !this._ended ) { + const state_for_message:State = this.__findStateWithTransitionForMessage(m); + + if (null !== state_for_message) { + this.__processMessage(state_for_message, m); + } else { + throw new Error(`No message: '${m.msgId}' for state: '${this.current_state_name}'`); + } + } else { + throw new Error(`Session is ended. Message ${m.msgId} is discarded.`); + } + } + + __processMessage( state_for_message : State, m : Message ) { + + const tr : Transition = state_for_message.transitionForMessage(m); + const transition_event : string = tr.event; + + if ( !this.__invoke(transition_event+"_preGuard", m) ) { + + this.__exitAllStatesUpToStateWithTransitionForMessage(state_for_message, m); + + this.__invoke(transition_event + "_transition", m); + + let next:State; + + if (! this.__invoke(transition_event + "_postGuard", m) ) { + next = tr.final_state; + } else { + next = state_for_message; + } + + this.__setCurrentState(next, m); + + if (next.isFinal()) { + this.__popAllStates(m); + this.__endSession(m); + } + } + } + + fireCustomEvent( message : any ) { + for( let o of this._observers ) { + o.customEvent( { + session : this, + message : null, + current_state_name : this.current_state_name, + prev_state_name : this.prev_state.name, + custom_message : message + } ); + } + } + + __notifySessionEnded( m : Message ) { + this.__notify( m, "sessionEnded" ); + } + + __notifyContextCreated( m : Message ) { + this.__notify( m, "contextCreated" ); + } + + __notifyContextDestroyed( m : Message ) { + this.__notify( m, "contextDestroyed"); + } + + __notifyStateChange( m : Message ) { + this.__notify( m, "stateChanged" ); + } + + __notify( m : Message, method : string ) { + for( let o of this._observers ) { + (o)[method] && (o)[method]( { + session : this, + message : m, + current_state_name : this.current_state_name, + prev_state_name : this.prev_state_name + } ); + } + } + + get controller() { + return this._session_controller; + } + + printStackTrace() { + if ( this._states.length===0 ) { + console.log("session empty"); + } else { + console.log("session stack trace:"); + this._states.forEach( function( s ) { + s.printStackTrace(); + }); + } + } +} + +export class SessionMessageControllerMessageQueue { + + _session : Session; + _triggering_message : Message; + _messages_queue : Message[]; + _callback : SessionConsumeMessagePromise; + + constructor( session : Session, m : Message, callback? : SessionConsumeMessagePromise ) { + this._session = session; + this._callback = typeof callback!=="undefined" ? callback : null; + this._triggering_message = m; + this._messages_queue = [ m ]; + } + + postMessage( m : Message ) { + this._messages_queue.push( m ); + } + + __consumeMessage() : boolean { + + let ret : boolean; + + if ( this._messages_queue.length ) { + const m = this._messages_queue.shift(); + + try { + this._session.__messageImpl(m); + ret = false; + } catch (e) { + console.error(`consume for message '${m.msgId}' got exception: `, e); + this._messages_queue= []; + this._callback.__error( this._session, e ); + ret = true; + } + + } else { + ret = true; + if ( this._callback ) { + this._callback.__success( this._session, this._triggering_message ); + } + } + + return ret; + } +} + +export class SessionMessagesController { + + _session : Session; + _message_queues : SessionMessageControllerMessageQueue[]; + _consuming : boolean; + + constructor( session : Session ) { + this._message_queues = []; + this._session = session; + this._consuming = false; + } + + dispatchMessage( m : Message, callback? : SessionConsumeMessagePromise ) { + this._message_queues.push( new SessionMessageControllerMessageQueue(this._session, m, callback) ); + this.__consumeMessage(); + } + + postMessage( m : Message ) { + this._message_queues[0].postMessage( m ); + this.__consumeMessage(); + } + + __consumeMessage() { + if ( !this._consuming ) { + this._consuming = true; + setImmediate( this.__consumeOne.bind(this) ); + } + } + + __consumeOne() { + if (this._message_queues.length) { + if ( this._message_queues[0].__consumeMessage() ) { + this._message_queues.shift(); + } + } + + if ( this._message_queues.length ) { + setImmediate( this.__consumeOne.bind(this) ); + } else { + this._consuming = false; + } + } +} + +export class Automata { + + static RegisterFSM( file : string|FSMJson ) { + + if ( typeof file==="string" ) { + + } else { + FSMRegistry.register( file ); + } + } + + static CreateSession( controller : T, fsm_name : string, o? : SessionObserver ) : SessionConsumeMessagePromise { + return FSMRegistry.createSession( controller, fsm_name, o ); + } +} \ No newline at end of file diff --git a/test/test.html b/test/test.html deleted file mode 100644 index df8c4cd..0000000 --- a/test/test.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - Automata test. - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/test1.js b/test/test1.js deleted file mode 100644 index 501b855..0000000 --- a/test/test1.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @author Ibon Tolosana, @hyperandroid - * - * See LICENSE file. - * - * - * - * Sample 1 - Simple FSM - * - * This sample shows how to define common FSM session callback points. - * Either on logic object, or by defining a callback. - * In both cases, 'this' is defined to be the session's logic object. - * - */ - -context= require("automata"); - -var Controller= function() { - - this.a_enter= function( session, state, transition, msg ) { - console.log("a enter "+state.toString()); - }; - - this.a_exit= function( session, state, transition, msg ) { - console.log("a exit "+state.toString()); - }; - - this.b_enter= function( session, state, transition, msg ) { - console.log("b enter "+state.toString()); - }; - - this.b_exit= function( session, state, transition, msg ) { - console.log("b exit "+state.toString()); - }; - - this.c_exit= function( session, state, transition, msg ) { - console.log("c exit "+state.toString()); - }; - - this.ab_transition= function( session, state, transition, msg ) { - console.log("transition: "+transition.toString()); - }; - - this.bc_transition= function( session, state, transition, msg ) { - console.log("transition: "+transition.toString()); - }; - - this.Test1_enter= function( session, state, transition, msg ) { - console.log("test1 enter "+state.toString()); - }; - - this.Test1_exit= function( session, state, transition, msg ) { - console.log("test1 exit "+state.toString()); - }; -}; - -context.registerFSM( { - - name : "Test1", - - state : [ - { - name : "a", - initial : true - }, - { - name : "b" - }, - { - name : "c", - onEnter : function( session, state, transition, msg ) { - console.log("Enter c"); - } - } - ], - - transition : [ - { - event : "ab", - from : "a", - to : "b" - }, - { - event : "bc", - from : "b", - to : "c" - } - ] -} ); - -var session= context.createSession({ - fda: "Test1", - controller: new Controller() -} ); -session.start( function onStartProcessEnds(session) { - session.consume( { msgId: "ab" } ); - session.consume( { msgId: "bc" } ); - } -); - diff --git a/test/test1.ts b/test/test1.ts new file mode 100644 index 0000000..75dde2b --- /dev/null +++ b/test/test1.ts @@ -0,0 +1,95 @@ +import {Automata,Message,Session} from "../src/automata"; + +class Controller { + + constructor() { + + } + + a_enter( session : Session, state : string, msg : Message ) { + console.log(state+" enter "); + }; + + a_exit( session : Session, state : string, msg : Message ) { + console.log(state+" exit "); + }; + + b_enter( session : Session, state : string, msg : Message ) { + console.log(state+" enter "); + }; + + b_exit( session : Session, state : string, msg : Message ) { + console.log(state+" exit "); + }; + + c_exit( session : Session, state : string, msg : Message ) { + console.log(state+" exit"); + }; + + c_enter( session : Session, state : string, msg : Message ) { + console.log(state+" enter"); + }; + + ab_transition( session : Session, state : string, msg : Message ) { + console.log("transition: "+msg.msgId); + }; + + bc_transition( session : Session, state : string, msg : Message ) { + console.log("transition: "+msg.msgId); + }; + + Test1_enter( session : Session, state : string, msg : Message ) { + console.log(state+" enter "); + }; + + Test1_exit( session : Session, state : string, msg : Message ) { + console.log(state+" exit "); + }; +} + +Automata.RegisterFSM( { + + name : "Test1", + state : ["a","b","c"], + initial_state : "a", + transition : [ + { + event : "ab", + from : "a", + to : "b" + }, + { + event : "bc", + from : "b", + to : "c" + } + ] +} ); + +Automata.CreateSession( + new Controller(), + "Test1" +).then( + function success( s : Session, m : Message ) { + + console.log("-------------- by message "+m.msgId); + + s.dispatchMessage( { msgId: "ab" } ).then( + function success( s : Session, m : Message ) { + console.log("-------------- by message "+m.msgId); + } + ); + + s.dispatchMessage( { msgId: "bc" } ).then( + function success( s : Session, m : Message ) { + console.log("-------------- by message "+m.msgId); + } + ); + }, + function error( s : Session, m : string ) { + + console.log("Error creating Session of type Test1, reason: '"+m+"'"); + } +); + + diff --git a/test/test2.js b/test/test2.js deleted file mode 100644 index a17e341..0000000 --- a/test/test2.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @author Ibon Tolosana, @hyperandroid - * - * See LICENSE file. - * - * - * Sample 2 - FSM with timed events - * - * This sample show how to define a timed transition. - * - */ - -context= require("automata"); - -context.registerFSM( { - - name : "Test2", - - state : [ - { - name : "a", - initial : true, - onExit : function( session, state, transition, msg ) { - console.log("Exit a"); - }, - onTimer : { // <-- Timed transition. - timeout: 4000, // after 4 seconds - event: { - msgId: "ab" // fire transition identified by "ab" if exists. - } - } - }, - { - name : "b", - onEnter : function( session, state, transition, msg ) { - console.log("Enter b"); - } - }, - { - name : "c" - } - ], - - transition : [ - { - event : "ab", - from : "a", - to : "b" - }, - { - event : "bc", - from : "b", - to : "c" - } - ] -} ); - -var session1= context.createSession({ - fda: "Test2" -}); -session1.start(); - -var session2= context.createSession({ - fda : "Test2" -} ); - -session2.start(); -session2.consume( {msgId : "ab"} ); - -/* -will print: - -immediately -Exit a -Enter b -from session2 which has triggered a transition change - -and -Exit a -Enter b -after 4 seconds from session1. -*/ - diff --git a/test/test2.ts b/test/test2.ts new file mode 100644 index 0000000..7e83227 --- /dev/null +++ b/test/test2.ts @@ -0,0 +1,109 @@ +/** + * @author Ibon Tolosana, @hyperandroid + * + * See LICENSE file. + * + * + * Sample 2 - FSM with timed events + * + * This sample show how to define a timed transition. + * + */ + +import {Automata,Message,Session} from "../src/automata"; + +Automata.RegisterFSM( { + + name : "Test2", + state : ["a","b","c"], + initial_state : "a", + transition : [ + { + event : "ab", + from : "a", + to : "b", + timeout : { + millis : 4000, + data : {} + } + }, + { + event : "bc", + from : "b", + to : "c" + } + ] +} ); + +let __index= 0; + +class Controller { + + name : string; + + constructor( n : string ) { + this.name = n || "controller_"+__index++; + } + + a_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + }; + + a_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + }; + + b_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + }; + + b_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + }; + + c_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit"); + }; + + c_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter"); + }; + + ab_transition( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+"transition: "+msg.msgId); + }; + + bc_transition( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+"transition: "+msg.msgId); + }; + + Test2_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + }; + + Test2_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + }; +} + +Automata.CreateSession( new Controller("c1"), "Test2" ); +Automata.CreateSession( new Controller("c2"), "Test2" ).then( + function success( s : Session, m : Message ) { + s.dispatchMessage({msgId: "ab"}); + } +); + +/* +will print: + +immediately +Exit a +Enter b +from session2 which has triggered a transition change + +and +Exit a +Enter b +after 4 seconds from session1. +*/ + diff --git a/test/test3.js b/test/test3.js deleted file mode 100644 index feb68c7..0000000 --- a/test/test3.js +++ /dev/null @@ -1,170 +0,0 @@ -/** - * @author Ibon Tolosana, @hyperandroid - * - * See LICENSE file. - * - * - * - * Sample 3 - Guards - * - * This sample shows how transition guards work on Automata. To fire a transition, first of all an optional **pre-guard** - * function is tested. If this function throws an exception, Automata interprets a veto on this transition fire. During - * pre-guard stage, a veto means transition disposal, so no auto-transition is performed. This is useful for example, in - * a multiplayer game where while playing, a user abbadons the game and the game can continue playing. So instead of - * transitioning from State-playing to State-EndGame, a guard can decide to veto the transition. - * - * By definition, a guard **should not** modify the model, in this case, a Logic object. - * - * In the example, the guard will fail two times until the count reaches 3. - * At this moment, the transition is fired (its onTransition method is executed if exists), and after that, - * the **post-guard** condition is checked. PostGuard semantics are completely different. - * After firing the transition, the postGuard is checked. If this function **throws an exception** the transition - * turns into auto-transition, that means firing state change to current-state, and entering again current state. - * If not, the transition continues its natural flow and transition's next state is set as current state. - */ - -context= require("automata"); - - -var Controller= function() { - - this.count= 0; - - this.enter_b= function() { - console.log("enter b"); - }; - - this.enter= function( session, state, transition, msg ) { - console.log("enter "+state.toString()); - }; - - this.exit= function( session, state, transition, msg ) { - console.log("exit "+state.toString()); - }; - - this.action= function( session, state, transition, msg ) { - console.log("transition: "+transition.toString()); - }; - - this.pre_guard_tr_bc= function() { - this.count++; - console.log("count= "+this.count); - if ( this.count<3 ) { - throw context.newGuardException("PreGuard_tr_BC"); - } else { - console.log("Ok, go."); - } - }; - - this.post_guard_tr_bc= function() { - this.count++; - console.log("count= "+this.count); - if ( this.count<5 ) { - throw context.newGuardException("PostGuard_tr_BC"); - } - }; - - return this; -}; - -context.registerFSM( { - - name : "Test3", - - state : [ - { - name : "a", - initial : true, - onEnter : "enter", - onExit : "exit" - }, - { - name : "b", - onEnter : "enter_b", - onExit : "exit" - }, - { - name : "c", - onEnter : "enter", - onExit : "exit" - }, - { - name : "d", - onEnter : "enter", - onExit : "exit" - } - ], - - transition : [ - { - event : "ab", - from : "a", - to : "b", - onTransition: "action" - }, - { - event : "bc", - from : "b", - to : "c", - onTransition: "action", - onPreGuard : "pre_guard_tr_bc", - onPostGuard : "post_guard_tr_bc" - }, - { - event : "cd", - from : "b", - to : "c", - onTransition: "action" - } - ] -} ); - -var session= context.createSession({ - fda: "Test3", - controller: new Controller() -}); - -session.addListener( context.newSessionListener( { - finalStateReached : function( obj ) { - console.log("SessionListener finalStateReached " ); - }, - - /** - * - * @param obj {FSM.SessionStateChangeEvent} - */ - stateChanged : function( obj ) { - var ps= obj.prevState ? obj.prevState.getName() : "none"; - console.log("SessionListener stateChanged "+ps+" --> "+obj.state.getName() ); - } -} ) ); - -// start session. -session.start(); - -console.log(""); -console.log("Sent '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.consume( { msgId: "bc" } ); - -// fail on pre-guard. count=2, but no notification of state change sent. -console.log(""); -console.log("Sent 'bc'"); -session.consume( { msgId: "bc" } ); - -// on pre-guard. count=3. -// Ok go transition. -// Fail on post-guard -// so onExit State-b and onEnter State-b ( auto-transition ). Vetoed transition from State-b to State-c. -// notification of 'stateChanged' on the observer. -console.log(""); -console.log("Sent 'bc'"); -session.consume( { msgId: "bc" } ); - -console.log(""); -console.log("Sent 'bc'"); -session.consume( { msgId: "bc" } ); diff --git a/test/test3.ts b/test/test3.ts new file mode 100644 index 0000000..c0d198a --- /dev/null +++ b/test/test3.ts @@ -0,0 +1,174 @@ +/** + * @author Ibon Tolosana, @hyperandroid + * + * See LICENSE file. + * + * + * + * Sample 3 - Guards + * + * This sample shows how transition guards work on Automata. To fire a transition, first of all an optional **pre-guard** + * function is tested. If this function throws an exception, Automata interprets a veto on this transition fire. During + * pre-guard stage, a veto means transition disposal, so no auto-transition is performed. This is useful for example, in + * a multiplayer game where while playing, a user abbadons the game and the game can continue playing. So instead of + * transitioning from State-playing to State-EndGame, a guard can decide to veto the transition. + * + * By definition, a guard **should not** modify the model, in this case, a Logic object. + * + * In the example, the guard will fail two times until the count reaches 3. + * At this moment, the transition is fired (its onTransition method is executed if exists), and after that, + * the **post-guard** condition is checked. PostGuard semantics are completely different. + * After firing the transition, the postGuard is checked. If this function **throws an exception** the transition + * turns into auto-transition, that means firing state change to current-state, and entering again current state. + * If not, the transition continues its natural flow and transition's next state is set as current state. + */ + + +import {Automata,Message,Session,SessionObserverEvent} from "../src/automata"; + +class Controller { + + name : string; + count : number = 0; + + constructor( n : string ) { + this.count = 0; + this.name = n; + } + + a_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + }; + + a_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + }; + + b_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + }; + + b_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + }; + + c_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit"); + }; + + c_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter"); + } + + ab_transition( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+"transition: "+msg.msgId); + } + + bc_transition( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+"transition: "+msg.msgId); + } + + Test2_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + } + + Test2_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + } + + bc_preGuard(session : Session, state : string, msg : Message ) : boolean { + this.count++; + console.log("count= "+this.count); + if ( this.count<3 ) { + return true; + } + + console.log("Ok, go."); + return false; + } + + bc_postGuard(session : Session, state : string, msg : Message ) : boolean { + this.count++; + console.log("count= "+this.count); + return this.count>=5; + } + +} + +Automata.RegisterFSM( { + + name : "Test3", + state : ["a","b","c","d"], + initial_state : "a", + transition : [ + { + event : "ab", + from : "a", + to : "b" + }, + { + event : "bc", + from : "b", + to : "c" + }, + { + event : "cd", + from : "b", + to : "c" + } + ] +} ); + +Automata.CreateSession( + new Controller("c1"), + "Test3", + { + contextCreated : function( e : SessionObserverEvent ) { + }, + + contextDestroyed : function( e : SessionObserverEvent ) { + }, + + sessionEnded : function( e : SessionObserverEvent ) { + console.log("SessionListener finalStateReached ", e.message ); + }, + + customEvent : function( e : SessionObserverEvent ) { + }, + + stateChanged : function( e : SessionObserverEvent ) { + console.log("SessionListener stateChanged "+e.prev_state_name+" --> "+e.current_state_name ); + } + } +).then( + function success( session : Session, m : Message ) { + console.log(""); + console.log("Sent 'ab'"); + session.dispatchMessage( { msgId: "ab" } ); + + // fail on pre-guard. count=1, but no notification of state change sent. + console.log(""); + console.log("Sent 'bc'"); + session.dispatchMessage( { msgId: "bc" } ); + + // fail on pre-guard. count=2, but no notification of state change sent. + console.log(""); + console.log("Sent 'bc'"); + session.dispatchMessage( { msgId: "bc" } ); + + // on pre-guard. count=3. + // Ok go transition. + // Fail on post-guard + // so onExit State-b and onEnter State-b ( auto-transition ). Vetoed transition from State-b to State-c. + // notification of 'stateChanged' on the observer. + console.log(""); + console.log("Sent 'bc'"); + session.dispatchMessage( { msgId: "bc" } ); + + console.log(""); + console.log("Sent 'bc'"); + session.dispatchMessage( { msgId: "bc" } ); + } +); + + diff --git a/test/test4.js b/test/test4.js deleted file mode 100644 index be32734..0000000 --- a/test/test4.js +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @author Ibon Tolosana, @hyperandroid - * - * See LICENSE file. - * - * Sample 4 - SubStates - * - * Sub States is an Automata feature which allows to nest different registered FSM as states of other FSM. - * The mechanism is straightforward, just define a **substate** block in an FSM **state** definition block. - * Automata will handle automatically all the nesting procedure, call the FSM action hooks and set the system's new - * current state. - * - * A substate, or a FSM does not define neither onEnter nor onExit function callbacks. - * - */ - -context= require("automata"); - - -var Controller= function() { - - this.enter= function( session, state, transition, msg ) { - console.log("Enter "+state.toString()); - }; - - this.exit= function( session, state, transition, msg ) { - console.log("Exit "+state.toString()); - }; - - this.transition= function(session, state, transition, msg ) { - console.log("transition "+transition.toString()); - }; - - return this; -}; - -// Register one FSM model. -context.registerFSM( { - name : "SubStateTest", - - state : [ - { - name : "1", - initial : true, - onEnter : "enter", - onExit : "exit" - }, - { - name : "2", - onEnter : "enter", - onExit : "exit" - }, - { - name : "3", - onEnter : "enter", - onExit : "exit" - } - ], - - transition : [ - { - event : "12", - from : "1", - to : "2" - }, - { - event : "23", - from : "2", - to : "3" - } - ], - - onExit : function() { - console.log(" --> Exit sub-automata SubStateTest"); - }, - - onEnter : function() { - console.log(" --> Enter sub-automata SubStateTest"); - } - -} ); - -// register another FSM model - -context.registerFSM( { - - name : "Test4", - - state : [ - { - name : "a", - initial : true, - onEnter : "enter", - onExit : "exit" - }, - { - name : "b", - onEnter : "enter", - onExit : "exit" - }, - { - subState: "SubStateTest" - }, - { - name : "c", - onEnter : "enter", - onExit : "exit" - } - ], - - transition : [ - { - event : "ab", - from : "a", - to : "b", - onTransition: "transition" - }, - { - event : "bc", - from : "b", - to : "SubStateTest", - onTransition: "transition" - }, - { - event : "cd", - from : "SubStateTest", - to : "c", - onTransition: "transition" - } - ], - - onExit : function() { - console.log(" --> Exit automata Test4"); - }, - - onEnter : function() { - console.log(" --> Enter automata Test4"); - } - -} ); - -var session= context.createSession({ - fda : "Test4", - controller : new Controller() -}); - -session.start( function(session) { - - 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/test4.ts b/test/test4.ts new file mode 100644 index 0000000..e4652f6 --- /dev/null +++ b/test/test4.ts @@ -0,0 +1,152 @@ +/** + * @author Ibon Tolosana, @hyperandroid + * + * See LICENSE file. + * + * Sample 4 - SubStates + * + * Sub States is an Automata feature which allows to nest different registered FSM as states of other FSM. + * The mechanism is straightforward, just define a **substate** block in an FSM **state** definition block. + * Automata will handle automatically all the nesting procedure, call the FSM action hooks and set the system's new + * current state. + * + * A substate, or a FSM does not define neither onEnter nor onExit function callbacks. + * + */ + +import {Automata,Message,Session,SessionObserverEvent} from "../src/automata"; + +class Controller { + + name : string; + + constructor( n : string ) { + this.name = n; + } + + _a_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + } + + _a_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + } + + _b_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + } + + _b_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + } + + _c_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + } + + _c_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + } + + _1_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + } + + _1_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + } + + _2_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + } + + _2_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + } + + _3_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + } + + _3_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + } + + SubStateTest_enter( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" enter "); + } + + SubStateTest_exit( session : Session, state : string, msg : Message ) { + console.log(this.name+" "+state+" exit "); + } + +} + +// Register one FSM model. +Automata.RegisterFSM( { + name : "SubStateTest", + state : ["_1","_2","_3"], + initial_state : "_1", + transition : [ + { + event : "12", + from : "_1", + to : "_2" + }, + { + event : "23", + from : "_2", + to : "_3" + } + ] +} ); + +// register another FSM model + +Automata.RegisterFSM( { + + name : "Test4", + state : ["a","b","FSM:SubStateTest","c"], + initial_state : "a", + transition : [ + { + event : "ab", + from : "a", + to : "b", + }, + { + event : "bc", + from : "b", + to : "SubStateTest", + }, + { + event : "cd", + from : "SubStateTest", + to : "c", + } + ] +} ); + +var session= Automata.CreateSession( + new Controller("c1"), + "Test4" +).then( + + function success( session : Session, m : Message ) { + + session.dispatchMessage({msgId: "ab"}); + + session.dispatchMessage({msgId: "bc"}).then( + function success( s : Session, m: Message ) { + s.printStackTrace(); + } + ); + + session.dispatchMessage( { msgId : "cd" } ).then( + function success( s : Session, m: Message ) { + s.printStackTrace(); + } + ); + } +); + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9ec30c7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", + "outDir": "build/", + "module": "commonjs", + "declaration": true, + "noImplicitAny": true, + "sourceMap": true + }, + "exclude": [ + "node_modules","build" + ] +}