From 4333d9ab061a64abc70b104ffd07b476fb318cf8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 1 Jun 2025 23:08:59 +0300 Subject: [PATCH] feat: support alternative vcm models syntax --- res/devtools/syntax/vcm.toml | 3 + res/models/stairs.vcm | 4 ++ res/models/stairs.xml | 6 -- res/modules/internal/scripts_registry.lua | 2 +- src/assets/assetload_funcs.cpp | 40 +++++++---- src/coders/vcm.cpp | 21 ++++-- src/coders/xml.cpp | 77 ++++++++++++++++++++++ src/coders/xml.hpp | 4 ++ src/logic/scripting/lua/libs/libassets.cpp | 2 +- test/coders/xml.cpp | 24 +++++++ 10 files changed, 155 insertions(+), 28 deletions(-) create mode 100644 res/devtools/syntax/vcm.toml create mode 100644 res/models/stairs.vcm delete mode 100644 res/models/stairs.xml create mode 100644 test/coders/xml.cpp diff --git a/res/devtools/syntax/vcm.toml b/res/devtools/syntax/vcm.toml new file mode 100644 index 00000000..ca05b6a1 --- /dev/null +++ b/res/devtools/syntax/vcm.toml @@ -0,0 +1,3 @@ +language = "VCM" +extensions = ["vcm"] +line-comment-start = "#" diff --git a/res/models/stairs.vcm b/res/models/stairs.vcm new file mode 100644 index 00000000..2027c09e --- /dev/null +++ b/res/models/stairs.vcm @@ -0,0 +1,4 @@ +@box from (0,0,0) to (1,0.5,1) delete (top,south) +@box from (0,0.5,0) to (1,1,0.5) delete (bottom,south) +@rect from (0,0.5,0.5) right (1,0,0) up (0,0,0.5) texture "$2" +@rect from (0,0,0) right (1,0,0) up (0,1,0) texture "$1" diff --git a/res/models/stairs.xml b/res/models/stairs.xml deleted file mode 100644 index 5921e08c..00000000 --- a/res/models/stairs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/res/modules/internal/scripts_registry.lua b/res/modules/internal/scripts_registry.lua index db6fc321..ef347159 100644 --- a/res/modules/internal/scripts_registry.lua +++ b/res/modules/internal/scripts_registry.lua @@ -55,7 +55,7 @@ local function load_models_list(packs) local registry = export.registry for _, filename in ipairs(file.list("models")) do local ext = file.ext(filename) - if ext == "xml" then + if ext == "xml" or ext == "vcm" then registry[filename] = {type="model", unit=file.stem(filename)} table.insert(export.filenames, filename) end diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index af8fb3c9..d5994469 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -352,21 +352,35 @@ assetload::postfunc assetload::model( throw; } } - path = paths.find(file + ".xml"); - if (io::exists(path)) { - auto text = io::read_string(path); - try { - auto model = vcm::parse(path.string(), text).release(); - return [=](Assets* assets) { - request_textures(loader, *model); - assets->store(std::unique_ptr(model), name); - }; - } catch (const parsing_error& err) { - std::cerr << err.errorLog() << std::endl; - throw; + + std::array extensions { + ".xml", + ".vcm" + }; + + path = ""; + for (const auto& ext : extensions) { + auto newPath = paths.find(file + ext); + if (io::exists(newPath)) { + path = std::move(newPath); + break; } } - throw std::runtime_error("could not to find model " + util::quote(file)); + if (path.empty()) { + throw std::runtime_error("could not to find model " + util::quote(file)); + } + + auto text = io::read_string(path); + try { + auto model = vcm::parse(path.string(), text).release(); + return [=](Assets* assets) { + request_textures(loader, *model); + assets->store(std::unique_ptr(model), name); + }; + } catch (const parsing_error& err) { + std::cerr << err.errorLog() << std::endl; + throw; + } } static void read_anim_file( diff --git a/src/coders/vcm.cpp b/src/coders/vcm.cpp index e86fc138..20c50982 100644 --- a/src/coders/vcm.cpp +++ b/src/coders/vcm.cpp @@ -5,6 +5,7 @@ #include "xml.hpp" #include "util/stringutil.hpp" #include "graphics/commons/Model.hpp" +#include "io/io.hpp" using namespace vcm; using namespace xml; @@ -143,12 +144,18 @@ static std::unique_ptr load_model(const xmlelement& root) { std::unique_ptr vcm::parse( std::string_view file, std::string_view src ) { - auto doc = xml::parse(file, src); - const auto& root = *doc->getRoot(); - if (root.getTag() != "model") { - throw std::runtime_error( - "'model' tag expected as root, got '" + root.getTag() + "'" - ); + try { + auto doc = io::path(std::string(file)).extension() == ".xml" + ? xml::parse(file, src) : xml::parse_vcm(file, src, "model"); + const auto& root = *doc->getRoot(); + if (root.getTag() != "model") { + throw std::runtime_error( + "'model' tag expected as root, got '" + root.getTag() + "'" + ); + } + std::cout << xml::stringify(*doc) << std::endl; + return load_model(root); + } catch (const parsing_error& err) { + throw std::runtime_error(err.errorLog()); } - return load_model(root); } diff --git a/src/coders/xml.cpp b/src/coders/xml.cpp index 9ed51e0c..0763fa43 100644 --- a/src/coders/xml.cpp +++ b/src/coders/xml.cpp @@ -355,6 +355,83 @@ std::unique_ptr xml::parse( return parser.parse(); } +namespace { +class VcmParser : public BasicParser { +public: + VcmParser(std::string_view filename, std::string_view source) + : BasicParser(filename, source) { + document = std::make_unique("1.0", "UTF-8"); + hashComment = true; + } + + std::string parseValue() { + char c = peek(); + if (c == '"' || c == '\'') { + nextChar(); + return parseString(c); + } + if (c == '(') { + nextChar(); + // TODO: replace with array parsing after moving to dv::value's + std::string value = std::string(readUntil(')')); + expect(')'); + return value; + } + return std::string(readUntilWhitespace()); + } + + void parseSubElements(Node& node) { + skipWhitespace(); + while (hasNext()) { + if (peek() != '@') { + throw error("unexpected character in element"); + } + nextChar(); + auto subnodePtr = std::make_unique(parseName()); + auto subnode = subnodePtr.get(); + node.add(std::move(subnodePtr)); + + skipWhitespace(); + while (hasNext() && peek() != '@' && peek() != '{' && peek() != '}') { + std::string attrname = parseName(); + skipWhitespace(); + std::string value = parseValue(); + subnode->set(attrname, value); + skipWhitespace(); + } + if (!hasNext()) { + break; + } + char c = peek(); + if (c == '{') { + nextChar(); + parseSubElements(*subnode); + expect('}'); + skipWhitespace(); + } else if (c == '}') { + break; + } + } + } + + std::unique_ptr parse(const std::string& rootTag) { + auto root = std::make_unique(rootTag); + parseSubElements(*root); + document->setRoot(std::move(root)); + return std::move(document); + } +private: + std::unique_ptr document; +}; +} + +std::unique_ptr xml::parse_vcm( + std::string_view filename, std::string_view source, std::string_view tag +) { + VcmParser parser(filename, source); + return parser.parse(std::string(tag)); +} + inline void newline( std::stringstream& ss, bool nice, const std::string& indentStr, int indent ) { diff --git a/src/coders/xml.hpp b/src/coders/xml.hpp index f626dd34..24cf0fbd 100644 --- a/src/coders/xml.hpp +++ b/src/coders/xml.hpp @@ -126,5 +126,9 @@ namespace xml { std::string_view filename, std::string_view source ); + std::unique_ptr parse_vcm( + std::string_view filename, std::string_view source, std::string_view tag + ); + using xmlelement = Node; } diff --git a/src/logic/scripting/lua/libs/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp index 1e0d6f20..8cfb8ca8 100644 --- a/src/logic/scripting/lua/libs/libassets.cpp +++ b/src/logic/scripting/lua/libs/libassets.cpp @@ -51,7 +51,7 @@ static int l_parse_model(lua::State* L) { auto string = lua::require_lstring(L, 2); auto name = lua::require_string(L, 3); - if (format == "xml") { + if (format == "xml" || format == "vcm") { engine->getAssets()->store(vcm::parse(name, string), name); } else { throw std::runtime_error("unknown format " + util::quote(std::string(format))); diff --git a/test/coders/xml.cpp b/test/coders/xml.cpp new file mode 100644 index 00000000..1357a449 --- /dev/null +++ b/test/coders/xml.cpp @@ -0,0 +1,24 @@ +#include + +#include "coders/xml.hpp" +#include "coders/commons.hpp" + +TEST(XML, VCM) { + std::string code = "" + "@line x1 y1 z1\n" + "# @tst ysfdrg\n" + "@box {\n" + " @rect texture \"$2\"\n" + " @utro a 53.1\n" + "}\n" + ; + std::cout << code << std::endl; + + try { + auto document = xml::parse_vcm("", code, "test"); + std::cout << xml::stringify(*document); + } catch (const parsing_error& err) { + std::cerr << err.errorLog() << std::endl; + throw err; + } +}