VoxelEngine/src/engine/Engine.cpp
2025-04-02 17:39:31 +03:00

380 lines
10 KiB
C++

#include "Engine.hpp"
#ifndef GLEW_STATIC
#define GLEW_STATIC
#endif
#include "debug/Logger.hpp"
#include "assets/AssetsLoader.hpp"
#include "audio/audio.hpp"
#include "coders/GLSLExtension.hpp"
#include "coders/imageio.hpp"
#include "coders/json.hpp"
#include "coders/toml.hpp"
#include "coders/commons.hpp"
#include "content/ContentControl.hpp"
#include "core_defs.hpp"
#include "io/io.hpp"
#include "frontend/locale.hpp"
#include "frontend/menu.hpp"
#include "frontend/screens/Screen.hpp"
#include "graphics/render/ModelsGenerator.hpp"
#include "graphics/core/DrawContext.hpp"
#include "graphics/core/ImageData.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/ui/GUI.hpp"
#include "objects/rigging.hpp"
#include "logic/EngineController.hpp"
#include "logic/CommandsInterpreter.hpp"
#include "logic/scripting/scripting.hpp"
#include "logic/scripting/scripting_hud.hpp"
#include "network/Network.hpp"
#include "util/platform.hpp"
#include "window/Camera.hpp"
#include "window/input.hpp"
#include "window/Window.hpp"
#include "world/Level.hpp"
#include "Mainloop.hpp"
#include "ServerMainloop.hpp"
#include <iostream>
#include <assert.h>
#include <glm/glm.hpp>
#include <unordered_set>
#include <functional>
#include <utility>
static debug::Logger logger("engine");
static std::unique_ptr<ImageData> load_icon() {
try {
auto file = "res:textures/misc/icon.png";
if (io::exists(file)) {
return imageio::read(file);
}
} catch (const std::exception& err) {
logger.error() << "could not load window icon: " << err.what();
}
return nullptr;
}
Engine::Engine() = default;
Engine::~Engine() = default;
static std::unique_ptr<Engine> instance = nullptr;
Engine& Engine::getInstance() {
if (!instance) {
instance = std::make_unique<Engine>();
}
return *instance;
}
void Engine::initialize(CoreParameters coreParameters) {
params = std::move(coreParameters);
settingsHandler = std::make_unique<SettingsHandler>(settings);
cmd = std::make_unique<cmd::CommandsInterpreter>();
network = network::Network::create(settings.network);
logger.info() << "engine version: " << ENGINE_VERSION_STRING;
if (params.headless) {
logger.info() << "headless mode is enabled";
}
paths.setResourcesFolder(params.resFolder);
paths.setUserFilesFolder(params.userFolder);
paths.prepare();
if (!params.scriptFile.empty()) {
paths.setScriptFolder(params.scriptFile.parent_path());
}
loadSettings();
controller = std::make_unique<EngineController>(*this);
if (!params.headless) {
std::string title = "VoxelCore v" +
std::to_string(ENGINE_VERSION_MAJOR) + "." +
std::to_string(ENGINE_VERSION_MINOR);
if (ENGINE_DEBUG_BUILD) {
title += " [debug]";
}
auto [window, input] = Window::initialize(&settings.display, title);
if (!window || !input){
throw initialize_error("could not initialize window");
}
window->setFramerate(settings.display.framerate.get());
time.set(window->time());
if (auto icon = load_icon()) {
icon->flipY();
window->setIcon(icon.get());
}
this->window = std::move(window);
this->input = std::move(input);
loadControls();
gui = std::make_unique<gui::GUI>(*this);
if (ENGINE_DEBUG_BUILD) {
menus::create_version_label(*gui);
}
keepAlive(settings.display.fullscreen.observe(
[this](bool value) {
if (value != this->window->isFullscreen()) {
this->window->toggleFullscreen();
}
},
true
));
}
audio::initialize(!params.headless, settings.audio);
bool langNotSet = settings.ui.language.get() == "auto";
if (langNotSet) {
settings.ui.language.set(
langs::locale_by_envlocale(platform::detect_locale())
);
}
content = std::make_unique<ContentControl>(paths, *input, [this]() {
langs::setup(langs::get_current(), paths.resPaths.collectRoots());
if (!isHeadless()) {
loadAssets();
}
});
scripting::initialize(this);
if (!isHeadless()) {
gui->setPageLoader(scripting::create_page_loader());
}
keepAlive(settings.ui.language.observe([this](auto lang) {
langs::setup(lang, paths.resPaths.collectRoots());
}, true));
}
void Engine::loadSettings() {
io::path settings_file = EnginePaths::SETTINGS_FILE;
if (io::is_regular_file(settings_file)) {
logger.info() << "loading settings";
std::string text = io::read_string(settings_file);
try {
toml::parse(*settingsHandler, settings_file.string(), text);
} catch (const parsing_error& err) {
logger.error() << err.errorLog();
throw;
}
}
}
void Engine::loadControls() {
io::path controls_file = EnginePaths::CONTROLS_FILE;
if (io::is_regular_file(controls_file)) {
logger.info() << "loading controls";
std::string text = io::read_string(controls_file);
input->getBindings().read(
toml::parse(controls_file.string(), text), BindType::BIND
);
}
}
void Engine::updateHotkeys() {
if (input->jpressed(Keycode::F2)) {
saveScreenshot();
}
if (input->jpressed(Keycode::F8)) {
gui->toggleDebug();
}
if (input->jpressed(Keycode::F11)) {
settings.display.fullscreen.toggle();
}
}
void Engine::saveScreenshot() {
auto image = window->takeScreenshot();
image->flipY();
io::path filename = paths.getNewScreenshotFile("png");
imageio::write(filename.string(), image.get());
logger.info() << "saved screenshot as " << filename.string();
}
void Engine::run() {
if (params.headless) {
ServerMainloop(*this).run();
} else {
Mainloop(*this).run();
}
}
void Engine::postUpdate() {
network->update();
postRunnables.run();
scripting::process_post_runnables();
}
void Engine::updateFrontend() {
double delta = time.getDelta();
updateHotkeys();
audio::update(delta);
gui->act(delta, window->getSize());
screen->update(delta);
gui->postAct();
}
void Engine::nextFrame() {
window->setFramerate(
window->isIconified() && settings.display.limitFpsIconified.get()
? 20
: settings.display.framerate.get()
);
window->swapBuffers();
input->pollEvents();
}
void Engine::renderFrame() {
screen->draw(time.getDelta());
DrawContext ctx(nullptr, *window, nullptr);
gui->draw(ctx, *assets);
}
void Engine::saveSettings() {
logger.info() << "saving settings";
io::write_string(EnginePaths::SETTINGS_FILE, toml::stringify(*settingsHandler));
if (!params.headless) {
logger.info() << "saving bindings";
io::write_string(EnginePaths::CONTROLS_FILE, input->getBindings().write());
}
}
void Engine::close() {
saveSettings();
logger.info() << "shutting down";
if (screen) {
screen->onEngineShutdown();
screen.reset();
}
content.reset();
assets.reset();
cmd.reset();
if (gui) {
gui.reset();
logger.info() << "gui finished";
}
audio::close();
network.reset();
clearKeepedObjects();
scripting::close();
logger.info() << "scripting finished";
if (!params.headless) {
window.reset();
logger.info() << "window closed";
}
logger.info() << "engine finished";
}
void Engine::terminate() {
instance->close();
instance.reset();
}
EngineController* Engine::getController() {
return controller.get();
}
void Engine::setLevelConsumer(OnWorldOpen levelConsumer) {
this->levelConsumer = std::move(levelConsumer);
}
void Engine::loadAssets() {
logger.info() << "loading assets";
Shader::preprocessor->setPaths(&paths.resPaths);
auto content = this->content->get();
auto new_assets = std::make_unique<Assets>();
AssetsLoader loader(*this, *new_assets, paths.resPaths);
AssetsLoader::addDefaults(loader, content);
// no need
// correct log messages order is more useful
bool threading = false; // look at two upper lines
if (threading) {
auto task = loader.startTask([=](){});
task->waitForEnd();
} else {
while (loader.hasNext()) {
loader.loadNext();
}
}
assets = std::move(new_assets);
if (content) {
ModelsGenerator::prepare(*content, *assets);
}
assets->setup();
gui->onAssetsLoad(assets.get());
}
void Engine::setScreen(std::shared_ptr<Screen> screen) {
// reset audio channels (stop all sources)
audio::reset_channel(audio::get_channel_index("regular"));
audio::reset_channel(audio::get_channel_index("ambient"));
this->screen = std::move(screen);
}
void Engine::onWorldOpen(std::unique_ptr<Level> level, int64_t localPlayer) {
logger.info() << "world open";
levelConsumer(std::move(level), localPlayer);
}
void Engine::onWorldClosed() {
logger.info() << "world closed";
levelConsumer(nullptr, -1);
}
void Engine::quit() {
quitSignal = true;
if (!isHeadless()) {
window->setShouldClose(true);
}
}
bool Engine::isQuitSignal() const {
return quitSignal;
}
EngineSettings& Engine::getSettings() {
return settings;
}
Assets* Engine::getAssets() {
return assets.get();
}
EnginePaths& Engine::getPaths() {
return paths;
}
ResPaths& Engine::getResPaths() {
return paths.resPaths;
}
std::shared_ptr<Screen> Engine::getScreen() {
return screen;
}
SettingsHandler& Engine::getSettingsHandler() {
return *settingsHandler;
}
Time& Engine::getTime() {
return time;
}
const CoreParameters& Engine::getCoreParameters() const {
return params;
}
bool Engine::isHeadless() const {
return params.headless;
}
ContentControl& Engine::getContentControl() {
return *content;
}