#pragma once #include #include #include #include #include #include #include #include // Forward: els events poden dur un punter a `Entity` (p. ex. quin sarcòfag // s'ha obert). No cal incloure el header complet aquí. class Entity; // Event pub/sub minimalista. Hi ha un únic `EventBus` per mundus (propietat // de l'`EntityManager`); les entitats que el necessiten en reben un punter // via `Entity::setEventBus`. Aquest sistema cobreix les necessitats // actuals (sarcophagus_opened, level_complete, player_hit, on_tile_entered) // sense portar-hi senyalització més pesada. // // Decisió: `std::variant` per al payload — evita any/void* i facilita // consumir events tipats amb `std::get_if`. Si apareixen més tipus de // payload, s'amplia aquí sense tocar cridants existents. // Tile (col, row) — payload de `tile_painted` (i altres events de tile sense // metadada extra). No és un `pair` per evitar alias ambigus al // variant (dos ints sense nom). struct TilePos { int col{0}; int row{0}; }; // Tile pintat pel jugador — payload de `on_tile_entered`. Porta l'índex // de tile amb què el jugador pinta (cada Player pot pintar amb un tile // diferent, llegit del YAML del mapa). El PaintLayer filtra pintabilitat, // marca i re-publica `tile_painted` amb només la posició. struct PaintedTile { int col{0}; int row{0}; int tile_index{-1}; }; struct Event { std::string name; using Payload = std::variant; Payload payload; // Jugador que ha originat (o provocat) l'event. -1 = sense jugador // associat (events del món, com `sarcophagus_opened` des de la pròpia // entitat). Usat per al HUD (dispatch a P1/P2) i per a regles futures // de puntuació que atribueixin accions al jugador concret. int player_id{-1}; }; class EventBus { public: using Handler = std::function; using SubId = std::size_t; EventBus() = default; EventBus(const EventBus&) = delete; EventBus(EventBus&&) = delete; auto operator=(const EventBus&) -> EventBus& = delete; auto operator=(EventBus&&) -> EventBus& = delete; ~EventBus() = default; // Retorna un identificador únic per poder-se donar de baixa amb // `unsubscribe`. Els identificadors s'assignen globalment i no es // reutilitzen fins a overflow (acceptable per a la vida d'un nivell). auto subscribe(const std::string& name, Handler handler) -> SubId; void unsubscribe(SubId id); // Publica a tots els subscriptors registrats per al nom. Si no n'hi ha // cap, no fa res. void publish(const Event& event); // Forma ergonòmica: construeix un Event amb nom + payload opcional. void publish(const std::string& name) { publish(Event{.name = name, .payload = std::monostate{}}); } void publish(const std::string& name, Entity* entity) { publish(Event{.name = name, .payload = entity}); } void publish(const std::string& name, int value) { publish(Event{.name = name, .payload = value}); } void publish(const std::string& name, TilePos tile) { publish(Event{.name = name, .payload = tile}); } void publish(const std::string& name, PaintedTile painted) { publish(Event{.name = name, .payload = painted}); } private: struct Subscription { SubId id; Handler handler; }; std::unordered_map> subs_; std::uint64_t next_id_{1}; };