1 #ifndef STATE_MACHINE_CLASS
2 #define STATE_MACHINE_CLASS
4 /*****************************************************************************\
6 * Name : state_machine *
7 * Author : Chris Koeritz *
9 *******************************************************************************
10 * Copyright (c) 1992-$now By Author. This program is free software; you can *
11 * redistribute it and/or modify it under the terms of the GNU General Public *
12 * License as published by the Free Software Foundation; either version 2 of *
13 * the License or (at your option) any later version. This is online at: *
14 * http://www.fsf.org/copyleft/gpl.html *
15 * Please send any updates to: fred@gruntose.com *
16 \*****************************************************************************/
18 #include <basis/contracts.h>
19 #include <timely/time_stamp.h>
23 class state_machine_override_array;
24 class state_machine_state_array;
26 //! Monitors objects with multiple states and the transitions between states.
28 A state machine is a computational abstraction for any control process
29 that has discrete states and transitions between the states.
30 As used here, the "state_machine" is a base class for objects that can be
31 manipulated by the "transition_map" class. Your transition map must be
32 validated and you must use it to reset() your state_machine object before
33 any activity can occur. (One aspect of validation is that all states must be
34 reachable from the user-specified starting state. The other requirements
35 are documented below for transition_map.) Once validation is done, the
36 transition map manages the machine through the various states that are
37 defined and applies trigger values to modify the current state when asked
38 to do so. It also applies any defined timed transitions automatically.
39 This implementation of state machines is configured once (by defining the
40 transition_map object), but then the machines can be run repeatedly (via
41 the state_machine object).
44 class state_machine : public virtual basis::root_object
48 //!< sets up the machine in a blank state.
50 state_machine(const state_machine &to_copy);
51 //!< copies the machine provided in "to_copy".
53 virtual ~state_machine();
55 DEFINE_CLASS_NAME("state_machine");
57 state_machine &operator =(const state_machine &to_copy);
58 //!< assigns this to a copy of the machine provided in "to_copy".
61 //!< this is the main implementation function provided by derived classes.
62 /*!< this is implemented by derived classes that want to perform an action
63 when the state machine is pulsed (see below in transition_map), which is
64 why it is not pure virtual; a state machine might still be useful as a
65 state tracking object rather than needing to implement extended
66 functionality. this function is invoked whenever the state changes due to
67 a timed, range based or simple transition. one must be careful when
68 servicing this transition not to enmesh oneself in a snarled invocation
69 situation; if make_transition or time_slice or pulse are invoked from this
70 update function and conditions are right for another transition, then the
71 update function will be re-entered recursively. if the value returned
72 from update is non-zero, it is used to pulse the state machine again as a
73 new trigger value (this is safer than explicitly calling one of the
74 transition causing functions). note that this assumes that zero is an
75 invalid trigger. if your triggers include zero as a valid value, don't
76 try returning it through update; use pulse to force the triggering to
77 accept the invalid trigger instead. */
79 int current() const { return _current; }
80 //!< returns the current state.
81 /*!< NOTE: a zero value for a state means that it is uninitialized. */
83 int last() const { return _last; }
84 //!< returns the last active state.
86 int trigger() const { return _trig; }
87 //!< returns the trigger that caused this state.
88 /*!< this is only meaningful when the transition was caused by a range
89 transition. if it was, then ranged() will return true. */
91 enum transition_types { SIMPLE, RANGE, TIMED };
92 //!< the three types of transitions supported.
94 bool simple() const { return _type == SIMPLE; }
95 //!< returns true if the last transition was a simple type.
96 bool ranged() const { return _type == RANGE; }
97 //!< returns true if the last transition was a ranged type.
98 bool timed() const { return _type == TIMED; }
99 //!< returns true if the last transition was a timed type.
101 void set_state(int new_current, int new_last, int trigger,
102 transition_types type);
103 //!< sets the current state to "new_current" and the previous state to "new_last".
104 /*!< the "trigger" causing the transition, if any, will be stored also.
105 the "type" of transition is stored also. the time stamp for time spent in
106 the current state is reset. be very careful with this; if the two states
107 do not conform to the legal transitions in your map, then the state
108 machine will not behave properly. */
110 timely::time_stamp start() const;
111 //!< start time for the current state.
112 /*!< this records how long we've been in this state. */
114 void set_name(const basis::astring &name);
115 //!< sets the name to be printed in any debugging information for "this".
117 basis::astring get_name() const;
118 //!< retrieves the object's current name.
120 void override_timing(int current, int next, int duration);
121 //!< modifies the recorded timing for timed transitions.
122 /*!< this overrides the transition_map's time-out value for the transition
123 between the states "current" and "next". a time-out of "duration"
124 will be used instead of whatever was specified when the map was set
125 up. to erase an override, use zero as the "duration". */
127 int duration_override(int current, int next) const;
128 //!< reports if there's a duration override for a timed transition.
129 /*!< this returns the amount of time that this particular state_machine is
130 allowed to exist in the "current" state before progressing to the "next"
131 state. this has nothing to do with the transition_map; it is valid only
132 for this state_machine object. zero is returned if no override exists. */
135 friend class transition_map;
136 //!< manager buddy object.
137 int _current; //!< the current state of the state machine.
138 int _last; //!< the previous state for the state machine.
139 int _trig; //!< the last trigger value, if _ranged is true.
140 transition_types _type; //!< what kind of transition just occurred.
141 timely::time_stamp *_start; //!< the time this state started.
142 basis::astring *_name; //!< the name this state_machine reports itself as.
143 state_machine_override_array *_overrides; //!< the timing overrides.
145 int duration_index(int current, int next) const;
146 //!< finds the index of a duration override in our list.
147 /*!< this locates the duration override specified for the transition
148 between "current" and "next". it returns the index of that override, or
149 a negative number if the override is not found. */
154 //! The transition_map manages state machines and causes state changes to occur.
156 The transition_map is a heavyweight class that is a repository for all
157 information about transitions and which manages pushing the state machines
158 through the proper states.
160 The transition_map guarantees these characteristics:
162 0) the below characteristics are checked (in validate) and no state
163 machine object is allowed to operate until they are satisfied,
165 1) the machine starts in the specified starting state,
167 2) the current state is always one that has been added and approved,
169 3) transitions are allowed between states only if the transition has
170 been added and approved,
172 4) the update() function is invoked every time the machine hits a
173 transition between states (even if it is a transition to the same
176 5) range transitions are non-intersecting within one state,
177 *** 5 is unimplemented ***
179 6) all states are reachable from the starting state by some valid
182 7) each state has no more than one timed transition.
184 if any of these conditions are violated, then validate() will fail.
185 the machine will also not operate properly (at all) until the conditions
186 are satisfied by validate(). the states and transitions should
187 thus be carefully examined before turning them into a state machine....
190 class transition_map : public virtual basis::root_object
194 virtual ~transition_map();
196 // informational functions...
198 DEFINE_CLASS_NAME("transition_map");
200 bool valid() const { return _valid; }
201 //!< returns true if the transition_map is valid and ready for operation.
202 /*!< once the validate() call has succeeded and valid() is true, no more
203 configuration functions (see below) may be called until the reconfigure()
204 function is invoked. */
207 //!< returns the current number of states.
209 // validation functions...
212 OKAY = basis::common::OKAY,
213 DEFINE_OUTCOME(BAD_START, -49, "The start state has not been properly "
215 DEFINE_OUTCOME(OVERLAPPING_RANGES, -50, "The ranges overlap for two "
216 "transitions from a state"),
217 DEFINE_OUTCOME(UNREACHABLE, -51, "There is an unreachable state in the map")
219 basis::outcome validate(int &examine);
220 //!< checks to that all required transition conditions are satisfied.
221 /*!< OKAY is returned if they are and the map is then ready to operate.
222 other outcomes are returned if one or more of the conditions are not met:
223 BAD_START means that the starting state is badly specified.
224 OVERLAPPING_RANGES means that one state has two transitions that do not
225 have mutually exclusive ranges. UNREACHABLE means that a state is not
226 reachable from the starting state. for all of these cases, the "examine"
227 parameter is set to a state related to the problem. */
230 //!< puts the transition_map back into an unvalidated state.
231 /*!< this allows the characteristics of the map to be changed. validate()
232 must be called again before resuming operation using this map. */
234 // configuration functions...
236 // NOTE: all of the functions below will fail if the transition_map has
237 // already been validated previously and reconfigure() has not been called.
239 // NOTE: a zero value for a state means that it is uninitialized. thus, zero
240 // is never allowed as a value for a state or a transition endpoint. zero is
241 // grudgingly allowed as a trigger value, although that interferes with the
242 // state_machine object's update() method.
244 bool add_state(int state_number);
245 //!< registers a legal state in the transition_map.
246 /*!< no action will be taken for any state until it is registered. the
247 operation returns true for successful registration and false for errors
248 (such as when a state is already registered or is invalid). */
250 bool set_start(int starting_state);
251 //!< assigns a state as the first state.
252 /*!< if the "starting_state" does not already exist, it is an error and
253 false is returned. */
255 bool add_simple_transition(int current, int next);
256 //!< adds a transition between "current" and "next".
257 /*!< it is an error to use either an invalid "current" state or an invalid
258 "next" state. these errors are detected during the validate() function.
259 this type of transition is unspecialized--it does not respond to triggers
260 and does not occur due to timing. to make a state_machine undergo this
261 transition, make_transition() must be used. */
263 bool add_range_transition(int current, int next, int low, int high);
264 //!< adds a transition that listens to triggers in the pulse() method.
265 /*!< this uses "low" and "high" as the inclusive range of triggers that
266 will cause a transition to the "next" state when a state_machine is pulsed
267 in the "current" state. it is erroneous for these trigger values to
268 intersect with other values in the same "current" state. */
269 bool add_timed_transition(int current, int next, int duration);
270 //!< adds a transition that occurs after a timeout with no other activity.
271 /*!< adds a timed transition to the "next" state when the "current" state
272 has lasted "duration" milliseconds. this relies on the time_slice function
273 being called periodically, with appropriate regularity for the specified
274 "duration" (e.g., if one's time-out is 100 ms, then one might want to call
275 time_slice() every 20 ms or so to ensure that the transition is at most 20
276 ms late in firing. NOTE: this is not an argument for calling time_slice()
277 as fast as possible; it is an argument for realizing that the "duration"
278 must be carefully considered to meet one's timing deadlines). */
280 // transition functions...
282 // NOTE: all of these actions will fail if the transition_map has not been
285 bool make_transition(state_machine &m, int next);
286 //!< changes the state to the "next" listed for "m" given the current state.
287 /*!< it is an error to make a transition that has not been specified
288 through an add_X() transition function (false is returned). if the
289 transition succeeds, then the current_state will be "next". */
291 bool pulse(state_machine &m, int trigger);
292 //!< applies a "trigger" to possibly cause a range transition.
293 /*!< this causes the state_machine to accept the "trigger" as input and
294 perform at least one traversal of the transition_map. if the trigger
295 value is found in one of the range transitions for the current state, then
296 the next state specified in that transition becomes the current state and
297 the update() function is invoked (and true is returned). if the trigger
298 is not found in any range transition, then false is returned. */
300 bool time_slice(state_machine &m);
301 // allows the transition_map to process any timed transitions that may be
302 // required for the state_machine "m". the return value is true if such
303 // a transition was found.
305 bool reset(state_machine &m);
306 // resets the state_machine to the starting state in the transition_map.
307 // the update function is NOT invoked at the time of the reset. true is
308 // returned if the reset was successful; it would fail if the transition
309 // map has not been validated yet.
312 bool _valid; //!< records whether we've been validated or not.
313 int _start_state; //!< remembers the default starting state.
314 state_machine_state_array *_state_list;
315 //!< the list of transitions between states.
317 bool check_overlapping(int &examine);
318 //!< a validation function that looks for erroneous range transitions.
319 /*!< this returns true if there are no overlapping ranges found in the
320 range transitions for each state. */
322 bool check_reachability(int &examine);
323 //!< returns true if all states are reachable from the starting state.
325 int state_index(int state_id) const;
326 //!< returns the index of "state_id" in states, if it exists.
328 int transition_index(int state_index, int next, int &start);
329 //!< locates a transition into "next" for a state in our list.
330 /*!< returns the index of a transition between the state at "state_index"
331 and the "next" state by starting the search at index "start". if there
332 is no matching transition from the "start" index on in the transition
333 list, then a negative number is returned. "start" is updated to point
334 to the index just after a found transition, or to some random number if
335 the index is not found. */
338 //!< returns false if the current machine is hosed up.
340 // disallowed functions for now:
341 transition_map(const transition_map &);
342 transition_map &operator =(const transition_map &);