VoxelEngine/src/engine/Engine.cpp

461 lines
12 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/toml.hpp"
#include "coders/commons.hpp"
#include "devtools/Editor.hpp"
#include "devtools/Project.hpp"
#include "devtools/DebuggingServer.hpp"
#include "content/ContentControl.hpp"
#include "core_defs.hpp"
#include "io/io.hpp"
#include "io/settings_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/Shader.hpp"
#include "graphics/ui/GUI.hpp"
#include "graphics/ui/elements/Menu.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/input.hpp"
#include "window/Window.hpp"
#include "world/Level.hpp"
#include "Mainloop.hpp"
#include "ServerMainloop.hpp"
#include "WindowControl.hpp"
#include "EnginePaths.hpp"
#include <iostream>
#include <assert.h>
#include <glm/glm.hpp>
#include <unordered_set>
#include <functional>
#include <utility>
static debug::Logger logger("engine");
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::onContentLoad() {
editor->loadTools();
langs::setup(langs::get_current(), paths->resPaths.collectRoots());
if (isHeadless()) {
return;
}
for (auto& pack : content->getAllContentPacks()) {
auto configFolder = pack.folder / "config";
auto bindsFile = configFolder / "bindings.toml";
if (io::is_regular_file(bindsFile)) {
input->getBindings().read(
toml::parse(
bindsFile.string(), io::read_string(bindsFile)
),
BindType::BIND
);
}
}
loadAssets();
}
void Engine::initializeClient() {
windowControl = std::make_unique<WindowControl>(*this);
auto [window, input] = windowControl->initialize();
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.windowMode.observe(
[this](int value) {
WindowMode mode = static_cast<WindowMode>(value);
if (mode != this->window->getMode()) {
this->window->setMode(mode);
}
},
true
));
keepAlive(settings.debug.doTraceShaders.observe(
[](bool value) {
Shader::preprocessor->setTraceOutput(value);
},
true
));
keepAlive(this->input->addKeyCallback(Keycode::ESCAPE, [this]() {
auto& menu = *gui->getMenu();
if (menu.hasOpenPage() && menu.back()) {
return true;
}
return false;
}));
}
void Engine::initialize(CoreParameters coreParameters) {
params = std::move(coreParameters);
settingsHandler = std::make_unique<SettingsHandler>(settings);
logger.info() << "engine version: " << ENGINE_VERSION_STRING;
if (params.headless) {
logger.info() << "engine runs in headless mode";
}
if (params.projectFolder.empty()) {
params.projectFolder = params.resFolder;
}
paths = std::make_unique<EnginePaths>(params);
loadProject();
editor = std::make_unique<devtools::Editor>(*this);
cmd = std::make_unique<cmd::CommandsInterpreter>();
network = network::Network::create(settings.network);
if (!params.debugServerString.empty()) {
try {
debuggingServer = std::make_unique<devtools::DebuggingServer>(
*this, params.debugServerString
);
} catch (const std::runtime_error& err) {
throw initialize_error(
"debugging server error: " + std::string(err.what())
);
}
}
loadSettings();
controller = std::make_unique<EngineController>(*this);
if (!params.headless) {
initializeClient();
}
audio::initialize(!params.headless, settings.audio);
if (settings.ui.language.get() == "auto") {
settings.ui.language.set(
langs::locale_by_envlocale(platform::detect_locale())
);
}
content = std::make_unique<ContentControl>(*project, *paths, *input, [this]() {
onContentLoad();
});
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));
project->loadProjectStartScript();
if (!params.headless) {
project->loadProjectClientScript();
}
}
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)) {
windowControl->saveScreenshot();
}
if (input->pressed(Keycode::LEFT_CONTROL) && input->pressed(Keycode::F3) &&
input->jpressed(Keycode::U)) {
gui->toggleDebug();
}
if (input->jpressed(Keycode::F11)) {
windowControl->toggleFullscreen();
}
}
void Engine::run() {
if (params.headless) {
ServerMainloop(*this).run();
} else {
Mainloop(*this).run();
}
}
void Engine::postUpdate() {
network->update();
postRunnables.run();
scripting::process_post_runnables();
if (debuggingServer) {
debuggingServer->update();
}
}
void Engine::detachDebugger() {
debuggingServer.reset();
}
void Engine::applicationTick() {
if (project->setupCoroutine && project->setupCoroutine->isActive()) {
project->setupCoroutine->update();
}
}
void Engine::updateFrontend() {
double delta = time.getDelta();
updateHotkeys();
audio::update(delta);
gui->act(delta, window->getSize());
screen->update(delta);
gui->postAct();
}
void Engine::nextFrame(bool waitForRefresh) {
windowControl->nextFrame(waitForRefresh);
}
void Engine::startPauseLoop() {
bool initialCursorLocked = false;
if (!isHeadless()) {
initialCursorLocked = input->isCursorLocked();
if (initialCursorLocked) {
input->toggleCursor();
}
}
while (!isQuitSignal() && debuggingServer) {
network->update();
if (debuggingServer->update()) {
break;
}
if (isHeadless()) {
platform::sleep(1.0 / params.tps * 1000);
} else {
nextFrame(false);
}
}
if (initialCursorLocked) {
input->toggleCursor();
}
}
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";
if (input) {
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();
debuggingServer.reset();
network.reset();
clearKeepedObjects();
project.reset();
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
// todo: before setting to true, check if GLSLExtension thread safe
bool threading = false; // look at three 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::loadProject() {
io::path projectFile = "project:project.toml";
project = std::make_unique<Project>();
project->deserialize(io::read_object(projectFile));
logger.info() << "loaded project " << util::quote(project->name);
}
void Engine::setScreen(std::shared_ptr<Screen> screen) {
if (project->clientScript && this->screen) {
project->clientScript->onScreenChange(this->screen->getName(), false);
}
// 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);
if (this->screen) {
this->screen->onOpen();
}
if (project->clientScript && this->screen) {
project->clientScript->onScreenChange(this->screen->getName(), true);
window->setShouldRefresh();
}
}
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;
}