Вызов функции вектора с указанным указателем вектора как пустота * - PullRequest
0 голосов
/ 19 сентября 2018

Я работаю над реализацией игрового движка, используя принципы ECS в качестве упражнения.В моем текущем проекте есть класс ComponentManager, в котором хранятся все векторы, соответствующие каждому типу компонента.Минимальная версия класса выглядит следующим образом:

class ComponentManager{
private:
    std::vector<void*> componentHolder;

public:
    bool destroyEntity(int entityID, componentSignature toDestroy);

    template <class T>
    int registerComponent();
    template <class T>
    bool addComponent(int entity, T initialComp);
    template <class T>
    bool removeComponent(int entity);
};

componentHolder - это вектор void *, где каждая запись - это вектор, содержащий другой тип компонента.Причина, по которой я это делаю, заключается в том, что я хочу хранить все компоненты в непрерывной памяти, однако каждый компонент имеет свой тип.Если бы я использовал вектор указателей на некоторый базовый класс компонентов, это нарушило бы согласованность кэша, ориентированные на поток данных преимущества, которые я пытаюсь использовать с этим механизмом ECS.

Кроме того, Мой движок спроектирован так, что другие могут создавать пользовательские компоненты, просто определяя структуру, содержащую данные, которые они хотят, чтобы этот компонент сохранял, и регистрируя этот компонент при создании нового игрового «мира» (или экземпляра).Если вы предпочитаете).Эта регистрация выполняется с помощью функции registerComponent (), показанной выше, которая создает уникальный идентификатор для каждого типа компонента и определяется как:

template <class T> 
int ComponentManager::registerComponent(){
    componentHolder.push_back(new std::vector<T>);
    return type_id<T>();
}

Функция type_id () - это трюк, который я нашел из Этот вопрос об обмене стеками и позволяет мне сопоставлять типы компонентов с целыми числами, которые я использую в качестве индексов в векторе ComponentManager.Это хорошо сработало для создания / доступа к компонентам, поскольку эти функции являются шаблонами и получают тип переданного компонента (и в результате я могу статически приводить void *, который находится по индексу вектора componentHolder справатипа), вот пример этого:

template <class T>
bool ComponentManager::addComponent(int entityID){
    int compID = type_id<T>();
    std::vector<T>* allComponents = (std::vector<T>*)componentHolder[compID];

    if (compEntityID.find(entityID) == compEntityID.end()){
        (*allComponents).push_back(T());
        return true;
    }
    return false;
}

однако проблема возникает, когда я хочу полностью уничтожить сущность.Моя функция для уничтожения сущности просто требует идентификатора сущности и подписи компонента (набора битов, биты которого равны 1, соответствующие компонентам этой сущности), который хранится в объекте gameWorld и передается внутрь. Однако, так как destroyEntityФункция не получает типы, передаваемые в нее через шаблонные функции, и имеет только набор битов, чтобы узнать, какой тип компонента нужно уничтожить. Я не могу найти способ получить тип, чтобы я мог привести void * к нужному вектору.,Вот пример того, что я хочу, чтобы функция destroyEntity делала:

bool ComponentManager::destroyEntity(int entityID, componentSignature toDestroy){
for (int x = 0; x < MAX_COMPONENT; x++){
    if (toDestroy[x]){
        std::vector<??>* allComponents = (std::vector<??>*)componentHolder[x];  // Here is where the issue lies
            (*allComponents).erase((*allComponents).begin() + entityIndex); 
        }               
    }
}

Есть ли способ при регистрации компонента, например, чтобы я мог сохранить указатель функции на метод стирания каждого вектора, из которого я мог бы позже вызватьфункция destroyEntity ()?Или каким-то образом сохранить карту из целочисленного ID компонента, который я создаю при регистрации, в сам тип и использовать его позже для приведения?Типы компонентов, которые есть в игре, будут известны во время выполнения, поэтому я чувствую, что это должно быть выполнимо как-то?Также в качестве предостережения есть некоторая дополнительная логика, которую я должен выяснить, какой сущности принадлежит какой компонент в каждом компонентном векторе в componentHolder, который я пропустил для краткости, чтобы это не вызывало никаких проблем.

СпасибоAdvanced для вашей помощи / любые советы, которые вы можете предоставить!Я ценю, что вы читаете этот длинный пост, и я открыт для предложений!

1 Ответ

0 голосов
/ 19 сентября 2018
template<class...Ts>
using operation = void(*)(void* t, void*state, Ts...);

template<class...Ts>
struct invoker{
  operation<Ts...> f;
  std::shared_ptr<void> state;
  void operator()(void* t, Ts...ts)const{
    f(t, state.get(), std::forward<Ts>(ts)...);
  }
};
template<class T, class...Ts, class F>
invoker<Ts...> make_invoker(F&& f){
  return {
    [](void* pt, void* state, Ts...ts){
      auto* pf=static_cast<std::decay_t<F>*>(state);
      (*pf)( *static_cast<T*>(pt), std::forward<Ts>(ts)... );
    },
    std::make_shared<std::decay_t<F>>( std::forward<F>(f) )
  };
}

так как это поможет?Ну, вы можете сохранить как стереть по индексу, используя это.

    std::vector<??>* allComponents = (std::vector<??>*)componentHolder[x];  // Here is where the issue lies
        (*allComponents).erase((*allComponents).begin() + entityIndex); 

, что вы хотите f(void*, int), который делает выше.

template<class T>
invoker<int> erase_at_index(){
  return make_invoker<std::vector<T>,int>([]( auto&&vec, int index ){
    vec.erase(vec.begin()+index);
  };
}

просто сохранить std::vector<invoker<int>> erasers;.Когда добавляется новый тип, нажмите новый ластик, сделанный с помощью erase_at_index<T>.

Затем:

    erasers[x](componentHolder[x],entityIndex); 

и готово.

Общий ptr один раз для каждого типа;если эти издержки слишком велики, можно использовать выровненное хранилище и статические данные, утверждая, что F не слишком велико.

...