diff options
author | Stefan Radomski <github@mintwerk.de> | 2016-05-19 08:03:50 (GMT) |
---|---|---|
committer | Stefan Radomski <github@mintwerk.de> | 2016-05-19 08:03:50 (GMT) |
commit | 5de792adc6796b0f03d62124765b4af0676dde46 (patch) | |
tree | e700d6b008b21c037aebcc1882fd9286920b2987 /src/uscxml/interpreter/FastMicroStep.cpp | |
parent | f8e0c96fddfdd5f086e1bd973d6b0a19c39c93da (diff) | |
download | uscxml-5de792adc6796b0f03d62124765b4af0676dde46.zip uscxml-5de792adc6796b0f03d62124765b4af0676dde46.tar.gz uscxml-5de792adc6796b0f03d62124765b4af0676dde46.tar.bz2 |
Refactored for public headers and started documentation
Diffstat (limited to 'src/uscxml/interpreter/FastMicroStep.cpp')
-rw-r--r-- | src/uscxml/interpreter/FastMicroStep.cpp | 1152 |
1 files changed, 1152 insertions, 0 deletions
diff --git a/src/uscxml/interpreter/FastMicroStep.cpp b/src/uscxml/interpreter/FastMicroStep.cpp new file mode 100644 index 0000000..99c2d74 --- /dev/null +++ b/src/uscxml/interpreter/FastMicroStep.cpp @@ -0,0 +1,1152 @@ +/** + * @file + * @author 2012-2016 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#undef USCXML_VERBOSE + +#include "FastMicroStep.h" +#include "uscxml/util/DOM.h" +#include "uscxml/util/String.h" +#include "uscxml/util/Predicates.h" +#include "uscxml/util/Convenience.h" +#include "uscxml/interpreter/InterpreterMonitor.h" + +#include <easylogging++.h> + +#define BIT_ANY_SET(b) (!b.none()) +#define BIT_HAS(idx, bitset) (bitset[idx]) +#define BIT_HAS_AND(bitset1, bitset2) bitset1.intersects(bitset2) +#define BIT_SET_AT(idx, bitset) bitset[idx] = true; +#define BIT_CLEAR(idx, bitset) bitset[idx] = false; + +#define USCXML_GET_TRANS(i) (*_transitions[i]) +#define USCXML_GET_STATE(i) (*_states[i]) + +#define USCXML_CTX_PRISTINE 0x00 +#define USCXML_CTX_SPONTANEOUS 0x01 +#define USCXML_CTX_INITIALIZED 0x02 +#define USCXML_CTX_TOP_LEVEL_FINAL 0x04 +#define USCXML_CTX_TRANSITION_FOUND 0x08 +#define USCXML_CTX_FINISHED 0x10 +#define USCXML_CTX_STABLE 0x20 // only needed to signal onStable once + +#define USCXML_TRANS_SPONTANEOUS 0x01 +#define USCXML_TRANS_TARGETLESS 0x02 +#define USCXML_TRANS_INTERNAL 0x04 +#define USCXML_TRANS_HISTORY 0x08 +#define USCXML_TRANS_INITIAL 0x10 + +#define USCXML_STATE_ATOMIC 0x01 +#define USCXML_STATE_PARALLEL 0x02 +#define USCXML_STATE_COMPOUND 0x03 +#define USCXML_STATE_FINAL 0x04 +#define USCXML_STATE_HISTORY_DEEP 0x05 +#define USCXML_STATE_HISTORY_SHALLOW 0x06 +#define USCXML_STATE_INITIAL 0x07 +#define USCXML_STATE_HAS_HISTORY 0x80 /* highest bit */ +#define USCXML_STATE_MASK(t) (t & 0x7F) /* mask highest bit */ + +#define USCXML_NUMBER_STATES _states.size() +#define USCXML_NUMBER_TRANS _transitions.size() + +#ifdef __GNUC__ +# define likely(x) (__builtin_expect(!!(x), 1)) +# define unlikely(x) (__builtin_expect(!!(x), 0)) +#else +# define likely(x) (x) +# define unlikely(x) (x) +#endif + +namespace uscxml { + +using namespace XERCESC_NS; + +FastMicroStep::FastMicroStep(MicroStepCallbacks* callbacks) + : MicroStepImpl(callbacks), _flags(USCXML_CTX_PRISTINE), _isInitialized(false), _isCancelled(false) { +} + +FastMicroStep::~FastMicroStep() { + for (size_t i = 0; i < _states.size(); i++) { + delete(_states[i]); + } + for (size_t i = 0; i < _transitions.size(); i++) { + delete(_transitions[i]); + } +} + +void FastMicroStep::resortStates(DOMNode* node, const X& xmlPrefix) { + if (node->getNodeType() != DOMNode::ELEMENT_NODE) + return; + + /** + initials + deep histories + shallow histories + everything else + */ + + DOMElement* element = dynamic_cast<DOMElement*>(node); + + // shallow history states to top + DOMNode* child = element->getFirstChild(); + while(child) { + resortStates(child, xmlPrefix); + if (child->getNodeType() == DOMNode::ELEMENT_NODE && + TAGNAME_CAST(child) == xmlPrefix.str() + "history" && + (!HAS_ATTR(element, "type") || iequals(ATTR(element, "type"), "shallow"))) { + + DOMNode* tmp = child->getNextSibling(); + if (child != element->getFirstChild()) { + element->insertBefore(child, element->getFirstChild()); + } + child = tmp; + } else { + child = child->getNextSibling(); + } + } + + // deep history states to top + child = element->getFirstChild(); + while(child) { + resortStates(child, xmlPrefix); + if (child->getNodeType() == DOMNode::ELEMENT_NODE && + TAGNAME_CAST(child) == xmlPrefix.str() + "history" && + HAS_ATTR(element, "type") && + iequals(ATTR(element, "type"), "deep")) { + + DOMNode* tmp = child->getNextSibling(); + if (child != element->getFirstChild()) { + element->insertBefore(child, element->getFirstChild()); + } + child = tmp; + } else { + child = child->getNextSibling(); + } + } + + // initial states on top of histories even + child = element->getFirstChild(); + while(child) { + resortStates(child, xmlPrefix); + if (child->getNodeType() == DOMNode::ELEMENT_NODE && LOCALNAME_CAST(child) == "initial") { + DOMNode* tmp = child->getNextSibling(); + if (child != element->getFirstChild()) { + element->insertBefore(child, element->getFirstChild()); + } + child = tmp; + } else { + child = child->getNextSibling(); + } + } +} + +void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { + + _scxml = scxml; + _binding = (HAS_ATTR(_scxml, "binding") && iequals(ATTR(_scxml, "binding"), "late") ? LATE : EARLY); + _xmlPrefix = _scxml->getPrefix(); + _xmlNS = _scxml->getNamespaceURI(); + if (_xmlPrefix) { + _xmlPrefix = std::string(_xmlPrefix) + ":"; + } + + resortStates(_scxml, _xmlPrefix); + + // TODO: https://github.com/tklab-tud/uscxml/blob/master/src/uscxml/transform/ChartToC.cpp#L244 + + + /** -- All things states -- */ + + std::list<XERCESC_NS::DOMElement*> tmp; + size_t i, j; + + tmp = DOMUtils::inDocumentOrder({ + _xmlPrefix.str() + "state", + _xmlPrefix.str() + "parallel", + _xmlPrefix.str() + "scxml", + _xmlPrefix.str() + "initial", + _xmlPrefix.str() + "final", + _xmlPrefix.str() + "history" + }, _scxml); + + _states.resize(tmp.size()); + _configuration.resize(tmp.size()); + _history.resize(tmp.size()); + _initializedData.resize(tmp.size()); + _invocations.resize(tmp.size()); + + for (i = 0; i < _states.size(); i++) { + _states[i] = new State(); + _states[i]->documentOrder = i; + _states[i]->element = tmp.front(); + _states[i]->element->setUserData(X("uscxmlState"), _states[i], NULL); + _states[i]->completion.resize(_states.size()); + _states[i]->ancestors.resize(_states.size()); + _states[i]->children.resize(_states.size()); + tmp.pop_front(); + } + assert(tmp.size() == 0); + + if (_binding == Binding::EARLY && _states.size() > 0) { + // add all data elements to the first state + std::list<DOMElement*> dataModels = DOMUtils::filterChildElements(_xmlPrefix.str() + "datamodel", _states[0]->element, true); + dataModels.erase(std::remove_if(dataModels.begin(), + dataModels.end(), + [](DOMElement* elem) { + return isInEmbeddedDocument(elem); + }), + dataModels.end()); + + _states[0]->data = DOMUtils::filterChildElements(_xmlPrefix.str() + "data", dataModels, false); + } + + for (i = 0; i < _states.size(); i++) { + // collect states with an id attribute + if (HAS_ATTR(_states[i]->element, "id")) { + _stateIds[ATTR(_states[i]->element, "id")] = i; + } + + // check for executable content and datamodels + if (_states[i]->element->getChildElementCount() > 0) { + _states[i]->onEntry = DOMUtils::filterChildElements(_xmlPrefix.str() + "onentry", _states[i]->element); + _states[i]->onExit = DOMUtils::filterChildElements(_xmlPrefix.str() + "onexit", _states[i]->element); + _states[i]->invoke = DOMUtils::filterChildElements(_xmlPrefix.str() + "invoke", _states[i]->element); + + if (i == 0) { + // have global scripts as onentry of <scxml> + _states[i]->onEntry = DOMUtils::filterChildElements(_xmlPrefix.str() + "script", _states[i]->element, false); + } + + std::list<DOMElement*> doneDatas = DOMUtils::filterChildElements(_xmlPrefix.str() + "donedata", _states[i]->element); + if (doneDatas.size() > 0) { + _states[i]->doneData = doneDatas.front(); + } + + if (_binding == Binding::LATE) { + std::list<DOMElement*> dataModels = DOMUtils::filterChildElements(_xmlPrefix.str() + "datamodel", _states[i]->element); + if (dataModels.size() > 0) { + _states[i]->data = DOMUtils::filterChildElements(_xmlPrefix.str() + "data", dataModels, false); + } + } + } + + + // set the states type + if (false) { + } else if (iequals(TAGNAME(_states[i]->element), _xmlPrefix.str() + "initial")) { + _states[i]->type = USCXML_STATE_INITIAL; + } else if (isFinal(_states[i]->element)) { + _states[i]->type = USCXML_STATE_FINAL; + } else if (isHistory(_states[i]->element)) { + if (HAS_ATTR(_states[i]->element, "type") && iequals(ATTR(_states[i]->element, "type"), "deep")) { + _states[i]->type = USCXML_STATE_HISTORY_DEEP; + } else { + _states[i]->type = USCXML_STATE_HISTORY_SHALLOW; + } + } else if (isAtomic(_states[i]->element)) { + _states[i]->type = USCXML_STATE_ATOMIC; + } else if (isParallel(_states[i]->element)) { + _states[i]->type = USCXML_STATE_PARALLEL; + } else if (isCompound(_states[i]->element)) { + _states[i]->type = USCXML_STATE_COMPOUND; + } else { // <scxml> + _states[i]->type = USCXML_STATE_COMPOUND; + } + + // establish the states' completion + std::list<DOMElement*> completion = getCompletion(_states[i]->element); + for (j = 0; j < _states.size(); j++) { + if (!completion.empty() && _states[j]->element == completion.front()) { + _states[i]->completion[j] = true; + completion.pop_front(); + } else { + _states[i]->completion[j] = false; + } + } + assert(completion.size() == 0); + + // this is set when establishing the completion + if (_states[i]->element->getUserData(X("hasHistoryChild")) == _states[i]) { + _states[i]->type |= USCXML_STATE_HAS_HISTORY; + } + + // establish the states' ancestors + DOMNode* parent = _states[i]->element->getParentNode(); + if (parent && parent->getNodeType() == DOMNode::ELEMENT_NODE) { + State* uscxmlState = (State*)parent->getUserData(X("uscxmlState")); + // parent + _states[i]->parent = uscxmlState->documentOrder; + } + + while(parent && parent->getNodeType() == DOMNode::ELEMENT_NODE) { + State* uscxmlState = (State*)parent->getUserData(X("uscxmlState")); + + // ancestors + BIT_SET_AT(uscxmlState->documentOrder, _states[i]->ancestors); + + // children + BIT_SET_AT(i, uscxmlState->children); + parent = parent->getParentNode(); + } + } + + + /** -- All things transitions -- */ + + tmp = DOMUtils::inPostFixOrder({_xmlPrefix.str() + "transition"}, _scxml); + _transitions.resize(tmp.size()); + + for (i = 0; i < _transitions.size(); i++) { + _transitions[i] = new Transition(); + _transitions[i]->element = tmp.front(); + _transitions[i]->conflicts.resize(_transitions.size()); + _transitions[i]->exitSet.resize(_states.size()); + _transitions[i]->target.resize(_states.size()); + tmp.pop_front(); + } + assert(tmp.size() == 0); + + for (i = 0; i < _transitions.size(); i++) { + + // establish the transitions' exit set + assert(_transitions[i]->element != NULL); + std::cout << "i: " << i << std::endl << std::flush; + std::list<DOMElement*> exitList = getExitSet(_transitions[i]->element, _scxml); + for (j = 0; j < _states.size(); j++) { + if (!exitList.empty() && _states[j]->element == exitList.front()) { + _transitions[i]->exitSet[j] = true; + exitList.pop_front(); + } else { + _transitions[i]->exitSet[j] = false; + } + } + assert(exitList.size() == 0); + + // establish the transitions' conflict set + for (j = 0; j < _transitions.size(); j++) { + if (conflicts(_transitions[i]->element, _transitions[j]->element, _scxml)) { + _transitions[i]->conflicts[j] = true; + } else { + _transitions[i]->conflicts[j] = false; + } + } + + // establish the transitions' target set + std::list<std::string> targets = tokenize(ATTR(_transitions[i]->element, "target")); + for (auto tIter = targets.begin(); tIter != targets.end(); tIter++) { + if (_stateIds.find(*tIter) != _stateIds.end()) { + _transitions[i]->target[_stateIds[*tIter]] = true; + } + } + + // the transition's source + State* uscxmlState = (State*)(_transitions[i]->element->getParentNode()->getUserData(X("uscxmlState"))); + _transitions[i]->source = uscxmlState->documentOrder; + + + // the transition's type + if (!HAS_ATTR(_transitions[i]->element, "target")) { + _transitions[i]->type |= USCXML_TRANS_TARGETLESS; + } + + if (HAS_ATTR(_transitions[i]->element, "type") && iequals(ATTR(_transitions[i]->element, "type"), "internal")) { + _transitions[i]->type |= USCXML_TRANS_INTERNAL; + } + + if (!HAS_ATTR(_transitions[i]->element, "event")) { + _transitions[i]->type |= USCXML_TRANS_SPONTANEOUS; + } + + if (iequals(TAGNAME_CAST(_transitions[i]->element->getParentNode()), _xmlPrefix.str() + "history")) { + _transitions[i]->type |= USCXML_TRANS_HISTORY; + } + + if (iequals(TAGNAME_CAST(_transitions[i]->element->getParentNode()), _xmlPrefix.str() + "initial")) { + _transitions[i]->type |= USCXML_TRANS_INITIAL; + } + + + // the transitions event and condition + _transitions[i]->event = (HAS_ATTR(_transitions[i]->element, "event") ? ATTR(_transitions[i]->element, "event") : ""); + _transitions[i]->cond = (HAS_ATTR(_transitions[i]->element, "cond") ? ATTR(_transitions[i]->element, "cond") : ""); + + // is there executable content? + if (_transitions[i]->element->getChildElementCount() > 0) { + _transitions[i]->onTrans = _transitions[i]->element; + } + + } + _isInitialized = true; +} + +void FastMicroStep::markAsCancelled() { + _isCancelled = true; +} + +InterpreterState FastMicroStep::step(bool blocking) { + if (!_isInitialized) { + init(_scxml); + return USCXML_INITIALIZED; + } + + size_t i, j, k; + + boost::dynamic_bitset<> exitSet(_states.size(), false); + boost::dynamic_bitset<> entrySet(_states.size(), false); + boost::dynamic_bitset<> targetSet(_states.size(), false); + boost::dynamic_bitset<> tmpStates(_states.size(), false); + + boost::dynamic_bitset<> conflicts(_transitions.size(), false); + boost::dynamic_bitset<> transSet(_transitions.size(), false); + +#ifdef USCXML_VERBOSE + std::cerr << "Config: "; + printStateNames(_configuration); +#endif + + if (_flags & USCXML_CTX_FINISHED) + return USCXML_FINISHED; + + if (_flags & USCXML_CTX_TOP_LEVEL_FINAL) { + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), beforeCompletion); + + /* exit all remaining states */ + i = USCXML_NUMBER_STATES; + while(i-- > 0) { + if (BIT_HAS(i, _configuration)) { + /* call all on exit handlers */ + for (auto exitIter = USCXML_GET_STATE(i).onExit.begin(); exitIter != USCXML_GET_STATE(i).onExit.end(); exitIter++) { + try { + _callbacks->process(*exitIter); + } catch (...) { + // do nothing and continue with next block + } + } + /* Leave configuration intact */ +// BIT_CLEAR(i, _configuration); + } + + if (BIT_HAS(i, _invocations)) { + /* cancel all invokers */ + if (USCXML_GET_STATE(i).invoke.size() > 0) { + for (auto invIter = USCXML_GET_STATE(i).invoke.begin(); invIter != USCXML_GET_STATE(i).invoke.end(); invIter++) { + _callbacks->uninvoke(*invIter); + } + } + BIT_CLEAR(i, _invocations); + } + } + + _flags |= USCXML_CTX_FINISHED; + + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), afterCompletion); + + return USCXML_FINISHED; + } + + + if (_flags == USCXML_CTX_PRISTINE) { + + targetSet |= USCXML_GET_STATE(0).completion; + _flags |= USCXML_CTX_SPONTANEOUS | USCXML_CTX_INITIALIZED; + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), beforeMicroStep); + + goto ESTABLISH_ENTRYSET; + } + + if (_flags & USCXML_CTX_SPONTANEOUS) { + _event = Event(); + goto SELECT_TRANSITIONS; + } + + + if ((_event = _callbacks->dequeueInternal())) { + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeProcessingEvent, _event); + goto SELECT_TRANSITIONS; + } + + /* manage invocations */ + for (i = 0; i < USCXML_NUMBER_STATES; i++) { + /* uninvoke */ + if (!BIT_HAS(i, _configuration) && BIT_HAS(i, _invocations)) { + if (USCXML_GET_STATE(i).invoke.size() > 0) { + for (auto invIter = USCXML_GET_STATE(i).invoke.begin(); invIter != USCXML_GET_STATE(i).invoke.end(); invIter++) { + _callbacks->uninvoke(*invIter); + } + } + BIT_CLEAR(i, _invocations) + } + /* invoke */ + if (BIT_HAS(i, _configuration) && !BIT_HAS(i, _invocations)) { + if (USCXML_GET_STATE(i).invoke.size() > 0) { + for (auto invIter = USCXML_GET_STATE(i).invoke.begin(); invIter != USCXML_GET_STATE(i).invoke.end(); invIter++) { + try { + _callbacks->invoke(*invIter); + } catch (...) { + } + } + } + BIT_SET_AT(i, _invocations) + } + } + + // we dequeued all internal events and ought to signal stable configuration + if (!(_flags & USCXML_CTX_STABLE)) { + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), onStableConfiguration); + _microstepConfigurations.clear(); + _flags |= USCXML_CTX_STABLE; + } + + if ((_event = _callbacks->dequeueExternal(blocking))) { + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeProcessingEvent, _event); + goto SELECT_TRANSITIONS; + } + + if (_isCancelled) { + // finalize and exit + _flags |= USCXML_CTX_TOP_LEVEL_FINAL; + return USCXML_CANCELLED; + } + +// if (blocking) // we received the empty event to unblock +// return USCXML_IDLE; // we return IDLE nevertheless + + return USCXML_IDLE; + +SELECT_TRANSITIONS: + + // we read an event - unset stable to signal onstable again later + _flags &= ~USCXML_CTX_STABLE; + + for (i = 0; i < _transitions.size(); i++) { + /* never select history or initial transitions automatically */ + if unlikely(USCXML_GET_TRANS(i).type & (USCXML_TRANS_HISTORY | USCXML_TRANS_INITIAL)) + continue; + + /* is the transition active? */ + if (BIT_HAS(USCXML_GET_TRANS(i).source, _configuration)) { + /* is it non-conflicting? */ + if (!BIT_HAS(i, conflicts)) { + /* is it spontaneous with an event or vice versa? */ + if ((USCXML_GET_TRANS(i).event.size() == 0 && !_event) || + (USCXML_GET_TRANS(i).event.size() != 0 && _event)) { + /* is it enabled? */ + if ((!_event || _callbacks->isMatched(_event, USCXML_GET_TRANS(i).event)) && + (USCXML_GET_TRANS(i).cond.size() == 0 || _callbacks->isTrue(USCXML_GET_TRANS(i).cond))) { + + /* remember that we found a transition */ + _flags |= USCXML_CTX_TRANSITION_FOUND; + + /* transitions that are pre-empted */ + conflicts |= USCXML_GET_TRANS(i).conflicts; + + /* states that are directly targeted (resolve as entry-set later) */ + targetSet |= USCXML_GET_TRANS(i).target; + + /* states that will be left */ + exitSet |= USCXML_GET_TRANS(i).exitSet; + + BIT_SET_AT(i, transSet); + } + } + } + } + } + +#ifdef USCXML_VERBOSE + std::cerr << "Complete Exit: "; + printStateNames(exitSet); +#endif + + exitSet &= _configuration; + + if (_flags & USCXML_CTX_TRANSITION_FOUND) { + // trigger more sppontaneuous transitions + _flags |= USCXML_CTX_SPONTANEOUS; + _flags &= ~USCXML_CTX_TRANSITION_FOUND; + } else { + // spontaneuous transitions are exhausted + _flags &= ~USCXML_CTX_SPONTANEOUS; + return USCXML_MACROSTEPPED; + } + + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), beforeMicroStep); + +#ifdef USCXML_VERBOSE + std::cerr << "Targets: "; + printStateNames(targetSet); +#endif + +#ifdef USCXML_VERBOSE + std::cerr << "Exiting: "; + printStateNames(exitSet); +#endif + +#ifdef USCXML_VERBOSE + std::cerr << "History: "; + printStateNames(_history); +#endif + + + /* REMEMBER_HISTORY: */ + for (i = 0; i < _states.size(); i++) { + if unlikely(USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_HISTORY_SHALLOW || + USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_HISTORY_DEEP) { + /* a history state whose parent is about to be exited */ + if unlikely(BIT_HAS(USCXML_GET_STATE(i).parent, exitSet)) { + tmpStates = USCXML_GET_STATE(i).completion; + + /* set those states who were enabled */ + tmpStates &= _configuration; + + /* clear current history with completion mask */ + _history &= ~(USCXML_GET_STATE(i).completion); + + /* set history */ + _history |= tmpStates; + + } + } + } + +ESTABLISH_ENTRYSET: + /* calculate new entry set */ + entrySet = targetSet; + + /* iterate for ancestors */ + i = entrySet.find_first(); + while(i != boost::dynamic_bitset<>::npos) { + entrySet |= USCXML_GET_STATE(i).ancestors; + i = entrySet.find_next(i); + } + + /* iterate for descendants */ + i = entrySet.find_first(); + while(i != boost::dynamic_bitset<>::npos) { + + + switch (USCXML_STATE_MASK(USCXML_GET_STATE(i).type)) { + case USCXML_STATE_FINAL: + case USCXML_STATE_ATOMIC: + break; + + case USCXML_STATE_PARALLEL: { + entrySet |= USCXML_GET_STATE(i).completion; + break; + } + + case USCXML_STATE_HISTORY_SHALLOW: + case USCXML_STATE_HISTORY_DEEP: { + if (!BIT_HAS_AND(USCXML_GET_STATE(i).completion, _history) && + !BIT_HAS(USCXML_GET_STATE(i).parent, _configuration)) { + + /* nothing set for history, look for a default transition */ + for (j = 0; j < _transitions.size(); j++) { + if unlikely(USCXML_GET_TRANS(j).source == i) { + entrySet |= USCXML_GET_TRANS(j).target; + + if(USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_HISTORY_DEEP && + !BIT_HAS_AND(USCXML_GET_TRANS(j).target, USCXML_GET_STATE(i).children)) { + for (k = i + 1; k < _states.size(); k++) { + if (BIT_HAS(k, USCXML_GET_TRANS(j).target)) { + entrySet |= USCXML_GET_STATE(k).ancestors; + break; + } + } + } + BIT_SET_AT(j, transSet); + break; + } + /* Note: SCXML mandates every history to have a transition! */ + } + } else { + tmpStates = USCXML_GET_STATE(i).completion; + tmpStates &= _history; + entrySet |= tmpStates; + + if (USCXML_GET_STATE(i).type == (USCXML_STATE_HAS_HISTORY | USCXML_STATE_HISTORY_DEEP)) { + /* a deep history state with nested histories -> more completion */ + for (j = i + 1; j < USCXML_NUMBER_STATES; j++) { + if (BIT_HAS(j, USCXML_GET_STATE(i).completion) && + BIT_HAS(j, entrySet) && + (USCXML_GET_STATE(j).type & USCXML_STATE_HAS_HISTORY)) { + for (k = j + 1; k < USCXML_NUMBER_STATES; k++) { + /* add nested history to entry_set */ + if ((USCXML_STATE_MASK(USCXML_GET_STATE(k).type) == USCXML_STATE_HISTORY_DEEP || + USCXML_STATE_MASK(USCXML_GET_STATE(k).type) == USCXML_STATE_HISTORY_SHALLOW) && + BIT_HAS(k, USCXML_GET_STATE(j).children)) { + /* a nested history state */ + BIT_SET_AT(k, entrySet); + } + } + } + } + } + } + break; + } + + case USCXML_STATE_INITIAL: { + for (j = 0; j < USCXML_NUMBER_TRANS; j++) { + if (USCXML_GET_TRANS(j).source == i) { + BIT_SET_AT(j, transSet); + BIT_CLEAR(i, entrySet); + entrySet |= USCXML_GET_TRANS(j).target; + for (k = i + 1; k < USCXML_NUMBER_STATES; k++) { + if (BIT_HAS(k, USCXML_GET_TRANS(j).target)) { + entrySet |= USCXML_GET_STATE(k).ancestors; + } + } + } + } + break; + } + case USCXML_STATE_COMPOUND: { /* we need to check whether one child is already in entry_set */ + if (!BIT_HAS_AND(entrySet, USCXML_GET_STATE(i).children) && + (!BIT_HAS_AND(_configuration, USCXML_GET_STATE(i).children) || + BIT_HAS_AND(exitSet, USCXML_GET_STATE(i).children))) { + entrySet |= USCXML_GET_STATE(i).completion; + if (!BIT_HAS_AND(USCXML_GET_STATE(i).completion, USCXML_GET_STATE(i).children)) { + /* deep completion */ + for (j = i + 1; j < USCXML_NUMBER_STATES; j++) { + if (BIT_HAS(j, USCXML_GET_STATE(i).completion)) { + entrySet |= USCXML_GET_STATE(j).ancestors; + break; /* completion of compound is single state */ + } + } + } + } + break; + } + } + i = entrySet.find_next(i); + + } + + +#ifdef USCXML_VERBOSE + std::cerr << "Transitions: " << transSet << std::endl; +#endif + + /* EXIT_STATES: */ + /* we cannot use find_first due to ordering */ + i = USCXML_NUMBER_STATES; + while(i-- > 0) { + if (BIT_HAS(i, exitSet) && BIT_HAS(i, _configuration)) { + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeExitingState, USCXML_GET_STATE(i).element); + + /* call all on exit handlers */ + for (auto exitIter = USCXML_GET_STATE(i).onExit.begin(); exitIter != USCXML_GET_STATE(i).onExit.end(); exitIter++) { + try { + _callbacks->process(*exitIter); + } catch (...) { + // do nothing and continue with next block + } + } + BIT_CLEAR(i, _configuration); + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), afterExitingState, USCXML_GET_STATE(i).element); + + } + } + + /* TAKE_TRANSITIONS: */ + i = transSet.find_first(); + while(i != boost::dynamic_bitset<>::npos) { + if ((USCXML_GET_TRANS(i).type & (USCXML_TRANS_HISTORY | USCXML_TRANS_INITIAL)) == 0) { + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeTakingTransition, USCXML_GET_TRANS(i).element); + + if (USCXML_GET_TRANS(i).onTrans != NULL) { + + /* call executable content in non-history, non-initial transition */ + try { + _callbacks->process(USCXML_GET_TRANS(i).onTrans); + } catch (...) { + // do nothing and continue with next block + } + } + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), afterTakingTransition, USCXML_GET_TRANS(i).element); + + } + i = transSet.find_next(i); + } + +#ifdef USCXML_VERBOSE + std::cerr << "Entering: "; + printStateNames(entrySet); +#endif + + + /* ENTER_STATES: */ + i = entrySet.find_first(); + while(i != boost::dynamic_bitset<>::npos) { + + if (BIT_HAS(i, _configuration)) { + // already active + i = entrySet.find_next(i); + continue; + } + + /* these are no proper states */ + if unlikely(USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_HISTORY_DEEP || + USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_HISTORY_SHALLOW || + USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_INITIAL) { + i = entrySet.find_next(i); + continue; + } + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeEnteringState, USCXML_GET_STATE(i).element); + + BIT_SET_AT(i, _configuration); + + /* initialize data */ + if (!BIT_HAS(i, _initializedData)) { + for (auto dataIter = USCXML_GET_STATE(i).data.begin(); dataIter != USCXML_GET_STATE(i).data.end(); dataIter++) { + _callbacks->initData(*dataIter); + } + BIT_SET_AT(i, _initializedData); + } + + /* call all on entry handlers */ + for (auto entryIter = USCXML_GET_STATE(i).onEntry.begin(); entryIter != USCXML_GET_STATE(i).onEntry.end(); entryIter++) { + try { + _callbacks->process(*entryIter); + } catch (...) { + // do nothing and continue with next block + } + } + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), afterEnteringState, USCXML_GET_STATE(i).element); + + /* take history and initial transitions */ + for (j = 0; j < USCXML_NUMBER_TRANS; j++) { + if unlikely(BIT_HAS(j, transSet) && + (USCXML_GET_TRANS(j).type & (USCXML_TRANS_HISTORY | USCXML_TRANS_INITIAL)) && + USCXML_GET_STATE(USCXML_GET_TRANS(j).source).parent == i) { + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), beforeTakingTransition, USCXML_GET_TRANS(j).element); + + /* call executable content in transition */ + if (USCXML_GET_TRANS(j).onTrans != NULL) { + try { + _callbacks->process(USCXML_GET_TRANS(j).onTrans); + } catch (...) { + // do nothing and continue with next block + } + } + + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), afterTakingTransition, USCXML_GET_TRANS(j).element); + + } + } + + /* handle final states */ + if unlikely(USCXML_STATE_MASK(USCXML_GET_STATE(i).type) == USCXML_STATE_FINAL) { + if unlikely(USCXML_GET_STATE(i).ancestors.count() == 1 && BIT_HAS(0, USCXML_GET_STATE(i).ancestors)) { + // only the topmost scxml is an ancestor + _flags |= USCXML_CTX_TOP_LEVEL_FINAL; + } else { + /* raise done event */ + _callbacks->raiseDoneEvent(USCXML_GET_STATE(USCXML_GET_STATE(i).parent).element, USCXML_GET_STATE(i).doneData); + } + + /** + * are we the last final state to leave a parallel state?: + * 1. Gather all parallel states in our ancestor chain + * 2. Find all states for which these parallels are ancestors + * 3. Iterate all active final states and remove their ancestors + * 4. If a state remains, not all children of a parallel are final + */ + for (j = 0; j < USCXML_NUMBER_STATES; j++) { + if unlikely(USCXML_STATE_MASK(USCXML_GET_STATE(j).type) == USCXML_STATE_PARALLEL && + BIT_HAS(j, USCXML_GET_STATE(i).ancestors)) { + tmpStates.reset(); + k = _configuration.find_first(); + while (k != boost::dynamic_bitset<>::npos) { + if (BIT_HAS(j, USCXML_GET_STATE(k).ancestors)) { + if (USCXML_STATE_MASK(USCXML_GET_STATE(k).type) == USCXML_STATE_FINAL) { + tmpStates ^= USCXML_GET_STATE(k).ancestors; + } else { + BIT_SET_AT(k, tmpStates); + } + } + k = _configuration.find_next(k); + } + if (!tmpStates.any()) { + // raise done for state j + _callbacks->raiseDoneEvent(USCXML_GET_STATE(j).element, USCXML_GET_STATE(j).doneData); + } + } + } + } + } + USCXML_MONITOR_CALLBACK(_callbacks->getMonitor(), afterMicroStep); + + // are we running in circles? + if (_microstepConfigurations.find(_configuration) != _microstepConfigurations.end()) { + USCXML_MONITOR_CALLBACK1(_callbacks->getMonitor(), + reportIssue, + InterpreterIssue("Reentering same configuration during microstep - possible endless loop", + NULL, + InterpreterIssue::USCXML_ISSUE_WARNING)); + } + _microstepConfigurations.insert(_configuration); + + return USCXML_MICROSTEPPED; +} + +void FastMicroStep::reset() { + _isCancelled = false; + _flags = USCXML_CTX_PRISTINE; + _configuration.reset(); + _history.reset(); + _initializedData.reset(); + _invocations.reset(); + +} + +bool FastMicroStep::isInState(const std::string& stateId) { +#ifdef USCXML_VERBOSE + printStateNames(_configuration); +#endif + if (_stateIds.find(stateId) == _stateIds.end()) + return false; + return _configuration[_stateIds[stateId]]; +} + +std::list<XERCESC_NS::DOMElement*> FastMicroStep::getConfiguration() { + std::list<XERCESC_NS::DOMElement*> config; + size_t i = _configuration.find_first(); + while(i != boost::dynamic_bitset<>::npos) { + config.push_back(_states[i]->element); + i = _configuration.find_next(i); + } + return config; +} + + +std::list<DOMElement*> FastMicroStep::getHistoryCompletion(const DOMElement* history) { + std::set<std::string> elements; + elements.insert(_xmlPrefix.str() + "history"); + std::list<DOMElement*> histories = DOMUtils::inPostFixOrder(elements, _scxml); + + std::list<DOMElement*> covered; + std::list<DOMElement*> perParentcovered; + const DOMNode* parent = NULL; + + std::list<DOMElement*> completion; + + + if (parent != history->getParentNode()) { + covered.insert(covered.end(), perParentcovered.begin(), perParentcovered.end()); + perParentcovered.clear(); + parent = history->getParentNode(); + } + + bool deep = (HAS_ATTR(history, "type") && iequals(ATTR(history, "type"), "deep")); + + for (size_t j = 0; j < _states.size(); j++) { + if (_states[j]->element == history) + continue; + + if (DOMUtils::isDescendant((DOMNode*)_states[j]->element, history->getParentNode()) && isHistory(_states[j]->element)) { + ((DOMElement*)history)->setUserData(X("hasHistoryChild"), _states[j], NULL); + } + + if (DOMUtils::isMember(_states[j]->element, covered)) + continue; + + if (deep) { + if (DOMUtils::isDescendant(_states[j]->element, history->getParentNode()) && !isHistory(_states[j]->element)) { + completion.push_back(_states[j]->element); + } + } else { + if (_states[j]->element->getParentNode() == history->getParentNode() && !isHistory(_states[j]->element)) { + completion.push_back(_states[j]->element); + } + } + } + + return completion; +} + +#ifdef USCXML_VERBOSE +/** + * Print name of states contained in a (debugging). + */ +void FastMicroStep::printStateNames(const boost::dynamic_bitset<>& a) { + size_t i; + const char* seperator = ""; + for (i = 0; i < a.size(); i++) { + if (BIT_HAS(i, a)) { + std::cerr << seperator << (HAS_ATTR(USCXML_GET_STATE(i).element, X("id")) ? ATTR(USCXML_GET_STATE(i).element, X("id")) : "UNK"); + seperator = ", "; + } + } + std::cerr << std::endl; +} +#endif + +std::list<DOMElement*> FastMicroStep::getCompletion(const DOMElement* state) { + + if (isHistory(state)) { + // we already did in setHistoryCompletion + return getHistoryCompletion(state); + + } else if (isParallel(state)) { + return getChildStates(state); + + } else if (HAS_ATTR(state, "initial")) { + return getStates(tokenize(ATTR(state, "initial")), _scxml); + + } else { + std::list<DOMElement*> completion; + + std::list<DOMElement*> initElems = DOMUtils::filterChildElements(_xmlPrefix.str() + "initial", state); + if(initElems.size() > 0) { + // initial element is first child + completion.push_back(initElems.front()); + } else { + // first child state + DOMNodeList* children = state->getChildNodes(); + for (size_t i = 0; i < children->getLength(); i++) { + if (children->item(i)->getNodeType() != DOMNode::ELEMENT_NODE) + continue; + if (isState(dynamic_cast<DOMElement*>(children->item(i)))) { + completion.push_back(dynamic_cast<DOMElement*>(children->item(i))); + break; + } + } + } + return completion; + } + +} + + +#if 0 +/** + * See: http://www.w3.org/TR/scxml/#LegalStateConfigurations + */ +bool FastMicroStep::hasLegalConfiguration() { + + // The configuration contains exactly one child of the <scxml> element. + std::list<DOMElement*> scxmlChilds = getChildStates(_scxml, true); + DOMNode* foundScxmlChild; + for (auto sIter = scxmlChilds.begin(); sIter != scxmlChilds.end(); sIter++) { + DOMElement* state = *sIter; + if (isMember(state, config)) { + if (foundScxmlChild) { + LOG(ERROR) << "Invalid configuration: Multiple childs of scxml root are active '" << ATTR_CAST(foundScxmlChild, "id") << "' and '" << ATTR_CAST(scxmlChilds[i], "id") << "'"; + return false; + } + foundScxmlChild = scxmlChilds[i]; + } + } + if (!foundScxmlChild) { + LOG(ERROR) << "Invalid configuration: No childs of scxml root are active"; + + return false; + } + + // The configuration contains one or more atomic states. + bool foundAtomicState = false; + for (size_t i = 0; i < config.size(); i++) { + if (isAtomic(Element<std::string>(config[i]))) { + foundAtomicState = true; + break; + } + } + if (!foundAtomicState) { + LOG(ERROR) << "Invalid configuration: No atomic state is active"; + return false; + } + + // the configuration contains no history pseudo-states + for (size_t i = 0; i < config.size(); i++) { + if (isHistory(Element<std::string>(config[i]))) { + LOG(ERROR) << "Invalid configuration: history state " << ATTR_CAST(config[i], "id") << " is active"; + return false; + } + } + + + + // When the configuration contains an atomic state, it contains all of its <state> and <parallel> ancestors. + for (size_t i = 0; i < config.size(); i++) { + if (isAtomic(Element<std::string>(config[i]))) { + Node<std::string> parent = config[i]; + while(((parent = parent.getParentNode()) && parent.getNodeType() == Node_base::ELEMENT_NODE)) { + if (isState(Element<std::string>(parent)) && + (iequals(LOCALNAME(parent), "state") || + iequals(LOCALNAME(parent), "parallel"))) { + if (!isMember(parent, config)) { + LOG(ERROR) << "Invalid configuration: atomic state '" << ATTR_CAST(config[i], "id") << "' is active, but parent '" << ATTR_CAST(parent, "id") << "' is not"; + return false; + } + } + } + } + } + + // When the configuration contains a non-atomic <state>, it contains one and only one of the state's children + for (size_t i = 0; i < config.size(); i++) { + Element<std::string> configElem(config[i]); + if (!isAtomic(configElem) && !isParallel(configElem)) { + Node<std::string> foundChildState; + //std::cerr << config[i] << std::endl; + NodeSet<std::string> childs = getChildStates(config[i]); + for (size_t j = 0; j < childs.size(); j++) { + //std::cerr << childs[j] << std::endl; + if (isMember(childs[j], config)) { + if (foundChildState) { + LOG(ERROR) << "Invalid configuration: Multiple childs of compound '" << ATTR_CAST(config[i], "id") + << "' are active '" << ATTR_CAST(foundChildState, "id") << "' and '" << ATTR_CAST(childs[j], "id") << "'"; + return false; + } + foundChildState = childs[j]; + } + } + if (!foundChildState) { + LOG(ERROR) << "Invalid configuration: No childs of compound '" << ATTR_CAST(config[i], "id") << "' are active"; + return false; + } + } + } + + // If the configuration contains a <parallel> state, it contains all of its children + for (size_t i = 0; i < config.size(); i++) { + if (isParallel(Element<std::string>(config[i]))) { + NodeSet<std::string> childs = getChildStates(config[i]); + for (size_t j = 0; j < childs.size(); j++) { + if (!isMember(childs[j], config) && !isHistory(Element<std::string>(childs[j]))) { + LOG(ERROR) << "Invalid configuration: Not all children of parallel '" << ATTR_CAST(config[i], "id") << "' are active i.e. '" << ATTR_CAST(childs[j], "id") << "' is not"; + return false; + } + } + } + } + + // everything worked out fine! + return true; +} +#endif + +}
\ No newline at end of file |