ECSQL: Começando o protótipo: um mundo com entidades, componentes e sistemas

2024-12-15  |  
ECS SQL SQLite

Originalmente publicado em https://github.com/gilzoide/ecsql/blob/main/articles/02-prototyping-pt.md

No artigo anterior foi mostrado como podemos modelar mundos de ECS como bases de dados relacionais. Hoje vamos passar por algumas escolhas de design do protótipo que já comecei.

Tecnologias base

Entidades

Em ECSQL, entidades são representadas por IDs numéricos. Criei o tipo EntityID como um typedef para o tipo sqlite3_int64, que é o tipo do SQLite usado para IDs, e é basicamente isso.

namespace ecsql {
    typedef sqlite3_int64 EntityID;
}

Componentes

Em ECSQL cada tipo de componente é uma tabela na base de dados. Criei a classe Component que contém o nome do componente, seus campos e um opcionalmente um SQL adicional. SQLite usa tipagem flexível, então não é necessário especificar o tipo de um campo. Você pode usar valores "DEFAULT", restrições "CHECK" "NOT NULL" e "REFERENCES", qualquer especificação válida em colunas no SQLite é válido aqui. O SQL adicional pode ser usado para criar índices, triggers e views, dentre outros.

Exemplo:

ecsql::Component PositionComponent {
    // nome
    "Position",
    // campos
    {
        "x DEFAULT 0",
        "y DEFAULT 0",
        "z DEFAULT 0",
    },
    // (opcional) SQL adicional
    "",
};

Sistemas

Em ECSQL, sistemas são funções que operam em entidades e componentes do mundo. Sistemas possuem um nome, uma lista de declarações SQL e a implementação. A implementação recebe como parâmetro o mundo e uma lista de declarações SQL já preparadas. Declarações preparadas são mantidas entre chamadas do mesmo sistema, de modo que só gastamos tempo preparando SQL uma vez para cada sistema.

Exemplo:

ecsql::System SistemaDesenhaPonto {
    // nome
    "DesenhaPonto",
    // declarações SQL
    {
        R"(
            SELECT
                x, y, z,
                r, g, b, a
            FROM PointTag
                JOIN Position USING(entity_id)
                JOIN Color USING(entity_id)
        )",
    },
    // implementação
    [](ecsql::World& mundo, std::vector<ecsql::PreparedSQL>& sqls_preparados) {
        auto select_pontos_a_desenhar = sqls_preparados[0];
        for (ecsql::SQLRow row : select_pontos_a_desenhar()) {
            auto [posicao, cor] = row.get<Vector3, Color>();
            desenha_ponto(posicao, cor);
        }
    },
};

Sistemas Gancho

Esses sistemas usam ganchos de preupdate do SQLite e são chamados quando componentes são inseridos/atualizados/apagados. Eles são usados como ponte entre dados do SQL e dados nativos, de modo que dados nativos podem ser criados/atualizados/apagados junto com os dados de SQL correspondentes.

Eu escrevi mais sobre como conectar dados do SQL e dados de C++ aqui

Exemplo:

ecsql::HookSystem GanchoPosition {
    // nome do Componente
    "Position"
    // implementação
    [](ecsql::HookType gancho, ecsql::SQLBaseRow& linha_antiga, ecsql::SQLBaseRow& linha_nova) {
        switch (gancho) {
            case ecsql::HookType::OnInsert:
                // Position inserido
                break;

            case ecsql::HookType::OnUpdate:
                // Position atualizado
                break;

            case ecsql::HookType::OnDelete:
                // Position apagado
                break;
        }
    },
};

Mundo

Finalmente, o mundo ECS. Mundos contêm uma conexão com a base de dados, uma lista dos sistemas e sistemas gancho registrados. Existem métodos pra criar entidades, registrar componentes e sistemas, assim como um método update que roda todos os sistemas registrados.

Exemplo:

ecsql::World mundo;
mundo.register_component(PositionComponent);
mundo.register_system(SistemaDesenhaPonto);
mundo.register_hook_system(GanchoPosition);

while (jogo_esta_rodando()) {
    float delta_time = get_delta_time();
    mundo.update(delta_time);
}

Conclusão

Essa é a arquitetura base do projeto ECSQL. Nos próximos artigos mostrarei informações mais específicas de problemas e soluções da implementação do ECSQL. Até a próxima!



Curte esse conteúdo? Patrocine meu trabalho usando o botão abaixo!