<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[QStateMachine: How To Implement Logical OR Between States]]></title><description><![CDATA[<p dir="auto">The application I am working on can enter a Paused state for any one of several reasons. In the example I have included here I use just two reasons, called <strong>AppPause</strong> and <strong>LevelPause</strong></p>
<p dir="auto">I am using QStateMachine from Qt6.4</p>
<p dir="auto">The logic is that the application will be paused if either <strong>AppPause</strong> or <strong>LevelPause</strong> are true, or both. The application will resume only when neither are true.</p>
<p dir="auto">I struggled to find a simple way of representing this in a state chart. My attempt is shown in the diagram below.</p>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/9f1954ec-e704-44c1-ad4d-3f004c41edb7.jpg" alt="qstatemachine-or.jpg" class=" img-fluid img-markdown" /></p>
<p dir="auto">My first question is a general one: is there a simpler way of implementing this relationship between such states in a state chart, bearing in mind that it has to be realised using QStateMachine? It seems to be a lot of complexity for what is really just a logical OR.</p>
<p dir="auto">Secondly, using QStateMachine there is a problem with retaining history of the two compound states <strong>AppPause</strong> and <strong>LevelPause</strong>.</p>
<p dir="auto">If the starting states are <strong>AppPaused</strong> and <strong>LevelPaused</strong>, then two consequetive transitions <em>AppResume</em> then <em>LevelResume</em> are made, the following occurs:</p>
<p dir="auto">On <em>AppResume</em>: <strong>AppPaused</strong> is exited, <strong>AppUnpaused</strong> is entered.,<br />
On <em>LevelResume</em>: <strong>LevelPaused</strong> is exited then <strong>AppPause</strong> exits, then <strong>Paused</strong> exits. The state <strong>LevelUnpaused</strong> is not entered.<br />
This means that when a subsequent <em>AppPause</em> occurs the <strong>LevelPause</strong> state returns to <strong>LevelPaused</strong> not <strong>LevelUnpaused</strong>. This requires extra transitions to put the <strong>LevelPause</strong> compound state back to where it should be.</p>
<p dir="auto">Complete code for a QTest application that demonstrates this is below:</p>
<pre><code>#include &lt;QTest&gt;

#include &lt;QSignalSpy&gt;
#include &lt;QStateMachine&gt;
#include &lt;QState&gt;
#include &lt;QHistoryState&gt;

class State : public QState
{
    Q_OBJECT
public:
    State(QString name, QState * parent);
    State(QString name, QState::ChildMode mode, QState * parent);

    void onEntry(QEvent * e) override;
    void onExit(QEvent * e) override;

    const QString&amp; name() const;

    QString mName;
    int mEntryCount{ 0 };
};

State::State(QString name, QState * parent)
  : QState{ parent },
    mName{ name }
{
}

State::State(QString name, QState::ChildMode mode, QState * parent)
  : QState{ mode, parent },
    mName{ name }
{
}

void State::onEntry(QEvent * e)
{
    QState::onEntry(e);
    qDebug() &lt;&lt; "entered" &lt;&lt; mName;
    mEntryCount++;
}

void State::onExit(QEvent * e)
{
    QState::onExit(e);
    qDebug() &lt;&lt; "exited" &lt;&lt; mName;
    mEntryCount--;
}


class TestQStateMachine : public QObject
{
    Q_OBJECT

public:

signals:

    void appPause();
    void appResume();
    void levelPause();
    void levelResume();

private slots:
    void initTestCase();
    void testTransitions();
    void cleanupTestCase();

private:
    QStateMachine mStateMachine;

    State * mPauseState{ nullptr };
    State * mLevelUnpausedState{ nullptr };
};


void TestQStateMachine::initTestCase()
{
    State * app{ new State{ "app", &amp;mStateMachine } };
    State * pause{ new State{ "pause", QState::ParallelStates, &amp;mStateMachine } };
    State * appPause{ new State{ "app pause", pause } };
    State * levelPause{ new State{ "level pause", pause } };
    State * appPaused{ new State{ "app paused", appPause } };
    State * appUnpaused{ new State{ "app unpaused", appPause } };
    State * levelPaused{ new State{ "level paused", levelPause } };
    State * levelUnpaused{ new State{ "level unpaused", levelPause } };

    QHistoryState * aph{ new QHistoryState{ QHistoryState::HistoryType::ShallowHistory, appPause } };
    QHistoryState * lph{ new QHistoryState{ QHistoryState::HistoryType::ShallowHistory, levelPause } };
    aph-&gt;setDefaultState(appPaused);
    lph-&gt;setDefaultState(levelPaused);

    mPauseState = pause;
    mLevelUnpausedState = levelUnpaused;

    State * error{ new State{ "error", &amp;mStateMachine } };
    mStateMachine.setErrorState(error);

    appPause-&gt;setInitialState(appPaused);
    levelPause-&gt;setInitialState(levelPaused);

    mStateMachine.setInitialState(pause);

    app-&gt;addTransition(this, &amp;TestQStateMachine::appPause, appPaused);
    app-&gt;addTransition(this, &amp;TestQStateMachine::appPause, lph);
    app-&gt;addTransition(this, &amp;TestQStateMachine::levelPause, levelPaused);
    app-&gt;addTransition(this, &amp;TestQStateMachine::levelPause, aph);

    appPaused-&gt;addTransition(this, &amp;TestQStateMachine::appResume, appUnpaused);
    appUnpaused-&gt;addTransition(this, &amp;TestQStateMachine::appPause, appPaused);

    levelPaused-&gt;addTransition(this, &amp;TestQStateMachine::levelResume, levelUnpaused);
    levelUnpaused-&gt;addTransition(this, &amp;TestQStateMachine::levelPause, levelPaused);

    appUnpaused-&gt;addTransition(this, &amp;TestQStateMachine::levelResume, app);
    levelUnpaused-&gt;addTransition(this, &amp;TestQStateMachine::appResume, app);

    mStateMachine.start();
    QSignalSpy spy(&amp;mStateMachine, &amp;QStateMachine::started);
    spy.wait();
}

void TestQStateMachine::testTransitions()
{
    emit appResume(); // app paused =&gt; app unpaused

    emit levelResume(); // level paused exits but level unpaused is not entered, level pause is exited instead, then pause =&gt; app ...

    QCOMPARE(mPauseState-&gt;mEntryCount, 0);

    emit appPause(); // ... which means that here the level pause history state is level paused, not level unpaused

    QCOMPARE(mPauseState-&gt;mEntryCount, 1);
    QCOMPARE(mLevelUnpausedState-&gt;mEntryCount, 1); // This fails.
}

void TestQStateMachine::cleanupTestCase()
{
}


QTEST_MAIN(TestQStateMachine)
#include "qstatemachinetest.moc"
</code></pre>
<p dir="auto">CMakeLists.txt:</p>
<pre><code>cmake_minimum_required(VERSION 3.23)

project(qstatemachinetest LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 REQUIRED COMPONENTS Core StateMachine Test)

add_executable(qstatemachinetest
  qstatemachinetest.cpp
)
target_link_libraries(qstatemachinetest PRIVATE Qt6::Core Qt6::StateMachine Qt6::Test)

set_target_properties(qstatemachinetest PROPERTIES
    WIN32_EXECUTABLE OFF
    MACOSX_BUNDLE ON
)
</code></pre>
]]></description><link>https://forum.qt.io/topic/144382/qstatemachine-how-to-implement-logical-or-between-states</link><generator>RSS for Node</generator><lastBuildDate>Tue, 04 Aug 2026 12:24:11 GMT</lastBuildDate><atom:link href="https://forum.qt.io/topic/144382.rss" rel="self" type="application/rss+xml"/><pubDate>Fri, 14 Apr 2023 11:08:22 GMT</pubDate><ttl>60</ttl></channel></rss>