diff --git a/doc/en/main-page.md b/doc/en/main-page.md index e1f13230..94aaaebe 100644 --- a/doc/en/main-page.md +++ b/doc/en/main-page.md @@ -1,6 +1,6 @@ # Documentation -Documentation for release 0.27. +Documentation for release 0.28. ## Sections diff --git a/doc/en/scripting.md b/doc/en/scripting.md index 471bdb15..f205a237 100644 --- a/doc/en/scripting.md +++ b/doc/en/scripting.md @@ -11,7 +11,7 @@ Subsections: - [Libraries](#) - [app](scripting/builtins/libapp.md) - [base64](scripting/builtins/libbase64.md) - - [bjson, json, toml](scripting/filesystem.md) + - [bjson, json, toml, yaml](scripting/filesystem.md) - [block](scripting/builtins/libblock.md) - [byteutil](scripting/builtins/libbyteutil.md) - [cameras](scripting/builtins/libcameras.md) diff --git a/doc/en/scripting/filesystem.md b/doc/en/scripting/filesystem.md index b37c799f..8341e966 100644 --- a/doc/en/scripting/filesystem.md +++ b/doc/en/scripting/filesystem.md @@ -36,6 +36,22 @@ toml.parse(code: str) -> table Parses a TOML string into a table. +## *yaml* library + +The library contains functions for serializing and deserializing tables: + +```python +yaml.tostring(object: table) -> str +``` + +Serializes an object into a YAML string. + +```python +yaml.parse(code: str) -> table +``` + +Parses a YAML string into a table. + ## *bjson* library The library contains functions for working with the binary data exchange format [vcbjson](../../specs/binary_json_spec.md). diff --git a/doc/ru/main-page.md b/doc/ru/main-page.md index 72503052..8d034a88 100644 --- a/doc/ru/main-page.md +++ b/doc/ru/main-page.md @@ -1,6 +1,6 @@ # Документация -Документация версии 0.27. +Документация версии 0.28. ## Разделы diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md index 942241b6..79e53450 100644 --- a/doc/ru/scripting.md +++ b/doc/ru/scripting.md @@ -11,7 +11,7 @@ - [Библиотеки](#) - [app](scripting/builtins/libapp.md) - [base64](scripting/builtins/libbase64.md) - - [bjson, json, toml](scripting/filesystem.md) + - [bjson, json, toml, yaml](scripting/filesystem.md) - [block](scripting/builtins/libblock.md) - [byteutil](scripting/builtins/libbyteutil.md) - [cameras](scripting/builtins/libcameras.md) diff --git a/doc/ru/scripting/filesystem.md b/doc/ru/scripting/filesystem.md index faaaf9b7..10d82c60 100644 --- a/doc/ru/scripting/filesystem.md +++ b/doc/ru/scripting/filesystem.md @@ -36,6 +36,22 @@ toml.parse(code: str) -> table Парсит TOML строку в таблицу. +## Библиотека yaml + +Библиотека содержит функции для сериализации и десериализации таблиц: + +```python +yaml.tostring(object: table) -> str +``` + +Сериализует объект в YAML строку. + +```python +yaml.parse(code: str) -> table +``` + +Парсит YAML строку в таблицу. + ## Библиотека bjson Библиотека содержит функции для работы с двоичным форматом обмена данными [vcbjson](../../specs/binary_json_spec.md). diff --git a/res/content/base/package.json b/res/content/base/package.json index 4f992e4c..73b8dcd0 100644 --- a/res/content/base/package.json +++ b/res/content/base/package.json @@ -1,6 +1,6 @@ { "id": "base", "title": "Base", - "version": "0.27", + "version": "0.28", "description": "basic content package" } diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index e068295b..10f3f7ea 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -19,13 +19,14 @@ #include "items/ItemDef.hpp" #include "Assets.hpp" #include "assetload_funcs.hpp" +#include "engine/Engine.hpp" namespace fs = std::filesystem; static debug::Logger logger("assets-loader"); -AssetsLoader::AssetsLoader(Assets* assets, const ResPaths* paths) - : assets(assets), paths(paths) { +AssetsLoader::AssetsLoader(Engine& engine, Assets& assets, const ResPaths& paths) + : engine(engine), assets(assets), paths(paths) { addLoader(AssetType::SHADER, assetload::shader); addLoader(AssetType::TEXTURE, assetload::texture); addLoader(AssetType::FONT, assetload::font); @@ -73,7 +74,7 @@ void AssetsLoader::loadNext() { aloader_func loader = getLoader(entry.tag); auto postfunc = loader(this, paths, entry.filename, entry.alias, entry.config); - postfunc(assets); + postfunc(&assets); entries.pop(); } catch (std::runtime_error& err) { logger.error() << err.what(); @@ -101,7 +102,7 @@ static void add_layouts( AssetType::LAYOUT, file.string(), name, - std::make_shared(env) + std::make_shared(&loader.getEngine().getGUI(), env) ); } } @@ -199,7 +200,7 @@ void AssetsLoader::processPreloadConfig(const io::path& file) { } void AssetsLoader::processPreloadConfigs(const Content* content) { - auto preloadFile = paths->getMainRoot() / "preload.json"; + io::path preloadFile = "res:preload.json"; if (io::exists(preloadFile)) { processPreloadConfig(preloadFile); } @@ -211,7 +212,7 @@ void AssetsLoader::processPreloadConfigs(const Content* content) { continue; } const auto& pack = entry.second; - auto preloadFile = pack->getInfo().folder / "preload.json"; + preloadFile = pack->getInfo().folder / "preload.json"; if (io::exists(preloadFile)) { processPreloadConfig(preloadFile); } @@ -296,7 +297,11 @@ bool AssetsLoader::loadExternalTexture( return false; } -const ResPaths* AssetsLoader::getPaths() const { +Engine& AssetsLoader::getEngine() { + return engine; +} + +const ResPaths& AssetsLoader::getPaths() const { return paths; } @@ -324,7 +329,7 @@ std::shared_ptr AssetsLoader::startTask(runnable onDone) { std::make_shared>( "assets-loader-pool", [=]() { return std::make_shared(this); }, - [=](const assetload::postfunc& func) { func(assets); } + [this](const assetload::postfunc& func) { func(&assets); } ); pool->setOnComplete(std::move(onDone)); while (!entries.empty()) { diff --git a/src/assets/AssetsLoader.hpp b/src/assets/AssetsLoader.hpp index bf117a3d..e3c137ba 100644 --- a/src/assets/AssetsLoader.hpp +++ b/src/assets/AssetsLoader.hpp @@ -18,6 +18,11 @@ class ResPaths; class AssetsLoader; class Content; +class Engine; + +namespace gui { + class GUI; +} struct AssetCfg { virtual ~AssetCfg() { @@ -25,9 +30,10 @@ struct AssetCfg { }; struct LayoutCfg : AssetCfg { + gui::GUI* gui; scriptenv env; - LayoutCfg(scriptenv env) : env(std::move(env)) { + LayoutCfg(gui::GUI* gui, scriptenv env) : gui(gui), env(std::move(env)) { } }; @@ -51,7 +57,7 @@ struct AtlasCfg : AssetCfg { using aloader_func = std::function< assetload:: - postfunc(AssetsLoader*, const ResPaths*, const std::string&, const std::string&, std::shared_ptr)>; + postfunc(AssetsLoader*, const ResPaths&, const std::string&, const std::string&, std::shared_ptr)>; struct aloader_entry { AssetType tag; @@ -61,11 +67,12 @@ struct aloader_entry { }; class AssetsLoader { - Assets* assets; + Engine& engine; + Assets& assets; std::map loaders; std::queue entries; std::set> enqueued; - const ResPaths* paths; + const ResPaths& paths; void tryAddSound(const std::string& name); @@ -76,7 +83,7 @@ class AssetsLoader { void processPreloadConfig(const io::path& file); void processPreloadConfigs(const Content* content); public: - AssetsLoader(Assets* assets, const ResPaths* paths); + AssetsLoader(Engine& engine, Assets& assets, const ResPaths& paths); void addLoader(AssetType tag, aloader_func func); /// @brief Enqueue asset load @@ -98,7 +105,7 @@ public: std::shared_ptr startTask(runnable onDone); - const ResPaths* getPaths() const; + const ResPaths& getPaths() const; aloader_func getLoader(AssetType tag); /// @brief Enqueue core and content assets @@ -111,4 +118,6 @@ public: const std::string& name, const std::vector& alternatives ); + + Engine& getEngine(); }; diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index bad2bbb5..01c251b4 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -34,7 +34,7 @@ namespace fs = std::filesystem; static bool load_animation( Assets* assets, - const ResPaths* paths, + const ResPaths& paths, const std::string& atlasName, const std::string& directory, const std::string& name, @@ -43,14 +43,14 @@ static bool load_animation( assetload::postfunc assetload::texture( AssetsLoader*, - const ResPaths* paths, + const ResPaths& paths, const std::string& filename, const std::string& name, const std::shared_ptr& ) { - auto actualFile = paths->find(filename + ".png"); + auto actualFile = paths.find(filename + ".png"); try { - std::shared_ptr image(imageio::read(actualFile).release()); + std::shared_ptr image(imageio::read(actualFile)); return [name, image, actualFile](auto assets) { assets->store(Texture::from(image.get()), name); }; @@ -62,13 +62,13 @@ assetload::postfunc assetload::texture( assetload::postfunc assetload::shader( AssetsLoader*, - const ResPaths* paths, + const ResPaths& paths, const std::string& filename, const std::string& name, const std::shared_ptr& ) { - io::path vertexFile = paths->find(filename + ".glslv"); - io::path fragmentFile = paths->find(filename + ".glslf"); + io::path vertexFile = paths.find(filename + ".glslv"); + io::path fragmentFile = paths.find(filename + ".glslf"); std::string vertexSource = io::read_string(vertexFile); std::string fragmentSource = io::read_string(fragmentFile); @@ -104,14 +104,14 @@ static bool append_atlas(AtlasBuilder& atlas, const io::path& file) { assetload::postfunc assetload::atlas( AssetsLoader* loader, - const ResPaths* paths, + const ResPaths& paths, const std::string& directory, const std::string& name, const std::shared_ptr& config ) { auto atlasConfig = std::dynamic_pointer_cast(config); if (atlasConfig && atlasConfig->type == AtlasType::SEPARATE) { - for (const auto& file : paths->listdir(directory)) { + for (const auto& file : paths.listdir(directory)) { if (!imageio::is_read_supported(file.extension())) continue; loader->add( @@ -123,7 +123,7 @@ assetload::postfunc assetload::atlas( return [](auto){}; } AtlasBuilder builder; - for (const auto& file : paths->listdir(directory)) { + for (const auto& file : paths.listdir(directory)) { if (!imageio::is_read_supported(file.extension())) continue; if (!append_atlas(builder, file)) continue; } @@ -140,7 +140,7 @@ assetload::postfunc assetload::atlas( assetload::postfunc assetload::font( AssetsLoader*, - const ResPaths* paths, + const ResPaths& paths, const std::string& filename, const std::string& name, const std::shared_ptr& @@ -148,7 +148,7 @@ assetload::postfunc assetload::font( auto pages = std::make_shared>>(); for (size_t i = 0; i <= 1024; i++) { std::string pagefile = filename + "_" + std::to_string(i) + ".png"; - auto file = paths->find(pagefile); + auto file = paths.find(pagefile); if (io::exists(file)) { pages->push_back(imageio::read(file)); } else if (i == 0) { @@ -177,7 +177,7 @@ assetload::postfunc assetload::font( assetload::postfunc assetload::layout( AssetsLoader*, - const ResPaths* paths, + const ResPaths&, const std::string& file, const std::string& name, const std::shared_ptr& config @@ -189,6 +189,7 @@ assetload::postfunc assetload::layout( auto prefix = name.substr(0, pos); assets->store( UiDocument::read( + *cfg->gui, cfg->env, name, file, @@ -205,7 +206,7 @@ assetload::postfunc assetload::layout( } assetload::postfunc assetload::sound( AssetsLoader*, - const ResPaths* paths, + const ResPaths& paths, const std::string& file, const std::string& name, const std::shared_ptr& config @@ -219,13 +220,13 @@ assetload::postfunc assetload::sound( for (size_t i = 0; i < extensions.size(); i++) { extension = extensions[i]; // looking for 'sound_name' as base sound - auto soundFile = paths->find(file + extension); + auto soundFile = paths.find(file + extension); if (io::exists(soundFile)) { baseSound = audio::load_sound(soundFile, keepPCM); break; } // looking for 'sound_name_0' as base sound - auto variantFile = paths->find(file + "_0" + extension); + auto variantFile = paths.find(file + "_0" + extension); if (io::exists(variantFile)) { baseSound = audio::load_sound(variantFile, keepPCM); break; @@ -238,7 +239,7 @@ assetload::postfunc assetload::sound( // loading sound variants for (uint i = 1;; i++) { auto variantFile = - paths->find(file + "_" + std::to_string(i) + extension); + paths.find(file + "_" + std::to_string(i) + extension); if (!io::exists(variantFile)) { break; } @@ -264,12 +265,12 @@ static void request_textures(AssetsLoader* loader, const model::Model& model) { assetload::postfunc assetload::model( AssetsLoader* loader, - const ResPaths* paths, + const ResPaths& paths, const std::string& file, const std::string& name, const std::shared_ptr& ) { - auto path = paths->find(file + ".vec3"); + auto path = paths.find(file + ".vec3"); if (io::exists(path)) { auto bytes = io::read_bytes_buffer(path); auto modelVEC3 = std::make_shared(vec3::load(path.string(), bytes)); @@ -289,7 +290,7 @@ assetload::postfunc assetload::model( } }; } - path = paths->find(file + ".obj"); + path = paths.find(file + ".obj"); auto text = io::read_string(path); try { auto model = obj::parse(path.string(), text).release(); @@ -383,7 +384,7 @@ inline bool contains( static bool load_animation( Assets* assets, - const ResPaths* paths, + const ResPaths& paths, const std::string& atlasName, const std::string& directory, const std::string& name, @@ -391,20 +392,20 @@ static bool load_animation( ) { std::string animsDir = directory + "/animation"; - for (const auto& folder : paths->listdir(animsDir)) { + for (const auto& folder : paths.listdir(animsDir)) { if (!io::is_directory(folder)) continue; if (folder.name() != name) continue; //FIXME: if (fs::is_empty(folder)) continue; AtlasBuilder builder; - append_atlas(builder, paths->find(directory + "/" + name + ".png")); + append_atlas(builder, paths.find(directory + "/" + name + ".png")); std::vector> frameList; std::string animFile = folder.string() + "/animation.json"; if (io::exists(animFile)) { read_anim_file(animFile, frameList); } - for (const auto& file : paths->listdir(animsDir + "/" + name)) { + for (const auto& file : paths.listdir(animsDir + "/" + name)) { if (!frameList.empty() && !contains(frameList, file.stem())) { continue; diff --git a/src/assets/assetload_funcs.hpp b/src/assets/assetload_funcs.hpp index 3348edaa..eb26dc62 100644 --- a/src/assets/assetload_funcs.hpp +++ b/src/assets/assetload_funcs.hpp @@ -15,49 +15,49 @@ struct AssetCfg; namespace assetload { postfunc texture( AssetsLoader*, - const ResPaths* paths, + const ResPaths& paths, const std::string& filename, const std::string& name, const std::shared_ptr& settings ); postfunc shader( AssetsLoader*, - const ResPaths* paths, + const ResPaths& paths, const std::string& filename, const std::string& name, const std::shared_ptr& settings ); postfunc atlas( AssetsLoader*, - const ResPaths* paths, + const ResPaths& paths, const std::string& directory, const std::string& name, const std::shared_ptr& settings ); postfunc font( AssetsLoader*, - const ResPaths* paths, + const ResPaths& paths, const std::string& filename, const std::string& name, const std::shared_ptr& settings ); postfunc layout( AssetsLoader*, - const ResPaths* paths, + const ResPaths& paths, const std::string& file, const std::string& name, const std::shared_ptr& settings ); postfunc sound( AssetsLoader*, - const ResPaths* paths, + const ResPaths& paths, const std::string& file, const std::string& name, const std::shared_ptr& settings ); postfunc model( AssetsLoader*, - const ResPaths* paths, + const ResPaths& paths, const std::string& file, const std::string& name, const std::shared_ptr& settings diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 761557af..f98c436e 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -17,12 +17,12 @@ static debug::Logger logger("audio"); using namespace audio; namespace { - static speakerid_t nextId = 1; - static Backend* backend; - static std::unordered_map> speakers; - static std::unordered_map> streams; - static std::vector> channels; - static util::ObjectsKeeper objects_keeper {}; + speakerid_t nextId = 1; + Backend* backend; + std::unordered_map> speakers; + std::unordered_map> streams; + std::vector> channels; + util::ObjectsKeeper objects_keeper {}; } Channel::Channel(std::string name) : name(std::move(name)) { diff --git a/src/coders/BasicParser.hpp b/src/coders/BasicParser.hpp index e3a3dda8..ba1cf5a8 100644 --- a/src/coders/BasicParser.hpp +++ b/src/coders/BasicParser.hpp @@ -7,16 +7,20 @@ template class BasicParser { using StringT = std::basic_string; using StringViewT = std::basic_string_view; + + void skipWhitespaceHashComment(bool newline = true); protected: std::string_view filename; StringViewT source; uint pos = 0; uint line = 1; uint linestart = 0; + bool hashComment = false; - virtual void skipWhitespace(); + void skipWhitespace(bool newline = true); void skip(size_t n); void skipLine(); + void skipEmptyLines(); bool skipTo(const StringT& substring); void expect(CharT expected); void expect(const StringT& substring); diff --git a/src/coders/BasicParser.inl b/src/coders/BasicParser.inl index 2d15d8e9..3616b4a1 100644 --- a/src/coders/BasicParser.inl +++ b/src/coders/BasicParser.inl @@ -31,10 +31,17 @@ namespace { } template -void BasicParser::skipWhitespace() { +void BasicParser::skipWhitespace(bool newline) { + if (hashComment) { + skipWhitespaceHashComment(newline); + return; + } while (hasNext()) { char next = source[pos]; if (next == '\n') { + if (!newline) { + break; + } line++; linestart = ++pos; continue; @@ -47,6 +54,36 @@ void BasicParser::skipWhitespace() { } } +template +void BasicParser::skipWhitespaceHashComment(bool newline) { + while (hasNext()) { + char next = source[pos]; + if (next == '\n') { + if (!newline) { + break; + } + line++; + linestart = ++pos; + continue; + } + if (is_whitespace(next)) { + pos++; + } else { + break; + } + } + if (hasNext() && source[pos] == '#') { + if (!newline) { + readUntilEOL(); + return; + } + skipLine(); + if (hasNext() && (is_whitespace(source[pos]) || source[pos] == '#')) { + skipWhitespaceHashComment(newline); + } + } +} + template void BasicParser::skip(size_t n) { n = std::min(n, source.length() - pos); @@ -73,6 +110,12 @@ void BasicParser::skipLine() { } } +template +void BasicParser::skipEmptyLines() { + skipWhitespace(); + pos = linestart; +} + template bool BasicParser::skipTo(const std::basic_string& substring) { size_t idx = source.find(substring, pos); @@ -240,9 +283,12 @@ std::basic_string_view BasicParser::readUntilWhitespace() { template std::basic_string_view BasicParser::readUntilEOL() { int start = pos; - while (hasNext() && source[pos] != '\r' && source[pos] != '\n') { + while (hasNext() && source[pos] != '\n') { pos++; } + if (pos > start && source[pos - 1] == '\r') { + return source.substr(start, pos - start - 1); + } return source.substr(start, pos - start); } diff --git a/src/coders/json.cpp b/src/coders/json.cpp index 19110f90..bbc56360 100644 --- a/src/coders/json.cpp +++ b/src/coders/json.cpp @@ -13,13 +13,14 @@ using namespace json; namespace { class Parser : BasicParser { - dv::value parseList(); - dv::value parseObject(); - dv::value parseValue(); - public: + public: Parser(std::string_view filename, std::string_view source); dv::value parse(); + private: + dv::value parseList(); + dv::value parseObject(); + dv::value parseValue(); }; } diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp index 8fe05391..1652745a 100644 --- a/src/coders/toml.cpp +++ b/src/coders/toml.cpp @@ -16,16 +16,6 @@ using namespace toml; class TomlReader : BasicParser { dv::value root; - void skipWhitespace() override { - BasicParser::skipWhitespace(); - if (hasNext() && source[pos] == '#') { - skipLine(); - if (hasNext() && is_whitespace(peek())) { - skipWhitespace(); - } - } - } - // modified version of BaseParser.parseString // todo: extract common part std::string parseMultilineString() { @@ -214,6 +204,7 @@ class TomlReader : BasicParser { public: TomlReader(std::string_view file, std::string_view source) : BasicParser(file, source), root(dv::object()) { + hashComment = true; } dv::value read() { diff --git a/src/coders/yaml.cpp b/src/coders/yaml.cpp new file mode 100644 index 00000000..a9944ea3 --- /dev/null +++ b/src/coders/yaml.cpp @@ -0,0 +1,459 @@ +#include "yaml.hpp" +#include "BasicParser.hpp" + +#include + +using namespace yaml; + +namespace { + enum Chomping { + CLIP, STRIP, KEEP + }; + + class Parser : BasicParser { + public: + Parser(std::string_view filename, std::string_view source); + + dv::value parseValue(); + dv::value parseFullValue(int indent); + dv::value parseArray(int indent = 0); + dv::value parseObject(dv::value&& object, int indent = 0); + dv::value parseInlineArray(); + dv::value parseInlineObject(); + private: + int countIndent(); + bool expectIndent(int indent); + std::string_view readYamlIdentifier(); + std::string readMultilineString(int indent, bool eols, Chomping chomp); + }; +} + +inline bool is_yaml_identifier_char(int c) { + return c > 20 && c != ':' && c != ' ' && c != '\n' && c != '\r' && + c != '\t' && c != '\f' && c != '\v'; +} + +static dv::value perform_literal(std::string_view literal) { + if (literal == "true" || literal == "True" || + literal == "false" || literal == "False") { + return literal[0] == 't'; + } + if (literal == "null" || literal == "Null") { + return nullptr; + } + return std::string(literal); +} + +Parser::Parser(std::string_view filename, std::string_view source) + : BasicParser(filename, source) { + hashComment = true; +} + +bool Parser::expectIndent(int required) { + int indent = 0; + while (hasNext() && source[pos] == ' ' && indent < required) { + indent++; + pos++; + } + return indent >= required; +} + +std::string Parser::readMultilineString(int indent, bool eols, Chomping chomp) { + int next_indent = countIndent(); + if (next_indent <= indent) { + throw error("indentation error"); + } + std::stringstream ss; + ss << readUntilEOL(); + if (hasNext()) { + skip(1); + } + int trailingEmpties = 0; + while (true) { + while (expectIndent(next_indent)) { + trailingEmpties = 0; + ss << (eols ? '\n' : ' '); + ss << readUntilEOL(); + if (hasNext()) { + skip(1); + } + } + while (true) { + skipWhitespace(false); + if (!hasNext() || source[pos] != '\n') { + break; + } + skip(1); + trailingEmpties++; + } + if (!expectIndent(next_indent)) { + break; + } + pos = linestart; + } + if (chomp == KEEP) { + for (int i = 0; i < trailingEmpties - 1; i++) { + ss << (eols ? '\n' : ' '); + } + } + ss << '\n'; + + pos = linestart; + + auto string = ss.str(); + if (chomp == STRIP) { + util::trim(string); + } + return string; +} + +std::string_view Parser::readYamlIdentifier() { + char c = peek(); + if (!is_yaml_identifier_char(c)) { + throw error("identifier expected"); + } + int start = pos; + while (hasNext() && is_yaml_identifier_char(source[pos])) { + pos++; + } + return source.substr(start, pos - start); +} + +int Parser::countIndent() { + int indent = 0; + while (hasNext() && source[pos] == ' ') { + indent++; + pos++; + } + return indent; +} + +dv::value Parser::parseValue() { + char c = peek(); + if (is_digit(c)) { + return parseNumber(1); + } else if (c == '-' || c == '+') { + skip(1); + return parseNumber(c == '-' ? -1 : 1); + } else if (c == '"' || c == '\'') { + skip(1); + return parseString(c, true); + } else if (c == '[') { + return parseInlineArray(); + } else if (c == '{') { + return parseInlineObject(); + } else { + return perform_literal(readUntilEOL()); + } + throw error("unexpected character"); +} + +dv::value Parser::parseInlineArray() { + expect('['); + auto list = dv::list(); + while (peek() != ']') { + if (peek() == '#') { + skipLine(); + continue; + } + list.add(parseValue()); + + char next = peek(); + if (next == ',') { + pos++; + } else if (next == ']') { + break; + } else { + throw error("',' expected"); + } + } + pos++; + return list; +} + +dv::value Parser::parseInlineObject() { + expect('{'); + dv::value object = dv::object(); + while (peek() != '}') { + if (peek() == '#') { + skipLine(); + continue; + } + auto name = readYamlIdentifier(); + expect(':'); + object[std::string(name)] = parseValue(); + + char next = peek(); + if (next == ',') { + pos++; + } else if (next == '}') { + break; + } else { + throw error("',' expected"); + } + } + pos++; + return object; +} + +dv::value Parser::parseFullValue(int indent) { + dv::value value; + char c = source[pos]; + if (c == '\n') { + skip(1); + skipEmptyLines(); + int init_pos = pos; + int next_indent = countIndent(); + if (next_indent < indent) { + throw error("indentation error"); + } + if (source[pos] == '-') { + pos = init_pos; + return parseArray(next_indent); + } else { + pos = init_pos; + return parseObject(dv::object(), next_indent); + } + } else if (is_digit(c)) { + return parseNumber(1); + } else if (c == '-' || c == '+') { + skip(1); + return parseNumber(c == '-' ? -1 : 1); + } else if (c == '"' || c == '\'') { + skip(1); + return parseString(c, true); + } else if (c == '[') { + return parseInlineArray(); + } else if (c == '{') { + return parseInlineObject(); + } else if (c == '|' || c == '>') { + skip(1); + Chomping chomp = CLIP; + if (source[pos] == '-' || source[pos] == '+') { + chomp = source[pos] == '-' ? STRIP : KEEP; + skip(1); + } + skipWhitespace(false); + expectNewLine(); + return readMultilineString(indent, c == '|', chomp); + } else { + return perform_literal(readUntilEOL()); + } +} + +dv::value Parser::parseArray(int indent) { + dv::value list = dv::list(); + + while (hasNext()) { + skipEmptyLines(); + int next_indent = countIndent(); + if (next_indent < indent) { + pos = linestart; + break; + } + expect('-'); + skipWhitespace(); + size_t nlpos = source.find('\n', pos); + size_t colonpos = source.find(':', pos); + if (nlpos == std::string::npos && colonpos == std::string::npos) { + list.add(perform_literal(readUntilEOL())); + break; + } + if (nlpos < colonpos) { + list.add(parseFullValue(next_indent)); + skipLine(); + } else { + auto name = readYamlIdentifier(); + expect(':'); + skipWhitespace(false); + dv::value object = dv::object(); + object[std::string(name)] = parseFullValue(next_indent); + skipEmptyLines(); + next_indent = countIndent(); + if (next_indent > indent) { + pos = linestart; + object = parseObject(std::move(object), next_indent); + } else { + pos = linestart; + } + list.add(std::move(object)); + } + } + return list; +} + +dv::value Parser::parseObject(dv::value&& object, int indent) { + skipEmptyLines(); + while (hasNext()) { + size_t prev_pos = pos; + int next_indent = countIndent(); + if (source[pos] == '\n') { + skip(1); + continue; + } + if (next_indent < indent) { + pos = prev_pos; + break; + } + char c = peek(); + if (!is_yaml_identifier_char(c)) { + if (!is_whitespace(c)) { + throw error("invalid character"); + } + continue; + } + auto name = readYamlIdentifier(); + expect(':'); + skipWhitespace(false); + object[std::string(name)] = parseFullValue(indent); + skipEmptyLines(); + } + return object; +} + +dv::value yaml::parse(std::string_view filename, std::string_view source) { + return Parser(filename, source).parseObject(dv::object()); +} + +dv::value yaml::parse(std::string_view source) { + return parse("[string]", source); +} + +static void add_indent(std::stringstream& ss, int indent) { + for (int i = 0; i < indent; i++) { + ss << " "; + } +} + +static void insert_string( + std::stringstream& ss, const std::string& string, int indent +) { + bool has_spec_chars = false; + bool multiline = false; + for (char c : string) { + if (c < ' ' || c == '"' || c == '\'') { + has_spec_chars = true; + if (multiline) { + break; + } + } + if (c == '\n') { + multiline = true; + break; + } + } + if (multiline) { + ss << "|-\n"; + size_t offset = 0; + size_t newoffset = 0; + + do { + offset = newoffset; + if (offset == string.length() - 1 && string[offset] == '\n') { + break; + } + add_indent(ss, indent); + newoffset = string.find('\n', offset + 1); + if (newoffset == std::string::npos) { + ss << string.substr(offset); + break; + } else { + ss << string.substr(offset + 1, newoffset - offset - 1); + } + ss << '\n'; + } while (true); + } else { + if (has_spec_chars || string.empty()) { + ss << util::escape(string, false); + } else { + ss << string; + } + } +} + +static void to_string( + std::stringstream& ss, + const dv::value& value, + int indent, + bool eliminateIndent = false +) { + using dv::value_type; + + switch (value.getType()) { + case value_type::string: + insert_string(ss, value.asString(), indent); + break; + case value_type::number: + ss << std::setprecision(15) << value.asNumber(); + break; + case value_type::integer: + ss << value.asInteger(); + break; + case value_type::boolean: + ss << (value.asBoolean() ? "true" : "false"); + break; + case value_type::none: + ss << "null"; + break; + case value_type::object: { + if (value.empty()) { + ss << "{}"; + break; + } + bool first = true; + for (const auto& [key, elem] : value.asObject()) { + if (!first) { + ss << '\n'; + } + if (!eliminateIndent) { + add_indent(ss, indent); + } else { + eliminateIndent = false; + } + ss << key << ": "; + if ((elem.isObject() || elem.isList()) && !elem.empty()) { + ss << "\n"; + to_string(ss, elem, indent + 1); + } else { + to_string(ss, elem, indent + 1); + } + first = false; + } + break; + } + case value_type::list: { + if (value.empty()) { + ss << "[]"; + break; + } + bool first = true; + for (const auto& elem : value) { + if (!first) { + ss << '\n'; + } + if (!eliminateIndent) { + add_indent(ss, indent); + } else { + eliminateIndent = false; + } + ss << "- "; + to_string(ss, elem, indent + 1, true); + first = false; + } + break; + } + case value_type::bytes: { + const auto& bytes = value.asBytes(); + auto b64 = util::base64_encode(bytes.data(), bytes.size()); + b64 = util::join(util::split_by_n(b64, 64), '\n'); + insert_string(ss, b64, indent); + break; + } + } +} + +std::string yaml::stringify(const dv::value& value) { + std::stringstream ss; + to_string(ss, value, 0); + return ss.str(); +} diff --git a/src/coders/yaml.hpp b/src/coders/yaml.hpp new file mode 100644 index 00000000..3d871855 --- /dev/null +++ b/src/coders/yaml.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include "data/dv.hpp" + +namespace yaml { + dv::value parse(std::string_view filename, std::string_view source); + dv::value parse(std::string_view source); + + std::string stringify(const dv::value& value); +} diff --git a/src/constants.hpp b/src/constants.hpp index 1accd619..32acc83e 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -6,7 +6,7 @@ #include inline constexpr int ENGINE_VERSION_MAJOR = 0; -inline constexpr int ENGINE_VERSION_MINOR = 27; +inline constexpr int ENGINE_VERSION_MINOR = 28; #ifdef NDEBUG inline constexpr bool ENGINE_DEBUG_BUILD = false; @@ -14,7 +14,7 @@ inline constexpr bool ENGINE_DEBUG_BUILD = false; inline constexpr bool ENGINE_DEBUG_BUILD = true; #endif // NDEBUG -inline const std::string ENGINE_VERSION_STRING = "0.27"; +inline const std::string ENGINE_VERSION_STRING = "0.28"; /// @brief world regions format version inline constexpr uint REGION_FORMAT_VERSION = 3; diff --git a/src/content/ContentControl.cpp b/src/content/ContentControl.cpp new file mode 100644 index 00000000..dea7099f --- /dev/null +++ b/src/content/ContentControl.cpp @@ -0,0 +1,133 @@ +#include "ContentControl.hpp" + +#include "io/io.hpp" +#include "io/engine_paths.hpp" +#include "Content.hpp" +#include "ContentPack.hpp" +#include "ContentBuilder.hpp" +#include "ContentLoader.hpp" +#include "PacksManager.hpp" +#include "objects/rigging.hpp" +#include "logic/scripting/scripting.hpp" +#include "core_defs.hpp" + +static void load_configs(Input& input, const io::path& root) { + auto configFolder = root / "config"; +} + +ContentControl::ContentControl( + EnginePaths& paths, Input& input, std::function postContent +) + : paths(paths), + input(input), + postContent(std::move(postContent)), + basePacks(io::read_list("res:config/builtins.list")), + manager(std::make_unique()) { + manager->setSources({ + "world:content", + "user:content", + "res:content", + }); +} + +ContentControl::~ContentControl() = default; + +Content* ContentControl::get() { + return content.get(); +} + +const Content* ContentControl::get() const { + return content.get(); +} + +std::vector& ContentControl::getBasePacks() { + return basePacks; +} + +void ContentControl::resetContent() { + paths.setCurrentWorldFolder(""); + + scripting::cleanup(); + std::vector resRoots; + { + auto pack = ContentPack::createCore(paths); + resRoots.push_back({"core", pack.folder}); + load_configs(input, pack.folder); + } + manager->scan(); + for (const auto& pack : manager->getAll(basePacks)) { + resRoots.push_back({pack.id, pack.folder}); + } + paths.resPaths = ResPaths(resRoots); + content.reset(); + + contentPacks.clear(); + contentPacks = manager->getAll(basePacks); + + postContent(); +} + +void ContentControl::loadContent(const std::vector& names) { + manager->scan(); + contentPacks = manager->getAll(manager->assemble(names)); + loadContent(); +} + +void ContentControl::loadContent() { + scripting::cleanup(); + + std::vector names; + for (auto& pack : contentPacks) { + names.push_back(pack.id); + } + manager->scan(); + names = manager->assemble(names); + contentPacks = manager->getAll(names); + + std::vector entryPoints; + for (auto& pack : contentPacks) { + entryPoints.emplace_back(pack.id, pack.folder); + } + paths.setEntryPoints(std::move(entryPoints)); + + ContentBuilder contentBuilder; + corecontent::setup(input, contentBuilder); + + auto corePack = ContentPack::createCore(paths); + + auto allPacks = contentPacks; + allPacks.insert(allPacks.begin(), corePack); + + // Setup filesystem entry points + std::vector resRoots; + for (auto& pack : allPacks) { + resRoots.push_back({pack.id, pack.folder}); + } + paths.resPaths = ResPaths(resRoots); + // Load content + for (auto& pack : allPacks) { + ContentLoader(&pack, contentBuilder, paths.resPaths).load(); + load_configs(input, pack.folder); + } + content = contentBuilder.build(); + scripting::on_content_load(content.get()); + + ContentLoader::loadScripts(*content); + + postContent(); +} + +std::vector& ContentControl::getContentPacks() { + return contentPacks; +} + +std::vector ContentControl::getAllContentPacks() { + auto packs = contentPacks; + packs.insert(packs.begin(), ContentPack::createCore(paths)); + return packs; +} + +PacksManager& ContentControl::scan() { + manager->scan(); + return *manager; +} diff --git a/src/content/ContentControl.hpp b/src/content/ContentControl.hpp new file mode 100644 index 00000000..db08fa5a --- /dev/null +++ b/src/content/ContentControl.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +#include "ContentPack.hpp" + +class Content; +class PacksManager; +class EnginePaths; +class Input; + +namespace io { + class path; +} + +class ContentControl { +public: + ContentControl( + EnginePaths& paths, Input& input, std::function postContent + ); + ~ContentControl(); + + Content* get(); + + const Content* get() const; + + std::vector& getBasePacks(); + + /// @brief Reset content to base packs list + void resetContent(); + + void loadContent(const std::vector& names); + + void loadContent(); + + std::vector& getContentPacks(); + std::vector getAllContentPacks(); + + PacksManager& scan(); +private: + EnginePaths& paths; + Input& input; + std::unique_ptr content; + std::function postContent; + std::vector basePacks; + std::unique_ptr manager; + std::vector contentPacks; +}; diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index 73a9503e..0db38f88 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -15,12 +15,13 @@ namespace fs = std::filesystem; ContentPack ContentPack::createCore(const EnginePaths& paths) { return ContentPack { - "core", "Core", ENGINE_VERSION_STRING, "", "", "res:", "res:", {} + "core", "Core", ENGINE_VERSION_STRING, "", "", "res:", {} }; } const std::vector ContentPack::RESERVED_NAMES = { - "res", "abs", "local", "core", "user", "world", "none", "null"}; + "res", "abs", "local", "core", "user", "world", "none", "null" +}; contentpack_error::contentpack_error( std::string packId, io::path folder, const std::string& message @@ -70,7 +71,7 @@ static void checkContentPackId(const std::string& id, const io::path& folder) { } } -ContentPack ContentPack::read(const std::string& path, const io::path& folder) { +ContentPack ContentPack::read(const io::path& folder) { auto root = io::read_json(folder / PACKAGE_FILENAME); ContentPack pack; root.at("id").get(pack.id); @@ -90,7 +91,6 @@ ContentPack ContentPack::read(const std::string& path, const io::path& folder) { root.at("description").get(pack.description); root.at("source").get(pack.source); pack.folder = folder; - pack.path = path; if (auto found = root.at("dependencies")) { const auto& dependencies = *found; @@ -124,7 +124,7 @@ ContentPack ContentPack::read(const std::string& path, const io::path& folder) { } void ContentPack::scanFolder( - const std::string& path, const io::path& folder, std::vector& packs + const io::path& folder, std::vector& packs ) { if (!io::is_directory(folder)) { return; @@ -133,9 +133,7 @@ void ContentPack::scanFolder( if (!io::is_directory(packFolder)) continue; if (!is_pack(packFolder)) continue; try { - packs.push_back( - read(path + "/" + packFolder.name(), packFolder) - ); + packs.push_back(read(packFolder)); } catch (const contentpack_error& err) { std::cerr << "package.json error at " << err.getFolder().string(); std::cerr << ": " << err.what() << std::endl; diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index ed7c3eb8..be0b7c71 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -43,7 +43,6 @@ struct ContentPack { std::string creator = ""; std::string description = "no description"; io::path folder; - std::string path; std::vector dependencies; std::string source = ""; @@ -58,14 +57,10 @@ struct ContentPack { static const std::vector RESERVED_NAMES; static bool is_pack(const io::path& folder); - static ContentPack read( - const std::string& path, const io::path& folder - ); + static ContentPack read(const io::path& folder); static void scanFolder( - const std::string& path, - const io::path& folder, - std::vector& packs + const io::path& folder, std::vector& packs ); static std::vector worldPacksList( diff --git a/src/content/PacksManager.cpp b/src/content/PacksManager.cpp index 49c3014c..0e9925c8 100644 --- a/src/content/PacksManager.cpp +++ b/src/content/PacksManager.cpp @@ -7,7 +7,7 @@ PacksManager::PacksManager() = default; -void PacksManager::setSources(std::vector> sources) { +void PacksManager::setSources(std::vector sources) { this->sources = std::move(sources); } @@ -15,8 +15,8 @@ void PacksManager::scan() { packs.clear(); std::vector packsList; - for (auto& [path, folder] : sources) { - ContentPack::scanFolder(path, folder, packsList); + for (auto& folder : sources) { + ContentPack::scanFolder(folder, packsList); for (auto& pack : packsList) { packs.try_emplace(pack.id, pack); } diff --git a/src/content/PacksManager.hpp b/src/content/PacksManager.hpp index 32ac43d6..94ce7bdc 100644 --- a/src/content/PacksManager.hpp +++ b/src/content/PacksManager.hpp @@ -8,12 +8,12 @@ class PacksManager { std::unordered_map packs; - std::vector> sources; + std::vector sources; public: PacksManager(); /// @brief Set content packs sources (search folders) - void setSources(std::vector> sources); + void setSources(std::vector sources); /// @brief Scan sources and collect all found packs excluding duplication. /// Scanning order depends on sources order diff --git a/src/core_defs.cpp b/src/core_defs.cpp index def96d69..07eebee5 100644 --- a/src/core_defs.cpp +++ b/src/core_defs.cpp @@ -5,13 +5,12 @@ #include "content/ContentBuilder.hpp" #include "io/io.hpp" #include "io/engine_paths.hpp" -#include "window/Window.hpp" -#include "window/Events.hpp" #include "window/input.hpp" #include "voxels/Block.hpp" +#include "coders/toml.hpp" // All in-game definitions (blocks, items, etc..) -void corecontent::setup(ContentBuilder& builder) { +void corecontent::setup(Input& input, ContentBuilder& builder) { { Block& block = builder.blocks.create(CORE_AIR); block.replaceable = true; @@ -30,8 +29,8 @@ void corecontent::setup(ContentBuilder& builder) { auto bindsFile = "res:bindings.toml"; if (io::is_regular_file(bindsFile)) { - Events::loadBindings( - bindsFile, io::read_string(bindsFile), BindType::BIND + input.getBindings().read( + toml::parse(bindsFile, io::read_string(bindsFile)), BindType::BIND ); } diff --git a/src/core_defs.hpp b/src/core_defs.hpp index f269af10..d85d3448 100644 --- a/src/core_defs.hpp +++ b/src/core_defs.hpp @@ -28,8 +28,9 @@ inline const std::string BIND_PLAYER_FAST_INTERACTOIN = "player.fast_interaction"; inline const std::string BIND_HUD_INVENTORY = "hud.inventory"; +class Input; class ContentBuilder; namespace corecontent { - void setup(ContentBuilder& builder); + void setup(Input& input, ContentBuilder& builder); } diff --git a/src/data/StructLayout.cpp b/src/data/StructLayout.cpp index d0e207a1..94d21ecf 100644 --- a/src/data/StructLayout.cpp +++ b/src/data/StructLayout.cpp @@ -6,6 +6,7 @@ #include #include +#include "data/dv.hpp" #include "util/data_io.hpp" #include "util/stringutil.hpp" diff --git a/src/data/dv_fwd.hpp b/src/data/dv_fwd.hpp new file mode 100644 index 00000000..a3060df3 --- /dev/null +++ b/src/data/dv_fwd.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace dv { + class value; + struct optionalvalue; +} diff --git a/src/data/setting.hpp b/src/data/setting.hpp index 7068ebf7..2afa610c 100644 --- a/src/data/setting.hpp +++ b/src/data/setting.hpp @@ -7,6 +7,7 @@ #include "delegates.hpp" #include "typedefs.hpp" +#include "util/observer_handler.hpp" enum class setting_format { simple, percent }; @@ -41,15 +42,14 @@ public: : Setting(format), initial(value), value(value) { } - observer_handler observe(consumer callback, bool callOnStart = false) { + ObserverHandler observe(consumer callback, bool callOnStart = false) { const int id = nextid++; observers.emplace(id, callback); if (callOnStart) { callback(value); } - return std::shared_ptr(new int(id), [this](int* id) { //-V508 - observers.erase(*id); - delete id; + return ObserverHandler([this, id]() { + observers.erase(id); }); } diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 5778ed98..112ece7f 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -12,9 +12,7 @@ #include "coders/json.hpp" #include "coders/toml.hpp" #include "coders/commons.hpp" -#include "content/Content.hpp" -#include "content/ContentBuilder.hpp" -#include "content/ContentLoader.hpp" +#include "content/ContentControl.hpp" #include "core_defs.hpp" #include "io/io.hpp" #include "frontend/locale.hpp" @@ -31,10 +29,8 @@ #include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting_hud.hpp" #include "network/Network.hpp" -#include "util/listutil.hpp" #include "util/platform.hpp" #include "window/Camera.hpp" -#include "window/Events.hpp" #include "window/input.hpp" #include "window/Window.hpp" #include "world/Level.hpp" @@ -50,8 +46,6 @@ static debug::Logger logger("engine"); -namespace fs = std::filesystem; - static std::unique_ptr load_icon() { try { auto file = "res:textures/misc/icon.png"; @@ -65,20 +59,21 @@ static std::unique_ptr load_icon() { } Engine::Engine() = default; +Engine::~Engine() = default; -static std::unique_ptr engine; +static std::unique_ptr instance = nullptr; Engine& Engine::getInstance() { - if (!engine) { - engine = std::make_unique(); + if (!instance) { + instance = std::make_unique(); } - return *engine; + return *instance; } void Engine::initialize(CoreParameters coreParameters) { params = std::move(coreParameters); settingsHandler = std::make_unique(settings); - interpreter = std::make_unique(); + cmd = std::make_unique(); network = network::Network::create(settings.network); logger.info() << "engine version: " << ENGINE_VERSION_STRING; @@ -93,46 +88,80 @@ void Engine::initialize(CoreParameters coreParameters) { } loadSettings(); - auto resdir = paths.getResourcesFolder(); - controller = std::make_unique(*this); if (!params.headless) { - if (Window::initialize(&settings.display)){ + 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"); } - time.set(Window::time()); + window->setFramerate(settings.display.framerate.get()); + + time.set(window->time()); if (auto icon = load_icon()) { icon->flipY(); - Window::setIcon(icon.get()); + window->setIcon(icon.get()); } + this->window = std::move(window); + this->input = std::move(input); + loadControls(); - gui = std::make_unique(); + gui = std::make_unique(*this); if (ENGINE_DEBUG_BUILD) { - menus::create_version_label(*this); + 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(), - "res:" - )); + settings.ui.language.set( + langs::locale_by_envlocale(platform::detect_locale()) + ); } + content = std::make_unique(paths, *input, [this]() { + langs::setup(langs::get_current(), paths.resPaths.collectRoots()); + if (!isHeadless()) { + 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(); + } + }); scripting::initialize(this); if (!isHeadless()) { gui->setPageLoader(scripting::create_page_loader()); } keepAlive(settings.ui.language.observe([this](auto lang) { - setLanguage(lang); + langs::setup(lang, paths.resPaths.collectRoots()); }, true)); - basePacks = io::read_list("res:config/builtins.list"); } void Engine::loadSettings() { - io::path settings_file = paths.getSettingsFile(); + 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); @@ -146,33 +175,30 @@ void Engine::loadSettings() { } void Engine::loadControls() { - io::path controls_file = paths.getControlsFile(); + 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); - Events::loadBindings(controls_file.string(), text, BindType::BIND); + input->getBindings().read( + toml::parse(controls_file.string(), text), BindType::BIND + ); } } -void Engine::onAssetsLoaded() { - assets->setup(); - gui->onAssetsLoad(assets.get()); -} - void Engine::updateHotkeys() { - if (Events::jpressed(keycode::F2)) { + if (input->jpressed(Keycode::F2)) { saveScreenshot(); } - if (Events::jpressed(keycode::F8)) { + if (input->jpressed(Keycode::F8)) { gui->toggleDebug(); } - if (Events::jpressed(keycode::F11)) { + if (input->jpressed(Keycode::F11)) { settings.display.fullscreen.toggle(); } } void Engine::saveScreenshot() { - auto image = Window::takeScreenshot(); + auto image = window->takeScreenshot(); image->flipY(); io::path filename = paths.getNewScreenshotFile("png"); imageio::write(filename.string(), image.get()); @@ -197,39 +223,38 @@ void Engine::updateFrontend() { double delta = time.getDelta(); updateHotkeys(); audio::update(delta); - gui->act(delta, Viewport(Window::width, Window::height)); + gui->act(delta, window->getSize()); screen->update(delta); gui->postAct(); } void Engine::nextFrame() { - Window::setFramerate( - Window::isIconified() && settings.display.limitFpsIconified.get() + window->setFramerate( + window->isIconified() && settings.display.limitFpsIconified.get() ? 20 : settings.display.framerate.get() ); - Window::swapBuffers(); - Events::pollEvents(); + window->swapBuffers(); + input->pollEvents(); } void Engine::renderFrame() { screen->draw(time.getDelta()); - Viewport viewport(Window::width, Window::height); - DrawContext ctx(nullptr, viewport, nullptr); + DrawContext ctx(nullptr, *window, nullptr); gui->draw(ctx, *assets); } void Engine::saveSettings() { logger.info() << "saving settings"; - io::write_string(paths.getSettingsFile(), toml::stringify(*settingsHandler)); + io::write_string(EnginePaths::SETTINGS_FILE, toml::stringify(*settingsHandler)); if (!params.headless) { logger.info() << "saving bindings"; - io::write_string(paths.getControlsFile(), Events::writeBindings()); + io::write_string(EnginePaths::CONTROLS_FILE, input->getBindings().write()); } } -Engine::~Engine() { +void Engine::close() { saveSettings(); logger.info() << "shutting down"; if (screen) { @@ -238,7 +263,7 @@ Engine::~Engine() { } content.reset(); assets.reset(); - interpreter.reset(); + cmd.reset(); if (gui) { gui.reset(); logger.info() << "gui finished"; @@ -249,45 +274,34 @@ Engine::~Engine() { scripting::close(); logger.info() << "scripting finished"; if (!params.headless) { - Window::terminate(); + window.reset(); logger.info() << "window closed"; } logger.info() << "engine finished"; } void Engine::terminate() { - engine.reset(); + instance->close(); + instance.reset(); } EngineController* Engine::getController() { return controller.get(); } -cmd::CommandsInterpreter* Engine::getCommandsInterpreter() { - return interpreter.get(); -} - -PacksManager Engine::createPacksManager(const io::path& worldFolder) { - PacksManager manager; - manager.setSources({ - {"world:content", worldFolder.empty() ? worldFolder : worldFolder / "content"}, - {"user:content", "user:content"}, - {"res:content", "res:content"} - }); - return manager; -} - void Engine::setLevelConsumer(OnWorldOpen levelConsumer) { this->levelConsumer = std::move(levelConsumer); } void Engine::loadAssets() { logger.info() << "loading assets"; - Shader::preprocessor->setPaths(resPaths.get()); + Shader::preprocessor->setPaths(&paths.resPaths); + + auto content = this->content->get(); auto new_assets = std::make_unique(); - AssetsLoader loader(new_assets.get(), resPaths.get()); - AssetsLoader::addDefaults(loader, content.get()); + AssetsLoader loader(*this, *new_assets, paths.resPaths); + AssetsLoader::addDefaults(loader, content); // no need // correct log messages order is more useful @@ -301,139 +315,11 @@ void Engine::loadAssets() { } } assets = std::move(new_assets); - - if (content == nullptr) { - return; + if (content) { + ModelsGenerator::prepare(*content, *assets); } - for (auto& [name, def] : content->blocks.getDefs()) { - if (def->model == BlockModel::custom && def->modelName.empty()) { - assets->store( - std::make_unique( - ModelsGenerator::loadCustomBlockModel( - def->customModelRaw, *assets, !def->shadeless - ) - ), - name + ".model" - ); - def->modelName = def->name + ".model"; - } - } - for (auto& [name, def] : content->items.getDefs()) { - assets->store( - std::make_unique( - ModelsGenerator::generate(*def, *content, *assets) - ), - name + ".model" - ); - } -} - -static void load_configs(const io::path& root) { - auto configFolder = root / "config"; - auto bindsFile = configFolder / "bindings.toml"; - if (io::is_regular_file(bindsFile)) { - Events::loadBindings( - bindsFile.string(), io::read_string(bindsFile), BindType::BIND - ); - } -} - -void Engine::loadContent() { - scripting::cleanup(); - - std::vector names; - for (auto& pack : contentPacks) { - names.push_back(pack.id); - } - - ContentBuilder contentBuilder; - corecontent::setup(contentBuilder); - - paths.setContentPacks(&contentPacks); - PacksManager manager = createPacksManager(paths.getCurrentWorldFolder()); - manager.scan(); - names = manager.assemble(names); - contentPacks = manager.getAll(names); - - auto corePack = ContentPack::createCore(paths); - - // Setup filesystem entry points - std::vector resRoots { - {"core", corePack.folder} - }; - for (auto& pack : contentPacks) { - resRoots.push_back({pack.id, pack.folder}); - } - resPaths = std::make_unique("res:", resRoots); - - // Load content - { - ContentLoader(&corePack, contentBuilder, *resPaths).load(); - load_configs(corePack.folder); - } - for (auto& pack : contentPacks) { - ContentLoader(&pack, contentBuilder, *resPaths).load(); - load_configs(pack.folder); - } - content = contentBuilder.build(); - interpreter->reset(); - scripting::on_content_load(content.get()); - - ContentLoader::loadScripts(*content); - - langs::setup("res:", langs::current->getId(), contentPacks); - if (!isHeadless()) { - loadAssets(); - onAssetsLoaded(); - } -} - -void Engine::resetContent() { - scripting::cleanup(); - std::vector resRoots; - { - auto pack = ContentPack::createCore(paths); - resRoots.push_back({"core", pack.folder}); - load_configs(pack.folder); - } - auto manager = createPacksManager(io::path()); - manager.scan(); - for (const auto& pack : manager.getAll(basePacks)) { - resRoots.push_back({pack.id, pack.folder}); - } - resPaths = std::make_unique("res:", resRoots); - contentPacks.clear(); - content.reset(); - - langs::setup("res:", langs::current->getId(), contentPacks); - if (!isHeadless()) { - loadAssets(); - onAssetsLoaded(); - } - - contentPacks = manager.getAll(basePacks); -} - -void Engine::loadWorldContent(const io::path& folder) { - contentPacks.clear(); - auto packNames = ContentPack::worldPacksList(folder); - PacksManager manager; - manager.setSources( - {{"world:content", folder.empty() ? folder : folder / "content"}, - {"user:content", "user:content"}, - {"res:content", "res:content"}} - ); - manager.scan(); - contentPacks = manager.getAll(manager.assemble(packNames)); - paths.setCurrentWorldFolder(folder); - loadContent(); -} - -void Engine::loadAllPacks() { - PacksManager manager = createPacksManager(paths.getCurrentWorldFolder()); - manager.scan(); - auto allnames = manager.getAllNames(); - contentPacks = manager.getAll(manager.assemble(allnames)); + assets->setup(); + gui->onAssetsLoad(assets.get()); } void Engine::setScreen(std::shared_ptr screen) { @@ -443,10 +329,6 @@ void Engine::setScreen(std::shared_ptr screen) { this->screen = std::move(screen); } -void Engine::setLanguage(std::string locale) { - langs::setup("res:", std::move(locale), contentPacks); -} - void Engine::onWorldOpen(std::unique_ptr level, int64_t localPlayer) { logger.info() << "world open"; levelConsumer(std::move(level), localPlayer); @@ -460,7 +342,7 @@ void Engine::onWorldClosed() { void Engine::quit() { quitSignal = true; if (!isHeadless()) { - Window::setShouldClose(true); + window->setShouldClose(true); } } @@ -468,10 +350,6 @@ bool Engine::isQuitSignal() const { return quitSignal; } -gui::GUI* Engine::getGUI() { - return gui.get(); -} - EngineSettings& Engine::getSettings() { return settings; } @@ -480,34 +358,12 @@ Assets* Engine::getAssets() { return assets.get(); } -const Content* Engine::getContent() const { - return content.get(); -} - -Content* Engine::getWriteableContent() { - return content.get(); -} - -std::vector Engine::getAllContentPacks() { - auto packs = getContentPacks(); - packs.insert(packs.begin(), ContentPack::createCore(paths)); - return packs; -} - -std::vector& Engine::getContentPacks() { - return contentPacks; -} - -std::vector& Engine::getBasePacks() { - return basePacks; -} - EnginePaths& Engine::getPaths() { return paths; } -ResPaths* Engine::getResPaths() { - return resPaths.get(); +ResPaths& Engine::getResPaths() { + return paths.resPaths; } std::shared_ptr Engine::getScreen() { @@ -518,10 +374,6 @@ SettingsHandler& Engine::getSettingsHandler() { return *settingsHandler; } -network::Network& Engine::getNetwork() { - return *network; -} - Time& Engine::getTime() { return time; } @@ -533,3 +385,7 @@ const CoreParameters& Engine::getCoreParameters() const { bool Engine::isHeadless() const { return params.headless; } + +ContentControl& Engine::getContentControl() { + return *content; +} diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp index ccfabeed..f352d12b 100644 --- a/src/engine/Engine.hpp +++ b/src/engine/Engine.hpp @@ -4,10 +4,6 @@ #include "typedefs.hpp" #include "settings.hpp" -#include "assets/Assets.hpp" -#include "content/content_fwd.hpp" -#include "content/ContentPack.hpp" -#include "content/PacksManager.hpp" #include "io/engine_paths.hpp" #include "io/settings_io.hpp" #include "util/ObjectsKeeper.hpp" @@ -15,17 +11,15 @@ #include "Time.hpp" #include -#include #include -#include +class Window; +class Assets; class Level; class Screen; -class EnginePaths; -class ResPaths; +class ContentControl; class EngineController; -class SettingsHandler; -struct EngineSettings; +class Input; namespace gui { class GUI; @@ -62,13 +56,12 @@ class Engine : public util::ObjectsKeeper { std::unique_ptr settingsHandler; std::unique_ptr assets; std::shared_ptr screen; - std::vector contentPacks; - std::unique_ptr content; - std::unique_ptr resPaths; + std::unique_ptr content; std::unique_ptr controller; - std::unique_ptr interpreter; + std::unique_ptr cmd; std::unique_ptr network; - std::vector basePacks; + std::unique_ptr window; + std::unique_ptr input; std::unique_ptr gui; PostRunnables postRunnables; Time time; @@ -87,6 +80,7 @@ public: static Engine& getInstance(); void initialize(CoreParameters coreParameters); + void close(); static void terminate(); @@ -98,9 +92,6 @@ public: void updateFrontend(); void renderFrame(); void nextFrame(); - - /// @brief Called after assets loading when all engine systems are initialized - void onAssetsLoaded(); /// @brief Set screen (scene). /// nullptr may be used to delete previous screen before creating new one, @@ -108,29 +99,8 @@ public: /// @param screen nullable screen void setScreen(std::shared_ptr screen); - /// @brief Change locale to specified - /// @param locale isolanguage_ISOCOUNTRY (example: en_US) - void setLanguage(std::string locale); - - /// @brief Load all selected content-packs and reload assets - void loadContent(); - - /// @brief Reset content to base packs list - void resetContent(); - - /// @brief Collect world content-packs and load content - /// @see loadContent - /// @param folder world folder - void loadWorldContent(const io::path& folder); - - /// @brief Collect all available content-packs from res/content - void loadAllPacks(); - /// @brief Get active assets storage instance Assets* getAssets(); - - /// @brief Get main UI controller - gui::GUI* getGUI(); /// @brief Get writeable engine settings structure instance EngineSettings& getSettings(); @@ -139,7 +109,7 @@ public: EnginePaths& getPaths(); /// @brief Get engine resource paths controller - ResPaths* getResPaths(); + ResPaths& getResPaths(); void onWorldOpen(std::unique_ptr level, int64_t localPlayer); void onWorldClosed(); @@ -148,18 +118,6 @@ public: bool isQuitSignal() const; - /// @brief Get current Content instance - const Content* getContent() const; - - Content* getWriteableContent(); - - /// @brief Get selected content packs - std::vector& getContentPacks(); - - std::vector getAllContentPacks(); - - std::vector& getBasePacks(); - /// @brief Get current screen std::shared_ptr getScreen(); @@ -171,19 +129,36 @@ public: void saveScreenshot(); EngineController* getController(); - cmd::CommandsInterpreter* getCommandsInterpreter(); - - PacksManager createPacksManager(const io::path& worldFolder); void setLevelConsumer(OnWorldOpen levelConsumer); SettingsHandler& getSettingsHandler(); - network::Network& getNetwork(); - Time& getTime(); const CoreParameters& getCoreParameters() const; bool isHeadless() const; + + ContentControl& getContentControl(); + + gui::GUI& getGUI() { + return *gui; + } + + Input& getInput() { + return *input; + } + + Window& getWindow() { + return *window; + } + + network::Network& getNetwork() { + return *network; + } + + cmd::CommandsInterpreter& getCmd() { + return *cmd; + } }; diff --git a/src/engine/Mainloop.cpp b/src/engine/Mainloop.cpp index 2de6291d..ecfd17bc 100644 --- a/src/engine/Mainloop.cpp +++ b/src/engine/Mainloop.cpp @@ -14,6 +14,7 @@ Mainloop::Mainloop(Engine& engine) : engine(engine) { void Mainloop::run() { auto& time = engine.getTime(); + auto& window = engine.getWindow(); engine.setLevelConsumer([this](auto level, int64_t localPlayer) { if (level == nullptr) { @@ -32,10 +33,10 @@ void Mainloop::run() { engine.setScreen(std::make_shared(engine)); logger.info() << "main loop started"; - while (!Window::isShouldClose()){ - time.update(Window::time()); + while (!window.isShouldClose()){ + time.update(window.time()); engine.updateFrontend(); - if (!Window::isIconified()) { + if (!window.isIconified()) { engine.renderFrame(); } engine.postUpdate(); diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index 844fd4e6..574c22a5 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -69,10 +69,6 @@ void ContentGfxCache::refresh() { ContentGfxCache::~ContentGfxCache() = default; -const Content* ContentGfxCache::getContent() const { - return &content; -} - const model::Model& ContentGfxCache::getModel(blockid_t id) const { const auto& found = models.find(id); if (found == models.end()) { diff --git a/src/frontend/ContentGfxCache.hpp b/src/frontend/ContentGfxCache.hpp index cb5e2ad7..b5816c82 100644 --- a/src/frontend/ContentGfxCache.hpp +++ b/src/frontend/ContentGfxCache.hpp @@ -6,6 +6,7 @@ #include #include +#include "maths/UVRegion.hpp" #include "graphics/commons/Model.hpp" class Content; @@ -15,10 +16,6 @@ class Block; struct UVRegion; struct GraphicsSettings; -namespace model { - struct Model; -} - class ContentGfxCache { const Content& content; const Assets& assets; @@ -41,8 +38,6 @@ public: const model::Model& getModel(blockid_t id) const; - const Content* getContent() const; - void refresh(const Block& block, const Atlas& atlas); void refresh(); diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index 1bc6e9e2..5dfdf822 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -12,27 +12,33 @@ #include "objects/Player.hpp" #include "voxels/Block.hpp" #include "world/Level.hpp" +#include "engine/Engine.hpp" LevelFrontend::LevelFrontend( + Engine& engine, Player* currentPlayer, LevelController* controller, - Assets& assets, const EngineSettings& settings ) : level(*controller->getLevel()), controller(controller), - assets(assets), + assets(*engine.getAssets()), contentCache(std::make_unique( level.content, assets, settings.graphics )) { assets.store( BlocksPreview::build( - *contentCache, assets, *level.content.getIndices() + engine.getWindow(), + *contentCache, + *engine.getAssets(), + *level.content.getIndices() ), "block-previews" ); + + auto& rassets = assets; controller->getBlocksController()->listenBlockInteraction( - [currentPlayer, controller, &assets](auto player, const auto& pos, const auto& def, BlockInteraction type) { + [currentPlayer, controller, &rassets](auto player, const auto& pos, const auto& def, BlockInteraction type) { const auto& level = *controller->getLevel(); auto material = level.content.findBlockMaterial(def.material); if (material == nullptr) { @@ -40,7 +46,7 @@ LevelFrontend::LevelFrontend( } if (type == BlockInteraction::step) { - auto sound = assets.get(material->stepsSound); + auto sound = rassets.get(material->stepsSound); glm::vec3 pos {}; auto soundsCamera = currentPlayer->currentCamera.get(); if (soundsCamera == currentPlayer->spCamera.get() || @@ -66,10 +72,10 @@ LevelFrontend::LevelFrontend( audio::Sound* sound = nullptr; switch (type) { case BlockInteraction::placing: - sound = assets.get(material->placeSound); + sound = rassets.get(material->placeSound); break; case BlockInteraction::destruction: - sound = assets.get(material->breakSound); + sound = rassets.get(material->breakSound); break; default: break; @@ -95,14 +101,6 @@ Level& LevelFrontend::getLevel() { return level; } -const Level& LevelFrontend::getLevel() const { - return level; -} - -const Assets& LevelFrontend::getAssets() const { - return assets; -} - ContentGfxCache& LevelFrontend::getContentGfxCache() { return *contentCache; } diff --git a/src/frontend/LevelFrontend.hpp b/src/frontend/LevelFrontend.hpp index b53d267a..cc4f996b 100644 --- a/src/frontend/LevelFrontend.hpp +++ b/src/frontend/LevelFrontend.hpp @@ -5,6 +5,7 @@ class Level; class Assets; class Player; +class Engine; class ContentGfxCache; class LevelController; struct EngineSettings; @@ -12,20 +13,18 @@ struct EngineSettings; class LevelFrontend { Level& level; LevelController* controller; - const Assets& assets; + Assets& assets; std::unique_ptr contentCache; public: LevelFrontend( + Engine& engine, Player* currentPlayer, LevelController* controller, - Assets& assets, const EngineSettings& settings ); ~LevelFrontend(); Level& getLevel(); - const Level& getLevel() const; - const Assets& getAssets() const; const ContentGfxCache& getContentGfxCache() const; ContentGfxCache& getContentGfxCache(); LevelController* getController() const; diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp index 1dfa18aa..5e8df9f1 100644 --- a/src/frontend/UiDocument.cpp +++ b/src/frontend/UiDocument.cpp @@ -21,11 +21,11 @@ void UiDocument::rebuildIndices() { gui::UINode::getIndices(root, map); } -const uinodes_map& UiDocument::getMap() const { +const UINodesMap& UiDocument::getMap() const { return map; } -uinodes_map& UiDocument::getMapWriteable() { +UINodesMap& UiDocument::getMapWriteable() { return map; } @@ -54,6 +54,7 @@ scriptenv UiDocument::getEnvironment() const { } std::unique_ptr UiDocument::read( + gui::GUI& gui, const scriptenv& penv, const std::string& name, const io::path& file, @@ -66,7 +67,7 @@ std::unique_ptr UiDocument::read( ? scripting::create_doc_environment(scripting::get_root_environment(), name) : scripting::create_doc_environment(penv, name); - gui::UiXmlReader reader(env); + gui::UiXmlReader reader(gui, env); auto view = reader.readXML(file.string(), *xmldoc->getRoot()); view->setId("root"); uidocscript script {}; @@ -80,8 +81,7 @@ std::unique_ptr UiDocument::read( } std::shared_ptr UiDocument::readElement( - const io::path& file, const std::string& fileName + gui::GUI& gui, const io::path& file, const std::string& fileName ) { - auto document = read(nullptr, file.name(), file, fileName); - return document->getRoot(); + return read(gui, nullptr, file.name(), file, fileName)->getRoot(); } diff --git a/src/frontend/UiDocument.hpp b/src/frontend/UiDocument.hpp index 298e63bc..9d6e6eca 100644 --- a/src/frontend/UiDocument.hpp +++ b/src/frontend/UiDocument.hpp @@ -9,6 +9,7 @@ #include "io/fwd.hpp" namespace gui { + class GUI; class UINode; } @@ -18,12 +19,12 @@ struct uidocscript { bool onclose : 1; }; -using uinodes_map = std::unordered_map>; +using UINodesMap = std::unordered_map>; class UiDocument { std::string id; uidocscript script; - uinodes_map map; + UINodesMap map; std::shared_ptr root; scriptenv env; public: @@ -37,20 +38,21 @@ public: void rebuildIndices(); const std::string& getId() const; - const uinodes_map& getMap() const; - uinodes_map& getMapWriteable(); + const UINodesMap& getMap() const; + UINodesMap& getMapWriteable(); std::shared_ptr getRoot() const; std::shared_ptr get(const std::string& id) const; const uidocscript& getScript() const; scriptenv getEnvironment() const; static std::unique_ptr read( + gui::GUI&, const scriptenv& parent_env, const std::string& name, const io::path& file, const std::string& fileName ); static std::shared_ptr readElement( - const io::path& file, const std::string& fileName + gui::GUI&, const io::path& file, const std::string& fileName ); }; diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 779597e1..67b43f5a 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -35,8 +35,8 @@ using namespace gui; -static std::shared_ptr