[Solved] State Machine Design Problem



  • I have a fairly large and complicated app that is based on QStateMachine and uses Qt 4.7.

    Many of my states just display a Widget, then wait for a signal from the Widget to let me know what the user did. My state then does whatever processing is required and emits a signal that is wired up to cause a state transition.

    Other states communicate with a server. The communications are asynchronous, so the state starts the operation, then receives "percent done" updates, then either a success or failure signal when the operation completes. Again, the state does whatever processing is required, then emits a signal to cause a state transition.

    Currently, the signal handlers in my states just emit the signals inline. Most of the signals don't carry arguments, but some do.

    A new requirement has arisen. In the states where I was formerly just waiting for a signal back from the Widget I will now need to periodically run one of the async server operations. I can't disable the Widget buttons or anything silly like that, so if I receive a signal from the Widget while I am waiting for the server call to complete I can't just emit the signal that would cause the state transition. I need to wait until the server call finishes.

    I'm trying to figure out how to do the signal handling but defer issuing a signal until the server call completes. Whatever I do needs to be able to just emit the signal immediately when there is no server call in progress.

    I considered converting all of the Q_EMIT signalName() lines into function calls like emitSignalName(). Then there could be a member function that took a pointer to an emitSignalName() function. If there is no server call in progress, just call through the pointer. If there is a server call, store the pointer and call through it when the server call completes. That's fairly simple, but the signals that send data are a problem. The data needs to be stored somewhere for use in the deferred emit of the signal.

    I also considered defining a MySignal base class that defines a virtual emit() function, then inheriting a separate class for each signal type I define in my state classes. Each of them would overload emit() to emit the signal it defines. When my state gets a signal from the Widget it would create an instance of the proper MySignal class, then pass a pointer to it to a function I would define in the state's base class. That function would check for a server operation in progress. If one is in progress, it would store the pointer (as a MySignal base class pointer) for later. When the server call completes, we would find the stored pointer and call it's emit() function. The emit() function would be called immediately if there were no server call in progress. This handles the signature issue because each signal class can have a unique constructor that takes all the arguments, and the emit() method would know how to use them. But it's cumbersome, and the signals would be emitted by the signal objects, not by the state object. That messes up my signal wiring for the state machine.

    I could change all of my signals so that they all take a void* argument, and make the receiver of the signal be responsible for knowing what the pointer really points to. That solves the function signature problem. But it's nasty.

    I hoped that I would have an "Aha" moment while writing this, but that didn't happen. I'm also wondering if I'm overlooking something that would simplify the whole problem. Does anyone have any ideas?

    ========
    mrjj replied suggesting functors might be of help. That got me thinking of places in one of the support libraries where I had used boost::function and boost::bind to store functors for callbacks.

    Using boost::bind for this made it all possible. I now call a template function like this:

    emit(boost::bind(mySignal, this, arg0, ..., argn);

    when I want to emit a signal that would cause a state transition. That lets me check whether a server operation is in progress. If not, I just call through the functor to emit the signal, but if an operation is in progress I can store the functor, without knowing anything about what signal it calls or what arguments it passes (boost::bind stores copies of the arguments), and when my server operation completes I retrieve the functor and call through it to emit the signal.

    Thanks to mrjj for the reply. It got me thinking in the right direction.



  • Would QObject::blockSignals do the trick?

    Edit: fixed the link to 4.8 version


  • Qt Champions 2016

    @Rush-Manbert said:

    Hi and welcome
    Just a idea/though/random input

    If you could make it work with function calls like emitSignalName()
    but have issue with those that has data, would
    a functor object work for that solution?
    http://www.cprogramming.com/tutorial/functors-function-objects-in-c++.html

    Would still need different functors or some way of knowing which signal to send later so might end up having the same issues as your idea with MySignal base class.



  • @JohanSolo Thanks for the suggestion. I had done some reading about blockSignals() but I went back and looked again. The problem with blockSignals() is that it still leaves me needing to know that I wanted to emit a particular signal but couldn't. I somehow need to know how to re-emit the signal later when the server interaction completes.

    An event filter would almost work. I could add a filter for my QState objects. In that case what comes to the eventFilter() method is a QSignalEvent. If I could detect in the filter that my server operation was in progress, then I could save away the event (or the event parameters) then use them to post the event equivalent of the original signal at the correct time. I could dynamic cast the event pointer and even peel off the arguments, but there does not appear to be any public constructor for a QStateMachine::SignalEvent, so I have no way to reconstruct a copy of the original event.

    I also looked at using QAbstractTransition or QSignalTransition, but those basically have the same problem. There's no way to store the event to use later.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.