diff --git a/headers/enduro2d/core/engine.hpp b/headers/enduro2d/core/engine.hpp index 3e402553..39edfb25 100644 --- a/headers/enduro2d/core/engine.hpp +++ b/headers/enduro2d/core/engine.hpp @@ -70,6 +70,18 @@ namespace e2d bool fullscreen_{false}; }; + class timer_parameters { + public: + timer_parameters& minimal_framerate(u32 value) noexcept; + timer_parameters& maximal_framerate(u32 value) noexcept; + + u32 minimal_framerate() const noexcept; + u32 maximal_framerate() const noexcept; + private: + u32 minimal_framerate_{30u}; + u32 maximal_framerate_{1000u}; + }; + class parameters { public: parameters() = delete; @@ -79,21 +91,25 @@ namespace e2d parameters& company_name(str_view value) noexcept; parameters& debug_params(const debug_parameters& value); parameters& window_params(const window_parameters& value); + parameters& timer_params(const timer_parameters& value); str& game_name() noexcept; str& company_name() noexcept; debug_parameters& debug_params() noexcept; window_parameters& window_params() noexcept; + timer_parameters& timer_params() noexcept; const str& game_name() const noexcept; const str& company_name() const noexcept; const debug_parameters& debug_params() const noexcept; const window_parameters& window_params() const noexcept; + const timer_parameters& timer_params() const noexcept; private: str game_name_{"noname"}; str company_name_{"noname"}; debug_parameters debug_params_; window_parameters window_params_; + timer_parameters timer_params_; }; public: engine(const parameters& params); @@ -102,6 +118,13 @@ namespace e2d template < typename Application, typename... Args > bool start(Args&&... args); bool start(application_uptr app); + + f32 time() const noexcept; + f32 delta_time() const noexcept; + + u32 frame_rate() const noexcept; + u32 frame_count() const noexcept; + f32 realtime_time() const noexcept; private: class internal_state; std::unique_ptr state_; diff --git a/headers/enduro2d/utils/time.hpp b/headers/enduro2d/utils/time.hpp index de83da03..676e9b63 100644 --- a/headers/enduro2d/utils/time.hpp +++ b/headers/enduro2d/utils/time.hpp @@ -8,27 +8,6 @@ #include "_utils.hpp" -namespace e2d { namespace time -{ - template < typename T > - const seconds& second() noexcept { - static seconds second = seconds(T(1)); - return second; - } - - template < typename T > - const seconds& minute() noexcept { - static seconds minute = second() * T(60); - return minute; - } - - template < typename T > - const seconds& hour() noexcept { - static seconds hour = minute() * T(60); - return hour; - } -}} - namespace e2d { template < typename T > @@ -143,6 +122,63 @@ namespace e2d { namespace time } }} +namespace e2d { namespace time +{ + template < typename T > + const seconds& second() noexcept { + static seconds second = seconds(T(1)); + return second; + } + + template < typename T > + const milliseconds& second_ms() noexcept { + static milliseconds second_ms = to_milliseconds(second()); + return second_ms; + } + + template < typename T > + const microseconds& second_us() noexcept { + static microseconds second_us = to_microseconds(second()); + return second_us; + } + + template < typename T > + const seconds& minute() noexcept { + static seconds minute = second() * T(60); + return minute; + } + + template < typename T > + const milliseconds& minute_ms() noexcept { + static milliseconds minute_ms = to_milliseconds(minute()); + return minute_ms; + } + + template < typename T > + const microseconds& minute_us() noexcept { + static microseconds minute_us = to_microseconds(minute()); + return minute_us; + } + + template < typename T > + const seconds& hour() noexcept { + static seconds hour = minute() * T(60); + return hour; + } + + template < typename T > + const milliseconds& hour_ms() noexcept { + static milliseconds hour_ms = to_milliseconds(hour()); + return hour_ms; + } + + template < typename T > + const microseconds& hour_us() noexcept { + static microseconds hour_us = to_microseconds(hour()); + return hour_us; + } +}} + namespace e2d { namespace time { template < typename TimeTag > diff --git a/sources/enduro2d/core/engine.cpp b/sources/enduro2d/core/engine.cpp index c40b60af..3d61fe95 100644 --- a/sources/enduro2d/core/engine.cpp +++ b/sources/enduro2d/core/engine.cpp @@ -83,6 +83,28 @@ namespace e2d return console_logging_; } + // + // engine::timer_parameters + // + + engine::timer_parameters& engine::timer_parameters::minimal_framerate(u32 value) noexcept { + minimal_framerate_ = value; + return *this; + } + + engine::timer_parameters& engine::timer_parameters::maximal_framerate(u32 value) noexcept { + maximal_framerate_ = value; + return *this; + } + + u32 engine::timer_parameters::minimal_framerate() const noexcept { + return minimal_framerate_; + } + + u32 engine::timer_parameters::maximal_framerate() const noexcept { + return maximal_framerate_; + } + // // engine::window_parameters // @@ -142,6 +164,11 @@ namespace e2d return *this; } + engine::parameters& engine::parameters::timer_params(const timer_parameters& value) { + timer_params_ = value; + return *this; + } + str& engine::parameters::game_name() noexcept { return game_name_; } @@ -158,6 +185,10 @@ namespace e2d return window_params_; } + engine::timer_parameters& engine::parameters::timer_params() noexcept { + return timer_params_; + } + const str& engine::parameters::game_name() const noexcept { return game_name_; } @@ -174,14 +205,93 @@ namespace e2d return window_params_; } + const engine::timer_parameters& engine::parameters::timer_params() const noexcept { + return timer_params_; + } + // - // engine::internal_state + // engine // class engine::internal_state final : private e2d::noncopyable { public: - internal_state() = default; + internal_state(const parameters& params) + : timer_params_(params.timer_params()) + { + const auto first_frame_time = math::clamp( + math::max(timer_params_.minimal_framerate(), timer_params_.maximal_framerate()), + 1u, + 1000u); + delta_time_us_.store( + (time::second_us() / math::numeric_cast(first_frame_time)).value); + } ~internal_state() noexcept = default; + public: + f32 time() const noexcept { + return time::to_seconds( + make_microseconds(time_us_.load()).cast_to()).value; + } + + f32 delta_time() const noexcept { + return time::to_seconds( + make_microseconds(delta_time_us_.load()).cast_to()).value; + } + + u32 frame_rate() const noexcept { + return frame_rate_.load(); + } + + u32 frame_count() const noexcept { + return frame_count_.load(); + } + + f32 realtime_time() const noexcept { + const auto delta_us = time::now_us().cast_to() - init_time_; + return time::to_seconds(delta_us.cast_to()).value; + } + public: + void calculate_end_frame_timers() noexcept { + const auto second_us = time::second_us(); + + const auto minimal_delta_time_us = + second_us / math::numeric_cast(math::clamp( + timer_params_.maximal_framerate(), 1u, 1000u)); + + const auto maximal_delta_time_us = + second_us / math::numeric_cast(math::clamp( + timer_params_.minimal_framerate(), 1u, 1000u)); + + auto now_us = time::now_us().cast_to(); + while ( now_us - prev_frame_time_ < minimal_delta_time_us ) { + std::this_thread::yield(); + now_us = time::now_us().cast_to(); + } + + delta_time_us_.store(math::minimized( + now_us - prev_frame_time_, + maximal_delta_time_us).value); + + time_us_.store((now_us - init_time_).value); + prev_frame_time_ = now_us; + + frame_count_.fetch_add(1); + frame_rate_counter_.fetch_add(1); + while ( now_us - prev_frame_rate_time_ >= second_us ) { + prev_frame_rate_time_ += second_us; + frame_rate_.store(frame_rate_counter_.exchange(0)); + } + } + private: + timer_parameters timer_params_; + microseconds init_time_{time::now_us().cast_to()}; + microseconds prev_frame_time_{time::now_us().cast_to()}; + microseconds prev_frame_rate_time_{time::now_us().cast_to()}; + std::atomic_uint64_t time_us_{0}; + std::atomic_uint64_t delta_time_us_{0}; + std::atomic_uint32_t frame_rate_{0}; + std::atomic_uint32_t frame_count_{0}; + std::atomic_uint32_t frame_rate_counter_{0}; + u8 _pad[4] = {0}; }; // @@ -189,7 +299,7 @@ namespace e2d // engine::engine(const parameters& params) - : state_(new internal_state()) + : state_(new internal_state(params)) { // setup debug @@ -244,20 +354,48 @@ namespace e2d engine::~engine() noexcept = default; bool engine::start(application_uptr app) { + E2D_ASSERT(main_thread() == std::this_thread::get_id()); + if ( !app || !app->initialize() ) { the().error("ENGINE: Failed to initialize application"); return false; } - try { - while ( app->frame_tick() ) { - the().frame_tick(); - window::poll_events(); + + while ( true ) { + try { + if ( !app->frame_tick() ) { + break; + } + state_->calculate_end_frame_timers(); + } catch ( ... ) { + app->shutdown(); + throw; } - } catch ( ... ) { - app->shutdown(); - throw; + the().frame_tick(); + window::poll_events(); } + app->shutdown(); return true; } + + f32 engine::time() const noexcept { + return state_->time(); + } + + f32 engine::delta_time() const noexcept { + return state_->delta_time(); + } + + u32 engine::frame_rate() const noexcept { + return state_->frame_rate(); + } + + u32 engine::frame_count() const noexcept { + return state_->frame_count(); + } + + f32 engine::realtime_time() const noexcept { + return state_->realtime_time(); + } } diff --git a/untests/sources/untests_utils/time.cpp b/untests/sources/untests_utils/time.cpp index 0d2b9ae6..d84ae168 100644 --- a/untests/sources/untests_utils/time.cpp +++ b/untests/sources/untests_utils/time.cpp @@ -10,8 +10,16 @@ using namespace e2d; TEST_CASE("time") { { REQUIRE(time::second().value == 1); + REQUIRE(time::second_ms().value == 1000); + REQUIRE(time::second_us().value == 1'000'000); + REQUIRE(time::minute().value == 60); + REQUIRE(time::minute_ms().value == 60000); + REQUIRE(time::minute_us().value == 60'000'000); + REQUIRE(time::hour().value == 60 * 60); + REQUIRE(time::hour_ms().value == 60 * 60 * 1000); + REQUIRE(time::hour_us().value == 60 * 60 * i64(1'000'000)); } { REQUIRE(make_seconds(1).convert_to().value == 1000);