summaryrefslogtreecommitdiffstats
path: root/src/corelib/statemachine/qstatemachine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/statemachine/qstatemachine.cpp')
-rw-r--r--src/corelib/statemachine/qstatemachine.cpp867
1 files changed, 706 insertions, 161 deletions
diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp
index 1350c8d99e..bea6822ecc 100644
--- a/src/corelib/statemachine/qstatemachine.cpp
+++ b/src/corelib/statemachine/qstatemachine.cpp
@@ -1,7 +1,7 @@
/****************************************************************************
**
-** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
-** Contact: http://www.qt-project.org/legal
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
@@ -10,9 +10,9 @@
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and Digia. For licensing terms and
-** conditions see http://qt.digia.com/licensing. For further information
-** use the contact form at http://qt.digia.com/contact-us.
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
@@ -23,8 +23,8 @@
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
-** In addition, as a special exception, Digia gives you certain additional
-** rights. These rights are described in the Digia Qt LGPL Exception
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
@@ -177,6 +177,212 @@ QT_BEGIN_NAMESPACE
// #define QSTATEMACHINE_DEBUG
// #define QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
+struct CalculationCache {
+ struct TransitionInfo {
+ QList<QAbstractState*> effectiveTargetStates;
+ QSet<QAbstractState*> exitSet;
+ QAbstractState *transitionDomain;
+
+ bool effectiveTargetStatesIsKnown: 1;
+ bool exitSetIsKnown : 1;
+ bool transitionDomainIsKnown : 1;
+
+ TransitionInfo()
+ : transitionDomain(0)
+ , effectiveTargetStatesIsKnown(false)
+ , exitSetIsKnown(false)
+ , transitionDomainIsKnown(false)
+ {}
+ };
+
+ typedef QHash<QAbstractTransition *, TransitionInfo> TransitionInfoCache;
+ TransitionInfoCache cache;
+
+ bool effectiveTargetStates(QAbstractTransition *t, QList<QAbstractState *> *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<QAbstractState *> &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<QAbstractState *> *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<QAbstractState *> &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)
+
+Returns 'true' if state1 is a descendant of state2 (a child, or a child of a child, or a child of a
+child of a child, etc.) Otherwise returns 'false'.
+*/
+static inline bool isDescendant(const QAbstractState *state1, const QAbstractState *state2)
+{
+ Q_ASSERT(state1 != 0);
+
+ for (QAbstractState *it = state1->parentState(); it != 0; it = it->parentState()) {
+ if (it == state2)
+ return true;
+ }
+
+ return false;
+}
+
+static bool containsDecendantOf(const QSet<QAbstractState *> &states, const QAbstractState *node)
+{
+ foreach (QAbstractState *s, states)
+ if (isDescendant(s, node))
+ return true;
+
+ 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)
+
+If state2 is null, returns the set of all ancestors of state1 in ancestry order (state1's parent
+followed by the parent's parent, etc. up to an including the <scxml> element). If state2 is
+non-null, returns in ancestry order the set of all ancestors of state1, up to but not including
+state2. (A "proper ancestor" of a state is its parent, or the parent's parent, or the parent's
+parent's parent, etc.))If state2 is state1's parent, or equal to state1, or a descendant of state1,
+this returns the empty set.
+*/
+static QVector<QState*> getProperAncestors(const QAbstractState *state, const QAbstractState *upperBound)
+{
+ Q_ASSERT(state != 0);
+ QVector<QState*> result;
+ result.reserve(16);
+ for (QState *it = state->parentState(); it && it != upperBound; it = it->parentState()) {
+ result.append(it);
+ }
+ return result;
+}
+
+/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
+
+function getEffectiveTargetStates(transition)
+
+Returns the states that will be the target when 'transition' is taken, dereferencing any history states.
+
+function getEffectiveTargetStates(transition)
+ targets = new OrderedSet()
+ for s in transition.target
+ if isHistoryState(s):
+ if historyValue[s.id]:
+ targets.union(historyValue[s.id])
+ else:
+ targets.union(getEffectiveTargetStates(s.transition))
+ else:
+ targets.add(s)
+ return targets
+*/
+static QList<QAbstractState *> getEffectiveTargetStates(QAbstractTransition *transition, CalculationCache *cache)
+{
+ Q_ASSERT(cache);
+
+ QList<QAbstractState *> targetsList;
+ if (cache->effectiveTargetStates(transition, &targetsList))
+ return targetsList;
+
+ QSet<QAbstractState *> targets;
+ foreach (QAbstractState *s, transition->targetStates()) {
+ if (QHistoryState *historyState = QStateMachinePrivate::toHistoryState(s)) {
+ QList<QAbstractState*> historyConfiguration = QHistoryStatePrivate::get(historyState)->configuration;
+ if (!historyConfiguration.isEmpty()) {
+ // There is a saved history, so apply that.
+ targets.unite(historyConfiguration.toSet());
+ } else if (QAbstractState *defaultState = historyState->defaultState()) {
+ // Qt does not support initial transitions, but uses the default state of the history state for this.
+ targets.insert(defaultState);
+ } else {
+ // Woops, we found a history state without a default state. That's not valid!
+ QStateMachinePrivate *m = QStateMachinePrivate::get(historyState->machine());
+ m->setError(QStateMachine::NoDefaultStateInHistoryStateError, historyState);
+ }
+ } else {
+ targets.insert(s);
+ }
+ }
+
+ targetsList = targets.toList();
+ cache->insert(transition, targetsList);
+ return targetsList;
+}
+
template <class T>
static uint qHash(const QPointer<T> &p)
{ return qHash(p.data()); }
@@ -246,21 +452,45 @@ static int indexOfDescendant(QState *s, QAbstractState *desc)
QList<QAbstractState*> childStates = QStatePrivate::get(s)->childStates();
for (int i = 0; i < childStates.size(); ++i) {
QAbstractState *c = childStates.at(i);
- if ((c == desc) || QStateMachinePrivate::isDescendantOf(desc, c)) {
+ if ((c == desc) || isDescendant(desc, c)) {
return i;
}
}
return -1;
}
+bool QStateMachinePrivate::transitionStateEntryLessThan(QAbstractTransition *t1, QAbstractTransition *t2)
+{
+ QState *s1 = t1->sourceState(), *s2 = t2->sourceState();
+ if (s1 == s2) {
+ QList<QAbstractTransition*> 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<QAbstractState*>() << 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)
{
if (s1->parent() == s2->parent()) {
return s1->parent()->children().indexOf(s1)
< s2->parent()->children().indexOf(s2);
- } else if (isDescendantOf(s1, s2)) {
+ } else if (isDescendant(s1, s2)) {
return false;
- } else if (isDescendantOf(s2, s1)) {
+ } else if (isDescendant(s2, s1)) {
return true;
} else {
Q_ASSERT(s1->machine() != 0);
@@ -276,9 +506,9 @@ bool QStateMachinePrivate::stateExitLessThan(QAbstractState *s1, QAbstractState
if (s1->parent() == s2->parent()) {
return s2->parent()->children().indexOf(s2)
< s1->parent()->children().indexOf(s1);
- } else if (isDescendantOf(s1, s2)) {
+ } else if (isDescendant(s1, s2)) {
return true;
- } else if (isDescendantOf(s2, s1)) {
+ } else if (isDescendant(s2, s1)) {
return false;
} else {
Q_ASSERT(s1->machine() != 0);
@@ -289,17 +519,20 @@ bool QStateMachinePrivate::stateExitLessThan(QAbstractState *s1, QAbstractState
}
}
-QState *QStateMachinePrivate::findLCA(const QList<QAbstractState*> &states) const
+QState *QStateMachinePrivate::findLCA(const QList<QAbstractState*> &states, bool onlyCompound) const
{
if (states.isEmpty())
return 0;
- QList<QState*> ancestors = properAncestors(states.at(0), rootState()->parentState());
+ QVector<QState*> ancestors = getProperAncestors(states.at(0), rootState()->parentState());
for (int i = 0; i < ancestors.size(); ++i) {
QState *anc = ancestors.at(i);
+ if (onlyCompound && !isCompound(anc))
+ continue;
+
bool ok = true;
for (int j = states.size() - 1; (j > 0) && ok; --j) {
const QAbstractState *s = states.at(j);
- if (!isDescendantOf(s, anc))
+ if (!isDescendant(s, anc))
ok = false;
}
if (ok)
@@ -308,40 +541,27 @@ QState *QStateMachinePrivate::findLCA(const QList<QAbstractState*> &states) cons
return 0;
}
-bool QStateMachinePrivate::isPreempted(const QAbstractState *s, const QSet<QAbstractTransition*> &transitions) const
+QState *QStateMachinePrivate::findLCCA(const QList<QAbstractState*> &states) const
{
- QSet<QAbstractTransition*>::const_iterator it;
- for (it = transitions.constBegin(); it != transitions.constEnd(); ++it) {
- QAbstractTransition *t = *it;
- QList<QAbstractState*> lst = t->targetStates();
- if (!lst.isEmpty()) {
- lst.prepend(t->sourceState());
- QAbstractState *lca = findLCA(lst);
- if (isDescendantOf(s, lca)) {
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q_func() << ':' << transitions << "preempts selection of a transition from"
- << s << "because" << s << "is a descendant of" << lca;
-#endif
- return true;
- }
- }
- }
- return false;
+ return findLCA(states, true);
}
-QSet<QAbstractTransition*> QStateMachinePrivate::selectTransitions(QEvent *event) const
+QList<QAbstractTransition*> QStateMachinePrivate::selectTransitions(QEvent *event, CalculationCache *cache)
{
+ Q_ASSERT(cache);
Q_Q(const QStateMachine);
- QSet<QAbstractTransition*> enabledTransitions;
- QSet<QAbstractState*>::const_iterator it;
+
+ QVarLengthArray<QAbstractState *> configuration_sorted;
+ foreach (QAbstractState *s, configuration) {
+ if (isAtomic(s))
+ configuration_sorted.append(s);
+ }
+ std::sort(configuration_sorted.begin(), configuration_sorted.end(), stateEntryLessThan);
+
+ QList<QAbstractTransition*> enabledTransitions;
const_cast<QStateMachine*>(q)->beginSelectTransitions(event);
- for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) {
- QAbstractState *state = *it;
- if (!isAtomic(state))
- continue;
- if (isPreempted(state, enabledTransitions))
- continue;
- QList<QState*> lst = properAncestors(state, rootState()->parentState());
+ foreach (QAbstractState *state, configuration_sorted) {
+ QVector<QState*> lst = getProperAncestors(state, Q_NULLPTR);
if (QState *grp = toStandardState(state))
lst.prepend(grp);
bool found = false;
@@ -354,28 +574,115 @@ QSet<QAbstractTransition*> QStateMachinePrivate::selectTransitions(QEvent *event
#ifdef QSTATEMACHINE_DEBUG
qDebug() << q << ": selecting transition" << t;
#endif
- enabledTransitions.insert(t);
+ enabledTransitions.append(t);
found = true;
break;
}
}
}
}
+
+ if (!enabledTransitions.isEmpty()) {
+ removeConflictingTransitions(enabledTransitions, cache);
+#ifdef QSTATEMACHINE_DEBUG
+ qDebug() << q << ": enabled transitions after removing conflicts:" << enabledTransitions;
+#endif
+ }
const_cast<QStateMachine*>(q)->endSelectTransitions(event);
return enabledTransitions;
}
-void QStateMachinePrivate::microstep(QEvent *event, const QList<QAbstractTransition*> &enabledTransitions)
+/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
+
+function removeConflictingTransitions(enabledTransitions):
+ filteredTransitions = new OrderedSet()
+ // toList sorts the transitions in the order of the states that selected them
+ for t1 in enabledTransitions.toList():
+ t1Preempted = false;
+ transitionsToRemove = new OrderedSet()
+ for t2 in filteredTransitions.toList():
+ if computeExitSet([t1]).hasIntersection(computeExitSet([t2])):
+ if isDescendant(t1.source, t2.source):
+ transitionsToRemove.add(t2)
+ else:
+ t1Preempted = true
+ break
+ if not t1Preempted:
+ for t3 in transitionsToRemove.toList():
+ filteredTransitions.delete(t3)
+ filteredTransitions.add(t1)
+
+ return filteredTransitions
+
+Note: the implementation below does not build the transitionsToRemove, but removes them in-place.
+*/
+void QStateMachinePrivate::removeConflictingTransitions(QList<QAbstractTransition*> &enabledTransitions, CalculationCache *cache)
+{
+ Q_ASSERT(cache);
+
+ if (enabledTransitions.size() < 2)
+ return; // There is no transition to conflict with.
+
+ QList<QAbstractTransition*> filteredTransitions;
+ filteredTransitions.reserve(enabledTransitions.size());
+ std::sort(enabledTransitions.begin(), enabledTransitions.end(), transitionStateEntryLessThan);
+
+ foreach (QAbstractTransition *t1, enabledTransitions) {
+ bool t1Preempted = false;
+ QSet<QAbstractState*> exitSetT1 = computeExitSet_Unordered(t1, cache);
+ QList<QAbstractTransition*>::iterator t2It = filteredTransitions.begin();
+ while (t2It != filteredTransitions.end()) {
+ QAbstractTransition *t2 = *t2It;
+ if (t1 == t2) {
+ // Special case: someone added the same transition object to a state twice. In this
+ // case, t2 (which is already in the list) "preempts" t1.
+ t1Preempted = true;
+ break;
+ }
+
+ QSet<QAbstractState*> exitSetT2 = computeExitSet_Unordered(t2, cache);
+ if (exitSetT1.intersect(exitSetT2).isEmpty()) {
+ // No conflict, no cry. Next patient please.
+ ++t2It;
+ } else {
+ // Houston, we have a conflict. Check which transition can be removed.
+ if (isDescendant(t1->sourceState(), t2->sourceState())) {
+ // t1 preempts t2, so we can remove t2
+ t2It = filteredTransitions.erase(t2It);
+ } else {
+ // t2 preempts t1, so there's no use in looking further and we don't need to add
+ // t1 to the list.
+ t1Preempted = true;
+ break;
+ }
+ }
+ }
+ if (!t1Preempted)
+ filteredTransitions.append(t1);
+ }
+
+ enabledTransitions = filteredTransitions;
+}
+
+void QStateMachinePrivate::microstep(QEvent *event, const QList<QAbstractTransition*> &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<QAbstractState*> exitedStates = computeStatesToExit(enabledTransitions);
+ QList<QAbstractState*> exitedStates = computeExitSet(enabledTransitions, cache);
QHash<RestorableId, QVariant> pendingRestorables = computePendingRestorables(exitedStates);
QSet<QAbstractState*> statesForDefaultEntry;
- QList<QAbstractState*> enteredStates = computeStatesToEnter(enabledTransitions, statesForDefaultEntry);
+ QList<QAbstractState*> enteredStates = computeEntrySet(enabledTransitions, statesForDefaultEntry, cache);
+
+#ifdef QSTATEMACHINE_DEBUG
+ qDebug() << q_func() << ": computed exit set:" << exitedStates;
+ qDebug() << q_func() << ": computed entry set:" << enteredStates;
+#endif
QHash<QAbstractState*, QList<QPropertyAssignment> > assignmentsForEnteredStates =
computePropertyAssignments(enteredStates, pendingRestorables);
@@ -409,38 +716,82 @@ void QStateMachinePrivate::microstep(QEvent *event, const QList<QAbstractTransit
#endif
}
-QList<QAbstractState*> QStateMachinePrivate::computeStatesToExit(const QList<QAbstractTransition*> &enabledTransitions)
+/* The function as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
+
+procedure computeExitSet(enabledTransitions)
+
+For each transition t in enabledTransitions, if t is targetless then do nothing, else compute the
+transition's domain. (This will be the source state in the case of internal transitions) or the
+least common compound ancestor state of the source state and target states of t (in the case of
+external transitions. Add to the statesToExit set all states in the configuration that are
+descendants of the domain.
+
+function computeExitSet(transitions)
+ statesToExit = new OrderedSet
+ for t in transitions:
+ if (t.target):
+ domain = getTransitionDomain(t)
+ for s in configuration:
+ if isDescendant(s,domain):
+ statesToExit.add(s)
+ return statesToExit
+*/
+QList<QAbstractState*> QStateMachinePrivate::computeExitSet(const QList<QAbstractTransition*> &enabledTransitions,
+ CalculationCache *cache)
+{
+ Q_ASSERT(cache);
+
+ QList<QAbstractState*> statesToExit_sorted = computeExitSet_Unordered(enabledTransitions, cache).toList();
+ std::sort(statesToExit_sorted.begin(), statesToExit_sorted.end(), stateExitLessThan);
+ return statesToExit_sorted;
+}
+
+QSet<QAbstractState*> QStateMachinePrivate::computeExitSet_Unordered(const QList<QAbstractTransition*> &enabledTransitions,
+ CalculationCache *cache)
{
+ Q_ASSERT(cache);
+
QSet<QAbstractState*> statesToExit;
-// QSet<QAbstractState*> statesToSnapshot;
- for (int i = 0; i < enabledTransitions.size(); ++i) {
- QAbstractTransition *t = enabledTransitions.at(i);
- QList<QAbstractState*> lst = t->targetStates();
- if (lst.isEmpty())
- continue;
- lst.prepend(t->sourceState());
- QAbstractState *lca = findLCA(lst);
- if (lca == 0) {
+ foreach (QAbstractTransition *t, enabledTransitions)
+ statesToExit.unite(computeExitSet_Unordered(t, cache));
+ return statesToExit;
+}
+
+QSet<QAbstractState*> QStateMachinePrivate::computeExitSet_Unordered(QAbstractTransition *t,
+ CalculationCache *cache)
+{
+ Q_ASSERT(cache);
+
+ QSet<QAbstractState*> statesToExit;
+ if (cache->exitSet(t, &statesToExit))
+ return statesToExit;
+
+ QList<QAbstractState *> 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());
- lst = pendingErrorStates.toList();
- lst.prepend(t->sourceState());
+ QList<QAbstractState *> lst = pendingErrorStates.toList();
+ lst.prepend(t->sourceState());
- lca = findLCA(lst);
- Q_ASSERT(lca != 0);
- }
+ domain = findLCCA(lst);
+ Q_ASSERT(domain != 0);
+ }
- {
- QSet<QAbstractState*>::const_iterator it;
- for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) {
- QAbstractState *s = *it;
- if (isDescendantOf(s, lca))
- statesToExit.insert(s);
- }
- }
+ foreach (QAbstractState* s, configuration) {
+ if (isDescendant(s, domain))
+ statesToExit.insert(s);
}
- QList<QAbstractState*> statesToExit_sorted = statesToExit.toList();
- std::sort(statesToExit_sorted.begin(), statesToExit_sorted.end(), stateExitLessThan);
- return statesToExit_sorted;
+
+ cache->insert(t, statesToExit);
+ return statesToExit;
}
void QStateMachinePrivate::exitStates(QEvent *event, const QList<QAbstractState*> &statesToExit_sorted,
@@ -457,7 +808,7 @@ void QStateMachinePrivate::exitStates(QEvent *event, const QList<QAbstractState*
for (it = configuration.constBegin(); it != configuration.constEnd(); ++it) {
QAbstractState *s0 = *it;
if (QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) {
- if (isAtomic(s0) && isDescendantOf(s0, s))
+ if (isAtomic(s0) && isDescendant(s0, s))
QHistoryStatePrivate::get(h)->configuration.append(s0);
} else if (s0->parentState() == s) {
QHistoryStatePrivate::get(h)->configuration.append(s0);
@@ -500,30 +851,23 @@ void QStateMachinePrivate::executeTransitionContent(QEvent *event, const QList<Q
}
}
-QList<QAbstractState*> QStateMachinePrivate::computeStatesToEnter(const QList<QAbstractTransition *> &enabledTransitions,
- QSet<QAbstractState *> &statesForDefaultEntry)
+QList<QAbstractState*> QStateMachinePrivate::computeEntrySet(const QList<QAbstractTransition *> &enabledTransitions,
+ QSet<QAbstractState *> &statesForDefaultEntry,
+ CalculationCache *cache)
{
+ Q_ASSERT(cache);
+
QSet<QAbstractState*> statesToEnter;
if (pendingErrorStates.isEmpty()) {
- for (int i = 0; i < enabledTransitions.size(); ++i) {
- QAbstractTransition *t = enabledTransitions.at(i);
- QList<QAbstractState*> lst = t->targetStates();
- if (lst.isEmpty())
- continue;
- QAbstractState *src = t->sourceState();
- if (src)
- lst.prepend(src);
- QState *lca = findLCA(lst);
- for (int j = src ? 1 : 0; j < lst.size(); ++j) {
- QAbstractState *s = lst.at(j);
- addStatesToEnter(s, lca, statesToEnter, statesForDefaultEntry);
+ foreach (QAbstractTransition *t, enabledTransitions) {
+ foreach (QAbstractState *s, t->targetStates()) {
+ addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry);
}
- if (isParallel(lca)) {
- QList<QAbstractState*> lcac = QStatePrivate::get(lca)->childStates();
- foreach (QAbstractState* child,lcac) {
- if (!statesToEnter.contains(child))
- addStatesToEnter(child,lca,statesToEnter,statesForDefaultEntry);
- }
+
+ QList<QAbstractState *> effectiveTargetStates = getEffectiveTargetStates(t, cache);
+ QAbstractState *ancestor = getTransitionDomain(t, effectiveTargetStates, cache);
+ foreach (QAbstractState *s, effectiveTargetStates) {
+ addAncestorStatesToEnter(s, ancestor, statesToEnter, statesForDefaultEntry);
}
}
}
@@ -542,6 +886,60 @@ QList<QAbstractState*> QStateMachinePrivate::computeStatesToEnter(const QList<QA
return statesToEnter_sorted;
}
+/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
+
+function getTransitionDomain(transition)
+
+Return the compound state such that 1) all states that are exited or entered as a result of taking
+'transition' are descendants of it 2) no descendant of it has this property.
+
+function getTransitionDomain(t)
+ tstates = getEffectiveTargetStates(t)
+ if not tstates:
+ return null
+ elif t.type == "internal" and isCompoundState(t.source) and tstates.every(lambda s: isDescendant(s,t.source)):
+ return t.source
+ else:
+ return findLCCA([t.source].append(tstates))
+*/
+QAbstractState *QStateMachinePrivate::getTransitionDomain(QAbstractTransition *t,
+ const QList<QAbstractState *> &effectiveTargetStates,
+ CalculationCache *cache) const
+{
+ Q_ASSERT(cache);
+
+ if (effectiveTargetStates.isEmpty())
+ return 0;
+
+ QAbstractState *domain = Q_NULLPTR;
+ if (cache->transitionDomain(t, &domain))
+ return domain;
+
+ 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;
+ }
+ }
+ }
+
+ QList<QAbstractState *> states(effectiveTargetStates);
+ if (QAbstractState *src = t->sourceState())
+ states.prepend(src);
+ domain = findLCCA(states);
+ cache->insert(t, domain);
+ return domain;
+}
+
void QStateMachinePrivate::enterStates(QEvent *event, const QList<QAbstractState*> &exitedStates_sorted,
const QList<QAbstractState*> &statesToEnter_sorted,
const QSet<QAbstractState*> &statesForDefaultEntry,
@@ -610,10 +1008,9 @@ void QStateMachinePrivate::enterStates(QEvent *event, const QList<QAbstractState
QState *parent = s->parentState();
if (parent) {
if (parent != rootState()) {
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": emitting finished signal for" << parent;
-#endif
- QStatePrivate::get(parent)->emitFinished();
+ QFinalState *finalState = qobject_cast<QFinalState *>(s);
+ Q_ASSERT(finalState);
+ emitStateFinished(parent, finalState);
}
QState *grandparent = parent->parentState();
if (grandparent && isParallel(grandparent)) {
@@ -627,10 +1024,9 @@ void QStateMachinePrivate::enterStates(QEvent *event, const QList<QAbstractState
}
}
if (allChildStatesFinal && (grandparent != rootState())) {
-#ifdef QSTATEMACHINE_DEBUG
- qDebug() << q << ": emitting finished signal for" << grandparent;
-#endif
- QStatePrivate::get(grandparent)->emitFinished();
+ QFinalState *finalState = qobject_cast<QFinalState *>(s);
+ Q_ASSERT(finalState);
+ emitStateFinished(grandparent, finalState);
}
}
}
@@ -719,30 +1115,126 @@ void QStateMachinePrivate::addStatesToEnter(QAbstractState *s, QState *root,
}
}
-void QStateMachinePrivate::addAncestorStatesToEnter(QAbstractState *s, QState *root,
+/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ has a bug. See
+ * QTBUG-44963 for details. The algorithm here is as described in
+ * http://www.w3.org/Voice/2013/scxml-irp/SCXML.htm as of Friday March 13, 2015.
+
+procedure addDescendantStatesToEnter(state,statesToEnter,statesForDefaultEntry, defaultHistoryContent):
+ if isHistoryState(state):
+ if historyValue[state.id]:
+ for s in historyValue[state.id]:
+ addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
+ for s in historyValue[state.id]:
+ addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
+ else:
+ defaultHistoryContent[state.parent.id] = state.transition.content
+ for s in state.transition.target:
+ addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
+ for s in state.transition.target:
+ addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
+ else:
+ statesToEnter.add(state)
+ if isCompoundState(state):
+ statesForDefaultEntry.add(state)
+ for s in state.initial.transition.target:
+ addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
+ for s in state.initial.transition.target:
+ addAncestorStatesToEnter(s, state, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
+ else:
+ if isParallelState(state):
+ for child in getChildStates(state):
+ if not statesToEnter.some(lambda s: isDescendant(s,child)):
+ addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
+*/
+void QStateMachinePrivate::addDescendantStatesToEnter(QAbstractState *state,
+ QSet<QAbstractState*> &statesToEnter,
+ QSet<QAbstractState*> &statesForDefaultEntry)
+{
+ if (QHistoryState *h = toHistoryState(state)) {
+ QList<QAbstractState*> historyConfiguration = QHistoryStatePrivate::get(h)->configuration;
+ if (!historyConfiguration.isEmpty()) {
+ foreach (QAbstractState *s, historyConfiguration)
+ addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry);
+ foreach (QAbstractState *s, historyConfiguration)
+ addAncestorStatesToEnter(s, state->parentState(), statesToEnter, statesForDefaultEntry);
+
+#ifdef QSTATEMACHINE_DEBUG
+ qDebug() << q_func() << ": restoring"
+ << ((QHistoryStatePrivate::get(h)->historyType == QHistoryState::DeepHistory) ? "deep" : "shallow")
+ << "history from" << state << ':' << historyConfiguration;
+#endif
+ } else {
+ QList<QAbstractState*> defaultHistoryContent;
+ if (QHistoryStatePrivate::get(h)->defaultState)
+ defaultHistoryContent.append(QHistoryStatePrivate::get(h)->defaultState);
+
+ if (defaultHistoryContent.isEmpty()) {
+ setError(QStateMachine::NoDefaultStateInHistoryStateError, h);
+ } else {
+ foreach (QAbstractState *s, defaultHistoryContent)
+ addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry);
+ foreach (QAbstractState *s, defaultHistoryContent)
+ addAncestorStatesToEnter(s, state->parentState(), statesToEnter, statesForDefaultEntry);
+#ifdef QSTATEMACHINE_DEBUG
+ qDebug() << q_func() << ": initial history targets for" << state << ':' << defaultHistoryContent;
+#endif
+ }
+ }
+ } else {
+ if (state == rootState()) {
+ // Error has already been set by exitStates().
+ Q_ASSERT(error != QStateMachine::NoError);
+ return;
+ }
+ statesToEnter.insert(state);
+ if (isCompound(state)) {
+ statesForDefaultEntry.insert(state);
+ if (QAbstractState *initial = toStandardState(state)->initialState()) {
+ Q_ASSERT(initial->machine() == q_func());
+
+ // Qt does not support initial transitions (which is a problem for parallel states).
+ // The way it simulates this for other states, is by having a single initial state.
+ statesForDefaultEntry.insert(initial);
+
+ addDescendantStatesToEnter(initial, statesToEnter, statesForDefaultEntry);
+ addAncestorStatesToEnter(initial, state, statesToEnter, statesForDefaultEntry);
+ } else {
+ setError(QStateMachine::NoInitialStateError, state);
+ return;
+ }
+ } else if (isParallel(state)) {
+ QState *grp = toStandardState(state);
+ foreach (QAbstractState *child, QStatePrivate::get(grp)->childStates()) {
+ if (!containsDecendantOf(statesToEnter, child))
+ addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry);
+ }
+ }
+ }
+}
+
+
+/* The algorithm as described in http://www.w3.org/TR/2014/WD-scxml-20140529/ :
+
+procedure addAncestorStatesToEnter(state, ancestor, statesToEnter, statesForDefaultEntry, defaultHistoryContent)
+ for anc in getProperAncestors(state,ancestor):
+ statesToEnter.add(anc)
+ if isParallelState(anc):
+ for child in getChildStates(anc):
+ if not statesToEnter.some(lambda s: isDescendant(s,child)):
+ addDescendantStatesToEnter(child,statesToEnter,statesForDefaultEntry, defaultHistoryContent)
+*/
+void QStateMachinePrivate::addAncestorStatesToEnter(QAbstractState *s, QAbstractState *ancestor,
QSet<QAbstractState*> &statesToEnter,
QSet<QAbstractState*> &statesForDefaultEntry)
{
- QList<QState*> ancs = properAncestors(s, root);
- for (int i = 0; i < ancs.size(); ++i) {
- QState *anc = ancs.at(i);
+ foreach (QState *anc, getProperAncestors(s, ancestor)) {
if (!anc->parentState())
continue;
statesToEnter.insert(anc);
if (isParallel(anc)) {
- QList<QAbstractState*> lst = QStatePrivate::get(anc)->childStates();
- for (int j = 0; j < lst.size(); ++j) {
- QAbstractState *child = lst.at(j);
- bool hasDescendantInList = false;
- QSet<QAbstractState*>::const_iterator it;
- for (it = statesToEnter.constBegin(); it != statesToEnter.constEnd(); ++it) {
- if (isDescendantOf(*it, child)) {
- hasDescendantInList = true;
- break;
- }
- }
- if (!hasDescendantInList)
- addStatesToEnter(child, anc, statesToEnter, statesForDefaultEntry);
+ foreach (QAbstractState *child, QStatePrivate::get(anc)->childStates()) {
+ if (!containsDecendantOf(statesToEnter, child))
+ addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry);
}
}
}
@@ -780,27 +1272,6 @@ bool QStateMachinePrivate::isAtomic(const QAbstractState *s) const
|| (ss && QStatePrivate::get(ss)->isMachine && (ss != rootState()));
}
-
-bool QStateMachinePrivate::isDescendantOf(const QAbstractState *state, const QAbstractState *other)
-{
- Q_ASSERT(state != 0);
- for (QAbstractState *s = state->parentState(); s != 0; s = s->parentState()) {
- if (s == other)
- return true;
- }
- return false;
-}
-
-QList<QState*> QStateMachinePrivate::properAncestors(const QAbstractState *state, const QState *upperBound)
-{
- Q_ASSERT(state != 0);
- QList<QState*> result;
- for (QState *s = state->parentState(); s && s != upperBound; s = s->parentState()) {
- result.append(s);
- }
- return result;
-}
-
QState *QStateMachinePrivate::toStandardState(QAbstractState *state)
{
if (state && (QAbstractStatePrivate::get(state)->stateType == QAbstractStatePrivate::StandardState))
@@ -882,11 +1353,10 @@ QVariant QStateMachinePrivate::savedValueForRestorable(const QList<QAbstractStat
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
qDebug() << q_func() << ": savedValueForRestorable(" << exitedStates_sorted << object << propertyName << ")";
#endif
- RestorableId id(object, propertyName);
for (int i = exitedStates_sorted.size() - 1; i >= 0; --i) {
QAbstractState *s = exitedStates_sorted.at(i);
QHash<RestorableId, QVariant> restorables = registeredRestorablesForState.value(s);
- QHash<RestorableId, QVariant>::const_iterator it = restorables.constFind(id);
+ QHash<RestorableId, QVariant>::const_iterator it = restorables.constFind(RestorableId(object, propertyName));
if (it != restorables.constEnd()) {
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
qDebug() << q_func() << ": using" << it.value() << "from" << s;
@@ -897,7 +1367,7 @@ QVariant QStateMachinePrivate::savedValueForRestorable(const QList<QAbstractStat
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
qDebug() << q_func() << ": falling back to current value";
#endif
- return id.first->property(id.second);
+ return object->property(propertyName);
}
void QStateMachinePrivate::registerRestorable(QAbstractState *state, QObject *object, const QByteArray &propertyName,
@@ -948,14 +1418,15 @@ QList<QPropertyAssignment> QStateMachinePrivate::restorablesToPropertyList(const
QList<QPropertyAssignment> result;
QHash<RestorableId, QVariant>::const_iterator it;
for (it = restorables.constBegin(); it != restorables.constEnd(); ++it) {
- if (!it.key().first) {
+ const RestorableId &id = it.key();
+ if (!id.object()) {
// Property object was deleted
continue;
}
#ifdef QSTATEMACHINE_RESTORE_PROPERTIES_DEBUG
- qDebug() << q_func() << ": restoring" << it.key().first << it.key().second << "to" << it.value();
+ qDebug() << q_func() << ": restoring" << id.object() << id.proertyName() << "to" << it.value();
#endif
- result.append(QPropertyAssignment(it.key().first, it.key().second, it.value(), /*explicitlySet=*/false));
+ result.append(QPropertyAssignment(id.object(), id.propertyName(), it.value(), /*explicitlySet=*/false));
}
return result;
}
@@ -1082,6 +1553,9 @@ void QStateMachinePrivate::setError(QStateMachine::Error errorCode, QAbstractSta
Q_ASSERT(currentErrorState != rootState());
if (currentErrorState != 0) {
+#ifdef QSTATEMACHINE_DEBUG
+ qDebug() << q << ": entering error state" << currentErrorState << "from" << currentContext;
+#endif
QState *lca = findLCA(QList<QAbstractState*>() << currentErrorState << currentContext);
addStatesToEnter(currentErrorState, lca, pendingErrorStates, pendingErrorStatesForDefaultEntry);
} else {
@@ -1303,8 +1777,8 @@ QAbstractTransition *QStateMachinePrivate::createInitialTransition() const
: QAbstractTransition()
{ setTargetStates(targets); }
protected:
- virtual bool eventTest(QEvent *) { return true; }
- virtual void onTransition(QEvent *) {}
+ virtual bool eventTest(QEvent *) Q_DECL_OVERRIDE { return true; }
+ virtual void onTransition(QEvent *) Q_DECL_OVERRIDE {}
};
QState *root = rootState();
@@ -1371,6 +1845,8 @@ void QStateMachinePrivate::_q_start()
registerMultiThreadedSignalTransitions();
+ startupHook();
+
#ifdef QSTATEMACHINE_DEBUG
qDebug() << q << ": starting";
#endif
@@ -1378,6 +1854,7 @@ void QStateMachinePrivate::_q_start()
processingScheduled = true; // we call _q_process() below
QList<QAbstractTransition*> transitions;
+ CalculationCache calculationCache;
QAbstractTransition *initialTransition = createInitialTransition();
transitions.append(initialTransition);
@@ -1385,8 +1862,7 @@ void QStateMachinePrivate::_q_start()
executeTransitionContent(&nullEvent, transitions);
QList<QAbstractState*> exitedStates = QList<QAbstractState*>();
QSet<QAbstractState*> statesForDefaultEntry;
- QList<QAbstractState*> enteredStates = computeStatesToEnter(transitions,
- statesForDefaultEntry);
+ QList<QAbstractState*> enteredStates = computeEntrySet(transitions, statesForDefaultEntry, &calculationCache);
QHash<RestorableId, QVariant> pendingRestorables;
QHash<QAbstractState*, QList<QPropertyAssignment> > assignmentsForEnteredStates =
computePropertyAssignments(enteredStates, pendingRestorables);
@@ -1430,17 +1906,21 @@ 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;
break;
}
- QSet<QAbstractTransition*> enabledTransitions;
+ QList<QAbstractTransition*> enabledTransitions;
+ CalculationCache calculationCache;
+
QEvent *e = new QEvent(QEvent::None);
- enabledTransitions = selectTransitions(e);
+ enabledTransitions = selectTransitions(e, &calculationCache);
if (enabledTransitions.isEmpty()) {
delete e;
e = 0;
@@ -1449,7 +1929,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;
@@ -1460,7 +1940,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;
@@ -1473,15 +1953,17 @@ void QStateMachinePrivate::_q_process()
}
}
if (!enabledTransitions.isEmpty()) {
+ didChange = true;
q->beginMicrostep(e);
- microstep(e, enabledTransitions.toList());
+ 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
@@ -1494,6 +1976,7 @@ void QStateMachinePrivate::_q_process()
switch (stopProcessingReason) {
case EventQueueEmpty:
+ processedPendingEvents(didChange);
break;
case Finished:
state = NotRunning;
@@ -1510,6 +1993,7 @@ void QStateMachinePrivate::_q_process()
emit q->runningChanged(false);
break;
}
+ endMacrostep(didChange);
}
void QStateMachinePrivate::_q_startDelayedEventTimer(int id, int delay)
@@ -1619,6 +2103,63 @@ void QStateMachinePrivate::cancelAllDelayedEvents()
delayedEvents.clear();
}
+void QStateMachinePrivate::emitStateFinished(QState *forState, QFinalState *guiltyState)
+{
+ Q_UNUSED(guiltyState);
+ Q_ASSERT(guiltyState);
+
+#ifdef QSTATEMACHINE_DEBUG
+ Q_Q(QStateMachine);
+ qDebug() << q << ": emitting finished signal for" << forState;
+#endif
+
+ 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).
+
+ 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
@@ -1629,8 +2170,8 @@ public:
: QAbstractTransition()
{ setTargetState(target); }
protected:
- void onTransition(QEvent *) { deleteLater(); }
- bool eventTest(QEvent *) { return true; }
+ void onTransition(QEvent *) Q_DECL_OVERRIDE { deleteLater(); }
+ bool eventTest(QEvent *) Q_DECL_OVERRIDE { return true; }
};
} // namespace
@@ -2191,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;
}