Tutorial de SDL – Parte 23 – Gerenciamento avançado do tempo

Continuando nossa série de artigos traduzido do site lazyfoo, agora que criamos um timer básico, iremos criar um que possa ser iniciado/pausado e parado.

//The application time based timer
class LTimer
{
   public:
   //Initializes variables
   LTimer();
   //The various clock actions
   void start();
   void stop();
   void pause();
   void unpause();
   //Gets the timer's time
   Uint32 getTicks();
   //Checks the status of the timer
   bool isStarted();
   bool isPaused();
   private:
   //The clock time when the timer started
   Uint32 mStartTicks;
   //The ticks stored when the timer was paused
   Uint32 mPausedTicks;
   //The timer status
   bool mPaused;
   bool mStarted;
};
Para podermos usar esses novos recursos, iremos criar uma classe para nosso timer. Ela irá ter todas as funções básicas (iniciar/parar/pausar/retomar) e verificação do status. Em termos de atributos, teremos o tempo inicial como antes, uma variável para armazenar o tempo quando pausamos, e flags de status para registrar se o times está em execução ou parado.
LTimer::LTimer()
{
   //Initialize the variables
   mStartTicks = 0;
   mPausedTicks = 0;
   mPaused = false;
   mStarted = false;
}
Nosso construtor inicializa os atributos internos.
void LTimer::start()
{
   //Start the timer
   mStarted = true;
   //Unpause the timer
   mPaused = false;
   //Get the current clock time
   mStartTicks = SDL_GetTicks();
   mPausedTicks = 0;
}
A função start configura as flags startedpaused, obtém o tempo inicial do timer e inicializa o tempo de pausa para 0. Para esse timer, se quisermos reinicia-lo apenas chamamos esta função novamente. Como podemos iniciar o timer se ele estiver pausado e/ou em execução, precisamos nos certificar de limpar os dados de paused.
void LTimer::stop()
{
   //Stop the timer
   mStarted = false;
   //Unpause the timer
   mPaused = false;
   //Clear tick variables
   mStartTicks = 0;
   mPausedTicks = 0;
}
A função stop basicamente reinicializa todas as variáveis.
void LTimer::pause()
{
   //If the timer is running and isn't already paused
   if( mStarted && !mPaused )
   {
      //Pause the timer
      mPaused = true;
      //Calculate the paused ticks
      mPausedTicks = SDL_GetTicks() - mStartTicks;
      mStartTicks = 0;
   }
}
Quando tivermos que pausar o timer, necessitamos verificar se o timer está sendo executado pois faz sentido pausar o timer que não foi iniciado. Se o timer estiver sendo executado, configuramos a flag paused, armazenamos o tempo quando o timer foi pausado em mPausedTicks e resetamos o tempo inicial.
void LTimer::unpause()
{
   //If the timer is running and paused
   if( mStarted && mPaused )
   {
      //Unpause the timer
      mPaused = false;
      //Reset the starting ticks
      mStartTicks = SDL_GetTicks() - mPausedTicks;
      //Reset the paused ticks mPausedTicks = 0;
   }
}
Assim, quando retomamos o timer, queremos nos certificar que o timer esteja sendo executado e pausado porque não podemos retomar um timer que esteja parado ou em execução. Configuramos a flag paused e o novo tempo inicial.
Digamos que você inicia o timer quando SDL_GetTicks reprota 5000 ms e em seguida pause ele em 10000 ms. Isso significa que o tempo relativo do tempo de pausa é 5000 ms. Se quisermos retomar o timer quando SDL_GetTicks reprota 20000 ms, o novo tempo inicial será 20000 – 5000 ou 15000 ms. Dessa forma o tempo relativo ainda será 5000 ms do tempo atual retornado por SDL_GetTicks.
Uint32 LTimer::getTicks()
{
   //The actual timer time
   Uint32 time = 0;
   //If the timer is running
   if( mStarted )
   {
      //If the timer is paused
      if( mPaused )
      {
         //Return the number of ticks when the timer was paused
         time = mPausedTicks;
      }
      else
      {
         //Return the current time minus the start time
         time = SDL_GetTicks() - mStartTicks;
      }
   }
   return time;
}
Obter o tempo é um pouco mais complicado, já que nosso timer pode estar sendo executado, pausado ou parado. Se o timer estiver parado, apenas retornados o valor inicial 0. Se o timer estiver pausado, retornamos o tempo armazenado quando ele foi pausado. Se o timer estiver em execução e não pausado, retornamos o tempo relativo à quando ele foi iniciado.
bool LTimer::isStarted()
{
   //Timer is running and paused or unpaused
   return mStarted;
}
bool LTimer::isPaused()
{
   //Timer is running and paused
   return mPaused && mStarted;
}
Aqui temos algumas métodos acessórios para verificar o status do timer.
//Main loop flag
bool quit = false;
//Event handler
SDL_Event e;
//Set text color as black
SDL_Color textColor = { 0, 0, 0, 255 };
//The application timer
LTimer timer;
//In memory text stream
std::stringstream timeText;
Antes de entrarmos no loop principal, declaramos um objeto de timer e uma stream de string para armazenar o valor do tempo como texto.
else if( e.type == SDL_KEYDOWN )
{
   //Start/stop
   if( e.key.keysym.sym == SDLK_s )
   {
      if( timer.isStarted() )
      {
         timer.stop();
      }
      else
      {
         timer.start();
      }
   }
   //Pause/unpause
   else if( e.key.keysym.sym == SDLK_p )
   {
      if( timer.isPaused() )
      {
         timer.unpause();
      }
      else
      {
         timer.pause();
      }
   }
}
Quando pressionamos a tecla s, verificamos se o timer foi iniciado. Caso afirmativo, paramos ele. Caso não tenha sido, iniciamos. Quando pressionamos a tecla p, verificamos se o timer está pausado. Caso afirmativo, retomamos a execução. Caso contrário, pausamos ele.
//Set text to be rendered
timeText.str( "" );
timeText << "Seconds since start time " << ( timer.getTicks() / 1000.f ) ;
//Render text
if( !gTimeTextTexture.loadFromRenderedText( timeText.str().c_str(), textColor ) )
{
   printf( "Unable to render time texture!\n" );
}
//Clear screen
SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
SDL_RenderClear( gRenderer );
//Render textures
gStartPromptTexture.render( ( SCREEN_WIDTH - gStartPromptTexture.getWidth() ) / 2, 0 );
gPausePromptTexture.render( ( SCREEN_WIDTH - gPausePromptTexture.getWidth() ) / 2, gStartPromptTexture.getHeight() );
gTimeTextTexture.render( ( SCREEN_WIDTH - gTimeTextTexture.getWidth() ) / 2, ( SCREEN_HEIGHT - gTimeTextTexture.getHeight() ) / 2 );
//Update screen
SDL_RenderPresent( gRenderer );
Antes de renderizar, escrevemos o tempo atual em um stream de string. A razão para dividirmos esse valor por 1000 é por queremos o tempo em segundos.
Após isso renderizamos o texto em uma textura e finalmente desenhamos todas as texturas na tela.
Baixe os arquivos de código fonte do exemplo desse artigo aqui.