如何存储指针的矢量,包含不同的类型
How to store pointers of vectors, containing different types
我目前正在学习更多关于ECS模式的知识,并一直在尝试创建我自己的实现作为实践。我决定在遍历组件时,通过将所有不同的组件打包成向量,而不是使用指针向量,使其对缓存更友好。
我一直在阅读的参考建议将每个不同的组件类型放入不同的数组并循环它,像这样:
AIComponent aiComponents[MAX_NUM];
PhysicsComponent physicsComponents[MAX_NUM];
RenderComponent renderComponents[MAX_NUM];
while (!gameOver)
{
// Process AI.
for (int i = 0; i < numEntities; i++)
{
aiComponents[i].update();
}
// Update physics.
for (int i = 0; i < numEntities; i++)
{
physicsComponents[i].update();
}
// Draw to screen.
for (int i = 0; i < numEntities; i++)
{
renderComponents[i].render();
}
// Other game loop machinery for timing...
}
我发现这是非常有限的,因为它将要求每次我创建一个新的组件,我将不得不手工创建数组和数组循环。
我更喜欢像这样的单个字段:
// A vector of pointers to other vectors of different types.
// For example, componentPool[0] could be RenderComponent and then
// componentPool[1] could be PhysicsComponent
vector< vector<AnyConcreteComponentType>* > componentPool;
for (int i = 0; i < componentPool.size(); i++)
{
for (auto& component : componentPool[i]) {
Update(component);
}
}
这将允许我在我的init()中动态地添加新系统,像这样:
AddComponent(entityId, RenderComponent());
它将自动扩展componentPool以添加一个新的RenderComponent插槽,然后它将指向新创建的矢量,然后我可以有效地迭代。
问题是我不知道你会怎么做,甚至不知道怎么做是最优的。我想,在访问vector之前,必须有模板、类型转换和一种方法来知道我需要什么类型,但除此之外,我没有任何线索。
假设:
- 你的问题是一个XY问题,你只认为存储指针到不同类型的向量可能是一个解决方案而不是解决方案,和
- 你知道编译时的组件类型列表(而不是允许运行时注册),
,这是相对容易实现的方式,通过元组和一些机制来迭代它。
首先,在给定组件类型列表的情况下,我们需要一个元函数来生成正确的元组类型:
namespace detail {
template<std::size_t N, typename... Components>
std::tuple<std::array<Components, N>...>
makeComponentPool(std::tuple<Components...>) noexcept;
} // namespace detail
template<std::size_t N, typename ComponentTup>
using ComponentPool = decltype(detail::makeComponentPool<N>(std::declval<ComponentTup>()));
// example:
static_assert(std::is_same<
ComponentPool<10, std::tuple<AIComponent, PhysicsComponent, RenderComponent>>,
std::tuple<
std::array<AIComponent, 10>,
std::array<PhysicsComponent, 10>,
std::array<RenderComponent, 10>
>
>::value);
则需要某种遍历元组的方法;boost::fusion::for_each
在这里工作得很好,或者我们可以滚动我们自己的:
namespace detail {
template<typename TupT, typename FunT, std::size_t... Is>
void for_each(TupT&& tup, FunT&& f, std::index_sequence<Is...>) {
using expand = int[];
(void)expand{0, (f(std::get<Is>(std::forward<TupT>(tup))), void(), 0)...};
}
} // namespace detail
template<
typename TupT, typename FunT,
std::size_t TupSize = std::tuple_size<std::decay_t<TupT>>::value
>
void for_each(TupT&& tup, FunT&& f) {
detail::for_each(
std::forward<TupT>(tup), std::forward<FunT>(f),
std::make_index_sequence<TupSize>{}
);
}
现在我们需要做一个决定:每个组件类型应该有相同的公共访问点吗?问题中既有update()
也有render()
;但是,如果我们可以给这些都赋予相同的名称(例如process()
),那么事情就非常简单了:
struct AIComponent { void process() { } };
struct PhysicsComponent { void process() { } };
struct RenderComponent { void process() { } };
class Game {
using ComponentTypes = std::tuple<AIComponent, PhysicsComponent, RenderComponent>;
static constexpr std::size_t MAX_NUM = 3;
ComponentPool<MAX_NUM, ComponentTypes> componentPool;
std::atomic_bool gameOver{false};
public:
void runGame() {
while (!gameOver) {
for_each(componentPool, [](auto& components) {
for (auto& component : components) {
component.process();
}
});
}
}
void endGame() { gameOver = true; }
};
在线演示
<子>(注意。在演示中,给process()
一个参数只是为了说明,而不是因为任何实现需求。)子>
现在你只需要管理MAX_NUM
和ComponentTypes
,其他的一切都到位了。
然而,如果你想允许不同组件类型的不同接入点(例如update()
用于AIComponent
和PhysicsComponent
,但render()
用于RenderComponent
,如问题所示),那么我们显然还有一些工作要做。一种方法是为调用访问点添加一个间接级别,而以最小的开销(语法和运行时)干净地完成该任务的一种方法是使用一些实用程序来创建本地重载集,以便组件处理可以按类型进行简单的特殊处理。下面是一个基本的实现,适用于所有的函函数(包括lambdas),但不适用于函数指针:
template<typename FunT, typename... FunTs>
struct overloaded : private FunT, private overloaded<FunTs...> {
overloaded() = default;
template<typename FunU, typename... FunUs>
overloaded(FunU&& f, FunUs&&... fs)
: FunT(std::forward<FunU>(f)),
overloaded<FunTs...>(std::forward<FunUs>(fs)...)
{ }
using FunT::operator();
using overloaded<FunTs...>::operator();
};
template<typename FunT>
struct overloaded<FunT> : private FunT {
overloaded() = default;
template<typename FunU>
overloaded(FunU&& f) : FunT(std::forward<FunU>(f)) { }
using FunT::operator();
};
template<typename... FunTs>
overloaded<std::decay_t<FunTs>...> overload(FunTs&&... fs) {
return {std::forward<FunTs>(fs)...};
}
如果需要,可以在网上找到更健壮的overload
实现,但即使有这个简单的实现,我们现在也可以做以下事情:
struct AIComponent { void update() { } };
struct PhysicsComponent { void update() { } };
struct RenderComponent { void render() { } };
class Game {
using ComponentTypes = std::tuple<AIComponent, PhysicsComponent, RenderComponent>;
static constexpr std::size_t MAX_NUM = 3;
ComponentPool<MAX_NUM, ComponentTypes> componentPool;
std::atomic_bool gameOver{false};
public:
void runGame() {
// `auto` overload is the least specialized so `update()` is the default
static auto process = overload(
[]( auto& comp) { comp.update(); },
[](RenderComponent& comp) { comp.render(); }
);
while (!gameOver) {
for_each(componentPool, [](auto& components) {
std::for_each(begin(components), end(components), process);
// alternatively, equivalently:
//for (auto& component : components) {
// process(component);
//}
});
}
}
void endGame() { gameOver = true; }
};
在线演示
现在您需要管理MAX_NUM
和ComponentTypes
,并且还可能为process
添加一个新的重载(尽管如果您忘记了,您将得到一个编译器错误)。
你想从你的对象中得到一个定义良好的接口,所以我们可以简单地使用接口和多重继承,但是你的组件列表不能是这里的归属指针。
#include <vector>
#include <iostream>
struct ComponentInterface {
virtual void update();
};
struct AIComponent : public ComponentInterface {
virtual void update() override {
std::cout << "AIComponentn";
}
};
struct GraphicsStuff {};
public RenderComponent : public GraphicsStuff, ComponentInterface {
virtual void update() override {
std::cout << "Rendern";
}
};
int main() {
std::vector<ComponentInterface*> components;
AIComponent ai;
RenderComponent render;
components.push_back(&ai);
components.push_back(&render);
for (auto&& comp: components) {
comp->update();
}
}
- C++ 写入路径名中包含不需要的空字符的文件
- 生成包含给定类型的 N 个参数的可变参数列表的最佳方法?
- 无法打开包含文件'Graphics.hpp'没有这样的文件或目录,Visual Studio的其他包含不起作用
- 标头包含不是 .c 程序所必需的,而是.cpp程序需要的
- 无法转换 .CATPart 文件.错误:输入文件路径似乎包含不支持的字符
- 如何自定义模板以不包含某些类型
- 检查元组是否包含特定类型的元素
- C++包含任何类型的模板类对象的映射
- 为什么 std::vector 允许对其包含的类型使用可抛出的移动构造函数?
- 按相互包含对类型进行排序
- C++ - 在类中包含不是类对象属性的变量是否是一种不好的做法
- 函数指针数组,其中包含不同类的类函数
- 给定仅包含布尔类型成员的结构的两个对象 s1 和 s2,只要 s1 的成员为 true,请检查 s2 的每个成员是否为真
- 是否有类型特征显示一种类型是否可能包含其他类型的值
- 获取模板类包含的类型
- 如果包含的类型是可简单复制的类型,则 std::Optional 是否为可平凡复制的类型
- 包含派生类型的指针的基类
- C++检查构造函数是否包含给定类型的参数
- 为什么即使在声明中指定了返回类型,也必须在函数定义中包含返回类型?
- Ubuntu 不提供包含uint24_t类型的 Clang 标准