타이머 간략화
Project/Win32API로 던그리드 모작

타이머 간략화

Woon2World :: Programming & Art Life

 

이전 구현

 

https://jartlife.tistory.com/64

 

게임 타이머 구현

가변 프레임 이전의 게임 타이머 구현은 아래 글에서 볼 수 있다. https://jartlife.tistory.com/52 게임 프레임 관리 게임의 동작 게임은 일정 시간 간격마다 사용자 입력을 처리하고 객체들을 업데이트

jartlife.tistory.com

 

이전 구현은 위 글에서 볼 수 있다.

 

중첩 클래스가 가독성을 좀 심하게 나쁘게 했다.

그리고 타이머 클래스가 지고 있는 책임이 너무 무거운 것 같다.

 

게임의 기능을 추가하려고 그 복잡한 클래스 중첩 구조를 추적해서 update()와 render() 함수를 건드리기란..

 

별도의 클래스를 추가하는데 timer.h 파일을 매번 건드려야 한다는 것도 심각한 설계 미스다.

 

타이머를 다시 하나로 통합하였고, 타이머가 지지 않아도 될 책임은 분리하였다.

 

또한 타이머를 정적 클래스로 사용하지 않고 여러 개의 인스턴스 생성이 가능하도록 바꾸었다.

타이머를 단 한 개밖에 안 만들더라도, 특정 지역의 객체로 만들어 전역 접근이 불가능하게 할 필요가 있었다.

 

 

 

 


개요

  • class timer
    : 윈도우API에서 시간 관련 기능을 담당하는 클래스입니다.

    • const UINT timer_id
      : 타이머끼리 구분하는 id입니다.
    • float clock
      : 해당 타이머를 이용할 때 사용할 단위 시간입니다.
      예를 들어, 한 애니메이션의 프레임 전환은 5 * clock 마다 이루어질 수 있습니다.
      타이머 자체적으론 이용하지 않지만, 타이머에 의존하는 클래스들은 이 변수를 이용하여 쉽게 배속 효과 같은 기능을 구현할 수 있을 것입니다.

    • void alarm( const float ms_delay, std::function<void()>&& to_do )
      : ms_delay 뒤에 to_do 를 실행합니다.
      일정 시간 뒤에 동작이 이루어지도록 하고 싶을 때 사용합니다.

    • void setFPS( const float fps )
      : FPS( Frames Per Second )를 설정합니다.

    • void update()
      : 윈도우의 타이머에서 timer_id 의 타이머가 불릴 때마다 update()를 직접 호출해야 합니다.
      윈도우 타이머에게 책임을 부여하여 타이머 클래스 내부에 게임의 세부사항을 구현할 필요가 없습니다.
      따라서 타이머 객체를 손쉽게 game과 scene과 같은 클래스의 멤버로 만들 수 있습니다.

    • const float getlag() const
      : 현재 지연 정도를 반환합니다.
      지연이 심하면 몇몇 작업을 생략해야 할 것입니다.

    • const float getobjFPS() const
      : 설정된 FPS를 반환합니다.

    • const float getcurFPS() const
      : 현재 FPS를 반환합니다.
      설정된 FPS는 높더라도, 성능의 한계로 실제 FPS는 높지 않을 수 있습니다
    • const float get_ms_per_frame() const
      : 프레임 당 시간을 반환합니다.

 

 

 

 


코드

 

#ifndef _timer
#define _timer
 
#ifdef max
#undef max        // c 매크로 max를 제거하기 위함
#endif
 
#include <chrono>
#include <iostream>
#include "TMP.h"
#include <algorithm>
#include <queue>
#include <numeric>
#include <deque>
#include <functional>
#include <utility>
 
class timer
{
private:
    struct delayed
    {
        const bool operator>const delayed& other )
        {
            return ms_delay > other.ms_delay;
        }
 
        float ms_delay;
        std::function< void() > to_do;
    };
 
public:
    float clock;
    const UINT timer_id;
 
    void alarm( float ms_delay, std::function< void() >&& to_do )
    {
        alarms.push( delayed{ ms_delay, to_do } );
    }
 
    void setFPS( const float fps )
    {
        objfps = fps;
        curfps = fps;
        ms_per_frame = 1000 / fps;
        KillTimer( hWnd, timer_id );
        SetTimer( hWnd, timer_id, static_cast< UINT >( ms_per_frame ), nullptr );
    }
 
    void update()
    {
        using namespace std::chrono;
        static system_clock::time_point last_tp = system_clock::now();
        auto cur_tp = system_clock::now();
 
        auto interval = duration_cast< nanoseconds >( cur_tp - last_tp ).count()
            / static_cast< float >( nanoseconds::period::den / milliseconds::period::den );
 
        prevent_overflow();
        process_alarm();
        update_curfps( interval );
 
        ms_time += interval;
        lag = std::max( lag + interval - ms_time, 0.f );
 
        last_tp = cur_tp;
    }
 
    const float getlag() const
    {
        return lag;
    }
 
    const float getobjFPS() const 
    {
        return objfps;
    }
 
    const float getcurFPS() const
    {
        return curfps;
    }
 
    const float get_ms_per_frame() const
    {
        return ms_per_frame;
    }
 
    timer( HWND hWnd, const UINT timer_id, const float fps, const float clock = 10.f ) : ms_time{ 0 }, lag{ 0 },
        hWnd{ hWnd }, timer_id{ timer_id }, clock{ clock }, objfps{ fps }, curfps{ fps }, ms_per_frame{ 1000 / fps }
    {
        SetTimer( hWnd, timer_id, static_cast< UINT >( ms_per_frame ), nullptr );
    }
 
    ~timer()
    {
        KillTimer( hWnd, timer_id );
    }
 
    // 복사는 이용되지 않을 것이라 가정해 구현하지 않았다.
    // timer는 id 정보를 들고 있으므로, 후에 구현한다면 독립된 id를 보장하는 복사 동작이 필요하다.
 
    timer( const timer& other ) = default;
 
    timer& operator=const timer& ) = default;
 
private:
    float lag;
    float ms_per_frame;
    float curfps;
    float objfps;
    float ms_time;
    std::priority_queue< delayed, std::vector< delayed >std::greater<> > alarms;
    HWND hWnd;
 
    void prevent_overflow()
    {
        static constexpr float limit = 10'000'000.f;
        if ( ms_time > limit )
        {
            ms_time -= limit;
            sub_delays( limit );
        }
    }
 
    void sub_delays( const float val )
    {
        decltype( alarms ) temp;
 
        while ( !alarms.empty() )
        {
            auto a = alarms.top();
            a.ms_delay -= val;
 
            temp.push( std::move( a ) );
            alarms.pop();
        }
 
        alarms = std::move( temp );
    }
 
    void process_alarm()
    {
        while ( is_there_an_alarm_on_time() )
        {
            alarms.top().to_do();
            alarms.pop();
        }
    }
 
    const bool is_there_an_alarm_on_time()
    {
        if ( alarms.empty() )
        {
            return false;
        }
        return alarms.top().ms_delay < ms_time;
    }
 
    void update_curfps( const float interval )
    {
        curfps = 1000 / interval;
    }
};
 
#endif
cs