From bd15b23987e9d6d1f42fa8987e5c1ff5a1eeee8b Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Fri, 20 Mar 2015 14:20:16 +0100 Subject: QStateMachine: cache expensive calculations. As nothing changes in the state machine when selecting transitions for events and then calculating the exit- and entry-sets, some calculations can be cached. The exit set for a transition was calculated multiple times. First in removeConflictingTransitions, where the two loops would each calculate them multiple times. Then secondly in microstep(), which would calculate the exit set for all transitions. Transition selection, exit set calculation, and entry set calculation all calculate the transition domain and effective target states for transitions. Change-Id: I217328a73db2f71e371eb5f60a0c7b222303f0ca Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/corelib/statemachine/qstatemachine.cpp | 243 +++++++++++++++++++++++------ src/corelib/statemachine/qstatemachine_p.h | 17 +- 2 files changed, 205 insertions(+), 55 deletions(-) (limited to 'src/corelib/statemachine') diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp index f02e27330d..d91b4ba14a 100644 --- a/src/corelib/statemachine/qstatemachine.cpp +++ b/src/corelib/statemachine/qstatemachine.cpp @@ -177,6 +177,100 @@ QT_BEGIN_NAMESPACE // #define QSTATEMACHINE_DEBUG // #define QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG +struct CalculationCache { + struct TransitionInfo { + QList effectiveTargetStates; + QSet exitSet; + QAbstractState *transitionDomain; + + bool effectiveTargetStatesIsKnown: 1; + bool exitSetIsKnown : 1; + bool transitionDomainIsKnown : 1; + + TransitionInfo() + : transitionDomain(0) + , effectiveTargetStatesIsKnown(false) + , exitSetIsKnown(false) + , transitionDomainIsKnown(false) + {} + }; + + typedef QHash TransitionInfoCache; + TransitionInfoCache cache; + + bool effectiveTargetStates(QAbstractTransition *t, QList *targets) const + { + Q_ASSERT(targets); + + TransitionInfoCache::const_iterator cacheIt = cache.find(t); + if (cacheIt == cache.end() || !cacheIt->effectiveTargetStatesIsKnown) + return false; + + *targets = cacheIt->effectiveTargetStates; + return true; + } + + void insert(QAbstractTransition *t, const QList &targets) + { + TransitionInfoCache::iterator cacheIt = cache.find(t); + TransitionInfo &ti = cacheIt == cache.end() + ? *cache.insert(t, TransitionInfo()) + : *cacheIt; + + Q_ASSERT(!ti.effectiveTargetStatesIsKnown); + ti.effectiveTargetStates = targets; + ti.effectiveTargetStatesIsKnown = true; + } + + bool exitSet(QAbstractTransition *t, QSet *exits) const + { + Q_ASSERT(exits); + + TransitionInfoCache::const_iterator cacheIt = cache.find(t); + if (cacheIt == cache.end() || !cacheIt->exitSetIsKnown) + return false; + + *exits = cacheIt->exitSet; + return true; + } + + void insert(QAbstractTransition *t, const QSet &exits) + { + TransitionInfoCache::iterator cacheIt = cache.find(t); + TransitionInfo &ti = cacheIt == cache.end() + ? *cache.insert(t, TransitionInfo()) + : *cacheIt; + + Q_ASSERT(!ti.exitSetIsKnown); + ti.exitSet = exits; + ti.exitSetIsKnown = true; + } + + bool transitionDomain(QAbstractTransition *t, QAbstractState **domain) const + { + Q_ASSERT(domain); + + TransitionInfoCache::const_iterator cacheIt = cache.find(t); + if (cacheIt == cache.end() || !cacheIt->transitionDomainIsKnown) + return false; + + *domain = cacheIt->transitionDomain; + return true; + } + + void insert(QAbstractTransition *t, QAbstractState *domain) + { + TransitionInfoCache::iterator cacheIt = cache.find(t); + TransitionInfo &ti = cacheIt == cache.end() + ? *cache.insert(t, TransitionInfo()) + : *cacheIt; + + Q_ASSERT(!ti.transitionDomainIsKnown); + ti.transitionDomain = domain; + ti.transitionDomainIsKnown = true; + } +}; + /* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : function isDescendant(state1, state2) @@ -245,8 +339,14 @@ function getEffectiveTargetStates(transition) targets.add(s) return targets */ -static QSet getEffectiveTargetStates(QAbstractTransition *transition) +static QList getEffectiveTargetStates(QAbstractTransition *transition, CalculationCache *cache) { + Q_ASSERT(cache); + + QList targetsList; + if (cache->effectiveTargetStates(transition, &targetsList)) + return targetsList; + QSet targets; foreach (QAbstractState *s, transition->targetStates()) { if (QHistoryState *historyState = QStateMachinePrivate::toHistoryState(s)) { @@ -266,7 +366,10 @@ static QSet getEffectiveTargetStates(QAbstractTransition *tran targets.insert(s); } } - return targets; + + targetsList = targets.toList(); + cache->insert(transition, targetsList); + return targetsList; } template @@ -417,8 +520,9 @@ QState *QStateMachinePrivate::findLCCA(const QList &states) con return findLCA(states, true); } -QList QStateMachinePrivate::selectTransitions(QEvent *event) +QList QStateMachinePrivate::selectTransitions(QEvent *event, CalculationCache *cache) { + Q_ASSERT(cache); Q_Q(const QStateMachine); QVarLengthArray configuration_sorted; @@ -453,7 +557,7 @@ QList QStateMachinePrivate::selectTransitions(QEvent *even } if (!enabledTransitions.isEmpty()) { - removeConflictingTransitions(enabledTransitions); + removeConflictingTransitions(enabledTransitions, cache); #ifdef QSTATEMACHINE_DEBUG qDebug() << q << ": enabled transitions after removing conflicts:" << enabledTransitions; #endif @@ -486,15 +590,20 @@ function removeConflictingTransitions(enabledTransitions): Note: the implementation below does not build the transitionsToRemove, but removes them in-place. */ -void QStateMachinePrivate::removeConflictingTransitions(QList &enabledTransitions) +void QStateMachinePrivate::removeConflictingTransitions(QList &enabledTransitions, CalculationCache *cache) { + Q_ASSERT(cache); + + if (enabledTransitions.size() == 1) + return; // There is no transition to conflict with. + QList filteredTransitions; filteredTransitions.reserve(enabledTransitions.size()); std::sort(enabledTransitions.begin(), enabledTransitions.end(), transitionStateEntryLessThan); foreach (QAbstractTransition *t1, enabledTransitions) { bool t1Preempted = false; - QSet exitSetT1 = computeExitSet_Unordered(QList() << t1); + QSet exitSetT1 = computeExitSet_Unordered(t1, cache); QList::iterator t2It = filteredTransitions.begin(); while (t2It != filteredTransitions.end()) { QAbstractTransition *t2 = *t2It; @@ -505,7 +614,7 @@ void QStateMachinePrivate::removeConflictingTransitions(QList exitSetT2 = computeExitSet_Unordered(QList() << t2); + QSet exitSetT2 = computeExitSet_Unordered(t2, cache); if (exitSetT1.intersect(exitSetT2).isEmpty()) { // No conflict, no cry. Next patient please. ++t2It; @@ -529,17 +638,20 @@ void QStateMachinePrivate::removeConflictingTransitions(QList &enabledTransitions) +void QStateMachinePrivate::microstep(QEvent *event, const QList &enabledTransitions, + CalculationCache *cache) { + Q_ASSERT(cache); + #ifdef QSTATEMACHINE_DEBUG qDebug() << q_func() << ": begin microstep( enabledTransitions:" << enabledTransitions << ')'; qDebug() << q_func() << ": configuration before exiting states:" << configuration; #endif - QList exitedStates = computeExitSet(enabledTransitions); + QList exitedStates = computeExitSet(enabledTransitions, cache); QHash pendingRestorables = computePendingRestorables(exitedStates); QSet statesForDefaultEntry; - QList enteredStates = computeEntrySet(enabledTransitions, statesForDefaultEntry); + QList enteredStates = computeEntrySet(enabledTransitions, statesForDefaultEntry, cache); #ifdef QSTATEMACHINE_DEBUG qDebug() << q_func() << ": computed exit set:" << exitedStates; @@ -598,42 +710,61 @@ function computeExitSet(transitions) statesToExit.add(s) return statesToExit */ -QList QStateMachinePrivate::computeExitSet(const QList &enabledTransitions) +QList QStateMachinePrivate::computeExitSet(const QList &enabledTransitions, + CalculationCache *cache) { - QList statesToExit_sorted = computeExitSet_Unordered(enabledTransitions).toList(); + Q_ASSERT(cache); + + QList statesToExit_sorted = computeExitSet_Unordered(enabledTransitions, cache).toList(); std::sort(statesToExit_sorted.begin(), statesToExit_sorted.end(), stateExitLessThan); return statesToExit_sorted; } -QSet QStateMachinePrivate::computeExitSet_Unordered(const QList &enabledTransitions) +QSet QStateMachinePrivate::computeExitSet_Unordered(const QList &enabledTransitions, + CalculationCache *cache) { + Q_ASSERT(cache); + QSet statesToExit; - for (int i = 0; i < enabledTransitions.size(); ++i) { - QAbstractTransition *t = enabledTransitions.at(i); - QList effectiveTargetStates = getEffectiveTargetStates(t).toList(); - QAbstractState *domain = getTransitionDomain(t, effectiveTargetStates); - if (domain == Q_NULLPTR && !t->targetStates().isEmpty()) { - // So we didn't find the least common ancestor for the source and target states of the - // transition. If there were not target states, that would be fine: then the transition - // will fire any events or signals, but not exit the state. - // - // However, there are target states, so it's either a node without a parent (or parent's - // parent, etc), or the state belongs to a different state machine. Either way, this - // makes the state machine invalid. - if (error == QStateMachine::NoError) - setError(QStateMachine::NoCommonAncestorForTransitionError, t->sourceState()); - QList lst = pendingErrorStates.toList(); - lst.prepend(t->sourceState()); - - domain = findLCCA(lst); - Q_ASSERT(domain != 0); - } + foreach (QAbstractTransition *t, enabledTransitions) + statesToExit.unite(computeExitSet_Unordered(t, cache)); + return statesToExit; +} - foreach (QAbstractState* s, configuration) { - if (isDescendant(s, domain)) - statesToExit.insert(s); - } - } +QSet QStateMachinePrivate::computeExitSet_Unordered(QAbstractTransition *t, + CalculationCache *cache) +{ + Q_ASSERT(cache); + + QSet statesToExit; + if (cache->exitSet(t, &statesToExit)) + return statesToExit; + + QList effectiveTargetStates = getEffectiveTargetStates(t, cache); + QAbstractState *domain = getTransitionDomain(t, effectiveTargetStates, cache); + if (domain == Q_NULLPTR && !t->targetStates().isEmpty()) { + // So we didn't find the least common ancestor for the source and target states of the + // transition. If there were not target states, that would be fine: then the transition + // will fire any events or signals, but not exit the state. + // + // However, there are target states, so it's either a node without a parent (or parent's + // parent, etc), or the state belongs to a different state machine. Either way, this + // makes the state machine invalid. + if (error == QStateMachine::NoError) + setError(QStateMachine::NoCommonAncestorForTransitionError, t->sourceState()); + QList lst = pendingErrorStates.toList(); + lst.prepend(t->sourceState()); + + domain = findLCCA(lst); + Q_ASSERT(domain != 0); + } + + foreach (QAbstractState* s, configuration) { + if (isDescendant(s, domain)) + statesToExit.insert(s); + } + + cache->insert(t, statesToExit); return statesToExit; } @@ -695,8 +826,11 @@ void QStateMachinePrivate::executeTransitionContent(QEvent *event, const QList QStateMachinePrivate::computeEntrySet(const QList &enabledTransitions, - QSet &statesForDefaultEntry) + QSet &statesForDefaultEntry, + CalculationCache *cache) { + Q_ASSERT(cache); + QSet statesToEnter; if (pendingErrorStates.isEmpty()) { foreach (QAbstractTransition *t, enabledTransitions) { @@ -704,8 +838,8 @@ QList QStateMachinePrivate::computeEntrySet(const QList effectiveTargetStates = getEffectiveTargetStates(t).toList(); - QAbstractState *ancestor = getTransitionDomain(t, effectiveTargetStates); + QList effectiveTargetStates = getEffectiveTargetStates(t, cache); + QAbstractState *ancestor = getTransitionDomain(t, effectiveTargetStates, cache); foreach (QAbstractState *s, effectiveTargetStates) { addAncestorStatesToEnter(s, ancestor, statesToEnter, statesForDefaultEntry); } @@ -742,11 +876,19 @@ function getTransitionDomain(t) else: return findLCCA([t.source].append(tstates)) */ -QAbstractState *QStateMachinePrivate::getTransitionDomain(QAbstractTransition *t, const QList &effectiveTargetStates) const +QAbstractState *QStateMachinePrivate::getTransitionDomain(QAbstractTransition *t, + const QList &effectiveTargetStates, + CalculationCache *cache) const { + Q_ASSERT(cache); + if (effectiveTargetStates.isEmpty()) return 0; + QAbstractState *domain = Q_NULLPTR; + if (cache->transitionDomain(t, &domain)) + return domain; + #if 0 // Qt only has external transitions, so skip the special case for the internal transitions if (QState *tSource = t->sourceState()) { @@ -768,7 +910,9 @@ QAbstractState *QStateMachinePrivate::getTransitionDomain(QAbstractTransition *t QList states(effectiveTargetStates); if (QAbstractState *src = t->sourceState()) states.prepend(src); - return findLCCA(states); + domain = findLCCA(states); + cache->insert(t, domain); + return domain; } void QStateMachinePrivate::enterStates(QEvent *event, const QList &exitedStates_sorted, @@ -1683,6 +1827,7 @@ void QStateMachinePrivate::_q_start() processingScheduled = true; // we call _q_process() below QList transitions; + CalculationCache calculationCache; QAbstractTransition *initialTransition = createInitialTransition(); transitions.append(initialTransition); @@ -1690,7 +1835,7 @@ void QStateMachinePrivate::_q_start() executeTransitionContent(&nullEvent, transitions); QList exitedStates = QList(); QSet statesForDefaultEntry; - QList enteredStates = computeEntrySet(transitions, statesForDefaultEntry); + QList enteredStates = computeEntrySet(transitions, statesForDefaultEntry, &calculationCache); QHash pendingRestorables; QHash > assignmentsForEnteredStates = computePropertyAssignments(enteredStates, pendingRestorables); @@ -1743,8 +1888,10 @@ void QStateMachinePrivate::_q_process() break; } QList enabledTransitions; + CalculationCache calculationCache; + QEvent *e = new QEvent(QEvent::None); - enabledTransitions = selectTransitions(e); + enabledTransitions = selectTransitions(e, &calculationCache); if (enabledTransitions.isEmpty()) { delete e; e = 0; @@ -1753,7 +1900,7 @@ void QStateMachinePrivate::_q_process() #ifdef QSTATEMACHINE_DEBUG qDebug() << q << ": dequeued internal event" << e << "of type" << e->type(); #endif - enabledTransitions = selectTransitions(e); + enabledTransitions = selectTransitions(e, &calculationCache); if (enabledTransitions.isEmpty()) { delete e; e = 0; @@ -1764,7 +1911,7 @@ void QStateMachinePrivate::_q_process() #ifdef QSTATEMACHINE_DEBUG qDebug() << q << ": dequeued external event" << e << "of type" << e->type(); #endif - enabledTransitions = selectTransitions(e); + enabledTransitions = selectTransitions(e, &calculationCache); if (enabledTransitions.isEmpty()) { delete e; e = 0; @@ -1778,7 +1925,7 @@ void QStateMachinePrivate::_q_process() } if (!enabledTransitions.isEmpty()) { q->beginMicrostep(e); - microstep(e, enabledTransitions); + microstep(e, enabledTransitions, &calculationCache); q->endMicrostep(e); } #ifdef QSTATEMACHINE_DEBUG diff --git a/src/corelib/statemachine/qstatemachine_p.h b/src/corelib/statemachine/qstatemachine_p.h index a88f95f1f5..28fd96f507 100644 --- a/src/corelib/statemachine/qstatemachine_p.h +++ b/src/corelib/statemachine/qstatemachine_p.h @@ -75,6 +75,7 @@ class QState; class QAbstractAnimation; #endif +struct CalculationCache; class QStateMachine; class Q_CORE_EXPORT QStateMachinePrivate : public QStatePrivate { @@ -124,13 +125,14 @@ public: void clearHistory(); QAbstractTransition *createInitialTransition() const; - void removeConflictingTransitions(QList &enabledTransitions); - void microstep(QEvent *event, const QList &transitionList); - QList selectTransitions(QEvent *event); + void removeConflictingTransitions(QList &enabledTransitions, CalculationCache *cache); + void microstep(QEvent *event, const QList &transitionList, CalculationCache *cache); + QList selectTransitions(QEvent *event, CalculationCache *cache); void exitStates(QEvent *event, const QList &statesToExit_sorted, const QHash > &assignmentsForEnteredStates); - QList computeExitSet(const QList &enabledTransitions); - QSet computeExitSet_Unordered(const QList &enabledTransitions); + QList computeExitSet(const QList &enabledTransitions, CalculationCache *cache); + QSet computeExitSet_Unordered(const QList &enabledTransitions, CalculationCache *cache); + QSet computeExitSet_Unordered(QAbstractTransition *t, CalculationCache *cache); void executeTransitionContent(QEvent *event, const QList &transitionList); void enterStates(QEvent *event, const QList &exitedStates_sorted, const QList &statesToEnter_sorted, @@ -141,9 +143,10 @@ public: #endif ); QList computeEntrySet(const QList &enabledTransitions, - QSet &statesForDefaultEntry); + QSet &statesForDefaultEntry, CalculationCache *cache); QAbstractState *getTransitionDomain(QAbstractTransition *t, - const QList &effectiveTargetStates) const; + const QList &effectiveTargetStates, + CalculationCache *cache) const; void addDescendantStatesToEnter(QAbstractState *state, QSet &statesToEnter, QSet &statesForDefaultEntry); -- cgit v1.2.3 From c07f5b801bd6a94fe862073eb1f1965115a56385 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Mon, 13 Apr 2015 13:54:20 +0200 Subject: QStateMachine: add internal transitions. The behavior of "external" and "internal" transitions is identical, except in the case of a transition whose source state is a compound state and whose target(s) is a descendant of the source. In such a case, an internal transition will not exit and re-enter its source state, while an external one will. [ChangeLog][State machine] Added support for internal transitions. Change-Id: I9efb1e7368ee52aa2544eb84709a00ae3d5350d3 Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/corelib/statemachine/qabstracttransition.cpp | 46 ++++++++++++++++++++++++ src/corelib/statemachine/qabstracttransition.h | 10 ++++++ src/corelib/statemachine/qabstracttransition_p.h | 1 + src/corelib/statemachine/qstatemachine.cpp | 25 +++++++------ 4 files changed, 69 insertions(+), 13 deletions(-) (limited to 'src/corelib/statemachine') diff --git a/src/corelib/statemachine/qabstracttransition.cpp b/src/corelib/statemachine/qabstracttransition.cpp index f128acd54e..81b38ea4c4 100644 --- a/src/corelib/statemachine/qabstracttransition.cpp +++ b/src/corelib/statemachine/qabstracttransition.cpp @@ -101,7 +101,35 @@ QT_BEGIN_NAMESPACE parallel group state. */ +/*! + \property QAbstractTransition::transitionType + + \brief indicates whether this transition is an internal transition, or an external transition. + + Internal and external transitions behave the same, except for the case of a transition whose + source state is a compound state and whose target(s) is a descendant of the source. In such a + case, an internal transition will not exit and re-enter its source state, while an external one + will. + + By default, the type is an external transition. +*/ + +/*! + \enum QAbstractTransition::TransitionType + + This enum specifies the kind of transition. By default, the type is an external transition. + + \value ExternalTransition Any state that is the source state of a transition (which is not a + target-less transition) is left, and re-entered when necessary. + \value InternalTransition If the target state of a transition is a sub-state of a compound state, + and that compound state is the source state, an internal transition will + not leave the source state. + + \sa QAbstractTransition::transitionType +*/ + QAbstractTransitionPrivate::QAbstractTransitionPrivate() + : transitionType(QAbstractTransition::ExternalTransition) { } @@ -248,6 +276,24 @@ void QAbstractTransition::setTargetStates(const QList &targets) emit targetStatesChanged(QPrivateSignal()); } +/*! + Returns the type of the transition. +*/ +QAbstractTransition::TransitionType QAbstractTransition::transitionType() const +{ + Q_D(const QAbstractTransition); + return d->transitionType; +} + +/*! + Sets the type of the transition to \a type. +*/ +void QAbstractTransition::setTransitionType(TransitionType type) +{ + Q_D(QAbstractTransition); + d->transitionType = type; +} + /*! Returns the state machine that this transition is part of, or 0 if the transition is not part of a state machine. diff --git a/src/corelib/statemachine/qabstracttransition.h b/src/corelib/statemachine/qabstracttransition.h index 768a364a4b..bf32b3e825 100644 --- a/src/corelib/statemachine/qabstracttransition.h +++ b/src/corelib/statemachine/qabstracttransition.h @@ -59,7 +59,14 @@ class Q_CORE_EXPORT QAbstractTransition : public QObject Q_PROPERTY(QState* sourceState READ sourceState) Q_PROPERTY(QAbstractState* targetState READ targetState WRITE setTargetState NOTIFY targetStateChanged) Q_PROPERTY(QList targetStates READ targetStates WRITE setTargetStates NOTIFY targetStatesChanged) + Q_PROPERTY(TransitionType transitionType READ transitionType WRITE setTransitionType) public: + enum TransitionType { + ExternalTransition, + InternalTransition + }; + Q_ENUM(TransitionType) + QAbstractTransition(QState *sourceState = 0); virtual ~QAbstractTransition(); @@ -69,6 +76,9 @@ public: QList targetStates() const; void setTargetStates(const QList &targets); + TransitionType transitionType() const; + void setTransitionType(TransitionType type); + QStateMachine *machine() const; #ifndef QT_NO_ANIMATION diff --git a/src/corelib/statemachine/qabstracttransition_p.h b/src/corelib/statemachine/qabstracttransition_p.h index d89d057497..4b0644acd9 100644 --- a/src/corelib/statemachine/qabstracttransition_p.h +++ b/src/corelib/statemachine/qabstracttransition_p.h @@ -73,6 +73,7 @@ public: void emitTriggered(); QList > targetStates; + QAbstractTransition::TransitionType transitionType; #ifndef QT_NO_ANIMATION QList animations; diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp index d91b4ba14a..6e36f93c40 100644 --- a/src/corelib/statemachine/qstatemachine.cpp +++ b/src/corelib/statemachine/qstatemachine.cpp @@ -889,23 +889,22 @@ QAbstractState *QStateMachinePrivate::getTransitionDomain(QAbstractTransition *t if (cache->transitionDomain(t, &domain)) return domain; -#if 0 - // Qt only has external transitions, so skip the special case for the internal transitions - if (QState *tSource = t->sourceState()) { - if (isCompound(tSource)) { - bool allDescendants = true; - foreach (QAbstractState *s, effectiveTargetStates) { - if (!isDescendant(s, tSource)) { - allDescendants = false; - break; + if (t->transitionType() == QAbstractTransition::InternalTransition) { + if (QState *tSource = t->sourceState()) { + if (isCompound(tSource)) { + bool allDescendants = true; + foreach (QAbstractState *s, effectiveTargetStates) { + if (!isDescendant(s, tSource)) { + allDescendants = false; + break; + } } - } - if (allDescendants) - return tSource; + if (allDescendants) + return tSource; + } } } -#endif QList states(effectiveTargetStates); if (QAbstractState *src = t->sourceState()) -- cgit v1.2.3 From cecd52b89ae6c58476c39079830908d22f52ef2d Mon Sep 17 00:00:00 2001 From: Fawzi Mohamed Date: Tue, 17 Feb 2015 17:37:32 +0100 Subject: qstatemachine: add methods detect when a machine has processed an event currently when adding an event it is not possible to know when processing it has finished. In particular if the event is ignored no method is called. Adding virtual methods to the private implementation (binary compatibility). These methods allow for extended automatic testing of the state machines. (cherry picked from commit e7feb956280105113b3e58f12e5f32f54199a95a) Change-Id: Iaa48fb9d7f6a6cde1a8a7a2bece7b4df55c147e8 Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/corelib/statemachine/qstatemachine.cpp | 50 ++++++++++++++++++++++++++++-- src/corelib/statemachine/qstatemachine_p.h | 4 +++ 2 files changed, 52 insertions(+), 2 deletions(-) (limited to 'src/corelib/statemachine') diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp index 6e36f93c40..7e9d99a416 100644 --- a/src/corelib/statemachine/qstatemachine.cpp +++ b/src/corelib/statemachine/qstatemachine.cpp @@ -1878,9 +1878,11 @@ void QStateMachinePrivate::_q_process() Q_ASSERT(!processing); processing = true; processingScheduled = false; + beginMacrostep(); #ifdef QSTATEMACHINE_DEBUG qDebug() << q << ": starting the event processing loop"; #endif + bool didChange = false; while (processing) { if (stop) { processing = false; @@ -1923,15 +1925,17 @@ void QStateMachinePrivate::_q_process() } } if (!enabledTransitions.isEmpty()) { + didChange = true; q->beginMicrostep(e); microstep(e, enabledTransitions, &calculationCache); q->endMicrostep(e); } -#ifdef QSTATEMACHINE_DEBUG else { + noMicrostep(); +#ifdef QSTATEMACHINE_DEBUG qDebug() << q << ": no transitions enabled"; - } #endif + } delete e; } #ifdef QSTATEMACHINE_DEBUG @@ -1944,6 +1948,7 @@ void QStateMachinePrivate::_q_process() switch (stopProcessingReason) { case EventQueueEmpty: + processedPendingEvents(didChange); break; case Finished: state = NotRunning; @@ -1960,6 +1965,7 @@ void QStateMachinePrivate::_q_process() emit q->runningChanged(false); break; } + endMacrostep(didChange); } void QStateMachinePrivate::_q_startDelayedEventTimer(int id, int delay) @@ -2081,6 +2087,46 @@ void QStateMachinePrivate::emitStateFinished(QState *forState, QFinalState *guil QStatePrivate::get(forState)->emitFinished(); } +/* + This function is called when the state machine is performing no + microstep because no transition is enabled (i.e. an event is ignored). + + The default implementation does nothing. +*/ +void QStateMachinePrivate::noMicrostep() +{ } + +/* + This function is called when the state machine has reached a stable + state (no pending events), and has not finished yet. + For each event the state machine receives it is guaranteed that + 1) beginMacrostep is called + 2) selectTransition is called at least once + 3) begin/endMicrostep is called at least once or noMicrostep is called + at least once (possibly both, but at least one) + 4) the state machine either enters an infinite loop, or stops (runningChanged(false), + and either finished or stopped are emitted), or processedPendingEvents() is called. + 5) if the machine is not in an infinite loop endMacrostep is called + + didChange is set to true if at least one microstep was performed, it is possible + that the machine returned to exactly the same state as before, but some transitions + were triggered. + + The default implementation does nothing. +*/ +void QStateMachinePrivate::processedPendingEvents(bool didChange) +{ + Q_UNUSED(didChange); +} + +void QStateMachinePrivate::beginMacrostep() +{ } + +void QStateMachinePrivate::endMacrostep(bool didChange) +{ + Q_UNUSED(didChange); +} + namespace _QStateMachine_Internal{ class GoToStateTransition : public QAbstractTransition diff --git a/src/corelib/statemachine/qstatemachine_p.h b/src/corelib/statemachine/qstatemachine_p.h index 28fd96f507..5584bc91ab 100644 --- a/src/corelib/statemachine/qstatemachine_p.h +++ b/src/corelib/statemachine/qstatemachine_p.h @@ -128,6 +128,10 @@ public: void removeConflictingTransitions(QList &enabledTransitions, CalculationCache *cache); void microstep(QEvent *event, const QList &transitionList, CalculationCache *cache); QList selectTransitions(QEvent *event, CalculationCache *cache); + virtual void noMicrostep(); + virtual void processedPendingEvents(bool didChange); + virtual void beginMacrostep(); + virtual void endMacrostep(bool didChange); void exitStates(QEvent *event, const QList &statesToExit_sorted, const QHash > &assignmentsForEnteredStates); QList computeExitSet(const QList &enabledTransitions, CalculationCache *cache); -- cgit v1.2.3 From eb4bf7df60a95028e33c721f249ab328738ac462 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Wed, 22 Apr 2015 12:24:16 +0200 Subject: QStateMachine: Fix transition ordering. When there are conflicting transitions, a transition that is nested deeper (i.e. more specific) has priority. If two transitions have the same nesting level, the one that comes first in the document order gets priority. Before this patch, only the document order was considered. Change-Id: I58f188c270cabe2c386a783ceef7a0a955105425 Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/corelib/statemachine/qstatemachine.cpp | 37 ++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) (limited to 'src/corelib/statemachine') diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp index 7e9d99a416..25c9343943 100644 --- a/src/corelib/statemachine/qstatemachine.cpp +++ b/src/corelib/statemachine/qstatemachine.cpp @@ -299,6 +299,17 @@ static bool containsDecendantOf(const QSet &states, const QAbs return false; } +static int descendantDepth(const QAbstractState *state, const QAbstractState *ancestor) +{ + int depth = 0; + for (const QAbstractState *it = state; it != 0; it = it->parentState()) { + if (it == ancestor) + break; + ++depth; + } + return depth; +} + /* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ : function getProperAncestors(state1, state2) @@ -451,10 +462,25 @@ static int indexOfDescendant(QState *s, QAbstractState *desc) bool QStateMachinePrivate::transitionStateEntryLessThan(QAbstractTransition *t1, QAbstractTransition *t2) { QState *s1 = t1->sourceState(), *s2 = t2->sourceState(); - if (s1 == s2) - return QStatePrivate::get(s1)->transitions().indexOf(t1) < QStatePrivate::get(s2)->transitions().indexOf(t2); - else - return stateEntryLessThan(t1->sourceState(), t2->sourceState()); + if (s1 == s2) { + QList transitions = QStatePrivate::get(s1)->transitions(); + return transitions.indexOf(t1) < transitions.indexOf(t2); + } else if (isDescendant(s1, s2)) { + return true; + } else if (isDescendant(s2, s1)) { + return false; + } else { + Q_ASSERT(s1->machine() != 0); + QStateMachinePrivate *mach = QStateMachinePrivate::get(s1->machine()); + QState *lca = mach->findLCA(QList() << s1 << s2); + Q_ASSERT(lca != 0); + int s1Depth = descendantDepth(s1, lca); + int s2Depth = descendantDepth(s2, lca); + if (s1Depth == s2Depth) + return (indexOfDescendant(lca, s1) < indexOfDescendant(lca, s2)); + else + return s1Depth > s2Depth; + } } bool QStateMachinePrivate::stateEntryLessThan(QAbstractState *s1, QAbstractState *s2) @@ -594,7 +620,7 @@ void QStateMachinePrivate::removeConflictingTransitions(QList filteredTransitions; @@ -2081,6 +2107,7 @@ void QStateMachinePrivate::emitStateFinished(QState *forState, QFinalState *guil Q_ASSERT(guiltyState); #ifdef QSTATEMACHINE_DEBUG + Q_Q(QStateMachine); qDebug() << q << ": emitting finished signal for" << forState; #endif -- cgit v1.2.3 From 92c2783f7761be6709042b54b7183d2276a7174a Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Wed, 22 Apr 2015 14:17:13 +0200 Subject: QStateMachine: allow posting of events when starting. This allows subclasses to submit any queued events that have to be handled before normal operation starts. For example, if an error event got generated during initialization which has to be handled by the state machine, the startup hook in the private class can be used to post those events and have the state machine handle them appropriately. Change-Id: I62249a31d8840f47bc19920870ad5da9647e61f9 Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/corelib/statemachine/qstatemachine.cpp | 14 ++++++++++++-- src/corelib/statemachine/qstatemachine_p.h | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'src/corelib/statemachine') diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp index 25c9343943..bea6822ecc 100644 --- a/src/corelib/statemachine/qstatemachine.cpp +++ b/src/corelib/statemachine/qstatemachine.cpp @@ -1845,6 +1845,8 @@ void QStateMachinePrivate::_q_start() registerMultiThreadedSignalTransitions(); + startupHook(); + #ifdef QSTATEMACHINE_DEBUG qDebug() << q << ": starting"; #endif @@ -2114,6 +2116,10 @@ void QStateMachinePrivate::emitStateFinished(QState *forState, QFinalState *guil QStatePrivate::get(forState)->emitFinished(); } +void QStateMachinePrivate::startupHook() +{ +} + /* This function is called when the state machine is performing no microstep because no transition is enabled (i.e. an event is ignored). @@ -2726,14 +2732,18 @@ void QStateMachine::setRunning(bool running) event queue. Events are processed in the order posted. The state machine takes ownership of the event and deletes it once it has been processed. - You can only post events when the state machine is running. + You can only post events when the state machine is running or when it is starting up. \sa postDelayedEvent() */ void QStateMachine::postEvent(QEvent *event, EventPriority priority) { Q_D(QStateMachine); - if (d->state != QStateMachinePrivate::Running) { + switch (d->state) { + case QStateMachinePrivate::Running: + case QStateMachinePrivate::Starting: + break; + default: qWarning("QStateMachine::postEvent: cannot post event when the state machine is not running"); return; } diff --git a/src/corelib/statemachine/qstatemachine_p.h b/src/corelib/statemachine/qstatemachine_p.h index 5584bc91ab..5db6489fa9 100644 --- a/src/corelib/statemachine/qstatemachine_p.h +++ b/src/corelib/statemachine/qstatemachine_p.h @@ -202,6 +202,7 @@ public: void cancelAllDelayedEvents(); virtual void emitStateFinished(QState *forState, QFinalState *guiltyState); + virtual void startupHook(); #ifndef QT_NO_PROPERTIES class RestorableId { -- cgit v1.2.3 From 7d1ec1ae9e263df1a655e13f8feea7b5a5c7d9ed Mon Sep 17 00:00:00 2001 From: Volker Krause Date: Mon, 11 May 2015 10:45:38 +0200 Subject: Reorder member variables to avoid padding. Saves 8 byte in each case on 64bit systems, no change on 32bit systems. Change-Id: I2a2e8786fc7914ee9ae369ba05bedfc9e5e0ca5c Reviewed-by: Marc Mutz --- src/corelib/statemachine/qeventtransition_p.h | 2 +- src/corelib/statemachine/qstate_p.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/corelib/statemachine') diff --git a/src/corelib/statemachine/qeventtransition_p.h b/src/corelib/statemachine/qeventtransition_p.h index 64ab945187..59b0fcb30f 100644 --- a/src/corelib/statemachine/qeventtransition_p.h +++ b/src/corelib/statemachine/qeventtransition_p.h @@ -61,8 +61,8 @@ public: void unregister(); void maybeRegister(); - bool registered; QObject *object; + bool registered; QEvent::Type eventType; }; diff --git a/src/corelib/statemachine/qstate_p.h b/src/corelib/statemachine/qstate_p.h index 28bb176b56..2ce0c13522 100644 --- a/src/corelib/statemachine/qstate_p.h +++ b/src/corelib/statemachine/qstate_p.h @@ -103,8 +103,8 @@ public: QAbstractState *initialState; QState::ChildMode childMode; mutable bool childStatesListNeedsRefresh; - mutable QList childStatesList; mutable bool transitionsListNeedsRefresh; + mutable QList childStatesList; mutable QList transitionsList; #ifndef QT_NO_PROPERTIES -- cgit v1.2.3 From 009b11c30086f2ab40d3e86691f34cad31999a8f Mon Sep 17 00:00:00 2001 From: Marc Mutz Date: Thu, 30 Apr 2015 10:23:58 +0200 Subject: QStateMachine: fix RestorableId exception specification We're interested in whether qHash(QByteArray) throws, not declval, of course. Change-Id: If3ba6e90aba69d0d4d12ac289e817f0d9705a601 Reviewed-by: Olivier Goffart (Woboq GmbH) --- src/corelib/statemachine/qstatemachine_p.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/corelib/statemachine') diff --git a/src/corelib/statemachine/qstatemachine_p.h b/src/corelib/statemachine/qstatemachine_p.h index 5db6489fa9..426f2732df 100644 --- a/src/corelib/statemachine/qstatemachine_p.h +++ b/src/corelib/statemachine/qstatemachine_p.h @@ -210,7 +210,8 @@ public: QObject *obj; QByteArray prop; // two overloads because friends can't have default arguments - friend uint qHash(const RestorableId &key, uint seed) Q_DECL_NOEXCEPT_EXPR(noexcept(std::declval())) + friend uint qHash(const RestorableId &key, uint seed) + Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(std::declval()))) { return qHash(qMakePair(key.obj, key.prop), seed); } friend uint qHash(const RestorableId &key) Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(key, 0U))) { return qHash(key, 0U); } -- cgit v1.2.3 From 67d255f18343d74bbc9a0eec460995ca615473be Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Tue, 12 May 2015 13:06:46 +0200 Subject: QStateMachine: empty the whole internal queue before external queue If the internal queue contained multiple events, but the first one did not select any transitions, the external event queue would be checked before the remaining events in the internal queue. Change-Id: I1a7f49afdefaaf2b4330bf13b079b61344385ea0 Task-number: QTBUG-46059 Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/corelib/statemachine/qstatemachine.cpp | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) (limited to 'src/corelib/statemachine') diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp index bea6822ecc..e5d019dc8b 100644 --- a/src/corelib/statemachine/qstatemachine.cpp +++ b/src/corelib/statemachine/qstatemachine.cpp @@ -1925,7 +1925,7 @@ void QStateMachinePrivate::_q_process() delete e; e = 0; } - if (enabledTransitions.isEmpty() && ((e = dequeueInternalEvent()) != 0)) { + while (enabledTransitions.isEmpty() && ((e = dequeueInternalEvent()) != 0)) { #ifdef QSTATEMACHINE_DEBUG qDebug() << q << ": dequeued internal event" << e << "of type" << e->type(); #endif @@ -1935,8 +1935,7 @@ void QStateMachinePrivate::_q_process() e = 0; } } - if (enabledTransitions.isEmpty()) { - if ((e = dequeueExternalEvent()) != 0) { + while (enabledTransitions.isEmpty() && ((e = dequeueExternalEvent()) != 0)) { #ifdef QSTATEMACHINE_DEBUG qDebug() << q << ": dequeued external event" << e << "of type" << e->type(); #endif @@ -1945,24 +1944,19 @@ void QStateMachinePrivate::_q_process() delete e; e = 0; } - } else { - if (isInternalEventQueueEmpty()) { - processing = false; - stopProcessingReason = EventQueueEmpty; - } - } } - if (!enabledTransitions.isEmpty()) { - didChange = true; - q->beginMicrostep(e); - microstep(e, enabledTransitions, &calculationCache); - q->endMicrostep(e); - } - else { + if (enabledTransitions.isEmpty()) { + processing = false; + stopProcessingReason = EventQueueEmpty; noMicrostep(); #ifdef QSTATEMACHINE_DEBUG qDebug() << q << ": no transitions enabled"; #endif + } else { + didChange = true; + q->beginMicrostep(e); + microstep(e, enabledTransitions, &calculationCache); + q->endMicrostep(e); } delete e; } -- cgit v1.2.3