Implementation of a state machine

  • 2021-11-30 01:11:57
  • OfStack

Without saying much, look at the code first:


interface IState
 {
  string Name { get; set; }
  // Afterpart processing 
  IList<IState> Nexts { get; set; }
  Func<IState /*this*/, IState /*next*/> Selector { get; set; }
  
 }
 class State : IState
 {
  public string Name { get; set; } = "State";

  IList<IState> IState.Nexts { get; set; } = new List<IState>();
  public Func<IState, IState> Selector { get; set; }
 }

The state is simple, with an Name identity, a postscript state list, and then a state selector.

For example, the state a can be transferred to the states b, c and d, so the selector is one of them. As for how to choose, let the user define the actual selector.


delegate bool HandleType<T>(IState current, IState previous,ref T value);
 interface IContext<T> : IEnumerator<T>, IEnumerable<T>
 {
  //data
  T Value { get; set; }
  // Front piece handling 
  IDictionary<Tuple<IState/*this*/, IState/*previous*/>, HandleType<T>> Handles { get; set; }
  IState CurrentState { get; set; }
  bool transition(IState next);
 }

Unlike the state class State, which focuses on the posterior state, the context class Context focuses on the antecedent state. When jumping to a new state, different policies are implemented according to the current state. For example, if you want to enter the state c, there are different handlers according to the current state a, b and d. This transfer handler corresponds to 11, so Tuple is used < The state entered, the current state > To describe a jump chain. Then bundle the related handlers with Dictionary.

Context will carry T Value data. What should I do with this data? I pass it to the handler through the ref parameter. Because I don't want IState to be concerned with the construction of the context, it only needs to be concerned with the actual data T value;

The context holds the data and the current state, and then lets the user control the transition of the state through transiton. There is a repeat, because IState has a selector to control the state transition. Why do you want to deal with it like this? I'm trying to construct a jump sequence. IEnumerator and IEnumerable interfaces are introduced, but the state can automatically jump under the action of selector, and then the result sequence is read with foreach (just don't know what use it is).


class Context<T> : IContext<T>
 {
  T data;
  T IContext<T>.Value { get=>data ; set=>data = value; }
  IDictionary<Tuple<IState, IState>, HandleType<T>> IContext<T>.Handles { get; set; } 
   = new Dictionary<Tuple<IState, IState>, HandleType<T>>();
  public IState CurrentState { get; set;}
  T IEnumerator<T>.Current => (this as IContext<T>).Value ;
  object IEnumerator.Current => (this as IContext<T>).Value;
  bool IContext<T>.transition(IState next)
  {
   IContext<T> context= this as IContext<T>;
   if (context.CurrentState == null || context.CurrentState.Nexts.Contains(next))
   {
    // Front piece handling 
    var key = Tuple.Create(next, context.CurrentState);
    if (context.Handles.ContainsKey(key) && context.Handles[key] !=null)
     if (!context.Handles[key](next, context.CurrentState,ref this.data))
      return false;

    context.CurrentState = next;
    return true;
   }
   return false;
  }
  bool IEnumerator.MoveNext()
  {
   // Afterpart processing 
   IContext<T> context = this as IContext<T>;
   IState current = context.CurrentState; 
   if (current == null)
    throw new Exception(" You must set the initial state ");
   if (context.CurrentState.Selector != null)
   {
    IState next= context.CurrentState.Selector(context.CurrentState);
    return context.transition(next);
   }
   return false;
  }
  void IEnumerator.Reset()
  {
   throw new NotImplementedException();
  }
  #region IDisposable Support
  private bool disposedValue = false; //  To detect redundant calls 
  protected virtual void Dispose(bool disposing)
  {
   if (!disposedValue)
   {
    if (disposing)
    {
     // TODO:  Release managed state ( Managed object ) . 
    }
    // TODO:  Release unmanaged resources ( Unmanaged object ) And replace the finalizer in the following. 
    // TODO:  Set the large field to  null . 
    disposedValue = true;
   }
  }
  // TODO:  Only if the above  Dispose(bool disposing)  The finalizer is replaced when you have code for releasing unmanaged resources. 
  // ~Context() {
  // //  Do not change this code. Put the cleanup code above  Dispose(bool disposing)  Medium. 
  // Dispose(false);
  // }
  //  Add this code to correctly implement the disposable pattern. 
  void IDisposable.Dispose()
  {
   //  Do not change this code. Put the cleanup code above  Dispose(bool disposing)  Medium. 
   Dispose(true);
   // TODO:  If the finalizer is replaced in the above, uncomment the following line. 
   // GC.SuppressFinalize(this);
  }
  IEnumerator<T> IEnumerable<T>.GetEnumerator()
  {
   return this;
  }
  IEnumerator IEnumerable.GetEnumerator()
  {
   return this;
  }
  #endregion
 }

Focus on the transition function and the MoveNext function.


bool IContext<T>.transition(IState next)
  {
   IContext<T> context= this as IContext<T>;
   if (context.CurrentState == null || context.CurrentState.Nexts.Contains(next))
   {
    // Front piece handling 
    var key = Tuple.Create(next, context.CurrentState);
    if (context.Handles.ContainsKey(key) && context.Handles[key] !=null)
     if (!context.Handles[key](next, context.CurrentState,ref this.data))
      return false;
    context.CurrentState = next;
    return true;
   }
   return false;
  }

What you do is very simple, that is, call the antecedent handler, and if the processing is successful, you will transfer the state, otherwise you will exit.


bool IEnumerator.MoveNext()
  {
   // Afterpart processing 
   IContext<T> context = this as IContext<T>;
   IState current = context.CurrentState; 
   if (current == null)
    throw new Exception(" You must set the initial state ");
   if (context.CurrentState.Selector != null)
   {
    IState next= context.CurrentState.Selector(context.CurrentState);
    return context.transition(next);
   }
   return false;
  }

MoveNext selects the next 1 states through a selector.

In general, my implementation of this state machine is just a framework, with no functionality, but I feel it is easier to write a state transition directory tree.

Users should first create a set of states, and then establish a directory tree structure. My implementation is rough, because users have to build directory tree, antecedent processor and afterware selector respectively. When I wrote the test code, I wrote a mesh structure of 9 states, and the results were a little dazzling. It would be better if we could unify 1.

Attention should be paid to the first state and the construction of the last state, otherwise it is impossible to stop and embed an infinite loop.


// Test code 
//--------- Create part ---------
string mess = "";//3   
IState s3 = new State() { Name = "s3" };
//2   
IState s2 = new State() { Name = "s2" };
//1   
IState s1 = new State() { Name = "s1" };
//--------- Combine ---------   
s1.Nexts = new List<IState> { s2, s3 };   
s2.Nexts = new List<IState> { s1, s3 };   
s3.Nexts = new List<IState> { }; // Attention end Writing style 
//--------- Context ---------    
//transition   
IContext<int> cont = new Context<int> { CurrentState=s1};//begin   
cont.Value = 0;
//--------- State processor --------- 
HandleType<int> funcLaji = (IState current, IState previous, ref int v) => { mess += $"{current.Name}: Garbage {previous.Name}\n"; v++; return true; };
//1   
cont.Handles.Add(Tuple.Create(s1 , default(IState)), funcLaji);   
cont.Handles.Add(Tuple.Create(s1, s2), funcLaji);
//2   
cont.Handles.Add(Tuple.Create(s2, s1), funcLaji);
//3   
cont.Handles.Add(Tuple.Create(s3, s1), funcLaji); 
cont.Handles.Add(Tuple.Create(s3, s2), funcLaji);
//--------- State selector ---------    
var rval = new Random();   
Func<int,int> round = x => rval.Next(x);   
s1.Selector = st => round(2)==0? s2:s3;   
s2.Selector = st => round(2)==0? s1:s3;

After construction, you can use this state machine.


// Selector jump    
mess += " Selector jump: \n------------------------\n";
foreach (var stor in cont)
    mess+=$" Number of state transitions: {stor}\n";
// Direct control jump 
mess += "\n Direct control state jump: \n------------------------\n";
cont.transition(s1);
cont.transition(s2);
cont.transition(s3);

Related articles: