Friday, February 14, 2014

Introducing HaveBoxStates. The .NET framework for better flows in your code

Level: 3 where 1 is noob and 5 is totally awesome
System: C# on .NET

Does this looks familiar?
You sketch a program, e.g. a games, like this:


And the implementation ended up, looking like this:


Often it is quite easy to draw a flow of a program or a part of it, but expressing it in code turns out to be hard. This is here where HaveBoxStates comes in. The purpose with HaveBoxStates, is to make it easy to express flows in code, just as we draw them,

HaveBoxStates 

HaveBoxStates is a statemachine/flow engine, to express flows in code. It is very flex and lightweight, and very easy to use. People who has worked with state machines before, might find HaveBoxStates a bit unusual, because the state machine/flow is not defined/configured before use. Even thou it doesn't have strictness predefined in a model, it still as strict. Trying to enter an invalid state would blow up the machine, just as if it was predefined in a model.

Best pratices 

The most important thing I can come up with is, you should always constructor dependency inject your logic into the states. HaveBoxStates comes with HaveBox out of the box, but through a ContainerAdaptor, you can use any container.

How to use it

The following examples are taken from the concept app. So if something needs to be elaborated, you can get the full picture there. Questions in comments is also okay :-). Notice the dependency injection in the PlayGame state.

Here is how to use it:

You draw a flow. It could be this flow:


For each state, you creates a class like this(in separated files, of course):


 using GameStateMachineConcept.Dtos;  
 using GameStateMachineConcept.Dtos;  
 using GameStateMachineConcept.GameEngine;  
 using HaveBoxStates;  
   
 public class Init : IState  
 {  
      public void ExecuteState(IStateMachine statemachine)  
      {  
           statemachine.SetNextStateTo<PlayGame>(new PlayerContext{ Points = 0, Lives = 3, });  
      }  
 }  
   
   
 public class PlayGame : IState  
 {  
      private IGameEngine _gameEngine;  
   
      public PlayGame(IGameEngine gameEngine)  
      {  
           _gameEngine = gameEngine;  
      }  
   
      public void ExecuteState(IStateMachine statemachine, PlayerContext playerContext)  
      {  
           var gameContext = new GameContext { PlayerContext = playerContext, GainedPoints = 0, };  
   
           _gameEngine.Play(gameContext);  
   
           statemachine.SetNextStateTo<Scores>(gameContext);  
      }  
 }  
   
   
 public class Scores : IState  
 {  
      public void ExecuteState(IStateMachine statemachine, GameContext GameContext)  
      {  
           GameContext.PlayerContext.Points += GameContext.GainedPoints;  
   
           if (GameContext.PlayerContext.Lives == 0)  
           {  
                statemachine.SetNextStateTo<Stop>();  
           }  
           else  
           {  
                statemachine.SetNextStateTo<PlayGame>(GameContext.PlayerContext);  
           }  
      }  
 }  
   

Each state must implement an ExecuteState of one of the following signatures:

public void ExecuteState(IStateMachine statemachine)

or

public void ExecuteState(IStateMachine statemachine, <any type> <any name>)

<any type> can be any reference type and <any name> can be any name. The second parameter, is for  DTOs and transferring data between the states. HaveBoxStates automatically cast data to right type, before calling the ExecuteState method.  You just have to give the type of the DTO, forget about casting and just use it.

Because the DTO type can be of any type, and we are not using generics. IState do not enforce implementing of the ExecuteStates, so you have to remember it or else you will get a runtime exception.

The Stop state

HaveBoxStates have one builtin state. The Stop state. If the state machine needs stop, go to the Stop state.

Wrapping it up


Setting up and starting a state machine is also quite easy. Register all states with their dependencies in a container and then instantiate a state machine with the container. Notice, do not register states to IState.

 using GameStateMachineConcept.GameEngine;  
 using GameStateMachineConcept.States;  
 using HaveBox;  
 using HaveBoxStates;  
 using HaveBoxStates.ContainerAdaptors;  
 using System;  
 using System.Collections.Generic;  
   
 namespace GameStateMachineConcept  
 {  
   public class Program  
   {  
     static void Main(string[] args)  
     {  
       var container = new Container();  
       container.Configure(config =>  
       {  
         config.For<IGameEngine>().Use<GameEngine.GameEngine>();  
         config.For<Init>().Use<Init>();  
         config.For<PlayGame>().Use<PlayGame>();  
         config.For<Scores>().Use<Scores>();  
       });  
   
       var stateMachine = new StateMachine(new HaveBoxAdaptor(container));  
       stateMachine.StartStateMachinesAtState<Init>();  
     }  
   }  
 }  
   

That is all :-)

No comments:

Post a Comment