Click here to Skip to main content
15,499,802 members
Articles / Programming Languages / C#
Article
Posted 25 Sep 2013

Tagged as

Stats

15.8K views
19 bookmarked

Using T4 templates for Finite State Machine generation

Rate me:
Please Sign up or sign in to vote.
4.67/5 (2 votes)
25 Sep 2013CPOL3 min read
Writing multiple finite states machines code is a tedious and boring task. With a little help from T4 Template, this process can be automated.

Introduction 

I was recently immersed in a great book,  "Agile Principles, Patterns, and Practices in C#" by  Micah Martin and Robert C. Martin (Pearson Education), and got intrigued by the authors State Machine Compiler, written in Java, which can generate Java, C++ and C# code, given a standardized input format. Since the meds for my Java allergy did not fully kick in, I started to wonder if I could replicate the SMC with a T4 template.

Background  

"Finite state automata are among the most useful abstractions in the software arsenal and are almost universally applicable. They provide a simple and elegant way to explore and define the behavior of a complex system. They also provide a powerful implementation strategy that is easy to understand and easy to modify." Martin, Micah; Martin, Robert C. (2006-07-20). Agile Principles, Patterns, and Practices in C# (Kindle Locations 10069-10071). Pearson Education. Kindle Edition.

There are many ways to implement a Finite State Machine. I truly like the elegance and style as demonstrated in the book. However, it still required me to have a set of predefined classes outside of the state machine itself. While this might be acceptable in most cases, it was not applicable to my project, where I often require dynamic compilation and load of classes. As such, I have slightly modified the book's sample, to allow for all related classes to be stored in one namespace. This might result in partial code duplication, but in my case it was worth the risk.

Using the code

To be able to use object initializers and LINQ queries inside a T4 template, I have decided to create a few helper classes first:

C#
namespace FiniteStateMachine
{

    public class FSMMachine
    {
        public string Name { get; set; }
        public string InitialState { get; set; }
        public FSMState[] States { get; set; }
    }

    public class FSMState
    {
        public string Name { get; set; }
        public FSMEvent[] Events { get; set; }
    }

    public class FSMEvent
    {
        public string Name { get; set; }
        public string NewState { get; set; }
        public string Action { get; set; }
    }

} 

 It is quite simple really:

  • FSMMachine contains basic definitions about my state machine, as in Name and InitialState, plus an array of allowed States
  • FSMState has a Name property, and a set of Events it recognizes
  • FSMEvent has a Name, NewState and Action properties

To define a state machine in my T4 template, I can simply use:

C#
    const string sLocked= "Locked";
    const string sUnlocked= "Unlocked";
    const string eCoin= "Coin";
    const string ePass="Pass";
    const string aUnlock="Unlock";
    const string aAlarm="Alarm";
    const string aLock="Lock";
    const string aThankYou="ThankYou";
 
    var stateMachine = new FSMMachine{ Name = "Turnstile", InitialState = sLocked, States = new FSMState[] { new FSMState
            {
                Name = sLocked,
                Events = new FSMEvent[] { new FSMEvent
                {
                    Name = eCoin,
                    NewState = sUnlocked,
                    Action = aUnlock 
                    }, 
                new FSMEvent
                {
                    Name = ePass,
                    NewState = sLocked,
                    Action = aAlarm 
                    },
                }
                }, 
            new FSMState
            {
                Name = sUnlocked,
                Events = new FSMEvent[] { new FSMEvent
                {
                    Name = eCoin,
                    NewState = sUnlocked,
                    Action = aThankYou 
                    }, 
                new FSMEvent
                {
                    Name = ePass,
                    NewState = sLocked,
                    Action = aLock 
                    },
                }
                }
            }};

    var events = stateMachine.States.SelectMany(s => s.Events.Select(s2 => s2.Name)).Distinct();
    var actions = stateMachine.States.SelectMany(s => s.Events.Select(s2 => s2.Action)).Distinct(); 

This roughly resembles the format originally published in the book.

FSMName Turnstile
Context TurnstileActions
Initial Locked
Exception FSMError
{
	Locked
	{
		Coin Unlocked Unlock
		Pass Locked Alarm
	}
	Unlocked
	{
		Coin Unlocked Thankyou
		Pass Locked Lock
	}
}

Since my context is isolated to the generated namespace, I did not have a need to create a special property for it in my FSMMachine class. Similarly, each generated namespace has its own FSMError implementation. 

You can also notice that all my states, event and actions are defined as string constants. I find it more convenient to use constants to avoid typing mistakes and streamline the machine definition.

When you save a T4 template, the built-in Custom Tool starts generating the output as defined by the T4 statements. 

For example, this T4 template:

C#
<#@ template hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>
Hello, world! 

would result in creating a text file, with the same name as the name of the template, and a single line saying hello to the entire world.  There are many constructs you can leverage in a T4 template, including LINQ and any of your custom libraries. To import an assembly, you must use: 

C#
<#@ assembly name="System.Core" #>
<#@ assembly name="..\FSM.dll" #> 

This will reference the System Core library, as well as the custom helpers classes we have initially created.

Second, just like in C#, you must import the namespace you need to process the custom template:

C#
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="FiniteStateMachine" #>
<#@ output extension=".cs" #>

Now you are ready to generate an IController interface using all possible actions, with the template looking like this: 

C#
public interface I<#=        stateMachine.Name #>Controller
{
<#    foreach(string act in actions){#>
 void <#=            act #>();
<#    } #>
}    

which given the above state machine definition will generate an interface like so:

C#
public interface ITurnstileController
{
 void Unlock();
 void Alarm();
 void ThankYou();
 void Lock();
}

This can be repeated over and over, with full support of the C# language, generating lines upon lines of C# code, which would otherwise have to be keyed in by hand. 

I have attached the FSM helper class along with the FSM T4 template and a sample generated state machine code, using the sample definition.

This technique turned out to be a great time saver for me. I hope it will help you, too.  Here is a test case, a slightly modified copy from the book, to confirm that the generated state machine code behaves as designed.

C#
namespace FSMTests
{
    using NUnit.Framework;

    using TurnstileMachine;

    [TestFixture]
    public class TTTurnstileTests
    {
        #region Fields

        private TurnstileControllerSpoof controllerSpoof;

        private TurnstileMachine turnstile;

        #endregion

        #region Public Methods and Operators

        [Test]
        public void CoinInLockedState()
        {
            this.turnstile.SetState(new Locked());
            this.turnstile.Coin();
            Assert.IsTrue(this.turnstile.GetCurrentState() is Unlocked);
            Assert.IsTrue(this.controllerSpoof.unlockCalled);
        }

        [Test]
        public void CoinInUnlockedState()
        {
            this.turnstile.SetState(new Unlocked());
            this.turnstile.Coin();
            Assert.IsTrue(this.turnstile.GetCurrentState() is Unlocked);
            Assert.IsTrue(this.controllerSpoof.thankYouCalled);
        }

        [Test]
        public void InitialConditions() { Assert.IsTrue(this.turnstile.GetCurrentState() is Locked); }

        [Test]
        public void PassInLockedState()
        {
            this.turnstile.SetState(new Locked());
            this.turnstile.Pass();
            Assert.IsTrue(this.turnstile.GetCurrentState() is Locked);
            Assert.IsTrue(this.controllerSpoof.alarmCalled);
        }

        [Test]
        public void PassInUnlockedState()
        {
            this.turnstile.SetState(new Unlocked());
            this.turnstile.Pass();
            Assert.IsTrue(this.turnstile.GetCurrentState() is Locked);
            Assert.IsTrue(this.controllerSpoof.lockCalled);
        }

        [SetUp]
        public void SetUp()
        {
            this.controllerSpoof = new TurnstileControllerSpoof();
            this.turnstile = new TurnstileMachine(this.controllerSpoof);
        }

        #endregion

        private class TurnstileControllerSpoof : ITurnstileController
        {
            #region Fields

            public bool alarmCalled;

            public bool lockCalled;

            public bool thankYouCalled;

            public bool unlockCalled;

            #endregion

            #region Public Methods and Operators

            public void Alarm() { this.alarmCalled = true; }

            public void Lock() { this.lockCalled = true; }

            public void ThankYou() { this.thankYouCalled = true; }

            public void Unlock() { this.unlockCalled = true; }

            #endregion
        }
    }

} 

History  

  • 9/25/13 Initial version.
  • 9/25/13 Added T4 template as a text file
 


License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect BI Software, Inc.
United States United States
A seasoned IT Professional. Programming and data processing artist. Contributor to StackOverflow.

Comments and Discussions

 
GeneralMy vote of 4 Pin
Champion Chen27-Sep-13 0:54
MemberChampion Chen27-Sep-13 0:54 
I am just a junior coder , and after reading i am more curious about the importance of FSM in your app and mind then how T4 implementd the code generation .There must be some reason , by which you willing "to risk duplication" .
Would plz tell me , and also provide a demo for this article?
GeneralRe: My vote of 4 Pin
Darek Danielewski29-Sep-13 6:32
MemberDarek Danielewski29-Sep-13 6:32 
GeneralRe: My vote of 4 Pin
Champion Chen7-Oct-13 17:47
MemberChampion Chen7-Oct-13 17:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.