65.9K
CodeProject is changing. Read more.
Home

Finite State Machine with Sub-state

starIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIconemptyStarIcon

1.67/5 (2 votes)

Oct 5, 2008

CPOL

3 min read

viewsIcon

41943

downloadIcon

744

Implementing Finite State Machine with Sub-state

Sample_FSM.jpg

Introduction

This is an idea of implementing Finite State Machine with sub-state. Suppose there is a State Model as above picture. Below is transition table:

 

Source state

Trigger

Target state

Comment

1

No State

Start

(Init)

Will go to any sub-state of Init (depends on configuration)

2

(Init)

Enter to (Init)

Init A

In case configuration is go to Init A

3

(Init)

Enter to (Init)

Init B

In case configuration is go to Init B

4

Init A

Load A

Loaded A

Go directly to Loaded A (is sub-state of Loaded)

5

Init B

Load B

Loaded B

Go directly to Loaded B (is sub-state of Loaded)

6

(Loaded)

Run

Processing

From any sub-state of Loaded go to Processing

7

(Executing)

Pause

Paused

From any sub-state of Executing go to Paused

8

Paused

Resume

(Executing)

Will go to sub-state which was activated before leaving state Executing

9

(Executing)

Enter to (Executing)

Loaded

In case Loaded was activated before leaving Executing

10

(Executing)

Enter to (Executing)

Processing

In case Processing was activated before leaving Executing

11

Processing

Stop

No State

 

 

Framework classes 

Two abstract classes StateMgmt and State will be used to implement any particular state model

FSM_Framework.jpg

StateMgmt: 

This class handles the entire state model. It handles triggers and changes current state according to received trigger.
This class contains only one state (called Root state). Root state contains sub-states and StateMgmt will use Root state to handle triggers. 

State: 

This class represents one particular state in model (can be container-state or smallest sub-state). It can contains many sub-state, and at one time it can have only one sub-state is activated.
If one state has sub-states, it can activate the route to any state which is its child (can be sub-state, or sub-state of sub-state…) by using method GoToState.

Example (see below picture):If state [C] is activated: the route from [Root] to [C] is: [Root] --> [A] --> [C]
Active_route_1.jpg
 

By using method GoToState, state [Root] can activate its child state [F] directly, and the route will be: [Root]  --> [B] --> [F] 

Active_route_2.jpg

 

Each state must have a pointer to StateMgmt instance, so it can change from current state to any state in model (by using method GoToState of class StateMgmt).

As default, when a state handles a trigger, if it has sub-states it will call method HandleTrigger of its direct activated sub-state. Therefore, which state handles actual triggers, it has to override method HandleTrigger

 

Implementation of class StateMgmt: 

    public abstract class StateMgmt
    {
        protected State rootState = null;

        public State GetCurrentState()
        {
            if (this.rootState != null)
            {
                return rootState.GetCurrentState();
            }
            return null;
        }

        public virtual void GoToState(int stateId)
        {
            if (rootState != null)
            {
                rootState.GoToState(stateId);
            }
            else
            {
                throw new Exception("Root state is not initialized");
            }
        }

        public virtual void HandleTrigger(int trigger)
        {
            if (this.rootState != null)
            {
                rootState.HandleTrigger(trigger);
            }
        }
    }

 

Implementation of class State: 

    public abstract class State
    {
        private int stateId = -1;

        protected State currentSubState = null;

        protected StateMgmt mgmt = null;

        protected ArrayList subStates = new ArrayList();

        public int StateId
        {
            get
            {
                return this.stateId;
            }
            set
            {
                this.stateId = value;
            }
        }

        public State(int stateId, StateMgmt mgmt)
        {
            this.stateId = stateId;
            this.mgmt = mgmt;
        }

        public abstract void Activated();

        public abstract string GetStateName();

        public State GetCurrentState()
        {
            if (this.currentSubState == null)
            {
                return this;
            }
            return this.currentSubState.GetCurrentState();
        }

        public virtual void GoToState(int stateId)
        {
            foreach (State s in subStates)
            {
                if (s.StateId == stateId)
                {
                    this.currentSubState = s;
                    s.Activated();
                    return;
                }
                else
                {
                    try
                    {
                        s.GoToState(stateId);
                        this.currentSubState = s;
                        return;
                    }
                    catch
                    {
                    }
                }
            }
            throw new Exception("Invalid target state");
        }

        public virtual void HandleTrigger(int trigger)
        {
            if (this.currentSubState != null)
            {
                this.currentSubState.HandleTrigger(trigger);
            }
        }

        public void AddSubState(State newState)
        {
            this.subStates.Add(newState);
        }
    }

Implementing an entire State model

Create classes inherit from two Framework classes: 

ClassesDemoFSM.jpg

In class DemoStateMgmt:
- Define some necessary constants (State Id and Trigger Id)
- Build up the structure of model by adding sub-states to root state of DemoStateMgmt

    public class DemoStateMgmt : StateMgmt
    {
        public const int State_NoState = 1;
        public const int State_Init = 2;
        public const int State_InitA = 3;
        public const int State_InitB = 4;
        public const int State_Executing = 5;
        public const int State_Loaded = 6;
        public const int State_LoadedA = 7;
        public const int State_LoadedB = 8;
        public const int State_Processing = 9;
        public const int State_Paused = 10;

        public const int Trigger_Start = 1;
        public const int Trigger_LoadA = 2;
        public const int Trigger_LoadB = 3;
        public const int Trigger_Run = 4;
        public const int Trigger_Pause = 5;
        public const int Trigger_Resume = 6;
        public const int Trigger_Stop = 7;

        public DemoStateMgmt()
        {
            //Create state Root
            this.rootState = new RootState(-1, this);
            
            //Create state Init and its sub-states
            Init stateInit = new Init(State_Init, this);
            stateInit.AddSubState(new InitA(State_InitA, this));
            stateInit.AddSubState(new InitB(State_InitB, this));

            //Create state Loaded and its sub-states
            Loaded stateLoaded = new Loaded(State_Loaded, this);
            stateLoaded.AddSubState(new LoadedA(State_LoadedA, this));
            stateLoaded.AddSubState(new LoadedB(State_LoadedB, this));

            //Create state Executing and its sub-states
            Executing stateExecuting = new Executing(State_Executing, this);
            stateExecuting.AddSubState(stateLoaded);
            stateExecuting.AddSubState(new Processing(State_Processing, this));

            //Add sub-states to state Root
            this.rootState.AddSubState(new NoState(State_NoState, this));
            this.rootState.AddSubState(stateInit);
            this.rootState.AddSubState(stateExecuting);
            this.rootState.AddSubState(new Paused(State_Paused, this));

            //Go to first state
            this.rootState.GoToState(State_NoState);
        }
    }		

Normally, each smallest sub-state will override method HandleTrigger. But in some cases when the model leaves out from a state which has sub-states, only this state needs to override method HandleTrigger.

Similar idea, when the model goes to a state which has sub-states, it only needs to go to this state. It is not necessary to indicate exactly which sub-sate it will go to. But in this case, it is very important to override method Activated of this state. This method will activate (go to) exactly its sub-state as desired.

Example with class Init: 

    public class Init : State
    {
        public Init(int stateId, StateMgmt mgmt)
            : base(stateId, mgmt)
        {
        }

        public override void Activated()
        {
            switch (Configuration.DefaultInit)
            {
                case Configuration.InitA:
                    GoToState(DemoStateMgmt.State_InitA);
                    return;
                case Configuration.InitB:
                    GoToState(DemoStateMgmt.State_InitB);
                    return;
            }
        }

        public override string GetStateName()
        {
            return "Init";
        }
    } 


Example with class Executing: 

    public class Executing : State
    {
        public Executing(int stateId, StateMgmt mgmt)
            : base(stateId, mgmt)
        {
        }

        public override void Activated()
        {
            this.currentSubState.Activated();
        }

        public override string GetStateName()
        {
            return "Executing";
        }

        public override void HandleTrigger(int trigger)
        {
            switch (trigger)
            {
                case DemoStateMgmt.Trigger_Pause:
                    Pause();
                    return;
                //case another:
                //case another:
            }
            base.HandleTrigger(trigger);
        }

        private void Pause()
        {
            this.mgmt.GoToState(DemoStateMgmt.State_Paused);
        }
    }

Above is explanation about the pattern and there is only source code of some classes. You can download the entire project. If you are not clear about any, or if you have any idea for this pattern, please feel free to contact with me by email: caohuuloc@gmail.com


History 

2008-10-05: Version 1.0.