diff --git a/scxml.workspace b/scxml.workspace new file mode 100644 index 0000000..bbcb33b --- /dev/null +++ b/scxml.workspace @@ -0,0 +1,8 @@ + + + + + + src + + diff --git a/scxml_core/include/scxml_core/scxml_sm_interface.h b/scxml_core/include/scxml_core/scxml_sm_interface.h index 9e8b49e..de408e5 100644 --- a/scxml_core/include/scxml_core/scxml_sm_interface.h +++ b/scxml_core/include/scxml_core/scxml_sm_interface.h @@ -3,11 +3,13 @@ #include #include #include +#include +#include namespace scxml_core { /** @brief Container for states and their associated transitions */ -using StateTransitionMap = std::map>; +using StateTransitionMap = std::map>>; /** @brief Creates a map of known states and transition events associated with those states */ StateTransitionMap getStateTransitionMap(const std::string& scxml_file); @@ -23,11 +25,24 @@ class ScxmlSMInterface public: ScxmlSMInterface(const std::string& scxml_file); + /** + * @brief checks if an event exists + */ + bool eventExists(const QString& event, std::set> events); + + /** + * @brief gets the state to which a desired transition occurs + * @param search_text - insert transition text you'd like to match + * @throws if you don't have that transition, it will return itself as it's neighbor + */ + + const QString getNeighbor(const QString& state, const QString& search_text); /** * @brief Adds a callback to the input state that will be invoked on entry to the state * @param async - flag for executing the input callback asynchronously * @throws exception if the state does not exist in the state machine */ + void addOnEntryCallback(const QString& state, const std::function& callback, bool async = false); /** diff --git a/scxml_core/src/demo.cpp b/scxml_core/src/demo.cpp index 7ab4643..102a1c2 100644 --- a/scxml_core/src/demo.cpp +++ b/scxml_core/src/demo.cpp @@ -38,45 +38,47 @@ int main(int argc, char** argv) // Get the active state and available events QStringList active_states = interface.getSM()->activeStateNames(); const QString& current_state = active_states.at(0); - QSet available_events = map.at(current_state); + std::set> available_events = map.at(current_state); std::stringstream ss; ss << "Available events: [ "; - for (const QString& event : available_events) + for (auto& pair : available_events) { + const QString& event = pair.first; ss << event.toStdString() << " "; } ss << "]"; - // Get user input as to which event to execute - bool done = false; - while (!done) - { - std::cout << ss.str() << std::endl; + // // Get user input as to which event to execute + // bool done = false; + // while (!done) + // { + // std::cout << ss.str() << std::endl; - // Get the character input - auto input = std::cin.get(); - // Throw away the enter input - std::cin.get(); + // // Get the character input + // auto input = std::cin.get(); + // // Throw away the enter input + // std::cin.get(); - if (std::isdigit(input)) - { - int idx = static_cast(input) - 48; - if (idx < available_events.size()) - { - interface.submitEvent(available_events.toList().at(idx)); - done = true; - } - else - { - std::cout << "Index " << idx << " was not in range [0, " << available_events.size() - 1 << "]" << std::endl; - } - } - else - { - std::cout << "Input must be numeric" << std::endl; - } - } + // if (std::isdigit(input)) + // { + // int idx = static_cast(input) - 48; + // if (idx < available_events.size()) + // { + // interface.submitEvent(available_events.toList().at(idx)); + // done = true; + // } + // else + // { + // std::cout << "Index " << idx << " was not in range [0, " << available_events.size() - 1 << "]" << + // std::endl; + // } + // } + // else + // { + // std::cout << "Input must be numeric" << std::endl; + // } + // } } } catch (const std::exception& ex) diff --git a/scxml_core/src/scxml_sm_interface.cpp b/scxml_core/src/scxml_sm_interface.cpp index 63ac0f7..aef81b7 100644 --- a/scxml_core/src/scxml_sm_interface.cpp +++ b/scxml_core/src/scxml_sm_interface.cpp @@ -10,6 +10,7 @@ static const char* HISTORY_STATE_ELEMENT = "history"; static const char* HISTORY_STATE_ID_ATTRIBUTE = "id"; static const char* TRANSITION_ELEMENT = "transition"; static const char* EVENT_ATTRIBUTE = "event"; +static const char* TARGET_ATTRIBUTE = "target"; /** * @brief Recursively adds states and transitions to the map @@ -18,7 +19,7 @@ static const char* EVENT_ATTRIBUTE = "event"; */ static void getStateTransitionsRecursive(tinyxml2::XMLElement* state, scxml_core::StateTransitionMap& map, - QSet inherited_events) + std::set> inherited_events) { using namespace tinyxml2; @@ -46,8 +47,14 @@ static void getStateTransitionsRecursive(tinyxml2::XMLElement* state, throw std::runtime_error("'" + std::string(TRANSITION_ELEMENT) + "' element does not have '" + std::string(EVENT_ATTRIBUTE) + "' attribute"); + const char* name = transition->Attribute(TARGET_ATTRIBUTE); + + std::pair list = std::make_pair(QString(event), QString(name)); + + inherited_events.insert(list); + // Add the event name to the map - map.at(state_id).insert(event); + map[state_id] = inherited_events; // Get the next transition element transition = transition->NextSiblingElement(TRANSITION_ELEMENT); @@ -71,7 +78,7 @@ static void getStateTransitionsRecursive(tinyxml2::XMLElement* state, std::string(HISTORY_STATE_ID_ATTRIBUTE) + "' attribute"); // Add this state to the map - map[QString(id)] = QSet{}; + map[QString(id)] = std::set>{}; // History states do not have transitions or nested states, so no need to recurse into it history = history->NextSiblingElement(HISTORY_STATE_ELEMENT); @@ -102,7 +109,7 @@ StateTransitionMap getStateTransitionMap(const std::string& scxml_file) StateTransitionMap map; while (state) { - getStateTransitionsRecursive(state, map, QSet{}); + getStateTransitionsRecursive(state, map, std::set>{}); state = state->NextSiblingElement(STATE_ELEMENT); } @@ -136,6 +143,43 @@ ScxmlSMInterface::ScxmlSMInterface(const std::string& scxml_file) } } +// use this to determine the next state in the state machine, given the name of the transition you'd like to query +const QString ScxmlSMInterface::getNeighbor(const QString& state, const QString& search_text) +{ + QString next_state; + + for (auto& pair : state_transition_map_.at(state)) + { + if (pair.first == search_text) + { + return pair.second; + } + } + return next_state; +} + +bool ScxmlSMInterface::eventExists(const QString& event, std::set> events) +{ + int i = 0; + const int j = events.size(); + for (auto& pair : events) + { + if (pair.first == event) + { + return true; + } + else + { + i = i + 1; + } + + if (i == j) + { + return false; + } + } +} + void ScxmlSMInterface::addOnEntryCallback(const QString& state, const std::function& callback, bool async) { if (state_transition_map_.find(state) == state_transition_map_.end()) @@ -165,7 +209,7 @@ bool ScxmlSMInterface::submitEvent(const QString& event, bool force) // Ensure at least one of the active states has the specified transition auto it = std::find_if(active_states.begin(), active_states.end(), [this, event](const QString& state) -> bool { - return this->state_transition_map_.at(state).contains(event); + return eventExists(event, state_transition_map_.at(state)); }); if (it == active_states.end()) @@ -185,7 +229,7 @@ bool ScxmlSMInterface::submitEvent(const QString& event, bool force) { for (const QString& state : active_states) { - if (state_transition_map_.at(state).contains(event)) + if (eventExists(event, state_transition_map_.at(state))) { // Check if the asynchronous callback is finished before submitting the event if (!future_map_.at(state).isFinished())