E-MailRelay
gstatemachine.h
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gstatemachine.h
19///
20
21#ifndef G_STATE_MACHINE_H
22#define G_STATE_MACHINE_H
23
24#include "gdef.h"
25#include "gexception.h"
26#include <map>
27
28namespace G
29{
30 template <typename T, typename State, typename Event, typename Argument> class StateMachine ;
31 class StateMachineImp ;
32}
33
34//| \class G::StateMachine
35/// A finite state machine class template.
36///
37/// The finite state machine has a persistant 'state'. When an 'event' is
38/// apply()d to the state machine, it undergoes a state 'transition'
39/// and then calls the associated 'action' method.
40///
41/// Any action method can return a boolean predicate value which is used to
42/// select between two transitions -- the 'normal' transition if the predicate
43/// is true, and an 'alternative' transition if false.
44///
45/// Transition states can be implemented by having the relevant action
46/// method call apply() on the state-machine. The state machine's state
47/// is always changed before any action method is called -- although only
48/// using the 'normal' transition, not the 'alternative' -- so this sort
49/// of reentrancy is valid, as long as the action method going into
50/// the transition state returns a predicate value of 'true'.
51///
52/// Default transitions for a given state are not supported directly. But
53/// note that protocol errors do not invalidate the state machine and
54/// do not result in a change of state. This means that client code can
55/// achieve the effect of default transitions by handling protocol errors
56/// for that state in a special manner.
57///
58/// Special states 'same' and 'any' can be defined to simplify the
59/// definition of state transitions. A transition with a 'source'
60/// state of 'any' will match any state. This is typically used
61/// for error events or timeouts. A transition with a 'destination'
62/// state of 'same' will not result in a state change. This is
63/// sometimes used when handling predicates -- the predicate can
64/// be used to control whether the state changes, or stays the
65/// same. The 'any' state is also used as a return value from
66/// apply() to signal a protocol error.
67///
68/// If the 'any' state is numerically the largest then it can be used
69/// to identify a default transition for the given event; transitions
70/// identified by an exact match with the current state will be
71/// chosen in preference to the 'any' transition.
72///
73/// The 'end' state is special in that predicates are ignored for
74/// transitions which have 'end' as their 'normal' destintation
75/// state. This is because of a special implementation feature
76/// which allows the state machine object to be deleted within the
77/// action method which causes a transition to the 'end' state.
78/// (This feature also means that transitions with an 'alternative'
79/// state of 'end' are not valid.)
80///
81/// Usage:
82/// \code
83/// class Protocol
84/// {
85/// struct ProtocolError {} ;
86/// enum class State { s_Same , sFoo , sBar , sEnd , s_Any } ;
87/// enum class Event { eFoo , eBar , eError } ;
88/// typedef StateMachine<Protocol,State,Event> Fsm ;
89/// Fsm m_fsm ;
90/// void doFoo( const std::string & , bool & ) {}
91/// void doBar( const std::string & , bool & ) { delete this ; }
92/// Event decode( const std::string & ) const ;
93/// public:
94/// Protocol() : m_fsm(State::sFoo,State::sBar,State::s_Same,State::s_Any)
95/// {
96/// m_fsm(Event::eFoo,State::sFoo,sBar,&Protocol::doFoo) ;
97/// m_fsm(Event::eBar,State::sBar,sEnd,&Protocol::doBar) ;
98/// }
99/// void apply( const std::string & event_string )
100/// {
101/// State s = m_fsm.apply( *this , decode(event_string) , event_string ) ;
102/// if( s == State::sEnd ) return ; // this already deleted by doBar()
103/// if( s == State::sAny ) throw ProtocolError() ;
104/// }
105/// } ;
106/// \endcode
107///
108template <typename T, typename State, typename Event, typename Argument>
110{
111public:
112 using Action = void (T::*)(Argument, bool &) ;
113
114 StateMachine( State s_start , State s_end , State s_same , State s_any ) ;
115 ///< Constructor.
116
117 void operator()( Event event , State from , State to , Action action ) ;
118 ///< Adds a transition. Special semantics apply if 'from' is
119 ///< 's_any', or if 'to' is 's_same'.
120
121 void operator()( Event event , State from , State to , Action action , State alt ) ;
122 ///< An overload which adds a transition with predicate support.
123 ///< The 'alt' state is taken as an alternative 'to' state
124 ///< if the action's predicate is returned as false.
125
126 State apply( T & t , Event event , Argument arg ) ;
127 ///< Applies an event. Calls the appropriate action method
128 ///< on object "t" and changes state. The state change
129 ///< takes into account the predicate returned by the
130 ///< action method.
131 ///<
132 ///< If the event is valid then the new state is returned.
133 ///< If the event results in a protocol error the StateMachine's
134 ///< state is unchanged, no action method is called, and
135 ///< this method returns 's_any' (see ctor).
136 ///<
137 ///< As a special implementation feature the StateMachine
138 ///< object may be deleted during the last action method
139 ///< callback (ie. the one which takes the state to the
140 ///< 's_end' state).
141
142 State state() const ;
143 ///< Returns the current state.
144
145 State reset( State new_state ) ;
146 ///< Sets the current state. Returns the old state.
147
148 Event event() const ;
149 ///< Returns the last-apply()d event.
150
151private:
152 static void throwError() ;
153
154private:
155 struct Transition /// A private structure used by G::StateMachine<>.
156 {
157 State from ;
158 State to ;
159 State alt ; // alternate "to" state if predicate false
160 Action action ;
161 Transition(State s1,State s2,Action a,State s3) :
162 from(s1) , to(s2) , alt(s3) , action(a) {}
163 } ;
164 using Map = std::multimap<Event,Transition> ;
165 using Map_value_type = typename Map::value_type ;
166 Map m_map ;
167 State m_state ;
168 State m_end ;
169 State m_same ;
170 State m_any ;
171 Event m_event{} ;
172} ;
173
174//| \class G::StateMachine
175/// A private non-template implementation class for G::StateMachine.
176///
177class G::StateMachineImp
178{
179public:
180 G_EXCEPTION( Error , tx("invalid state transition") )
181 static void throwError() ;
182 StateMachineImp() = delete ;
183} ;
184
185template <typename T, typename State, typename Event, typename Argument>
186G::StateMachine<T,State,Event,Argument>::StateMachine( State s_start , State s_end , State s_same , State s_any ) :
187 m_state(s_start) ,
188 m_end(s_end) ,
189 m_same(s_same) ,
190 m_any(s_any)
191{
192}
193
194template <typename T, typename State, typename Event, typename Argument>
195void G::StateMachine<T,State,Event,Argument>::operator()( Event event , State from , State to , Action action )
196{
197 operator()( event , from , to , action , to ) ;
198}
199
200template <typename T, typename State, typename Event, typename Argument>
201void G::StateMachine<T,State,Event,Argument>::operator()( Event event , State from , State to , Action action , State alt )
202{
203 if( to == m_any || alt == m_any || from == m_same ||
204 ( to == m_end && alt != to ) ||
205 ( alt == m_end && to != m_end ) )
206 StateMachineImp::throwError() ;
207
208 m_map.insert( Map_value_type( event , Transition(from,to,action,alt) ) ) ;
209}
210
211template <typename T, typename State, typename Event, typename Argument>
213{
214 State old_state = m_state ;
215 m_state = new_state ;
216 return old_state ;
217}
218
219template <typename T, typename State, typename Event, typename Argument>
221{
222 return m_state ;
223}
224
225template <typename T, typename State, typename Event, typename Argument>
226State G::StateMachine<T,State,Event,Argument>::apply( T & t , Event event , Argument arg )
227{
228 m_event = event ;
229 State state = m_state ;
230 auto p = m_map.find( event ) ; // look up in the multimap keyed on event + current-state
231 for( ; p != m_map.end() && (*p).first == event ; ++p )
232 {
233 if( (*p).second.from == m_any || (*p).second.from == m_state )
234 {
235 State old_state = m_state ; // change state
236 if( (*p).second.to != m_same )
237 state = m_state = (*p).second.to ;
238
239 State end = m_end ; // (avoid using members after the action method call)
240
241 bool predicate = true ;
242 (t.*((*p).second.action))( arg , predicate ) ; // perform action
243
244 if( state != end && !predicate ) // respond to predicate
245 {
246 State alt_state = (*p).second.alt ;
247 state = m_state = alt_state == m_same ? old_state : alt_state ;
248 }
249 return state ;
250 }
251 }
252 return m_any ;
253}
254
255template <typename T, typename State, typename Event, typename Argument>
257{
258 return m_event ;
259}
260
261#endif
262
A finite state machine class template.
State state() const
Returns the current state.
void operator()(Event event, State from, State to, Action action, State alt)
An overload which adds a transition with predicate support.
Event event() const
Returns the last-apply()d event.
State apply(T &t, Event event, Argument arg)
Applies an event.
State reset(State new_state)
Sets the current state. Returns the old state.
void operator()(Event event, State from, State to, Action action)
Adds a transition.
StateMachine(State s_start, State s_end, State s_same, State s_any)
Constructor.
Low-level classes.
Definition: garg.h:36
constexpr const char * tx(const char *p) noexcept
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84