/*****************************************************************************\ * * * Name : state_machine * * Author : Chris Koeritz * * * ******************************************************************************* * Copyright (c) 1992-$now By Author. This program is free software; you can * * redistribute it and/or modify it under the terms of the GNU General Public * * License as published by the Free Software Foundation; either version 2 of * * the License or (at your option) any later version. This is online at: * * http://www.fsf.org/copyleft/gpl.html * * Please send any updates to: fred@gruntose.com * \*****************************************************************************/ #include "state_machine.h" #include #include #include #include #include #include using namespace basis; using namespace loggers; using namespace timely; namespace processes { ////////////// //#define DEBUG_STATE_MACHINE // uncomment if you want the debugging version. //hmmm: implement logging... #undef LOG #ifndef DEBUG_STATE_MACHINE #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print) #else #define LOG(to_print) {} #endif ////////////// // checks whether the machine passed in is valid or not. #define CHECK_VALID(m) \ if (!m._current) return false; \ if (!m._last) return false // checks whether the current and next states exist or not. #define CHECK_STATES \ if (!current) return false; \ if (!next) return false; \ int indy = state_index(current); \ if (negative(indy)) return false; \ if (negative(state_index(next))) return false // moves to a new state and resets the new state's timestamp. #define MOVE_STATE(m, next, type, trigger) \ m._last = m._current; \ m._current = next; \ m._type = type; \ m._trig = trigger; \ m._start->reset() // locates a state or returns. #define FIND_STATE(state) \ int indy = state_index(state); \ if (negative(indy)) return ////////////// struct override { int current; int next; int duration; override(int _current = 0, int _next = 0, int _duration = 0) : current(_current), next(_next), duration(_duration) {} }; struct transition_info { enum transition_type { SIMPLE, RANGE, TIMED }; transition_type type; int next_state; int low_trigger, high_trigger; int time_span; // blank constructor. transition_info() : type(RANGE), next_state(0), low_trigger(0), high_trigger(0), time_span(0) { } transition_info(int next) : type(SIMPLE), next_state(next), low_trigger(0), high_trigger(0), time_span(0) { } transition_info(int next, int time) : type(TIMED), next_state(next), time_span(time), low_trigger(0), high_trigger(0) { } transition_info(int next, int low, int high) : type(RANGE), next_state(next), low_trigger(low), high_trigger(high), time_span(0) { } }; struct state_info { int state_id; // id for this state. array transitions; state_info() : state_id(0) {} // blank. state_info(int state_id_in) : state_id(state_id_in) {} }; ////////////// class state_machine_override_array : public array {}; class state_machine_state_array : public array {}; ////////////// state_machine::state_machine() : _current(0), _last(0), _trig(0), _type(SIMPLE), _start(new time_stamp()), _name(new astring()), _overrides(new state_machine_override_array) {} state_machine::state_machine(const state_machine &to_copy) : root_object(), _current(0), _last(0), _trig(0), _type(SIMPLE), _start(new time_stamp()), _name(new astring()), _overrides(new state_machine_override_array) { *this = to_copy; } state_machine::~state_machine() { WHACK(_start); WHACK(_name); WHACK(_overrides); } int state_machine::update() { return 0; } void state_machine::set_name(const astring &name) { *_name = name; } astring state_machine::get_name() const { return *_name; } state_machine &state_machine::operator = (const state_machine &to_copy) { if (&to_copy == this) return *this; _current = to_copy._current; _last = to_copy._last; _trig = to_copy._trig; _type = to_copy._type; *_start = *to_copy._start; *_name = *to_copy._name; *_overrides = *to_copy._overrides; //careful when overrides becomes hidden internal type. return *this; } int state_machine::duration_index(int current, int next) const { for (int i = 0; i < _overrides->length(); i++) if ( ((*_overrides)[i].current == current) && ((*_overrides)[i].next == next) ) return i; return common::NOT_FOUND; } void state_machine::set_state(int new_current, int new_last, int trigger, transition_types type) { _current = new_current; _last = new_last; _trig = trigger; _type = type; _start->reset(); } void state_machine::override_timing(int current, int next, int duration) { int indy = duration_index(current, next); if (non_negative(indy)) { // found an existing record for this current/next pair. if (!duration) { // zero duration means removal. _overrides->zap(indy, indy); return; } // reset the duration. (*_overrides)[indy].duration = duration; return; } // no existing record, so add one. *_overrides += override(current, next, duration); } int state_machine::duration_override(int current, int next) const { int indy = duration_index(current, next); if (negative(indy)) return 0; return (*_overrides)[indy].duration; } time_stamp state_machine::start() const { return *_start; } ////////////// transition_map::transition_map() : _valid(false), _start_state(0), _state_list(new state_machine_state_array) {} transition_map::~transition_map() { _valid = false; WHACK(_state_list); } // informational functions: int transition_map::states() const { return _state_list->length(); } int transition_map::state_index(int state_id) const { for (int i = 0; i < states(); i++) if ((*_state_list)[i].state_id == state_id) return i; return common::NOT_FOUND; } int transition_map::transition_index(int state_index, int next, int &start) { bounds_return(state_index, 0, states() - 1, common::BAD_INPUT); state_info &state = (*_state_list)[state_index]; bounds_return(start, 0, state.transitions.length() - 1, common::BAD_INPUT); // loop over the transitions by using our external index. for (; start < state.transitions.length(); start++) if (state.transitions[start].next_state == next) { start++; // position it after this index. return start - 1; // return this index. } return common::NOT_FOUND; } // configurational functions: void transition_map::reconfigure() { _valid = false; } outcome transition_map::validate(int &examine) { // check for a bad starting state... examine = _start_state; FIND_STATE(_start_state) BAD_START; if (!check_reachability(examine)) return UNREACHABLE; // a state is unreachable from the starting state. if (!check_overlapping(examine)) return OVERLAPPING_RANGES; // bad (overlapping) ranges were found in one state. _valid = true; // set us to operational. return OKAY; } bool transition_map::add_state(int state_number) { if (valid()) return false; // this is operational; no more config! if (!state_number) return false; // zero is disallowed. if (non_negative(state_index(state_number))) return false; // already exists. *_state_list += state_info(state_number); return true; } bool transition_map::set_start(int starting_state) { if (valid()) return false; // this is operational; no more config! if (!starting_state) return false; FIND_STATE(starting_state) false; // doesn't exist. _start_state = starting_state; return true; } bool transition_map::add_simple_transition(int current, int next) { if (valid()) return false; // this is operational; no more config! CHECK_STATES; (*_state_list)[indy].transitions += transition_info(next); return true; } bool transition_map::add_range_transition(int current, int next, int low, int high) { if (valid()) return false; // this is operational; no more config! CHECK_STATES; (*_state_list)[indy].transitions += transition_info(next, low, high); return true; } bool transition_map::add_timed_transition(int current, int next, int length) { if (valid()) return false; // this is operational; no more config! CHECK_STATES; state_info &found = (*_state_list)[indy]; // locates any existing timed transition and re-uses its slot. for (int i = 0; i < found.transitions.length(); i++) if (found.transitions[i].type == transition_info::TIMED) { found.transitions[i] = transition_info(next, length); return true; } // no existing timed transition found, so add a new one. (*_state_list)[indy].transitions += transition_info(next, length); return true; } // operational functions: bool transition_map::make_transition(state_machine &m, int next) { if (!valid()) return false; // this is not operational yet! CHECK_VALID(m); #ifdef DEBUG_STATE_MACHINE if (negative(state_index(m._current))) LOG(astring("(%s) transition_map::make_transition: bad logic error; machine's " "state is missing.", m._name->s())); if (negative(state_index(next))) LOG(astring("(%s) transition_map::make_transition: next state parameter is invalid.", m._name->s())); #endif FIND_STATE(m._current) false; // bad next state. int start = 0; if (negative(transition_index(indy, next, start))) return false; // no transition exists for that next state. MOVE_STATE(m, next, state_machine::SIMPLE, 0); int trig = m.update(); if (trig) return pulse(m, trig); return true; } bool transition_map::pulse(state_machine &m, int trigger) { if (!valid()) return false; // this is not operational yet! CHECK_VALID(m); #ifdef DEBUG_STATE_MACHINE if (negative(state_index(m._current))) LOG(astring("(%s) transition_map::pulse: bad logic error; state is missing.", m._name->s())); #endif FIND_STATE(m._current) false; // logic error! state_info &found = (*_state_list)[indy]; for (int i = 0; i < found.transitions.length(); i++) { if (found.transitions[i].type == transition_info::RANGE) { // found the right type of transition. transition_info &tran = found.transitions[i]; if ( (tran.low_trigger <= trigger) && (tran.high_trigger >= trigger) ) { // found a transition with an acceptable range. MOVE_STATE(m, tran.next_state, state_machine::RANGE, trigger); int trig = m.update(); if (trig) return pulse(m, trig); return true; } } } return false; } bool transition_map::time_slice(state_machine &m) { if (!valid()) return false; // this is not operational yet! CHECK_VALID(m); #ifdef DEBUG_STATE_MACHINE if (negative(state_index(m._current))) LOG(astring("(%s) transition_map::time_slice: bad logic error; state " "is missing.", m._name->s())); #endif FIND_STATE(m._current) false; // logic error! state_info &found = (*_state_list)[indy]; for (int i = 0; i < found.transitions.length(); i++) { if (found.transitions[i].type == transition_info::TIMED) { // found the right type of transition. transition_info &tran = found.transitions[i]; int duration = tran.time_span; int override = m.duration_override(m._current, tran.next_state); if (override) duration = override; if (*m._start < time_stamp(-duration)) { // found a transition with an expired time. MOVE_STATE(m, tran.next_state, state_machine::TIMED, 0); int trig = m.update(); if (trig) return pulse(m, trig); return true; } } } return false; } bool transition_map::reset(state_machine &m) { if (!valid()) return false; // this is not operational yet! m._current = _start_state; m._last = _start_state; m._trig = 0; m._type = state_machine::SIMPLE; m._start->reset(); return true; } bool transition_map::check_overlapping(int &examine) { FUNCDEF("check_overlapping"); examine = 0; for (int i = 0; i < states(); i++) { examine = i; for (int j = 0; j < (*_state_list)[i].transitions.length(); j++) { transition_info &a = (*_state_list)[i].transitions[j]; if (a.type != transition_info::RANGE) continue; for (int k = j + 1; k < (*_state_list)[i].transitions.length(); k++) { transition_info &b = (*_state_list)[i].transitions[k]; if (b.type != transition_info::RANGE) continue; if ( (b.low_trigger <= a.low_trigger) && (b.high_trigger >= a.low_trigger) ) { LOG(astring("intersecting range on low end!")); return false; } if ( (b.low_trigger <= a.high_trigger) && (b.high_trigger >= a.high_trigger) ) { LOG(astring("intersecting range on high end!")); return false; } } } } return true; } bool transition_map::check_reachability(int &examine) { FUNCDEF("check_reachability"); examine = 0; // list_to_add: the list of states that are definitely reachable and that // need to be considered. int_array list_to_add; list_to_add += _start_state; // already_checked: those states that have already been completely considered // as to their reachability. array already_checked(states()); for (int i = 0; i < already_checked.length(); i++) already_checked[i] = false; while (list_to_add.length()) { // the next state that IS reachable has all of the states reachable from // it added to the list of states that are to be checked... examine = list_to_add[0]; int indy = state_index(examine); if (negative(indy)) { LOG(a_sprintf("bad state index for state %d!", examine)); return false; } #ifdef DEBUG_STATE_MACHINE LOG(a_sprintf("state to add is %d, list size=%d.", examine, list_to_add.length())); #endif // delete the item that we are now checking. list_to_add.zap(0, 0); // check whether this item has already had its kids (those states reachable // from it) added to the list to add. if so, skip it. if (already_checked[indy]) continue; // update the information for this state we are considering in the list of // already checked states. already_checked[indy] = true; state_info &found = (*_state_list)[indy]; // all states this one can reach are added to the list to check. for (int i = 0; i < found.transitions.length(); i++) { #ifdef DEBUG_STATE_MACHINE LOG(astring("checking transition %d: ", i)); #endif int now_reachable = found.transitions[i].next_state; #ifdef DEBUG_STATE_MACHINE LOG(astring("now reaching %d.", now_reachable)); #endif if (now_reachable == examine) continue; else { int indy = state_index(now_reachable); if (already_checked[indy]) continue; } list_to_add += now_reachable; } } #ifdef DEBUG_STATE_MACHINE LOG("done checking reachability."); #endif for (int j = 0; j < already_checked.length(); j++) if (!already_checked.get(j)) { examine = (*_state_list)[j].state_id; LOG(a_sprintf("state %d is not reachable", examine)); return false; } return true; // all states are reachable. } } //namespace.