В моей игре Soldier
, который станет предателем, будет автоматически вытолкнут из Turret
, Tank
и большим количеством держателей. ( MCVE )
A Soldier
может находиться в 1 Turret
и 1 Tank
одновременно.
A Soldier
может находиться не более 1 Turret
и максимум 1 Tank
.
Вот классы Soldier
, Turret
и Tank
(В реальных случаях они находятся в 3 файлах): -
#include <iostream>
#include <string>
#include <vector>
struct Soldier{
private: int team=0;
public: void setTeam(int teamP){
team=teamP;
//should insert callback
}
};
struct Turret{
Soldier* gunner=nullptr;
//currently it is so easy to access Turret's gunner
};
struct Tank {
std::vector<Soldier*> passenger;
//currently it is so easy to access Tank's passenger by index
};
std::vector<Soldier*> global_soldier;
std::vector<Turret*> global_turret;
std::vector<Tank*> global_tank;
Вот main()
.
В настоящее время всякий раз, когда программист хочет установить команду для любого экземпляра Soldier
, он должен выполнить ejection вручную: -
int main(){
Soldier soldier1; global_soldier.push_back(&soldier1);
Turret turret1; global_turret.push_back(&turret1);
turret1.gunner=&soldier1;
//v game loop
soldier1.setTeam(2);
//v manual ejection (should be callback?)
for(auto& ele: global_turret){
if(ele->gunner==&soldier1){
ele->gunner=nullptr;
}
}
for(auto& ele: global_tank){
for(auto& elePass: ele->passenger){
if(elePass==&soldier1){
elePass=nullptr;
}
}
}
//^ manual ejection
std::cout<<"should print 1="<<(turret1.gunner==nullptr)<<std::endl;
}
Это приводит к большому количеству шаблонного кодапосле каждого звонка Soldier::setTeam(int)
(проблема обслуживания) и проблемы с производительностью.
Как это исправить?
Текущее преимущество, которое я не хочу терять: -
- Так легко получить доступ к наводчику Турели, иодин из пассажиров танка по индексу.
Мой обходной путь ( MCVE )
В Soldier
я создал концентратор обратного вызова (callback_changeTeams
).
В Turret
и Tank
я создаю определение обратного вызова для Soldier
(Turret::ChangeTeam
и Tank::ChangeTeam
).
Вот рабочий код.
Soldier
: -
class Soldier;
struct Callback{
public: virtual void changeTeam_virtual(Soldier* soldier)=0;
};
std::vector<Callback*> callback_changeTeams;
struct Soldier{
private: int team=0;
public: void setTeam(int teamP){
if(team==teamP){
}else{
team=teamP;
for(auto callback :callback_changeTeams)
callback->changeTeam_virtual(this);
}
//should insert callback
}
};
Turret
: -
struct Turret;
std::vector<Turret*> global_turret;
struct Turret{
Soldier* gunner=nullptr;
struct ChangeTeam : public Callback{
public: virtual void changeTeam_virtual(Soldier* soldier){
for(auto& ele: global_turret){
if(ele->gunner==soldier){
ele->gunner=nullptr;
}
}
}
};
};
Tank
(аналогично башне): -
struct Tank;
std::vector<Tank*> global_tank;
struct Tank {
std::vector<Soldier*> passenger;
struct ChangeTeam : public Callback{
public: virtual void changeTeam_virtual(Soldier* soldier){
for(auto& ele: global_tank){
for(auto& elePass: ele->passenger){
if(elePass==soldier){
elePass=nullptr;
}
}
}
}
};
};
Основной: -
std::vector<Soldier*> global_soldier;
int main(){
Turret::ChangeTeam turretCallback;
Tank::ChangeTeam tankCallback;
callback_changeTeams.push_back(&turretCallback);
callback_changeTeams.push_back(&tankCallback);
Soldier soldier1; global_soldier.push_back(&soldier1);
Turret turret1; global_turret.push_back(&turret1);
turret1.gunner=&soldier1;
//v game loop
soldier1.setTeam(2);
//v should be callback
std::cout<<"should print 1="<<(turret1.gunner==nullptr)<<std::endl;
}
Недостатки: -
1. Код дублирования между Turret
& Tank
.
Не так уж и плохо, но в реальном случае у меня есть много Turret
-подобных (которые хранят указатель на Soldier
) и Tank
-подобный (этот массив хранения Soldier
). Было бы много дублирования кода.
2. Все еще плохая производительность. Мне приходится повторять каждые Turret
и Tank
всякий раз, когда я просто изменяю настройку команды на Soldier
.
Это можно решить с помощью родительского кэша Turret
и Tank
,поэтому итерация не требуется.
Однако в реальном случае у меня много родительских типов, и это было бы грязно, например: -
struct Soldier{
//... some field / functions ...
Turret* parentTurret;
Tank* parentTank;
SomeParentClass1* parent1;
SomeParentClass2* parent2; // bra bra bra.. ... dirty messy
};
Мои случайные идеи (не очень полезно): умный указатель (std::shared_ptr
);std::unordered_map
;изменить дизайн-шаблон;сделать обратные вызовы фиксированными как пакетные;Я использую Entity Component System.