Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Game Development
  4. Pause and Resume a QTimer
QtWS25 Last Chance

Pause and Resume a QTimer

Scheduled Pinned Locked Moved Game Development
qtimerpauseresumetimer
8 Posts 3 Posters 2.6k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • B Offline
    B Offline
    bstone100
    wrote on last edited by bstone100
    #1

    I wrote a Timer class that adds pause and resume functionality to QTimer. I've found this useful for making games that employ a pause button.

    Pausing saves the remaining time and stops the timer. Resuming runs the timer once with the remaining time as its interval. Upon timeout, the old interval and singleShot properties are restored, and the timer starts if singleShot was false.

    Usage:

    
    ... Timer *generateEnemyTimer = new Timer(this, 200); ...
    
    ... generateEnemyTimer->start(); ...
    
    void Game::togglePause()
    {
        gamePaused = !gamePaused;
    
        if (gamePaused) {
    
            generateEnemyTimer->pause();
    
        } else {
    
            generateEnemyTimer->resume();
    
        }
    }
    

    timer.h

    #ifndef TIMER_H
    #define TIMER_H
    
    #include <QTimer>
    
    class Timer : public QTimer
    {
    public:
        Timer(QObject *parent = nullptr, int interval = 0, bool singleShot = false);
    
        void pause();
        void resume();
    
        void changeInterval(int newInterval);
        void changeSingleShot(bool newSingleShot);
    
    private:
        int remaining;
        int oldInterval;
        bool singleShot;
    
        void reset();
    };
    
    #endif // TIMER_H
    

    timer.cpp

    #include "timer.h"
    #include "QtCore/qdebug.h"
    
    Timer::Timer(QObject *parent, int interval, bool singleShot)
        : QTimer(parent), oldInterval(interval), singleShot(singleShot)
    {
        setInterval(interval);
        setSingleShot(singleShot);
    }
    
    // use instead of setInterval()
    void Timer::changeInterval(int newInterval)
    {
        oldInterval = newInterval;
        setInterval(newInterval);
    }
    
    // use instead of setSingleShot()
    void Timer::changeSingleShot(bool newSingleShot)
    {
        singleShot = newSingleShot;
        setSingleShot(newSingleShot);
    }
    
    // save remaining time
    void Timer::pause()
    {
    //    qDebug() << "pause";
    
        remaining = remainingTime();
        stop();
    }
    
    // run for remaining time
    void Timer::resume()
    {
        if (remaining < 0) return;
    
    //    qDebug() << "resume: " << remaining << " ms remaining";
    
        setInterval(remaining);
        setSingleShot(true);
        QObject::connect(this, &QTimer::timeout, this, &Timer::reset);
        start();
    }
    
    // return timer to state before pause
    void Timer::reset()
    {
    //    qDebug() << "resetting to " << oldInterval << " ms";
    
        setInterval(oldInterval);
        setSingleShot(singleShot);
        QObject::disconnect(this, &QTimer::timeout, this, &Timer::reset);
        if (!singleShot) {
            start();
        }
    }
    
    1 Reply Last reply
    1
    • Chris KawaC Offline
      Chris KawaC Offline
      Chris Kawa
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi, welcome to the forum.

      Thanks for sharing, but your implementation has pretty serious problem. You're shadowing non-virtual methods setInterval and setSingleShot. That's not gonna work correctly. Consider this:

      QTimer* t = new Timer();
      t->setInteval(200); // not a virtual call, so QTimer::setInterval is executed, not Timer::setInterval
      

      You don't need to override those methods, store these values or modify timer's settings at all. You can simply call this in resume()

      QTimer::singleShot(remaining, this, &QTimer::start);
      

      and it will resume the timer after the remaining time.

      Btw. If you want a single shot connection you can pass Qt::SingleShotConnection to QObject::connect, so that you don't have to disconnect it manually in the slot.

      JoeCFDJ B 2 Replies Last reply
      2
      • Chris KawaC Chris Kawa

        Hi, welcome to the forum.

        Thanks for sharing, but your implementation has pretty serious problem. You're shadowing non-virtual methods setInterval and setSingleShot. That's not gonna work correctly. Consider this:

        QTimer* t = new Timer();
        t->setInteval(200); // not a virtual call, so QTimer::setInterval is executed, not Timer::setInterval
        

        You don't need to override those methods, store these values or modify timer's settings at all. You can simply call this in resume()

        QTimer::singleShot(remaining, this, &QTimer::start);
        

        and it will resume the timer after the remaining time.

        Btw. If you want a single shot connection you can pass Qt::SingleShotConnection to QObject::connect, so that you don't have to disconnect it manually in the slot.

        JoeCFDJ Online
        JoeCFDJ Online
        JoeCFD
        wrote on last edited by
        #3

        @Chris-Kawa
        Qt::SingleShotConnection is a good one. But, This flag was introduced in Qt 6.0.

        1 Reply Last reply
        0
        • Chris KawaC Chris Kawa

          Hi, welcome to the forum.

          Thanks for sharing, but your implementation has pretty serious problem. You're shadowing non-virtual methods setInterval and setSingleShot. That's not gonna work correctly. Consider this:

          QTimer* t = new Timer();
          t->setInteval(200); // not a virtual call, so QTimer::setInterval is executed, not Timer::setInterval
          

          You don't need to override those methods, store these values or modify timer's settings at all. You can simply call this in resume()

          QTimer::singleShot(remaining, this, &QTimer::start);
          

          and it will resume the timer after the remaining time.

          Btw. If you want a single shot connection you can pass Qt::SingleShotConnection to QObject::connect, so that you don't have to disconnect it manually in the slot.

          B Offline
          B Offline
          bstone100
          wrote on last edited by
          #4

          @Chris-Kawa Thanks for your response. I modified my implementation based on your recommendation.

          The reason I had done it the way I did was because I needed the single shot timer to emit timeout() before starting, but I see now that this can be achieved with timerEvent().

          1 Reply Last reply
          0
          • Chris KawaC Offline
            Chris KawaC Offline
            Chris Kawa
            Lifetime Qt Champion
            wrote on last edited by Chris Kawa
            #5

            @bstone100 said:

            I needed the single shot timer to emit timeout() before starting, but I see now that this can be achieved with timerEvent()

            You don't even need the event. You can just emit it directly, e.g.

            QTimer::singleShot(remaining, this, [this](){
               emit timeout();
               if (!isSingleShot())
                  start();
            });
            

            Btw. you added an empty constructor to mimic the API of QTimer. Instead of this boilerplate you can just do this:

            class Timer : public QTimer
            {
            public:
               using QTimer::QTimer;  //This will "import" all the constructors the base class has
            ...
            
            B 1 Reply Last reply
            4
            • Chris KawaC Chris Kawa

              @bstone100 said:

              I needed the single shot timer to emit timeout() before starting, but I see now that this can be achieved with timerEvent()

              You don't even need the event. You can just emit it directly, e.g.

              QTimer::singleShot(remaining, this, [this](){
                 emit timeout();
                 if (!isSingleShot())
                    start();
              });
              

              Btw. you added an empty constructor to mimic the API of QTimer. Instead of this boilerplate you can just do this:

              class Timer : public QTimer
              {
              public:
                 using QTimer::QTimer;  //This will "import" all the constructors the base class has
              ...
              
              B Offline
              B Offline
              bstone100
              wrote on last edited by bstone100
              #6

              @Chris-Kawa
              What's curious is that I can't do

              emit timeout();
              

              because "timer.cpp:26:21: Too few arguments to function call, expected 1, have 0
              qtimer.h:181:10: 'timeout' declared here"

              Q_SIGNALS:
                  void timeout(QPrivateSignal);
              

              but I can do

              emit timeout({});
              

              although I don't think you're supposed to be able to.

              1 Reply Last reply
              0
              • Chris KawaC Offline
                Chris KawaC Offline
                Chris Kawa
                Lifetime Qt Champion
                wrote on last edited by
                #7

                @bstone100 said:

                What's curious is that I can't do emit timeout();

                Ah, yes, sorry, timeout is a private signal. Bummer. Private signals are public methods so that you can connect to them, but have that "hidden" parameter, so only the class instance can call it.

                but I can do emit timeout({});

                I guess you can do that, but it's a bit of a dirty cheat, because you're using a quirk of the language to instantiate an object of a private struct QTimer::QPrivateSignal via implicit conversion from an initializer list.
                Well, the next best thing would be an event, although calling the handler directly is not a nice thing to do. You can rather sendEvent() to itself, so that users can install a filter if they want.

                B 1 Reply Last reply
                0
                • Chris KawaC Chris Kawa

                  @bstone100 said:

                  What's curious is that I can't do emit timeout();

                  Ah, yes, sorry, timeout is a private signal. Bummer. Private signals are public methods so that you can connect to them, but have that "hidden" parameter, so only the class instance can call it.

                  but I can do emit timeout({});

                  I guess you can do that, but it's a bit of a dirty cheat, because you're using a quirk of the language to instantiate an object of a private struct QTimer::QPrivateSignal via implicit conversion from an initializer list.
                  Well, the next best thing would be an event, although calling the handler directly is not a nice thing to do. You can rather sendEvent() to itself, so that users can install a filter if they want.

                  B Offline
                  B Offline
                  bstone100
                  wrote on last edited by
                  #8

                  @Chris-Kawa You know what I just realized... the QTimer::singleShot method doesn't even work because you won't know the remaining time if you pause again during the single shot.

                  1 Reply Last reply
                  1

                  • Login

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • Categories
                  • Recent
                  • Tags
                  • Popular
                  • Users
                  • Groups
                  • Search
                  • Get Qt Extensions
                  • Unsolved