1 /*****************************************************************************\
3 * Name : state_machine *
4 * Author : Chris Koeritz *
6 *******************************************************************************
7 * Copyright (c) 1992-$now By Author. This program is free software; you can *
8 * redistribute it and/or modify it under the terms of the GNU General Public *
9 * License as published by the Free Software Foundation; either version 2 of *
10 * the License or (at your option) any later version. This is online at: *
11 * http://www.fsf.org/copyleft/gpl.html *
12 * Please send any updates to: fred@gruntose.com *
13 \*****************************************************************************/
15 #include "state_machine.h"
17 #include <basis/array.h>
18 #include <basis/functions.h>
19 #include <basis/guards.h>
20 #include <basis/astring.h>
21 #include <loggers/program_wide_logger.h>
22 #include <timely/time_stamp.h>
24 using namespace basis;
25 using namespace loggers;
26 using namespace timely;
32 //#define DEBUG_STATE_MACHINE
33 // uncomment if you want the debugging version.
35 //hmmm: implement logging...
37 #ifndef DEBUG_STATE_MACHINE
38 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
40 #define LOG(to_print) {}
45 // checks whether the machine passed in is valid or not.
46 #define CHECK_VALID(m) \
47 if (!m._current) return false; \
48 if (!m._last) return false
50 // checks whether the current and next states exist or not.
51 #define CHECK_STATES \
52 if (!current) return false; \
53 if (!next) return false; \
54 int indy = state_index(current); \
55 if (negative(indy)) return false; \
56 if (negative(state_index(next))) return false
58 // moves to a new state and resets the new state's timestamp.
59 #define MOVE_STATE(m, next, type, trigger) \
60 m._last = m._current; \
66 // locates a state or returns.
67 #define FIND_STATE(state) \
68 int indy = state_index(state); \
69 if (negative(indy)) return
73 struct override { int current; int next; int duration;
74 override(int _current = 0, int _next = 0, int _duration = 0)
75 : current(_current), next(_next), duration(_duration) {}
78 struct transition_info
86 int low_trigger, high_trigger;
91 : type(RANGE), next_state(0), low_trigger(0), high_trigger(0), time_span(0)
95 transition_info(int next)
96 : type(SIMPLE), next_state(next),
97 low_trigger(0), high_trigger(0), time_span(0)
101 transition_info(int next, int time)
102 : type(TIMED), next_state(next), time_span(time), low_trigger(0), high_trigger(0)
106 transition_info(int next, int low, int high)
107 : type(RANGE), next_state(next), low_trigger(low), high_trigger(high), time_span(0)
113 int state_id; // id for this state.
114 array<transition_info> transitions;
116 state_info() : state_id(0) {} // blank.
117 state_info(int state_id_in) : state_id(state_id_in) {}
122 class state_machine_override_array : public array<override> {};
123 class state_machine_state_array : public array<state_info> {};
127 state_machine::state_machine()
132 _start(new time_stamp()),
133 _name(new astring()),
134 _overrides(new state_machine_override_array)
137 state_machine::state_machine(const state_machine &to_copy)
143 _start(new time_stamp()),
144 _name(new astring()),
145 _overrides(new state_machine_override_array)
148 state_machine::~state_machine()
155 int state_machine::update() { return 0; }
157 void state_machine::set_name(const astring &name) { *_name = name; }
159 astring state_machine::get_name() const { return *_name; }
161 state_machine &state_machine::operator = (const state_machine &to_copy)
163 if (&to_copy == this) return *this;
164 _current = to_copy._current;
165 _last = to_copy._last;
166 _trig = to_copy._trig;
167 _type = to_copy._type;
168 *_start = *to_copy._start;
169 *_name = *to_copy._name;
170 *_overrides = *to_copy._overrides;
171 //careful when overrides becomes hidden internal type.
175 int state_machine::duration_index(int current, int next) const
177 for (int i = 0; i < _overrides->length(); i++)
178 if ( ((*_overrides)[i].current == current)
179 && ((*_overrides)[i].next == next) )
181 return common::NOT_FOUND;
184 void state_machine::set_state(int new_current, int new_last, int trigger,
185 transition_types type)
187 _current = new_current;
194 void state_machine::override_timing(int current, int next, int duration)
196 int indy = duration_index(current, next);
197 if (non_negative(indy)) {
198 // found an existing record for this current/next pair.
200 // zero duration means removal.
201 _overrides->zap(indy, indy);
204 // reset the duration.
205 (*_overrides)[indy].duration = duration;
208 // no existing record, so add one.
209 *_overrides += override(current, next, duration);
212 int state_machine::duration_override(int current, int next) const
214 int indy = duration_index(current, next);
215 if (negative(indy)) return 0;
216 return (*_overrides)[indy].duration;
219 time_stamp state_machine::start() const { return *_start; }
223 transition_map::transition_map()
226 _state_list(new state_machine_state_array)
229 transition_map::~transition_map()
235 // informational functions:
237 int transition_map::states() const { return _state_list->length(); }
239 int transition_map::state_index(int state_id) const
241 for (int i = 0; i < states(); i++)
242 if ((*_state_list)[i].state_id == state_id) return i;
243 return common::NOT_FOUND;
246 int transition_map::transition_index(int state_index, int next, int &start)
248 bounds_return(state_index, 0, states() - 1, common::BAD_INPUT);
249 state_info &state = (*_state_list)[state_index];
250 bounds_return(start, 0, state.transitions.length() - 1, common::BAD_INPUT);
251 // loop over the transitions by using our external index.
252 for (; start < state.transitions.length(); start++)
253 if (state.transitions[start].next_state == next) {
254 start++; // position it after this index.
255 return start - 1; // return this index.
257 return common::NOT_FOUND;
260 // configurational functions:
262 void transition_map::reconfigure() { _valid = false; }
264 outcome transition_map::validate(int &examine)
266 // check for a bad starting state...
267 examine = _start_state;
268 FIND_STATE(_start_state) BAD_START;
270 if (!check_reachability(examine)) return UNREACHABLE;
271 // a state is unreachable from the starting state.
272 if (!check_overlapping(examine)) return OVERLAPPING_RANGES;
273 // bad (overlapping) ranges were found in one state.
274 _valid = true; // set us to operational.
278 bool transition_map::add_state(int state_number)
280 if (valid()) return false; // this is operational; no more config!
281 if (!state_number) return false; // zero is disallowed.
282 if (non_negative(state_index(state_number))) return false; // already exists.
283 *_state_list += state_info(state_number);
287 bool transition_map::set_start(int starting_state)
289 if (valid()) return false; // this is operational; no more config!
290 if (!starting_state) return false;
292 FIND_STATE(starting_state) false; // doesn't exist.
294 _start_state = starting_state;
298 bool transition_map::add_simple_transition(int current, int next)
300 if (valid()) return false; // this is operational; no more config!
302 (*_state_list)[indy].transitions += transition_info(next);
306 bool transition_map::add_range_transition(int current, int next, int low, int high)
308 if (valid()) return false; // this is operational; no more config!
310 (*_state_list)[indy].transitions += transition_info(next, low, high);
314 bool transition_map::add_timed_transition(int current, int next, int length)
316 if (valid()) return false; // this is operational; no more config!
318 state_info &found = (*_state_list)[indy];
319 // locates any existing timed transition and re-uses its slot.
320 for (int i = 0; i < found.transitions.length(); i++)
321 if (found.transitions[i].type == transition_info::TIMED) {
322 found.transitions[i] = transition_info(next, length);
325 // no existing timed transition found, so add a new one.
326 (*_state_list)[indy].transitions += transition_info(next, length);
330 // operational functions:
332 bool transition_map::make_transition(state_machine &m, int next)
334 if (!valid()) return false; // this is not operational yet!
336 #ifdef DEBUG_STATE_MACHINE
337 if (negative(state_index(m._current)))
338 LOG(astring("(%s) transition_map::make_transition: bad logic error; machine's "
339 "state is missing.", m._name->s()));
340 if (negative(state_index(next)))
341 LOG(astring("(%s) transition_map::make_transition: next state parameter is invalid.",
344 FIND_STATE(m._current) false; // bad next state.
346 if (negative(transition_index(indy, next, start))) return false;
347 // no transition exists for that next state.
348 MOVE_STATE(m, next, state_machine::SIMPLE, 0);
349 int trig = m.update();
350 if (trig) return pulse(m, trig);
354 bool transition_map::pulse(state_machine &m, int trigger)
356 if (!valid()) return false; // this is not operational yet!
358 #ifdef DEBUG_STATE_MACHINE
359 if (negative(state_index(m._current)))
360 LOG(astring("(%s) transition_map::pulse: bad logic error; state is missing.", m._name->s()));
362 FIND_STATE(m._current) false; // logic error!
363 state_info &found = (*_state_list)[indy];
364 for (int i = 0; i < found.transitions.length(); i++) {
365 if (found.transitions[i].type == transition_info::RANGE) {
366 // found the right type of transition.
367 transition_info &tran = found.transitions[i];
368 if ( (tran.low_trigger <= trigger)
369 && (tran.high_trigger >= trigger) ) {
370 // found a transition with an acceptable range.
371 MOVE_STATE(m, tran.next_state, state_machine::RANGE, trigger);
372 int trig = m.update();
373 if (trig) return pulse(m, trig);
381 bool transition_map::time_slice(state_machine &m)
383 if (!valid()) return false; // this is not operational yet!
386 #ifdef DEBUG_STATE_MACHINE
387 if (negative(state_index(m._current)))
388 LOG(astring("(%s) transition_map::time_slice: bad logic error; state "
389 "is missing.", m._name->s()));
391 FIND_STATE(m._current) false; // logic error!
393 state_info &found = (*_state_list)[indy];
394 for (int i = 0; i < found.transitions.length(); i++) {
395 if (found.transitions[i].type == transition_info::TIMED) {
396 // found the right type of transition.
397 transition_info &tran = found.transitions[i];
398 int duration = tran.time_span;
399 int override = m.duration_override(m._current, tran.next_state);
400 if (override) duration = override;
401 if (*m._start < time_stamp(-duration)) {
402 // found a transition with an expired time.
403 MOVE_STATE(m, tran.next_state, state_machine::TIMED, 0);
404 int trig = m.update();
405 if (trig) return pulse(m, trig);
413 bool transition_map::reset(state_machine &m)
415 if (!valid()) return false; // this is not operational yet!
416 m._current = _start_state;
417 m._last = _start_state;
419 m._type = state_machine::SIMPLE;
424 bool transition_map::check_overlapping(int &examine)
426 FUNCDEF("check_overlapping");
428 for (int i = 0; i < states(); i++) {
430 for (int j = 0; j < (*_state_list)[i].transitions.length(); j++) {
431 transition_info &a = (*_state_list)[i].transitions[j];
432 if (a.type != transition_info::RANGE) continue;
433 for (int k = j + 1; k < (*_state_list)[i].transitions.length(); k++) {
434 transition_info &b = (*_state_list)[i].transitions[k];
435 if (b.type != transition_info::RANGE) continue;
436 if ( (b.low_trigger <= a.low_trigger)
437 && (b.high_trigger >= a.low_trigger) ) {
438 LOG(astring("intersecting range on low end!"));
441 if ( (b.low_trigger <= a.high_trigger)
442 && (b.high_trigger >= a.high_trigger) ) {
443 LOG(astring("intersecting range on high end!"));
452 bool transition_map::check_reachability(int &examine)
454 FUNCDEF("check_reachability");
457 // list_to_add: the list of states that are definitely reachable and that
458 // need to be considered.
459 int_array list_to_add;
460 list_to_add += _start_state;
462 // already_checked: those states that have already been completely considered
463 // as to their reachability.
464 array<bool> already_checked(states());
465 for (int i = 0; i < already_checked.length(); i++)
466 already_checked[i] = false;
468 while (list_to_add.length()) {
469 // the next state that IS reachable has all of the states reachable from
470 // it added to the list of states that are to be checked...
471 examine = list_to_add[0];
472 int indy = state_index(examine);
473 if (negative(indy)) {
474 LOG(a_sprintf("bad state index for state %d!", examine));
477 #ifdef DEBUG_STATE_MACHINE
478 LOG(a_sprintf("state to add is %d, list size=%d.", examine,
479 list_to_add.length()));
481 // delete the item that we are now checking.
482 list_to_add.zap(0, 0);
484 // check whether this item has already had its kids (those states reachable
485 // from it) added to the list to add. if so, skip it.
486 if (already_checked[indy]) continue;
488 // update the information for this state we are considering in the list of
489 // already checked states.
490 already_checked[indy] = true;
492 state_info &found = (*_state_list)[indy];
494 // all states this one can reach are added to the list to check.
495 for (int i = 0; i < found.transitions.length(); i++) {
496 #ifdef DEBUG_STATE_MACHINE
497 LOG(astring("checking transition %d: ", i));
499 int now_reachable = found.transitions[i].next_state;
500 #ifdef DEBUG_STATE_MACHINE
501 LOG(astring("now reaching %d.", now_reachable));
503 if (now_reachable == examine) continue;
505 int indy = state_index(now_reachable);
506 if (already_checked[indy]) continue;
508 list_to_add += now_reachable;
511 #ifdef DEBUG_STATE_MACHINE
512 LOG("done checking reachability.");
514 for (int j = 0; j < already_checked.length(); j++)
515 if (!already_checked.get(j)) {
516 examine = (*_state_list)[j].state_id;
517 LOG(a_sprintf("state %d is not reachable", examine));
520 return true; // all states are reachable.