0

I was trying to use the similar way that Kuba has done in post here. Basically I want to construct a overridden StatefulObject programatically using

void Command::buildStatefulCommands(DeviceComm* dev, COMMAND_TYPE cmdtype, const QAbstractProxyModel* entries)
{
// according to cmdtype and number of entries, build the transition mapping dynamically
....
switch (cmdtype) {
case CMDUploadEntries: {
    connectSignals();  // the same as in example
    send  (&s_start, dev, QByteArray::fromHex(entries.at(index,0))); // 'dev' changed to use my own DeviceComm for communication, 
    expect(&s_start, dev, QByteArray::fromHex(entries.at(index,1)), &s_ok_01, 3000, &s_failed);
    expect(&s_start, dev, QByteArray::fromHex(entries.at(index,2)), &s_ok_02, 3000, &s_failed);

....
}

The state transition building process of StatefulObject will be called everytime a button is clicked (because the data to send might be changed)

void MainWindow::on_btnSend_clicked(){
....
// the cmdtype is used for different tasks
Command* cmd = Command::getCommand(cmdtype);
    cmd->buildStatefulCommands(m_commDev, cmdtype, getDatabase());
   
    if (cmd->isRunning())
        cmd->stop();
    cmd->start();
}

Question 1. As the setup of statemachine is done multiple times, how to disconnect the old signal/slot pair? I guess every time 'send' invocation will cause a new lambda anonymous functor be created due to the capture.

void send(QAbstractState * src, QIODevice * dev, const QByteArray & data) {
// how to disconnect the previous connect?
conn = 
    QObject::connect(src, &QState::entered, dev, [dev, data]{
  dev->write(data);
     });
}

The second related question, instead of using 'readyRead' signal as in original 'expect' while adding transition

void expect(QState * src, QIODevice * dev, const QByteArray & pattern, QAbstractState * dst,
        int timeout = 0, QAbstractState * dstTimeout = nullptr)
{
   addTransition(src, dst, dev, SIGNAL(readyRead()), [dev, pattern]{
      return hasLine(dev, data);
   });
   if (timeout) delay(src, timeout, dstTimeout);
}

how can I use a signal 'responsePosted' that has argument (say, 'payload') to be connected with lambda that still has capture list (i.e. the pattern to check with)? I tried to use auto to cast into function pointer, but not supported with capture in it.

Q_SIGNALS:  // 'dev' signals
    void responsePosted(const QByteArray& payload);

....
// I want to have different pattern to check with while building the state transition table
addTransition(src, dst, dev, SIGNAL(responsePosted()), [dev, pattern](const QByteArray& payload){
  return dev->matching(payload, pattern);
 });

The template functor in GuardedSignalTransition for 'eventTest' does not seem to take any argument. How to wrap it with argument?

template <typename F>
class GuardedSignalTransition : public QSignalTransition {
   F m_guard;
   QByteArray m_pattern;   // << I want to store the pattern here
protected:
   bool eventTest(QEvent * ev) Q_DECL_OVERRIDE {
      return QSignalTransition::eventTest(ev) && m_guard();  // <<<< I guess 'ev' has the passed-in argument ?
   }

template <typename F> static GuardedSignalTransition<F> *
addTransition(QState * src, QAbstractState *target,
              const QObject * sender, const char * signal, F && guard) {  // What function type will F be?
   auto t = new GuardedSignalTransition<typename std::decay<F>::type>
         (sender, signal, std::forward<F>(guard));  // what should I put here ?
   t->setTargetState(target);
   src->addTransition(t);
   return t;
}

The std::forward, std::move, && annotation and std::decay are difficult to understand.

wanyancan
  • 370
  • 3
  • 8

1 Answers1

0

I kindof figoured out how to pass the lambda by using extra typename in template, but the two problems above are still not solved (i.e. remove old transition and use of lambda with capture and argument as && parameter) .

void Command::buildStatefulCommands(DeviceComm* dev, COMMAND_TYPE cmdtype, const QAbstractProxyModel* table)
{
...
   // the pattern to check is passed at construction call
   setTransition(&s_start, dev, QByteArray::fromHex("88"), &s_ok, 3000, &s_failed);
...

The 'pattern' to check and the lambda functor are passed to setTransition as

void Command::setTransition(QState* src, DeviceComm* dev, const QByteArray& ptn, QAbstractState* dst, int timeout, QAbstractState* dstTimeout)
{
    auto* c = static_cast<bool(*)(const QByteArray&, const QByteArray&)>([](const QByteArray& payload, const QByteArray& pattern) {
        
        return true;
    });
    // the ptn is saved in the 'Transition', while payload will be passed from SIGNAL
    addTransition(src, dst, dev, ptn, SIGNAL(responsePosted(const QByteArray&)), c );
    if (timeout) delay(src, timeout, dstTimeout);
}

The main addTransition part is implemented with three types in template. I want to learn how to simplify it? Currently, the functor's return type and arguments are explicitly declared.

template <typename F, typename G, typename H> static Command::MyTransition<F, G, H>*
Command::addTransition(QState* src, QAbstractState* target,
    const QObject* sender, const QByteArray& pattern, const char* signal, F(*guard)(G,H) ) {

    auto kk = new MyTransition<F,G,H>(sender, signal, pattern, guard);

    kk->setTargetState(target);
    src->addTransition(kk);
    return kk;
}

The customized QSignalTransition is defined as

template <class F, class G, class H> bool Command::MyTransition<F, G, H>::eventTest(QEvent* ev) {
    if (!QSignalTransition::eventTest(ev))
        return false;
    QStateMachine::SignalEvent* se = static_cast<QStateMachine::SignalEvent*>(ev);
    QList<QVariant> args = se->arguments();
    if (args.count() > 0) {
        // how to cast into G and H types  for payload and m_pattern?
        QByteArray payload = se->arguments().at(0).toByteArray();
        return m_guard(payload, m_pattern);
    }
    return QSignalTransition::eventTest(ev);
}

template <typename F, typename G, typename H> Command::MyTransition<F, G, H>::MyTransition(const QObject* sender, const char* signal, const H& pattern, F(*guard)(const G&, const H&))
    :QSignalTransition(sender, signal), m_guard(guard), m_pattern(pattern) {}

And the declaration part is

template <typename F, typename G, typename H>
class MyTransition : public QSignalTransition {
    F(*m_guard)(const G&, const H&);  // store the lambda functor
    H m_pattern;  // stor the pattern for successful reply
protected:
    bool eventTest(QEvent* ev) Q_DECL_OVERRIDE;
public:
    MyTransition(const QObject* sender, const char* signal, const H& pattern, F(*guard)(const G&, const H&));
};

I feel that the whole purpose of going such a long way is to ensure that a flexible way of defining state transition could be done. But as you can see here when calling m_guard, I have already made it exlicity what type the arguments are. Then what is the point of using template?

There's also another weird problem. The above code for storing the m_pattern, I have to use QByteArray m_pattern instead of "H m_pattern". Eventhough the m_pattern was initialized correctly in MyTransition constructor, inside eventTest(), it is still empty!

    class MyTransition : public QSignalTransition {
    ....
    H m_pattern;  // It will throw error at runtime in eventTest()
    }

The overridden eventTest() will throw error:

template <class F, class G, class H> bool MyTransition<F, G, H>::eventTest(QEvent* ev) {
...
qDebug() << "event test pattern: " << this->m_pattern; // will throw error
    return m_guard(qv.toByteArray(), this->m_pattern);
...
}
....
// inside lambda function, 
auto* c = static_cast<bool(*)(const QByteArray&, const QByteArray&)>([](const QByteArray& payload, const QByteArray& pattern) {
//      qDebug() << pattern.toHex(' ');  // will also throw error
        return payload.contains(pattern);
    });
    

There's no problem if I declare explicitly 'QByteArray m_pattern;'

wanyancan
  • 370
  • 3
  • 8