#pragma once #include #include #include #include #include "external/fkyaml_node.hpp" class Entity; // Factory amb registre obert a extensió: cada `concrete/*.cpp` declara un // registrador estàtic amb `REGISTER_ENTITY(name, Class)` que s'executa // abans de `main`. Afegir un tipus nou d'entitat = nova classe + nova // crida `REGISTER_ENTITY`, sense tocar el factory. // // El `Creator` rep el `fkyaml::node` de la plantilla de l'entitat (no el // spawn). És el constructor de la classe concreta qui extreu els camps // que li interessin (sprite.path, animations, stats, etc.). class EntityFactory { public: using Creator = std::function(const fkyaml::node&)>; static auto instance() -> EntityFactory&; // Idempotent per nom: si s'invoca dues vegades amb el mateix nom, // la segona sobreescriu (útil en tests que re-registren; no és // especialment defensiu perquè els registradors són d'execució única). void registerCreator(const std::string& name, Creator creator); // Retorna nullptr si el nom no té registrada cap creator. [[nodiscard]] auto create(const std::string& name, const fkyaml::node& yaml) const -> std::unique_ptr; // Diagnòstic: llista de noms registrats (per ordre d'inserció no // garantit). Útil al smoke per comprovar que tots els concrets // s'han auto-registrat. [[nodiscard]] auto registeredNames() const -> std::vector; private: EntityFactory() = default; std::unordered_map creators_; }; // La macro genera un objecte anònim dins un namespace anònim: el seu // constructor s'executa abans de `main` i registra la factory. El nom // del símbol es compon amb `TAG`, perquè així es poden registrar àlies // per una mateixa classe (p.ex. dues claus diferents al YAML lligades a // la mateixa classe C++) sense que els símbols xoquen. La forma curta // `REGISTER_ENTITY(NAME, CLASS)` reusa `CLASS` com a tag; per àlies cal // usar `REGISTER_ENTITY_AS` i passar un identificador únic. // // IMPORTANT: perquè el registrador es cridi, el `.cpp` que el conté ha // d'estar enllaçat directament a l'executable (no dins una STATIC lib // sense `--whole-archive`). Tots els `concrete/*.cpp` entren a APP_SOURCES. #define REGISTER_ENTITY_AS(NAME, CLASS, TAG) \ namespace { \ struct TAG##_Registrar { \ TAG##_Registrar() { \ EntityFactory::instance().registerCreator( \ NAME, \ [](const fkyaml::node& yaml) -> std::unique_ptr { \ return std::make_unique(yaml); \ }); \ } \ }; \ /* NOLINTNEXTLINE(cert-err58-cpp) -- inicialització estàtica intencionada: el constructor registra el creator a l'EntityFactory abans de main(); el patró és l'únic mecanisme C++ que permet auto-registre per tipus sense modificar la factory. */ \ const TAG##_Registrar g_##TAG##_registrar; \ } #define REGISTER_ENTITY(NAME, CLASS) REGISTER_ENTITY_AS(NAME, CLASS, CLASS)