Как я могу хранить типы шаблонов для последующего размещения? - PullRequest
0 голосов
/ 08 декабря 2018

Я пытаюсь написать класс EntityType, который может принимать и хранить переменное число типов Component.

struct Health { int amount; }
struct Position { float x, y; }

EntityType entityType = new EntityType<Health, Position>();

Затем я буду использовать этот класс EntityType в качестве схемыдля выделения тесно упакованной памяти для компонентов.

EntityManager.BatchCreate(3, entityType);
// Result: Health | Health | Health | Position | Position | Position

Создать шаблон класса с несколькими параметрами достаточно просто, но:

  1. Как сохранить типы, которые будут использоваться какплан распределения?
  2. Могу ли я узнать, какие типы в EntityType?

Моей первой мыслью о хранилище были кортежи, но я не уверен.Они принимают фактическое значение переданных типов, а не сами типы.Могу ли я работать с typeid как-то?

Я в основном пытаюсь воспроизвести в C ++ Что Unity делает в C # с EntityArchetype , который, я считаю, использует отражение.

1 Ответ

0 голосов
/ 11 декабря 2018
  1. Как сохранить типы, которые будут использоваться в качестве схемы распределения позже?

Поскольку вы знаете типы компонентов во время компиляции, вы можетеиспользуйте псевдоним типа:

template<class... Components>
struct Entities {
  /* ... to be implemented ... */
};

using HealthsPositions = Entities<Health, Position>;
Могу ли я запросить, какие типы есть в EntityType?

Да, и это также известно во время компиляции.Похоже, что в пространстве имен std нет помощника для проверки, содержится ли тип в списке типов (см. Разнообразие ответов на на этот вопрос ).Итак, вот еще один способ решить эту задачу template-metaprogramming в C ++ 14:

template<class Component, class EntitiesCs>
struct IsComponentOf;

template<class Component, class... Cs>
struct IsComponentOf<Component, Entities<Cs...>> {// partial specialization
  static constexpr bool value_() {
    bool ret = false;
    for(bool is_same : {std::is_same<Component, Cs>{}()...}) {
      ret |= is_same;
    }
    return ret;
// C++17 version with fold expression:
//     return (... || std::is_same<Component, Cs>{});
  }

  static constexpr bool value = value_();
  constexpr operator bool() const { return value; }
};

static_assert(IsComponentOf<Health, HealthsPositions>{}, "");
static_assert(IsComponentOf<Position, HealthsPositions>{}, "");
static_assert(not IsComponentOf<int, HealthsPositions>{}, "");

Можно ли как-то работать с typeid?

Да, но это альтернативный подход к тому, что я описал выше: вышеописанное работает во время компиляции.Оператор typeid происходит из мира информации о типах времени выполнения (RTTI).К сожалению, std::type_info не может использоваться во время компиляции.

Затем я буду использовать этот класс EntityType в качестве образца для выделения плотно упакованной памяти для компонентов.

EntityManager.BatchCreate(3, entityType);
// Result: Health | Health | Health | Position | Position | Position

Если вы действительно хотите, чтобы компоненты были плотно упакованы, и если вы хотите иметь возможность изменять размер «контейнера», то я не вижу простого решения.В идеальном случае HealthsPositions хранит, например,

  • элемент в виде указателя в памяти, где начинается первый компонент Health,
  • a std::size_t (илилюбой) элемент, который хранит количество компонентов для каждого типа, и
  • a std::size_t (или любой другой) элемент, который хранит емкость компонентов для каждого типа.

Для этого идеального случая требуетсянекоторое настраиваемое управление памятью (включая вопросы выравнивания).

Однако хорошей отправной точкой может быть альтернативный простой дизайн :

#include <cstddef>

#include <iostream>
#include <tuple>
#include <type_traits>
#include <vector>

struct Health { int amount; };
struct Position { float x; float y; };

template<class C0, class... Cs>
struct Entities {
  std::tuple<
    std::vector<C0>, std::vector<Cs>...
  > components;

  Entities(std::size_t size)
    : components{size, (0*sizeof(Cs) + size)...}
  {}
};

template<class Component, class... Cs>
constexpr std::vector<Component>& get(Entities<Cs...>& e) {
  using ComponentVector = std::vector<Component>;
  return std::get<ComponentVector>(e.components);
}

template<class Component, class... Cs>
constexpr const std::vector<Component>& get(const Entities<Cs...>& e) {
  using ComponentVector = std::vector<Component>;
  return std::get<ComponentVector>(e.components);
}

////////////////////////////////////////////////////////////////////////////////

using HealthsPositions = Entities<Health, Position>;

constexpr std::size_t expected_size =
  sizeof(std::vector<Health>) + sizeof(std::vector<Position>);

static_assert(sizeof(HealthsPositions) == expected_size, "");

int main() {
  std::size_t entity_count = 7;
  HealthsPositions hps(entity_count);

  get<Health>(hps).at(2).amount = 40;
  get<Position>(hps).at(5) = Position{3.5f, 8.4f};

  std::cout << "health address and value:\n";
  for(auto&& h : get<Health>(hps)) {
    std::cout << &h << "\t" << h.amount << "\n";
  }

  std::cout << "position address and value:\n";
  for(auto&& p : get<Position>(hps)) {
    std::cout << &p << "\t" << p.x << "\t" << p.y << "\n";
  }
}

Пример вывода:

health address and value:
0x55adba092eb0  0
0x55adba092eb4  0
0x55adba092eb8  40
0x55adba092ebc  0
0x55adba092ec0  0
0x55adba092ec4  0
0x55adba092ec8  0
position address and value:
0x55adba092e70  0   0
0x55adba092e78  0   0
0x55adba092e80  0   0
0x55adba092e88  0   0
0x55adba092e90  0   0
0x55adba092e98  3.5 8.4
0x55adba092ea0  0   0
...