Я работаю над компонентно-компонентной системой (ECS), вдохновленной Серия блогов Bitsquid. Мой ECS состоит из двух основных классов: System (отвечает за создание / уничтожение сущностей) и Property (отвечает за хранение std :: vector компонента для данной Системы).
Когда объект создается / уничтожается, система запускает некоторые сигналы, чтобы держать экземпляры свойства в курсе изменения состояния в системах, чтобы данные были непрерывными и хорошо отображенными.
Ниже приведен простой пример системы автомобилей, и у каждого автомобиля есть два свойства: позиция point2d, имя std :: string.
System<Car> cars;
Property<Car, point2d> positions(cars);
Property<Car, std::string> names(cars);
auto car0 = cars.add();
auto car1 = cars.add();
positions[car0] = {0.0, 0.0};
names[car0] = "Car 0";
positions[car1] = {1.0, 2.0};
names[car1] = "Car 1";
Таким образом, я могу хранить данные в отдельных «массивах».
Предположим, я хочу добавить 1000 сущностей. Я мог бы сделать это, призывая std::vector::reserve(1000)
на все свойства, а затем делать 1000 push_back
на каждом из них.
Я определил проблему с таким подходом: мне нужно 1 + N reserves
, если у меня есть N свойств. Мне было интересно, если бы я мог использовать std::vector<tuple<point2d, std::string>>
для обработки распределения памяти, но управлять данными, притворяясь (переинтерпретировать приведение?) У меня есть все point2d
хранится смежно, за которым следуют строки.
Таким образом, я мог бы воспользоваться std::vector
API для сохранения уведомлений / резервирования / изменения размера операций. Хотя мне пришлось бы адаптировать методы доступа (например, vector :: at, operator [], begin, end).
У вас есть идеи, как этого добиться? Или какие-то альтернативные предложения, если вы думаете, что это не очень хорошая идея?
Это невозможно, по крайней мере, не с std::tuple
, Вы можете рассмотреть tuple
как простой struct
с членами из tuple
Шаблон с аргументами. Это означает, что они будут выровнены в памяти друг за другом.
Вместо этого все ваши менеджеры могут реализовать интерфейс, который позволяет изменять размеры (резервировать), и вы можете зарегистрировать всех своих менеджеров в er .. ManagerOfManagers
это изменит их размер в цикле
Я (почти) закончил свою реализацию SoA, используя std::vector
в качестве базовой структуры данных.
Предположим, мы хотим создать SoA для типов <int, double, char>
, Вместо создания трех векторов, по одному для каждого типа, TupleVector<int, double, char>
класс создает единый std::vector
, Для доступа к данным / резервирования / изменения размера они воспроизводятся с помощью переинтерпретации приведения, вычисления смещения и размера кортежа.
Я только что создал простой тестовый код, который вызывает .resize(.size()+1)
10.000.000 раз, и моя реализация SoA оказывается в + -4 раза быстрее, чем при использовании отдельных std::vectors
,
Здесь вы можете увидеть код теста:
https://github.com/csguth/Entity/blob/master/Entity/TupleVectorBenchmark.cpp
Хотя эта реализация все еще нуждается в некоторых итераторских возможностях (тривиальных) и большем количестве сравнительного анализа.
Надеюсь, что это может быть полезно для кого-то !!
Кажется, что нет простого способа сделать std::vector<T>
оперировать некоторыми данными, кроме собственного массива Ts. Итак, ваши варианты включают поиск сторонней библиотеки SoA (Stucture of Arrays) или создание собственной.
Это может быть отправной точкой для класса SoA, который имитирует std::vector
Интерфейс:
// This snippet uses C++14 features
#include <functional>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
template <typename F, typename... Ts, std::size_t... Is>
void tuple_for_each(std::tuple<Ts...>& tuple, F f, std::index_sequence<Is...>) {
using expander = int[];
(void)expander{0, ((void)f(std::get<Is>(tuple)), 0)...};
}
template <typename F, typename... Ts>
void tuple_for_each(std::tuple<Ts...>& tuple, F f) {
tuple_for_each(tuple, f, std::make_index_sequence<sizeof...(Ts)>());
}
// Missing in this example:
// - full support for std::vector's interface (iterators, exception safety guarantees, etc.);
// - access to individual homogeneous vectors;
// - lots of other things.
template <typename T, typename... Ts>
class soa_vector {
std::tuple<std::vector<T>, std::vector<Ts>...> data_;
template <std::size_t>
void push_back_impl() const {}
template <std::size_t position, typename Value, typename... Values>
void push_back_impl(Value&& value, Values&&... values) {
std::get<position>(data_).push_back(std::forward<Value>(value));
push_back_impl<position + 1, Values...>(std::forward<Values>(values)...);
}
template<std::size_t... Is>
std::tuple<std::add_lvalue_reference_t<T>, std::add_lvalue_reference_t<Ts>...>
tuple_at(std::size_t position, std::index_sequence<Is...>) {
return std::make_tuple(std::ref(std::get<Is>(data_)[position])...);
}
public:
template <typename... Values>
std::enable_if_t<sizeof...(Values) == sizeof...(Ts) + 1, void>
push_back(Values&&... values) {
push_back_impl<0, Values...>(std::forward<Values>(values)...);
}
void reserve(std::size_t new_capacity) {
tuple_for_each(data_, [new_capacity](auto& vec) { vec.reserve(new_capacity); });
}
std::size_t size() const { return std::get<0>(data_).size(); }
std::tuple<std::add_lvalue_reference_t<T>, std::add_lvalue_reference_t<Ts>...>
operator[](std::size_t position) {
return tuple_at(position, std::make_index_sequence<sizeof...(Ts) + 1>());
}
};