/// <summary> /// Verifies transitions in order of onEvent, onResult and if not found will return the /// default transition /// </summary> /// <param name="stateFunc"> /// The function that is invoked that will return return message from the state processing /// and use it to check against the onResult queue. /// </param> /// <param name="eventMsg">The incoming message to validate against the onEvent list</param> /// <returns>The OnEvent, OnResult or default transition</returns> private ISpStateTransition GetTransition(bool onEntry, Func <ISpEventMessage, ISpEventMessage> stateFunc, ISpEventMessage msg) { return(WrapErr.ToErrorReportException(9999, () => { // Query the OnEvent queue for a transition BEFORE calling state function (OnEntry, OnTick) ISpStateTransition tr = this.GetOnEventTransition(msg); if (tr != null) { tr.ReturnMessage = (tr.ReturnMessage == null) ? this.MsgFactory.GetResponse(msg) : this.MsgFactory.GetResponse(tr.ReturnMessage); return tr; } // Only considered entered if you do not encounter a preemptive OnEvent transition on entry. In this way // you could get a situation where you cascade down several state depths based on higher up events if (onEntry) { this.isEntered = true; } // Get the transition object from the 'OnResult' queue if ((tr = this.GetOnResultTransition(stateFunc.Invoke(msg))) != null) { tr.ReturnMessage = this.MsgFactory.GetResponse(msg, tr.ReturnMessage); return tr; } // If no transitions registered return SameState with default success message return new SpStateTransition(SpStateTransitionType.SameState, null, this.MsgFactory.GetDefaultResponse(msg)); })); }
/// <summary> /// Get a clone of the transition object from the store or null if not found /// </summary> /// <param name="store">The store to search</param> /// <param name="eventMsg">The message to insert in the transition object</param> /// <returns>The transition object from the store or null if not found</returns> public static ISpStateTransition GetTransitionCloneFromStore(Dictionary <int, ISpStateTransition> store, ISpEventMessage eventMsg) { WrapErr.ChkParam(store, "store", 51009); WrapErr.ChkParam(eventMsg, "eventMsg", 51010); return(WrapErr.ToErrorReportException(51011, () => { if (store.Keys.Contains(eventMsg.EventId)) { // Clone Transition object from store since its pointers get reset later ISpStateTransition tr = (ISpStateTransition)store[eventMsg.EventId].Clone(); if (tr.ReturnMessage == null) { tr.ReturnMessage = eventMsg; } else { //Log.Info("SpTools", "GetTransitionCloneFromStore", "Found a transition AND - Held msg - transfering GUID"); } // TODO - Look at transfering the GUID here tr.ReturnMessage.Uid = eventMsg.Uid; //tr.ReturnMessage = eventMsg; return tr; } return null; })); }
/// <summary>Tick current state to execute the action based on the inputed message</summary> /// <param name="msg">The inputed message</param> /// <returns>The return message from the action</returns> public ISpEventMessage Tick([NotNull] ISpEventMessage?msg) { WrapErr.ChkDisposed(this.disposed, 50176); WrapErr.ChkParam(msg, "msg", 50172); // First tick to drive it from entry to regular ISpStateTransition <TMsgId>?tr = null; bool tmpIsStarted = this.isStarted; //Log.Debug("SpMachine", "Tick", String.Format("isStarted:{0}", this.isStarted)); if (!this.isStarted) { // The OnEntry must be called directly from the state machine for the first state. Subsequent // state transitions will insure that the OnEntry for the new state is called this.isStarted = true; tr = this.state.OnEntry(msg); } else { tr = this.state.OnTick(msg); } WrapErr.ChkVar(tr, 50177, () => String.Format( "The State '{0}' {1} Returned a Null Transition", this.state.FullName, tmpIsStarted ? "OnTick" : "OnEntry")); WrapErr.ChkVar(tr.ReturnMessage, 9999, "Null ReturnMessage"); return(tr.ReturnMessage); }
/// <summary> /// Get the transition object from the store or null if not found /// </summary> /// <param name="store">The store to search</param> /// <param name="eventMsg">The message to insert in the transition object</param> /// <returns>The transition object from the store or null if not found</returns> private ISpStateTransition GetTransitionFromStore(Dictionary <int, ISpStateTransition> store, ISpEventMessage eventMsg) { return(WrapErr.ToErrorReportException(50204, () => { ISpStateTransition t = SpTools.GetTransitionCloneFromStore(store, eventMsg); if (t != null) { this.LogTransition(t, eventMsg); } return t; })); }
private ISpStateTransition GetSuperStateOnEventTransition(ISpEventMessage msg) { ISpStateTransition tr = this.GetOnEventTransition(msg); if (tr != null) { tr.ReturnMessage = (tr.ReturnMessage == null) ? this.MsgFactory.GetResponse(msg) : this.MsgFactory.GetResponse(tr.ReturnMessage); } return(tr); }
/// <summary> /// Factor out the reporting of state transitions for clarity /// </summary> /// <param name="tr">The transition object</param> /// <param name="eventMsg">The event message which pushed this transition</param> private void LogTransition(ISpStateTransition tr, ISpEventMessage eventMsg) { WrapErr.ToErrorReportException(9999, () => { Log.Debug("SpState", "LogTransition", String.Format( "{0} OnMsg({1} - {2}) - From:{3} To:{4}", tr.TransitionType, this.GetCachedMsgTypeId(eventMsg.TypeId), this.GetCachedEventId(eventMsg.EventId), this.FullName, tr.NextState == null ? "Unknown" : tr.NextState.FullName)); }); }
/// <summary> /// Retrieve the transition object from the OnResults queue of the super state /// </summary> /// <param name="msg"></param> /// <returns></returns> private ISpStateTransition GetSuperStateOnResultTransition(ISpEventMessage msg) { // Check super state registered result transitions against Sub State event id ISpStateTransition tr = this.GetOnResultTransition(msg); WrapErr.ChkVar(tr, 9999, () => { return(String.Format( "State {0} Specified Exit but SuperState {1} has no handlers for that event id:{2}", this.currentState.FullName, this.FullName, this.GetCachedEventId(msg.EventId))); }); tr.ReturnMessage = this.MsgFactory.GetResponse(msg, tr.ReturnMessage); return(tr); }
/// <summary> /// Handle Defered transition objects from both the current substate and this super state /// </summary> /// <param name="tr">The current transition object</param> /// <param name="msg"></param> /// <param name="fromSuperState"> /// true if the Transition if from the super state, false if from the substate /// </param> /// <returns>The Transition</returns> private ISpStateTransition HandleDeferedStateTransitionType(ISpStateTransition tr, ISpEventMessage msg, bool fromSuperState) { // If the superstate iteself has a Defered transition it will return immediately to parent if (fromSuperState) { return(tr); } // Call super state override method that derived superstate will use to handle decision point and create a new message with event ISpEventMessage deferedMsg = this.OnRuntimeTransitionRequest(msg); // Process the transition type from the SubState level return(this.ReadTransitionType( this.GetSuperStateOnResultTransition(deferedMsg), deferedMsg, true)); }
/// <summary> /// Register a state transition for an event /// </summary> /// <param name="eventId">The id converter of the event type</param> /// <param name="transition">The transition object</param> public static void RegisterTransition(string type, ISpToInt eventId, ISpStateTransition transition, Dictionary<int, ISpStateTransition> store) { WrapErr.ChkParam(eventId, "eventId", 51004); WrapErr.ChkParam(transition, "transition", 51005); WrapErr.ChkParam(store, "store", 51006); // Wrap the id converter separately int tmp = WrapErr.ToErrorReportException(51007, () => { return String.Format("Error on Event Id Converter for '{0}' Event Type", type); }, () => { return eventId.ToInt(); }); // Duplicate transitions on same Event is a no no. WrapErr.ChkFalse(store.Keys.Contains(tmp), 51008, () => { return String.Format("Already Contain a '{0}' Transition for Id:{1}", type, tmp); }); store.Add(tmp, transition); }
/// <summary> /// Register a state transition for an event /// </summary> /// <param name="eventId">The id converter of the event type</param> /// <param name="transition">The transition object</param> public static void RegisterTransition(string type, ISpToInt eventId, ISpStateTransition transition, Dictionary <int, ISpStateTransition> store) { WrapErr.ChkParam(eventId, "eventId", 51004); WrapErr.ChkParam(transition, "transition", 51005); WrapErr.ChkParam(store, "store", 51006); // Wrap the id converter separately int tmp = WrapErr.ToErrorReportException(51007, () => { return(String.Format("Error on Event Id Converter for '{0}' Event Type", type)); }, () => { return(eventId.ToInt()); }); // Duplicate transitions on same Event is a no no. WrapErr.ChkFalse(store.Keys.Contains(tmp), 51008, () => { return(String.Format("Already Contain a '{0}' Transition for Id:{1}", type, tmp)); }); store.Add(tmp, transition); }
/// <summary> /// Execute on each tick period /// </summary> /// <param name="msg">The incoming message with event</param> /// <returns>The return transition object with result information</returns> public sealed override ISpStateTransition OnTick(ISpEventMessage msg) { //Log.Info(this.className, "OnTick", String.Format("'{0}' State", this.FullName)); WrapErr.ChkVar(this.entryState, 9999, "The 'SetEntryState() Must be Called in the Constructor"); WrapErr.ChkVar(this.currentState, 9999, "Current state is not set"); WrapErr.ChkTrue(this.IsEntryExcecuted, 9999, "Tick Being Called before OnEntry"); // If there are OnEvent transitions registered at the superstate level return immediately ISpStateTransition tr = GetSuperStateOnEventTransition(msg); if (tr != null) { return(tr); } return(this.GetTransition(this.currentState.OnTick, msg)); }
/// <summary> /// Handle the NextState Transition type by setting the next state as /// </summary> /// <param name="tr"></param> /// <param name="msg"></param> /// <returns></returns> private ISpStateTransition HandleNextStateTransitionType(ISpStateTransition tr, ISpEventMessage msg) { Log.Info(this.className, "HandleNextStateTransitionType", String.Format("'{0}' State", this.FullName)); WrapErr.ChkTrue(tr.TransitionType == SpStateTransitionType.NextState, 9999, () => { return(String.Format("{0} is not NextState", tr.TransitionType)); }); WrapErr.ChkVar(tr.NextState, 9999, () => { return (String.Format( "State {0} Specified Next State on Event {1} but Next State Null", this.currentState.FullName, this.GetCachedEventId(msg.EventId))); }); this.currentState.OnExit(); this.currentState = tr.NextState; return(this.currentState.OnEntry(this.MsgFactory.GetDefaultResponse(msg))); }
/// <summary> /// Execute logic on entry into this superstate /// </summary> /// <param name="msg">The incoming message with event</param> /// <returns>The return transition object with result information</returns> public sealed override ISpStateTransition OnEntry(ISpEventMessage msg) { Log.Info(this.className, "OnEntry", String.Format("'{0}' State Event {1}", this.FullName, this.GetCachedEventId(msg.EventId))); WrapErr.ChkVar(this.entryState, 9999, "The 'SentEntryState() Must be Called in the Constructor"); // Find if there are exit conditions OnEntry at the SuperState level and excecute them first // This will check OnEvent transitions queue and transitions from the overriden ExecOnEntry ISpStateTransition t = base.OnEntry(msg); if (t.TransitionType != SpStateTransitionType.SameState) { return(t); } // return transition this.currentState = this.entryState; return(this.currentState.OnEntry(msg)); }
/// <summary>Register a transition for a state</summary> /// <param name="type">string of transition type</param> /// <typeparam name="TMsgId">Event id</typeparam> /// <param name="msgId">The event message id</param> /// <param name="transition">Transition object</param> /// <param name="store">Transition store</param> public static void RegisterTransition <TMsgId>(string type, TMsgId msgId, ISpStateTransition <TMsgId>?transition, Dictionary <int, ISpStateTransition <TMsgId> >?store) where TMsgId : struct { //WrapErr.ChkParam(eventId, "msgId", 51004); WrapErr.ChkParam(transition, "transition", 51005); WrapErr.ChkParam(store, "store", 51006); WrapErr.ChkTrue(typeof(TMsgId).IsEnum, 9999, () => string.Format("Transition type {0} must be Enum", msgId.GetType().Name)); WrapErr.ChkTrue(typeof(TMsgId).GetEnumUnderlyingType() == typeof(Int32), 9999, () => string.Format("Transition type enum {0} must be derived from int", msgId.GetType().Name)); int tmp = Convert.ToInt32(msgId); // 51007 - failure of conversion number // Duplicate transitions on same Event is a no no. WrapErr.ChkFalse(store.Keys.Contains(tmp), 51008, () => { return(String.Format("Already Contain a '{0}' Transition for Id:{1}", type, tmp)); }); store.Add(tmp, transition); }
/// <summary> /// Read the transition object to determine behavior /// </summary> /// <param name="tr">The transition object</param> /// <param name="msg">the current event message</param> /// <param name="superStateLevelEvent"> /// true if the transition object is from the current substate, false if the transition was generated /// by the superstate based on a previous Defered Transition type generated from the substate. This /// prevents infinite recursion. /// </param> /// <returns>A Transtion object with the results of the state processing</returns> ISpStateTransition ReadTransitionType(ISpStateTransition tr, ISpEventMessage msg, bool superStateLevelEvent) { WrapErr.ChkVar(tr, 9999, "The transition is null"); switch (tr.TransitionType) { case SpStateTransitionType.SameState: return(tr); case SpStateTransitionType.NextState: return(this.HandleNextStateTransitionType(tr, msg)); case SpStateTransitionType.ExitState: return(this.HandleExitStateTransitionType(msg)); case SpStateTransitionType.Defered: return(this.HandleDeferedStateTransitionType(tr, msg, superStateLevelEvent)); default: WrapErr.ChkTrue(false, 9999, String.Format("Transition Type {0} not Handled", tr.TransitionType)); return(tr); } }
/// <summary> /// Handle the substate ExitState Transition Type by using the event msg passed to it to do a lookup /// of this super state's transitions. The super state should have a transition that has been /// registered to the same id /// </summary> /// <param name="msg"></param> /// <returns></returns> private ISpStateTransition HandleExitStateTransitionType(ISpEventMessage msg) { Log.Info(this.className, "HandleExitStateTransitionType", String.Format("'{0}' State", this.FullName)); // TODO - this is really only another kind of defered. The difference is that the superstate does not // TODO called at runtime to handle the event. Rather the event is passed to the super state's // TODO registered events. In this scenario it may not actually exit the super state but process // the event id to something else. The difference is that the results are being determined // by the registrations at the superstate level rather than the sub state level // Check super state registered result transitions against Sub State event id ISpStateTransition tr = this.GetSuperStateOnResultTransition(msg); WrapErr.ChkVar(tr, 9999, () => { return(String.Format( "State {0} Specified Exit but SuperState {1} has no handlers for that event id:{2}", this.currentState.FullName, this.FullName, this.GetCachedEventId(msg.EventId))); }); // At this point, the transition registered to the superstate should have everything set in it return(tr); }
/// <summary> /// Register a state transition from the result of state processing. /// </summary> /// <param name="eventId">The id converter of the event as the result of state processing</param> /// <param name="transition">The transition object</param> public void RegisterOnResultTransition(ISpToInt eventId, ISpStateTransition transition) { SpTools.RegisterTransition("OnResult", eventId, transition, this.onResultTransitions); }