refactor(em): un sol vector entities_ — polimorfisme per la
discriminació
Reverteix la separació en sacs introduïda al commit 62f4b85 (rama
anterior). Amb els virtuals afegits al commit precedent
(`onWhipHit`, `onJustSpawned`, `shouldReceiveContact`, `onRemoved`),
ja no cal separar `map_entities_` i `actors_`: cada entitat decideix
internament què fa, el manager itera un sol vector i confia en el
dispatch virtual.
Canvis:
- `entities_` torna a ser el contenidor únic. Es desfà el dispatch
`dynamic_cast<DynamicHazard*>` que `add()` feia per enrutar.
- `addSarcophagus`, `removeEntity`, `update`, `notifyPlayerTilePainted`,
`findFloorTriggerAt`, `renderAll`, `reload` simplificats: un sol
bucle, sense distingir categories.
- `checkPlayerCollisions` només recorre `entities_` cridant
`shouldReceiveContact` polimòrfic — la lògica de "saltar SPAWN"
viu a `DynamicHazard::shouldReceiveContact`.
- `getMapEntities()` i `getActors()` desapareixen. Es manté
`getEntities()` com a accessor únic.
- `WhipAttack::applyDamage` itera `getEntities()` cridant `onWhipHit`
polimòrfic. Les entitats no-whippables retornen false (cost: una
crida virtual buida) i el callsite no discrimina tipus.
- `GameScene::render` mergeja entities + players (dos transforms en
comptes de tres) per al z-order.
`pending_actors_` es manté: és un concepte ortogonal — entitats que
encara no s'han instanciat al món fins que GameScene les "spawneja"
al text-exit de ROOM_INTRO.
Beneficis del nou disseny:
- Cap dynamic_cast dispers per a la lògica de joc (només queden els
inevitables: validació de factory i ruteig de la Door).
- Afegir un nou enemic implica `class X : public DynamicHazard` i
override dels virtuals rellevants — res al manager.
- Si un sarc, antorxa, o decoració futura volgués reaccionar al
fueteig (trencar-se, encendre's), basta amb un `onWhipHit` override
i no cal tocar `WhipAttack::applyDamage`.
This commit is contained in:
1 parent
28ff425c89
commit
a29ea641ab
5 files changed
+61
-118
No files matched your search
@@ -74,19 +74,7 @@ auto EntityManager::add(std::unique_ptr<Entity> entity) -> Entity* {
|
||||
entity->setEventBus(&bus_);
|
||||
entity->setEntityManager(this);
|
||||
Entity* raw = entity.get();
|
||||
// Enrutament per categoria semàntica: si l'entitat és (o deriva de)
|
||||
// DynamicHazard va al sac `actors_`, la resta al sac `map_entities_`.
|
||||
// Així els consumidors que només volen actors itera el sac petit i
|
||||
// ens estalviem dynamic_casts dispersos al codi (colisió, whip...).
|
||||
if (dynamic_cast<DynamicHazard*>(entity.get()) != nullptr) {
|
||||
// Transferim l'ownership al unique_ptr<DynamicHazard> tipat fort.
|
||||
// El `release()` consumeix el seu valor com a argument del ctor —
|
||||
// sense crida nua que el linter pugui marcar com a ignorada.
|
||||
std::unique_ptr<DynamicHazard> hazard{static_cast<DynamicHazard*>(entity.release())};
|
||||
actors_.push_back(std::move(hazard));
|
||||
} else {
|
||||
map_entities_.push_back(std::move(entity));
|
||||
}
|
||||
entities_.push_back(std::move(entity));
|
||||
// cppcheck-suppress returnDanglingLifetime
|
||||
return raw;
|
||||
}
|
||||
@@ -107,15 +95,9 @@ auto EntityManager::removeEntity(Entity* observer) -> bool {
|
||||
// decrementar el seu comptador. La door es detecta per comparació de
|
||||
// punter (és única, no cal RTTI).
|
||||
observer->onRemoved();
|
||||
// Busquem en els dos sacs. El sarc i la door sempre viuen a
|
||||
// `map_entities_` per disseny; un actor (DynamicHazard) viu a `actors_`.
|
||||
if (auto it = std::ranges::find_if(map_entities_, [observer](const auto& up) { return up.get() == observer; }); it != map_entities_.end()) {
|
||||
map_entities_.erase(it);
|
||||
} else if (auto it_a = std::ranges::find_if(actors_, [observer](const auto& up) { return up.get() == observer; }); it_a != actors_.end()) {
|
||||
actors_.erase(it_a);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
auto it = std::ranges::find_if(entities_, [observer](const auto& up) { return up.get() == observer; });
|
||||
if (it == entities_.end()) { return false; }
|
||||
entities_.erase(it);
|
||||
if (WAS_DOOR) { door_ = nullptr; }
|
||||
// No tocar `opened_sarcs_` aquí: l'editor congela el joc i no pot
|
||||
// disparar-se cap esdeveniment derivat. Quan el GameScene faça el
|
||||
@@ -128,8 +110,7 @@ auto EntityManager::addSarcophagus(std::unique_ptr<Sarcophagus> sarc) -> Sarcoph
|
||||
sarc->setEventBus(&bus_);
|
||||
sarc->setEntityManager(this);
|
||||
Sarcophagus* raw = sarc.get();
|
||||
// El sarc és Interactable, viu al sac `map_entities_`.
|
||||
map_entities_.push_back(std::move(sarc));
|
||||
entities_.push_back(std::move(sarc));
|
||||
++total_sarcs_;
|
||||
// cppcheck-suppress returnDanglingLifetime
|
||||
return raw;
|
||||
@@ -137,34 +118,32 @@ auto EntityManager::addSarcophagus(std::unique_ptr<Sarcophagus> sarc) -> Sarcoph
|
||||
|
||||
void EntityManager::update(float delta_time) {
|
||||
// ATENCIÓ: si en el futur una entitat publica al bus events que
|
||||
// resulten en `entity_manager_.add()` (push_back als sacs), açò
|
||||
// resulten en `entity_manager_.add()` (push_back a `entities_`), açò
|
||||
// invalidaria l'iterador de la ranged-for. Avui no passa: els spawns
|
||||
// runtime venen de fora del cicle d'`update` (p.ex. el polvet del
|
||||
// saltador es spawneja durant `Player::tick`, abans d'aquest mètode).
|
||||
for (auto& e : map_entities_) { e->update(delta_time); }
|
||||
for (auto& a : actors_) { a->update(delta_time); }
|
||||
for (auto& e : entities_) { e->update(delta_time); }
|
||||
// Purga d'entitats marcades mortes aquest frame (efectes acabats,
|
||||
// p.ex. polvet d'aterratge). Les persistents (sarcs, momies, etc.)
|
||||
// mai no es marquen.
|
||||
std::erase_if(map_entities_, [](const auto& e) { return e->isDead(); });
|
||||
std::erase_if(actors_, [](const auto& a) { return a->isDead(); });
|
||||
std::erase_if(entities_, [](const auto& e) { return e->isDead(); });
|
||||
}
|
||||
|
||||
void EntityManager::notifyPlayerTilePainted(int col, int row) {
|
||||
// Fan-out als dos sacs. Avui només Sarcophagus consumeix (viu a
|
||||
// map_entities_), però els actors també reben la notificació per
|
||||
// si en el futur volen reaccionar (cost: una crida virtual buida).
|
||||
for (auto& e : map_entities_) { e->notifyTilePainted(col, row); }
|
||||
for (auto& a : actors_) { a->notifyTilePainted(col, row); }
|
||||
// Fan-out polimòrfic a totes les entitats: cada una decideix què fer
|
||||
// (`Sarcophagus` actualitza el seu perímetre, la resta queden en
|
||||
// no-op). Cost d'iterar 40 elements amb crida virtual buida ~ 1 µs;
|
||||
// qui paga el cost guanya simplicitat al callsite.
|
||||
for (auto& e : entities_) { e->notifyTilePainted(col, row); }
|
||||
}
|
||||
|
||||
auto EntityManager::findFloorTriggerAt(const SDL_FRect& rect) const -> const Entity* {
|
||||
// Cerca lineal sobre `map_entities_`: els floor decals trigger són
|
||||
// exclusivament StaticHazards (Hole; en el futur pinchos plans,
|
||||
// plaques, charcos). Els actors no qualifiquen com a decals — saltem
|
||||
// directament aquest sac, sense `triggersOnOverlap`/`isFloorDecal` per
|
||||
// a cada un. La doble condició filtra dins de map_entities_.
|
||||
for (const auto& entity : map_entities_) {
|
||||
// Cerca lineal: només `Hole` (i futurs decals trigger) compleixen la
|
||||
// doble condició `triggersOnOverlap` + `isFloorDecal`. Costos típics
|
||||
// (poques desenes d'entitats) menyspreables, una crida per frame des
|
||||
// dels consumidors actius (Player a l'aterratge, Mòmia a la decisió
|
||||
// de direcció).
|
||||
for (const auto& entity : entities_) {
|
||||
if (!entity->triggersOnOverlap()) { continue; }
|
||||
if (!entity->isFloorDecal()) { continue; }
|
||||
const auto BOX = entity->getCollisionRect();
|
||||
@@ -175,27 +154,9 @@ auto EntityManager::findFloorTriggerAt(const SDL_FRect& rect) const -> const Ent
|
||||
}
|
||||
|
||||
auto EntityManager::getEntities() const -> std::vector<Entity*> {
|
||||
// Concatenació map + actors per retro-compat (editor, z-sort debug).
|
||||
// Els callers que ja saben quin sac volen poden cridar
|
||||
// `getMapEntities` o `getActors` i estalviar la construcció del temp.
|
||||
std::vector<Entity*> out;
|
||||
out.reserve(map_entities_.size() + actors_.size());
|
||||
std::ranges::transform(map_entities_, std::back_inserter(out), [](const auto& e) { return e.get(); });
|
||||
std::ranges::transform(actors_, std::back_inserter(out), [](const auto& a) -> Entity* { return a.get(); });
|
||||
return out;
|
||||
}
|
||||
|
||||
auto EntityManager::getMapEntities() const -> std::vector<Entity*> {
|
||||
std::vector<Entity*> out;
|
||||
out.reserve(map_entities_.size());
|
||||
std::ranges::transform(map_entities_, std::back_inserter(out), [](const auto& e) { return e.get(); });
|
||||
return out;
|
||||
}
|
||||
|
||||
auto EntityManager::getActors() const -> std::vector<DynamicHazard*> {
|
||||
std::vector<DynamicHazard*> out;
|
||||
out.reserve(actors_.size());
|
||||
std::ranges::transform(actors_, std::back_inserter(out), [](const auto& a) { return a.get(); });
|
||||
out.reserve(entities_.size());
|
||||
std::ranges::transform(entities_, std::back_inserter(out), [](const auto& e) { return e.get(); });
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user