обратный вызов для очистки указателей (MCVE: автоматическое извлечение солдата-предателя из машины) - PullRequest
0 голосов
/ 01 октября 2019

В моей игре 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.

1 Ответ

1 голос
/ 01 октября 2019

Как вы сами указали, есть общий термин "держатель", охватывающий танки и башни.
Модель, которая используется в ваших классах. Затем вы можете сохранить в солдате ссылку на то, в чем он находится, и позволить этому соответствующим образом обрабатывать вход / выход солдата. Таким образом, вам нужно только искать очень короткий список солдат в танке и только в одном танке, в котором действительно сидит солдат.

struct Soldier;

struct Holder
{
public:
    virtual void AddSoldier(Soldier* Entering)=0;
    virtual void ExitSoldier(Soldier* Exiting)=0;
}

struct Turret:
public holder
{
public:
    virtual void AddSoldier(Soldier* Entering)
    {   
        gunner=Entering;
        AttachedTo->AddSoldier(Entering);
    }
    virtual void ExitSoldier(Soldier* Exiting)
    /* this only covers the complete way of exiting,
       add another method for moving only into the tank */
    {    if(gunner=Exiting)
         {
             gunner=nullptr;
         }
         if(AttachedTo)
         {
             AttachedTo->ExitSoldier(Exiting);
         }
    }
    Soldier* gunner=nullptr;
    Tank*    AttachedTo;
};
struct Tank
: public holder
{
    virtual void AddSoldier(Soldier* Entering)
    {
        /* push Entering,
           if not in already */
    }

    virtual void MoveSoldierToTurretIfFree(Soldier* NewGunner)
    {/* you know .. */}

    virtual void ExitSoldier(Soldier* Exiting)
    {    
          AttachedTurret->ExitSoldier(Exiting);
          /* find and remove Exiting,
            tolerating if already left */

    }
    std::vector<Soldier*> passenger;
    struct turret* AttachedTurret;
};

struct Soldier{
    private: int team=0;
             Holder* Within;
    public: void setTeam(int teamP){
        team=teamP;
        //should insert callback
    }
    void IsTraitor (void)
    {
        Within->ExitSolder(this);
        Within=nullptr;
    }
};

Я был немного щедрым с publicконечно, некоторая тонкая настройка возможна и желательна там.
Я написал код метода непосредственно в классе, вам нужно будет переместить его в файлы кода или, по крайней мере, в отдельные реализации, чтобы сделать его компилируемым;в противном случае заранее объявленного класса солдат может быть недостаточно.

...