Как загрузить родительские и дочерние объекты из XML с помощью tinyxml2? - PullRequest
1 голос
/ 09 июля 2020

Я использую XML для хранения файлов уровней для игрового движка, над которым я работаю. Я недавно добавил родительские объекты, где каждый объект имеет вектор указателей на его дочерние элементы, а также указатель на его родителя. У каждой сущности есть метод, который принимает указатель на добавление родителя, который также добавляет его к родительскому вектору потомков. Существует также метод добавления дочернего элемента, который делает то же самое, но в обратном порядке. Проблема: я понятия не имею, как загружать сущности с дочерними элементами из XML. Вот как должен выглядеть типичный файл сцены (у меня уже есть код для загрузки сущностей и их компонентов, я просто не уверен, как загрузить родительские элементы)

<scene>
    <entity name="Parent Entity">
        <transform posx="500" posy="100" scalex="1" scaley="1" rotation="0"/>
        <sprite image="Assets/Sprites/Avatar2.png"/>

        <entity name="Child Entity">
            <transform posx="0" posy="0" scalex="1" scaley="1" rotation="0"/>
            <sprite image="crimson-logo.png"/>
        </entity>
    </entity>

</scene>

Итак, по сути, что мне нужно сделать равен l oop по всем объектам и создайте их, используя метод createEntity моего менеджера объектов (который возвращает указатель на новый объект), проверьте, есть ли у них дочерние элементы в XML, а затем, если они есть, вызовите метод родительской сущности addChild с указателем на дочернюю сущность или метод addParent текущей сущности с указателем на родительскую сущность. Я предполагаю, что мне нужно использовать рекурсивную функцию, но как мне написать такую ​​функцию?

1 Ответ

1 голос
/ 09 июля 2020

Это классический тип задач, в которых вы используете рекурсию. Рекурсия сама по себе может быть сложной задачей, но особенно при работе с крошечным xml - не самым дружелюбным API.

ШАГ 1: A find Helper

Давайте сделаем его более дружелюбным. На всех уровнях мы захотим посетить все элементы «сущности». Давайте сделаем удобный помощник для использования Tiny XML, чтобы получить эти:

auto find(TiXmlElement const* node, char const* name) {
    std::vector<TiXmlElement const*> found;
    for (
            auto el = node->FirstChildElement(name);
            el;
            el = el->NextSiblingElement(name)
        )
    {
        found.push_back(el);
    }
    return found;
}

Пока все хорошо, теперь мы можем более легко запрашивать узлы с определенным именем.

ШАГ 2: Разбор отдельной сущности

Давайте на секунду забудем об этом помощнике и предположим, что у нас уже выбран узел «сущность». Теперь мы хотим преобразовать его в сущность (имя, спрайт и т. Д. c.) И вернуть вновь созданную сущность:

Entity* parse_entity(TiXmlElement const* node) {
    Entity* entity = g_manager.createEntity(node->Attribute("name"));
    // todo transforms, sprite info etc.
    return entity;
}

ШАГ 3: Построить дерево

Это звучит как сложная часть. Но на самом деле, используя наш помощник find сверху, все, что нам нужно, это:

void parse_sub_entities(TiXmlElement const* node, Entity* parent = nullptr) {
    for (auto el : find(node, "entity")) {
        auto entity = parse_entity(el);

        if (parent && entity) {
            entity->addParent(parent);
            parent->addChild(entity);
        }

        parse_sub_entities(el, entity);
    }
}

Все дочерние узлы анализируются с использованием parse_entity из шага 2, и только если у нас есть родительский узел, мы добавить отношения.

Чтобы завершить sh все это, мы рекурсивно в дочерние сущности, на этот раз передавая текущую сущность как родительскую.

Полная демонстрация

** Not Live On Coliru ** ¹

#include <tinyxml.h>
#include <vector>
#include <list>
#include <iostream>
#include <iomanip>

namespace { // helper functions for XML searching
    auto find(TiXmlElement const* node, char const* name) {
        std::vector<TiXmlElement const*> found;
        for (
                auto el = node->FirstChildElement(name);
                el;
                el = el->NextSiblingElement(name)
            )
        {
            found.push_back(el);
        }
        return found;
    }
}

auto scene = R"(<scene>
    <entity name="Parent Entity">
        <transform posx="500" posy="100" scalex="1" scaley="1" rotation="0"/>
        <sprite image="Assets/Sprites/Avatar2.png"/>

        <entity name="Child Entity">
            <transform posx="0" posy="0" scalex="1" scaley="1" rotation="0"/>
            <sprite image="crimson-logo.png"/>
        </entity>
    </entity>

</scene>)";

struct Entity {
    std::string _name;
    Entity* _parent = nullptr;
    std::vector<Entity*> _children;

    explicit Entity(std::string name = "unnamed") : _name(std::move(name)) {}

    void addChild(Entity* e) { _children.push_back(e); }
    void addParent(Entity* e) {
        assert(!_parent || _parent == e);
        _parent = e;
    }
};
struct Mgr {
    std::list<Entity> _entities;
    Entity* createEntity(std::string name) {
        return &_entities.emplace_back(name);
    };
} g_manager;

Entity* parse_entity(TiXmlElement const* node) {
    Entity* entity = g_manager.createEntity(node->Attribute("name"));
    // todo transforms, sprite info etc.
    return entity;
}

void parse_sub_entities(TiXmlElement const* node, Entity* parent = nullptr) {
    for (auto el : find(node, "entity")) {
        auto entity = parse_entity(el);

        if (parent && entity) {
            entity->addParent(parent);
            parent->addChild(entity);
        }

        parse_sub_entities(el, entity);
    }
}

int main() {
    TiXmlDocument doc;
    doc.Parse(scene);

    parse_sub_entities(doc.RootElement());

    std::cout << "Manager has " << g_manager._entities.size() << " entities\n";

    for (auto& e: g_manager._entities) {
        std::cout << "==== Entity: " << std::quoted(e._name) << "\n";
        if (e._parent)
            std::cout << " - has parent " << std::quoted(e._parent->_name) << "\n";
        for (auto child : e._children)
            std::cout << " - has child " << std::quoted(child->_name) << "\n";
    }
}

Распечатывает:

Manager has 2 entities
==== Entity: "Parent Entity"
 - has child "Child Entity"
==== Entity: "Child Entity"
 - has parent "Parent Entity"

¹ no tiny xml установлен на любом онлайн компилятор, который я знаю

...