In my game, Soldier
that becomes a traitor will be automatically ejected from Turret
, Tank
, and a lot of holder. (MCVE)
A Soldier
can reside in 1 Turret
and 1 Tank
at the same time.
A Soldier
can reside in at most 1 Turret
and at most 1 Tank
.
Here is the Soldier
, Turret
and Tank
class (In real cases, they are in 3 files) :-
#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;
Here is the main()
.
Currently whenever programmer want to set team of any instance of Soldier
, he has to do the ejection manually :-
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;
}
This leads to a lot of boilerplate code after every call of Soldier::setTeam(int)
(maintenance problem) and performance problem.
How to fix it?
Current advantage that I don't want to lose :-
- It is so easy to access the Turret's gunner, and one of a Tank's passenger by index.
My workaround (MCVE)
In Soldier
, I created a callback hub (callback_changeTeams
).
In Turret
and Tank
, I create a callback definition to Soldier
(Turret::ChangeTeam
and Tank::ChangeTeam
).
Here is the working code.
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
(similar as Turret's ) :-
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;
}
}
}
}
};
};
Main :-
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;
}
Disadvantages:-
1. Duplication code between Turret
& Tank
.
Not so bad, but in real case, I have a lot of Turret
-like (that store a pointer to Soldier
) and Tank
-like (that store array of Soldier
). It would be a lot of code duplication.
2. Still Bad performance. I have to iterate every Turret
and Tank
whenever I just change a team setting of a Soldier
.
This can be solved by cache parent Turret
and Tank
, so iteration is not required.
However, in real case, I have a lot of parent type and it would be dirty e.g. :-
struct Soldier{
//... some field / functions ...
Turret* parentTurret;
Tank* parentTank;
SomeParentClass1* parent1;
SomeParentClass2* parent2; // bra bra bra.. ... dirty messy
};
My random ideas (not much useful) : Smart pointer (std::shared_ptr
); std::unordered_map
; change design-pattern; make the callbacks commit as batch; I am using Entity Component System.