//------------------------------------------------------------------------------
// File: Fsm.cs
//------------------------------------------------------------------------------
// Finite State machine Library for .NET.
// (C) Copyright 2003 - 2005, Linus Flueler (linus.flueler@siemens.com)
// All rights reserved.
// The code and information is provided "as-is" without waranty of any kind,
// either expressed or implied.
//-----------------------------------------------------------------------------
// Project : .NET Realtime Library RTLib
// Namespace : RTLib
// Language/Compiler : Microsoft Visual C# .NET
// Date of Creation : 01-Dec-03
// Author : Linus Flueler
//-----------------------------------------------------------------------------
// History:
// 01-Dec-03 Linus Flueler Initial Version
//-----------------------------------------------------------------------------
//
using System;
using System.Reflection;
using System.Threading;
using System.Collections;
using System.Diagnostics;
namespace RTLib
{
/// <summary>
/// Fsm is the base class for user defined finite state machines.
/// During startup time, Fsm investigates the user defined handler methods
/// with reflection and creates appropriate structures for run time.
/// At run time it dispatches the events according to the investigated info.
/// The attribute FsmCoding can bee used to force using of attributes
/// (ECodingType.WithAttributes) or naming convention and signature match
/// (ECodingType.Automatic). Default is ECodingType.Automatic.
/// </summary>
public abstract class Fsm : FsmEventBase
{
/// <summary>
/// Default constructor
/// </summary>
public Fsm()
: this( true )
{
}
/// <summary>
/// Constructor for synchronized FSM
/// </summary>
/// <param name="synchronized"></param>
public Fsm( bool synchronized )
: base( synchronized )
{
classInfo = (FsmClassInfo)FsmClassInfo.fsmClassInfoMap[GetType()];
if ( classInfo == null )
{
InitializeFsmClassInfo();
}
Debug.Assert( classInfo != null );
}
/// <summary>
/// The Processor (Thread) which is associated with this FSM
/// </summary>
public FsmProcessor FsmProcessor
{
set { fsmProcessor = value; }
get { return fsmProcessor; }
}
/// <summary>
/// Returns true if there exists an enum data member called "state"
/// or with the attribute "FsmState". Otherwise, this class is used as
/// a projection plane for events in form of a event handlers without an
/// exlicit state.
/// </summary>
public bool HasStates
{
get { return this.classInfo.hasStates; }
}
/// <summary>
/// Send an event to this FSM. The event will be forwarded to the processor.
/// It is not called directly but in the context of the processor thread.
/// </summary>
/// <param name="ev"></param>
public void PushEvent( FsmEvent ev )
{
FsmProcessor.PushEvent( ev, this );
}
/// <summary>
/// Send a timer event to this FSM. The event will be forwarded to the processor.
/// It is not called directly but in the context of the processor thread.
/// </summary>
/// <param name="ev"></param>
public void PushEvent( FsmTimerEvent ev )
{
FsmProcessor.PushEvent( ev, this );
}
/// <summary>
/// Force this FSM to terminate. The other FSM's and the processor will
/// continue to run.
/// </summary>
public void Terminate()
{
FsmProcessor.TerminateFsm( this );
}
/// <summary>
/// This override is called as the first entry point in the life of this
/// FSM. It is always and only called in the context of the associated processor / thread.
/// </summary>
protected internal virtual void OnFsmEntry()
{
// do nothing, override this method in derived classes if needed.
}
/// <summary>
/// This override is called as the last entry point in the life of this
/// FSM. It is always and only called in the context of the associated processor / thread.
/// </summary>
protected internal virtual void OnFsmExit()
{
// do nothing, override this method in derived classes if needed.
}
/// <summary>
/// This method is called by the processor thread in order to execute the
/// first state. The first state is the initial value of the "state" member
/// variable.
/// Override is possible but not recommended.
/// </summary>
protected internal virtual void EnterFirstState()
{
Debug.Assert( classInfo != null );
if ( classInfo.hasStates )
{
Type typeFsm = this.GetType();
Int32 currState = GetCurrentState();
StateInfo stateInfo = classInfo.stateInfoArray[currState];
// Execute Entry state handler of current first state
if ( stateInfo.entryMethod != null )
{
stateInfo.entryMethod.Invoke(this,
new Object[]{null, Enum.ToObject(classInfo.stateEnumType,currState)});
// It is not allowed to change state in State Entry Handler!
Debug.Assert( currState == GetCurrentState() );
}
}
}
/// <summary>
/// This is the core event dispatching of the FSM. The FSM looks up the
/// curent state and calls the state and transition handlers if they are
/// defined.
/// Override is possible but not recommended.
/// </summary>
/// <param name="fsmEvent"></param>
protected internal virtual void OnFsmEvent(FsmEvent fsmEvent)
{
Debug.Assert( classInfo != null );
if ( classInfo.hasStates )
{
Int32 currState = GetCurrentState();
// execute transition
StateInfo stateInfo1 = classInfo.stateInfoArray[currState];
Debug.Assert( stateInfo1 != null );
MethodInfo methInfoTransition = (MethodInfo)stateInfo1.transitions[fsmEvent.GetType()];
if ( methInfoTransition != null )
{
// first execute exit method
if ( stateInfo1.exitMethod != null )
{
stateInfo1.exitMethod.Invoke(this, new Object[] {fsmEvent});
// it is not allowed to change state in State Exit Handler!
Debug.Assert( currState == GetCurrentState() );
}
// now execute transition method
methInfoTransition.Invoke(this, new Object[] {fsmEvent} );
// on return, FSM has probably a new state
int newState = GetCurrentState();
if ( newState != currState )
{
// Transition to other state
StateInfo stateInfo2 = classInfo.stateInfoArray[newState];
Debug.Assert( stateInfo2 != null );
// Execute Entry state handler of new state
if ( stateInfo2.entryMethod != null )
{
stateInfo2.entryMethod.Invoke(this, new Object[] {fsmEvent,
Enum.ToObject(classInfo.stateEnumType,currState)});
// it is not allowed to change state in State Entry Handler!
Debug.Assert( newState == GetCurrentState() );
}
}
else
{ // It is the same state --> transition loop
if ( stateInfo1.entryMethod != null )
{
stateInfo1.entryMethod.Invoke(this, new Object[] {fsmEvent,
Enum.ToObject(classInfo.stateEnumType,currState)});
// it is not allowed to change state in State Entry Handler!
Debug.Assert( currState == GetCurrentState() );
}
}
}
else
{
// There is no transition for this event in current state.
if ( stateInfo1.defaultTransitionMethod != null )
{
stateInfo1.defaultTransitionMethod.Invoke(this, new Object[] {fsmEvent} );
}
else
{
// Try to find an event handler
MethodInfo methInfoEvHnd = (MethodInfo)classInfo.eventHandlers[fsmEvent.GetType()];
if ( methInfoEvHnd != null )
{
methInfoEvHnd.Invoke(this, new Object[] {fsmEvent} );
}
else
{
// Call default handler
OnFsmEventDefault(fsmEvent);
}
}
}
}
else
{
// FSM has no states. Therefore event handlers are the only handlers to check.
MethodInfo methodInfo = (MethodInfo)classInfo.eventHandlers[fsmEvent.GetType()];
if ( methodInfo != null )
{
methodInfo.Invoke(this, new Object[] {fsmEvent} );
}
else
{
// error: No matching event handler available
Trace.WriteLine("FSM:" + this.GetType().Name + " Event:" + fsmEvent.GetType().Name + " no event handler found");
}
}
}
/// <summary>
/// This is the default handler if there was neither a transition handler nor an event handler
/// defined. Overriding this method allows to establish a custom error handler or a default
/// event handler defining a default behavior.
/// </summary>
/// <param name="fsmEvent"></param>
protected internal virtual void OnFsmEventDefault(FsmEvent fsmEvent)
{
// error: There exists neither a transition handler nor an event handler
if ( classInfo.hasStates )
{
Trace.WriteLine("FSM:" + this.GetType().Name + " Event:" + fsmEvent.GetType().Name + " not handled in state:"
+ Enum.GetName( classInfo.stateEnumType, GetCurrentState() ) );
}
else
{
Trace.WriteLine("FSM:" + this.GetType().Name + " Event:" + fsmEvent.GetType().Name + " not handled");
}
}
/// <summary>
/// Used in a event-, state- or transition handler to terminate the FSM.
/// </summary>
protected void TerminateFsm()
{
Debug.Assert(Thread.CurrentThread.GetHashCode() == fsmProcessor.Thread.GetHashCode());
fsmProcessor.PopFsm(this);
OnFsmExit();
Done();
}
/// <summary>
/// Legacy method to terminate an FSM from inside (handler) or from outside
/// (other thread). For inside calls, please use "TerminateFsm()" now.
/// For outside calls, please use "Terminate" now.
/// </summary>
public virtual void FsmDone()
{
if (Thread.CurrentThread.GetHashCode() == fsmProcessor.Thread.GetHashCode())
{
TerminateFsm();
}
else
{
fsmProcessor.TerminateFsm(this);
}
}
/// <summary>
/// Unfortunately, this method MUST be overridden, if states are used in this FSM.
/// It is needed since this FSM base class needs knowledge about the state defined in the derived
/// class. If you forget to define it, you will notice it immediately, since the default implementation
/// asserts to false.
/// </summary>
/// <returns></returns>
protected internal virtual int GetCurrentState()
{
Debug.Assert(false);
Trace.WriteLine("GetCurrentState must be overridden when defining a state variable");
return -1;
}
/// <summary>
/// Used internally to seach for attributes of members in the derived class.
/// </summary>
/// <param name="memberInfo"></param>
/// <param name="filterCriteria"></param>
/// <returns></returns>
private static bool HasFieldAttributeOfType(
MemberInfo memberInfo,
object filterCriteria )
{
return memberInfo.GetCustomAttributes((Type)filterCriteria,false).Length > 0;
}
/// <summary>
/// Used internally to find event-, state- and transition handlers in the derived class.
/// </summary>
/// <param name="memberInfo"></param>
/// <param name="filterCriteria"></param>
/// <returns></returns>
private static bool IsValidFsmHandler(
MemberInfo memberInfo,
object filterCriteria )
{
Debug.Assert(memberInfo.MemberType == MemberTypes.Method);
MethodInfo methodInfo = (MethodInfo)memberInfo;
// handler methods are never static
if ( methodInfo.IsStatic )
return false;
// discriminate methods with "FsmNoHandler" attribute
if (methodInfo.GetCustomAttributes(typeof(FsmNoHandlerAttribute),false).Length > 0 )
return false;
// check parameter(s)
// Method must not have a return type
if ( methodInfo.ReturnType != typeof(void) )
return false;
// Method must have max. two parameters
ParameterInfo[] parameterInfo = methodInfo.GetParameters();
if ( parameterInfo.Length > 2 )
return false;
// Method must not be defined in the Fsm base type
if ( methodInfo.DeclaringType == typeof(Fsm))
return false;
// else succeed
return true;
}
/// <summary>
/// Initializes the class info for an FSM. It is called when
/// the construcor is called the first time. This can not be done
/// in static constructor since derived class must already exist.
/// Constructing this information at startup time saves a lot of execution
/// time while dispatching events!
/// </summary>
private void InitializeFsmClassInfo()
{
Type typeFsm = GetType();
Debug.Assert( classInfo == null );
classInfo = new FsmClassInfo();
classInfo.name = typeFsm.Name;
FsmClassInfo.fsmClassInfoMap[typeFsm] = classInfo;
// Check, if client want to use attributes for defining state var and handler
Object[] fsmCodingAttributes = typeFsm.GetCustomAttributes(typeof(FsmCodingAttribute),true);
Debug.Assert( fsmCodingAttributes.Length <= 1 );
if ( fsmCodingAttributes.Length > 0 )
{
FsmCodingAttribute attr = (FsmCodingAttribute)fsmCodingAttributes[0];
classInfo.fsmCodingType = attr.CodingType;
}
// get state member variable if available
MemberInfo[] stateFields;
if ( classInfo.fsmCodingType == ECodingType.Automatic )
{
// No attribute needed, just take the member with the name "state"
stateFields = typeFsm.GetMember(
"state",
MemberTypes.Field,
BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic);
}
else
{
// Find a enum member with the attribute "FsmState"
stateFields = typeFsm.FindMembers(MemberTypes.Field,
BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic,
new MemberFilter(HasFieldAttributeOfType), typeof(FsmStateAttribute));
// Generate a GetCurrentState method for this FSM class
// Note: don't know how to implement ....
}
// Get all info from state valiable and prepare state info
FieldInfo stateField;
FieldInfo[] StateEnums;
Hashtable stringToStateMap = new Hashtable();
if ( stateFields.Length > 0 )
{
classInfo.hasStates = true;
// Get transition handlers and state handlers
// This FSM has a state field, get the filed info of it.
// Fill a string map with the enumeration value names for faster lookup
Debug.Assert( stateFields.Length == 1 );
stateField = (FieldInfo)stateFields[0];
classInfo.stateEnumType = stateField.FieldType;
Debug.Assert(classInfo.stateEnumType.IsSubclassOf(typeof(System.Enum)));
StateEnums = classInfo.stateEnumType.GetFields(
BindingFlags.Static|BindingFlags.Public|BindingFlags.NonPublic);
classInfo.stateInfoArray = new StateInfo[StateEnums.Length];
foreach ( FieldInfo fieldInfo in StateEnums )
{
int val = (int)fieldInfo.GetValue(null);
StateInfo stateInfo = new StateInfo();
classInfo.stateInfoArray[val] = stateInfo;
Debug.Assert(classInfo.stateInfoArray[val] == stateInfo);
stateInfo.name = fieldInfo.Name;
stateInfo.transitions = new Hashtable();
stringToStateMap.Add(fieldInfo.Name, stateInfo );
}
}
// Get all methods which are candidates for any kind of handlers
MemberInfo[] handlers = typeFsm.FindMembers(MemberTypes.Method,
BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic,
new MemberFilter(IsValidFsmHandler), null);
// Loop over all these methods
foreach ( MemberInfo mi in handlers )
{
MethodInfo meth = (MethodInfo)mi;
StateInfo stateInfo = null;
if ( classInfo.fsmCodingType == ECodingType.Automatic )
{
// check if it is a state or transition handler
bool bIsStateOrTransitionHandler = false;
int separatorPos = meth.Name.IndexOf("_");
if ( separatorPos >= 0 )
{
string prefix = meth.Name.Substring(0, separatorPos);
stateInfo = (StateInfo)stringToStateMap[prefix];
if ( stateInfo != null )
{
// It is a state or transition handler
bIsStateOrTransitionHandler = true;
}
}
if ( bIsStateOrTransitionHandler )
{
if ( meth.Name.EndsWith("_EntryState") )
{
Debug.Assert(meth.GetParameters().Length == 2);
Debug.Assert(meth.GetParameters()[0].ParameterType == typeof(FsmEvent));
Debug.Assert(meth.GetParameters()[1].ParameterType == classInfo.stateEnumType);
stateInfo.entryMethod = meth;
}
else if ( meth.Name.EndsWith("_ExitState") )
{
Debug.Assert(meth.GetParameters().Length == 1);
Debug.Assert(meth.GetParameters()[0].ParameterType == typeof(FsmEvent));
stateInfo.exitMethod = meth;
}
else if ( meth.GetParameters().Length == 1 )
{
if ( meth.GetParameters()[0].ParameterType == typeof(FsmEvent) )
{
// it is a default transition
stateInfo.defaultTransitionMethod = meth;
}
else if ( meth.GetParameters()[0].ParameterType.IsSubclassOf(typeof(FsmEvent)) )
{
// it is a transition
Type eventParamType = meth.GetParameters()[0].ParameterType;
Debug.Assert( stateInfo.transitions[eventParamType] == null );
stateInfo.transitions[eventParamType] = meth;
}
else
{
// Do nothing, it is not a FSM method
}
}
else
{
// Do nothing, it is not a FSM method
}
}
else
{
if ( meth.GetParameters().Length == 1 &&
meth.GetParameters()[0].ParameterType.IsSubclassOf(typeof(FsmEvent)) )
{
// Its an event handler
Type eventParamType = meth.GetParameters()[0].ParameterType;
// add [FsmNoHandler] to failing method when asserting here!
Debug.Assert( classInfo.eventHandlers[eventParamType] == null );
classInfo.eventHandlers[eventParamType] = meth;
}
else
{
// Do nothing, it is not a FSM method
}
}
}
else
{
// NOT Automatic, with attributes
object[] attribs;
// Is it a transition handler ?
attribs = mi.GetCustomAttributes(typeof(FsmTransitionHandlerAttribute), false);
if ( attribs.Length > 0 )
{
// yes, it is a transition handler, assign it to state info
FsmTransitionHandlerAttribute attrib = (FsmTransitionHandlerAttribute)attribs[0];
stateInfo = (StateInfo)stringToStateMap[attrib.FromState];
Debug.Assert( stateInfo != null );
Type eventParamType = meth.GetParameters()[0].ParameterType;
// Is it the default transition handler ?
if ( eventParamType == typeof(FsmEvent) )
{
// Yes, store it
stateInfo.defaultTransitionMethod = meth;
}
else
{
// It is a normal transiton handler
Debug.Assert( eventParamType.IsSubclassOf(typeof(FsmEvent)) );
Debug.Assert( stateInfo.transitions[eventParamType] == null );
stateInfo.transitions[eventParamType] = meth;
}
}
else
{
attribs = mi.GetCustomAttributes(typeof(FsmStateHandlerAttribute), false);
if ( attribs.Length > 0 )
{
// yes, it is a state handler
FsmStateHandlerAttribute attrib = (FsmStateHandlerAttribute)attribs[0];
stateInfo = (StateInfo)stringToStateMap[attrib.State];
Debug.Assert( stateInfo != null );
if ( attrib.HandlerType == EStateHandlerType.Entry )
{
Debug.Assert( meth.GetParameters().Length == 2 );
Debug.Assert( meth.GetParameters()[0].ParameterType == typeof(FsmEvent));
Debug.Assert( meth.GetParameters()[1].ParameterType == classInfo.stateEnumType);
Debug.Assert( stateInfo.entryMethod == null );
stateInfo.entryMethod = meth;
}
else if ( attrib.HandlerType == EStateHandlerType.Exit )
{
Debug.Assert( meth.GetParameters().Length == 1 );
Debug.Assert( meth.GetParameters()[0].ParameterType == typeof(FsmEvent));
Debug.Assert( stateInfo.exitMethod == null );
stateInfo.exitMethod = meth;
}
else
{
Trace.WriteLine( "Unexpected State Handler attribute value" );
Debug.Assert( false );
}
}
else
{
// it is neither a transition nor a state handler
// Is it a event handler
attribs = mi.GetCustomAttributes(typeof(FsmEventHandlerAttribute), false);
if ( attribs.Length > 0 )
{
// yes, it is an event handler
FsmEventHandlerAttribute attrib = (FsmEventHandlerAttribute)attribs[0];
Type eventParamType = meth.GetParameters()[0].ParameterType;
Debug.Assert( classInfo.eventHandlers[eventParamType] == null );
Debug.Assert( eventParamType.IsSubclassOf(typeof(FsmEvent)) );
classInfo.eventHandlers[eventParamType] = meth;
}
}
}
}
}
}
/// <summary>
/// The associated processor / thread
/// </summary>
private FsmProcessor fsmProcessor = null;
/// <summary>
/// Dispatching information of the derived class.
/// </summary>
private FsmClassInfo classInfo = null;
} // class Fsm
/// <summary>
/// Synchronized FSM.
/// </summary>
public abstract class FsmSync : Fsm
{
/// <summary>
/// Default constructor
/// </summary>
public FsmSync()
: base( true )
{
}
}
/// <summary>
/// This class encapsulates the dispatching information for an FSM class.
/// Constructing this information at startup time saves a lot of execution
/// time while dispatching events!
/// </summary>
internal class FsmClassInfo
{
/// <summary>
/// Constructor
/// </summary>
internal FsmClassInfo()
{
eventHandlers = new Hashtable();
fsmCodingType = ECodingType.Automatic;
stateInfoArray = null;
}
internal Hashtable eventHandlers;
internal StateInfo[] stateInfoArray;
internal ECodingType fsmCodingType;
internal bool hasStates = false;
internal Type stateEnumType;
internal string name; // debugging only
internal static Hashtable fsmClassInfoMap = new Hashtable();
}
/// <summary>
/// This class describes the dispatching information for a single
/// state in an FSM. Is part of FsmClassInfo.
/// </summary>
internal class StateInfo
{
internal StateInfo()
{
transitions = new Hashtable();
}
internal MethodInfo entryMethod;
internal MethodInfo exitMethod;
internal MethodInfo defaultTransitionMethod;
internal Hashtable transitions;
internal string name; // for debugging purposes only
}
//------------------------------------------------------------------------------
// Attributes
/// <summary>
/// Describes the way, FSM method mapping should be done in the startup phase.
/// </summary>
public enum ECodingType
{
/// <summary>
/// Mapping is done by naming convention
/// </summary>
Automatic,
/// <summary>
/// Mapping is done by attributes only
/// </summary>
WithAttributes
}
/// <summary>
/// Attribute defining the coding type:
/// "[FsmCoding(ECodingType.Automatic)]", default
/// "[FsmCoding(ECodingType.WithAttributes)]"
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class FsmCodingAttribute : System.Attribute
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="codingType">Kind of mapping to be used</param>
public FsmCodingAttribute(ECodingType codingType)
{
this.codingType = codingType;
}
/// <summary>
/// Kind of mapping to be used: Automatic or WithAttributes
/// </summary>
public ECodingType CodingType
{
get
{
return codingType;
}
}
/// <summary>
/// Coding type
/// </summary>
private ECodingType codingType;
}
/// <summary>
/// Defines the enum member, which is the state variable. There must be
/// defined only one state member!
/// With automatic mapping, the state variable must be named "state".
/// Example:
/// [FsmState()]
/// MyStateType m_myStateEnum;
/// </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple=false)]
public sealed class FsmStateAttribute : Attribute
{
/// <summary>
/// Constructor
/// </summary>
public FsmStateAttribute()
{
}
}
/// <summary>
/// Defines that this method is an event handler.
/// The method must have exactly one parameter which is of a type
/// which is derived from FsmEvent.
/// Example:
/// [FsmEventHandler]
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)]
public sealed class FsmEventHandlerAttribute : Attribute
{
/// <summary>
/// Constructor
/// </summary>
public FsmEventHandlerAttribute()
{
}
}
/// <summary>
/// Discriminator attribute. This is used with automatic mapping:
/// If a method has a name according to the naming convention, but it
/// should be presereved as a normal method, use this arribute.
/// Example:
/// [FsmNoHandler]
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)]
public sealed class FsmNoHandlerAttribute : Attribute
{
/// <summary>
/// Constructor
/// </summary>
public FsmNoHandlerAttribute()
{
}
}
/// <summary>
/// Defines a method as a transition handler. The method must have exactly
/// one parameter which is of a type which is derived from FsmEvent.
/// As a parameter, define the state for this transition.
/// Example:
/// [FsmTransitionHandler("StateA")]
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)]
public sealed class FsmTransitionHandlerAttribute : Attribute
{
/// <summary>
///
/// </summary>
/// <param name="fromState"></param>
public FsmTransitionHandlerAttribute( string fromState )
{
this.fromState = fromState;
}
/// <summary>
/// The state this transition is leaving from.
/// </summary>
public string FromState
{
get
{
return fromState;
}
}
private string fromState;
}
/// <summary>
/// Defines the kind of state handler.
/// </summary>
public enum EStateHandlerType
{
/// <summary>
/// State handler which is called on state entry.
/// </summary>
Entry,
/// <summary>
/// State handler handler which is called on state exit.
/// </summary>
Exit
};
/// <summary>
/// Defines a method as a state handler. The method must have exactly two
/// parameters: One which is of a type which is derived from FsmEvent and
/// one of the state type. With handlerType you define the kind of
/// state handler: enter or exit.
/// Example:
/// [FsmStateHandler("StateA", EStateHandlerType.Entry )]
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)]
public sealed class FsmStateHandlerAttribute : Attribute
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="state">State this state handler works on</param>
/// <param name="handlerType">Enter or Exit</param>
public FsmStateHandlerAttribute(
string state,
EStateHandlerType handlerType )
{
this.state = state;
this.handlerType = handlerType;
}
/// <summary>
/// The state, the state handler is is assigned to
/// </summary>
public string State
{
get
{
return state;
}
}
/// <summary>
/// The type of state handler which is used: entry or exit.
/// </summary>
public EStateHandlerType HandlerType
{
get
{
return handlerType;
}
}
private string state;
private EStateHandlerType handlerType;
}
} // namespace RTLib