Files
aee-arcade/source/game/entities/entity_factory.hpp
T

72 lines
5.6 KiB
C++

#pragma once
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#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<std::unique_ptr<Entity>(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<Entity>;
// 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<std::string>;
private:
EntityFactory() = default;
std::unordered_map<std::string, Creator> 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<Entity> { \
return std::make_unique<CLASS>(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)