mirror of
https://github.com/enduro2d/enduro2d.git
synced 2025-12-16 14:08:59 +07:00
533 lines
15 KiB
C++
533 lines
15 KiB
C++
/*******************************************************************************
|
|
* This file is part of the "Enduro2D"
|
|
* For conditions of distribution and use, see copyright notice in LICENSE.md
|
|
* Copyright (C) 2018-2020, by Matvey Cherevko (blackmatov@gmail.com)
|
|
******************************************************************************/
|
|
|
|
#include <enduro2d/core/engine.hpp>
|
|
|
|
#include <enduro2d/core/audio.hpp>
|
|
#include <enduro2d/core/dbgui.hpp>
|
|
#include <enduro2d/core/debug.hpp>
|
|
#include <enduro2d/core/deferrer.hpp>
|
|
#include <enduro2d/core/input.hpp>
|
|
#include <enduro2d/core/platform.hpp>
|
|
#include <enduro2d/core/profiler.hpp>
|
|
#include <enduro2d/core/render.hpp>
|
|
#include <enduro2d/core/vfs.hpp>
|
|
#include <enduro2d/core/window.hpp>
|
|
|
|
namespace
|
|
{
|
|
using namespace e2d;
|
|
|
|
template < typename Module, typename... Args >
|
|
Module& safe_module_initialize(Args&&... args) {
|
|
return modules::is_initialized<Module>()
|
|
? modules::instance<Module>()
|
|
: modules::initialize<Module>(std::forward<Args>(args)...);
|
|
}
|
|
|
|
void safe_register_predef_path(
|
|
vfs& the_vfs,
|
|
str_view scheme,
|
|
filesystem::predef_path predef_path)
|
|
{
|
|
str path;
|
|
if ( filesystem::extract_predef_path(path, predef_path) ) {
|
|
the_vfs.register_scheme_alias(scheme, url{"file", path});
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace e2d
|
|
{
|
|
//
|
|
// application
|
|
//
|
|
|
|
bool engine::application::initialize() {
|
|
return true;
|
|
}
|
|
|
|
void engine::application::shutdown() noexcept {
|
|
}
|
|
|
|
bool engine::application::frame_tick() {
|
|
return true;
|
|
}
|
|
|
|
void engine::application::frame_render() {
|
|
}
|
|
|
|
void engine::application::frame_finalize() {
|
|
}
|
|
|
|
//
|
|
// engine::debug_parameters
|
|
//
|
|
|
|
engine::debug_parameters& engine::debug_parameters::log_filename(str value) noexcept {
|
|
log_filename_ = std::move(value);
|
|
return *this;
|
|
}
|
|
|
|
engine::debug_parameters& engine::debug_parameters::file_logging(bool value) noexcept {
|
|
file_logging_ = value;
|
|
return *this;
|
|
}
|
|
|
|
engine::debug_parameters& engine::debug_parameters::console_logging(bool value) noexcept {
|
|
console_logging_ = value;
|
|
return *this;
|
|
}
|
|
|
|
const str& engine::debug_parameters::log_filename() const noexcept {
|
|
return log_filename_;
|
|
}
|
|
|
|
bool engine::debug_parameters::file_logging() const noexcept {
|
|
return file_logging_;
|
|
}
|
|
|
|
bool engine::debug_parameters::console_logging() const noexcept {
|
|
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
|
|
//
|
|
|
|
engine::window_parameters& engine::window_parameters::caption(str value) noexcept {
|
|
caption_ = std::move(value);
|
|
return *this;
|
|
}
|
|
|
|
engine::window_parameters& engine::window_parameters::size(const v2u& value) noexcept {
|
|
size_ = value;
|
|
return *this;
|
|
}
|
|
|
|
engine::window_parameters& engine::window_parameters::vsync(bool value) noexcept {
|
|
vsync_ = value;
|
|
return *this;
|
|
}
|
|
|
|
engine::window_parameters& engine::window_parameters::resizable(bool value) noexcept {
|
|
resizable_ = value;
|
|
return *this;
|
|
}
|
|
|
|
engine::window_parameters& engine::window_parameters::fullscreen(bool value) noexcept {
|
|
fullscreen_ = value;
|
|
return *this;
|
|
}
|
|
|
|
const str& engine::window_parameters::caption() const noexcept {
|
|
return caption_;
|
|
}
|
|
|
|
const v2u& engine::window_parameters::size() const noexcept {
|
|
return size_;
|
|
}
|
|
|
|
bool engine::window_parameters::vsync() const noexcept {
|
|
return vsync_;
|
|
}
|
|
|
|
bool engine::window_parameters::resizable() const noexcept {
|
|
return resizable_;
|
|
}
|
|
|
|
bool engine::window_parameters::fullscreen() const noexcept {
|
|
return fullscreen_;
|
|
}
|
|
|
|
//
|
|
// engine::parameters
|
|
//
|
|
|
|
engine::parameters::parameters(str game_name, str company_name) noexcept
|
|
: game_name_(std::move(game_name))
|
|
, company_name_(std::move(company_name)) {}
|
|
|
|
engine::parameters& engine::parameters::game_name(str value) noexcept {
|
|
game_name_ = std::move(value);
|
|
return *this;
|
|
}
|
|
|
|
engine::parameters& engine::parameters::company_name(str value) noexcept {
|
|
company_name_ = std::move(value);
|
|
return *this;
|
|
}
|
|
|
|
engine::parameters& engine::parameters::without_audio(bool value) {
|
|
without_audio_ = value;
|
|
return *this;
|
|
}
|
|
|
|
engine::parameters& engine::parameters::without_graphics(bool value) {
|
|
without_graphics_ = value;
|
|
return *this;
|
|
}
|
|
|
|
engine::parameters& engine::parameters::debug_params(debug_parameters value) noexcept {
|
|
debug_params_ = std::move(value);
|
|
return *this;
|
|
}
|
|
|
|
engine::parameters& engine::parameters::window_params(window_parameters value) noexcept {
|
|
window_params_ = std::move(value);
|
|
return *this;
|
|
}
|
|
|
|
engine::parameters& engine::parameters::timer_params(timer_parameters value) noexcept {
|
|
timer_params_ = std::move(value);
|
|
return *this;
|
|
}
|
|
|
|
str& engine::parameters::game_name() noexcept {
|
|
return game_name_;
|
|
}
|
|
|
|
str& engine::parameters::company_name() noexcept {
|
|
return company_name_;
|
|
}
|
|
|
|
bool& engine::parameters::without_audio() noexcept {
|
|
return without_audio_;
|
|
}
|
|
|
|
bool& engine::parameters::without_graphics() noexcept {
|
|
return without_graphics_;
|
|
}
|
|
|
|
engine::debug_parameters& engine::parameters::debug_params() noexcept {
|
|
return debug_params_;
|
|
}
|
|
|
|
engine::window_parameters& engine::parameters::window_params() noexcept {
|
|
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_;
|
|
}
|
|
|
|
const str& engine::parameters::company_name() const noexcept {
|
|
return company_name_;
|
|
}
|
|
|
|
bool engine::parameters::without_audio() const noexcept {
|
|
return without_audio_;
|
|
}
|
|
|
|
bool engine::parameters::without_graphics() const noexcept {
|
|
return without_graphics_;
|
|
}
|
|
|
|
const engine::debug_parameters& engine::parameters::debug_params() const noexcept {
|
|
return debug_params_;
|
|
}
|
|
|
|
const engine::window_parameters& engine::parameters::window_params() const noexcept {
|
|
return window_params_;
|
|
}
|
|
|
|
const engine::timer_parameters& engine::parameters::timer_params() const noexcept {
|
|
return timer_params_;
|
|
}
|
|
|
|
//
|
|
// engine
|
|
//
|
|
|
|
class engine::internal_state final : private e2d::noncopyable {
|
|
public:
|
|
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<u64>() / math::numeric_cast<u64>(first_frame_time)).value);
|
|
}
|
|
~internal_state() noexcept = default;
|
|
public:
|
|
f32 time() const noexcept {
|
|
return time::to_seconds(
|
|
make_microseconds(time_us_.load()).cast_to<f32>()).value;
|
|
}
|
|
|
|
f32 delta_time() const noexcept {
|
|
return time::to_seconds(
|
|
make_microseconds(delta_time_us_.load()).cast_to<f32>()).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<u64>() - init_time_;
|
|
return time::to_seconds(delta_us.cast_to<f32>()).value;
|
|
}
|
|
public:
|
|
void calculate_end_frame_timers() noexcept {
|
|
E2D_PROFILER_SCOPE("engine.wait_for_target_fps");
|
|
|
|
const auto second_us = time::second_us<u64>();
|
|
|
|
const auto minimal_delta_time_us =
|
|
second_us / math::numeric_cast<u64>(math::clamp(
|
|
timer_params_.maximal_framerate(), 1u, 1000u));
|
|
|
|
const auto maximal_delta_time_us =
|
|
second_us / math::numeric_cast<u64>(math::clamp(
|
|
timer_params_.minimal_framerate(), 1u, 1000u));
|
|
|
|
auto now_us = time::now_us<u64>();
|
|
while ( now_us - prev_frame_time_ < minimal_delta_time_us ) {
|
|
const auto sleep_us = minimal_delta_time_us - (now_us - prev_frame_time_);
|
|
const auto microsecond = make_microseconds<u64>(1000u);
|
|
if ( sleep_us > microsecond ) {
|
|
std::this_thread::sleep_for(time::to_chrono(sleep_us - microsecond));
|
|
} else {
|
|
std::this_thread::yield();
|
|
}
|
|
now_us = time::now_us<u64>();
|
|
}
|
|
|
|
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<u64> init_time_{time::now_us<u64>()};
|
|
microseconds<u64> prev_frame_time_{time::now_us<u64>()};
|
|
microseconds<u64> prev_frame_rate_time_{time::now_us<u64>()};
|
|
std::atomic<u64> time_us_{0};
|
|
std::atomic<u64> delta_time_us_{0};
|
|
std::atomic<u32> frame_rate_{0};
|
|
std::atomic<u32> frame_count_{0};
|
|
std::atomic<u32> frame_rate_counter_{0};
|
|
};
|
|
|
|
//
|
|
// engine
|
|
//
|
|
|
|
engine::engine(int argc, char *argv[], const parameters& params)
|
|
: state_(new internal_state(params))
|
|
{
|
|
// setup platform
|
|
|
|
safe_module_initialize<platform>(argc, argv);
|
|
|
|
// setup deferrer
|
|
|
|
safe_module_initialize<deferrer>();
|
|
|
|
// setup profiler
|
|
|
|
safe_module_initialize<profiler>(
|
|
the<deferrer>());
|
|
|
|
// setup debug
|
|
|
|
safe_module_initialize<debug>();
|
|
|
|
if ( params.debug_params().console_logging() ) {
|
|
the<debug>().register_sink<debug_console_sink>();
|
|
}
|
|
|
|
// setup vfs
|
|
|
|
safe_module_initialize<vfs>();
|
|
|
|
the<vfs>().register_scheme<filesystem_file_source>("file");
|
|
safe_register_predef_path(the<vfs>(), "home", filesystem::predef_path::home);
|
|
safe_register_predef_path(the<vfs>(), "appdata", filesystem::predef_path::appdata);
|
|
safe_register_predef_path(the<vfs>(), "desktop", filesystem::predef_path::desktop);
|
|
safe_register_predef_path(the<vfs>(), "working", filesystem::predef_path::working);
|
|
safe_register_predef_path(the<vfs>(), "documents", filesystem::predef_path::documents);
|
|
safe_register_predef_path(the<vfs>(), "resources", filesystem::predef_path::resources);
|
|
safe_register_predef_path(the<vfs>(), "executable", filesystem::predef_path::executable);
|
|
|
|
if ( params.debug_params().file_logging() ) {
|
|
url log_url = url("appdata://")
|
|
/ params.company_name()
|
|
/ params.game_name()
|
|
/ params.debug_params().log_filename();
|
|
output_stream_uptr log_stream = the<vfs>().write(log_url, false);
|
|
the<debug>().register_sink<debug_stream_sink>(std::move(log_stream));
|
|
}
|
|
|
|
// setup input
|
|
|
|
safe_module_initialize<input>();
|
|
|
|
// setup audio
|
|
|
|
const bool without_audio =
|
|
params.without_audio() ||
|
|
!!std::getenv("E2D_WITHOUT_AUDIO");
|
|
|
|
if ( !without_audio ) {
|
|
safe_module_initialize<audio>(
|
|
the<debug>());
|
|
}
|
|
|
|
// setup graphics
|
|
|
|
const bool without_graphics =
|
|
params.without_graphics() ||
|
|
!!std::getenv("E2D_WITHOUT_GRAPHICS");
|
|
|
|
if ( !without_graphics )
|
|
{
|
|
// setup window
|
|
|
|
safe_module_initialize<window>(
|
|
params.window_params().size(),
|
|
params.window_params().caption(),
|
|
params.window_params().vsync(),
|
|
params.window_params().resizable(),
|
|
params.window_params().fullscreen());
|
|
|
|
the<window>().register_event_listener<window_input_source>(the<input>());
|
|
|
|
// setup render
|
|
|
|
safe_module_initialize<render>(
|
|
the<debug>(),
|
|
the<window>());
|
|
|
|
// setup dbgui
|
|
|
|
safe_module_initialize<dbgui>(
|
|
the<debug>(),
|
|
the<input>(),
|
|
the<render>(),
|
|
the<window>());
|
|
}
|
|
}
|
|
|
|
engine::~engine() noexcept {
|
|
modules::shutdown<
|
|
dbgui,
|
|
render,
|
|
window,
|
|
audio,
|
|
input,
|
|
vfs,
|
|
debug,
|
|
profiler,
|
|
deferrer,
|
|
platform>();
|
|
}
|
|
|
|
bool engine::start(application_uptr app) {
|
|
E2D_ASSERT(is_in_main_thread());
|
|
|
|
if ( !app || !app->initialize() ) {
|
|
the<debug>().error("ENGINE: Failed to initialize application");
|
|
return false;
|
|
}
|
|
|
|
while ( true ) {
|
|
try {
|
|
the<dbgui>().frame_tick();
|
|
the<deferrer>().frame_tick();
|
|
|
|
if ( !app->frame_tick() ) {
|
|
break;
|
|
}
|
|
|
|
if ( the<window>().enabled() ) {
|
|
app->frame_render();
|
|
the<dbgui>().frame_render();
|
|
the<window>().swap_buffers();
|
|
}
|
|
|
|
app->frame_finalize();
|
|
state_->calculate_end_frame_timers();
|
|
} catch ( ... ) {
|
|
app->shutdown();
|
|
throw;
|
|
}
|
|
|
|
the<input>().frame_tick();
|
|
window::poll_events();
|
|
|
|
E2D_PROFILER_GLOBAL_EVENT("engine.end_of_frame");
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|