我正在研究一个面向数据的实体组件系统,其中组件类型和系统签名在编译时是已知的。
实体是组件的集合。可以在运行时从实体中添加/删除组件。
组件是一个小的无逻辑类。
签名是组件类型的编译时列表。如果实体包含签名所需的所有组件类型,则称该实体与签名匹配。
一个简短的代码示例将向您展示用户语法的外观以及预期用途:
// User-defined component types.
struct Comp0 : ecs::Component { /*...*/ };
struct Comp1 : ecs::Component { /*...*/ };
struct Comp2 : ecs::Component { /*...*/ };
struct Comp3 : ecs::Component { /*...*/ };
// User-defined system signatures.
using Sig0 = ecs::Requires<Comp0>;
using Sig1 = ecs::Requires<Comp1, Comp3>;
using Sig2 = ecs::Requires<Comp1, Comp2, Comp3>;
// Store all components in a compile-time type list.
using MyComps = ecs::ComponentList
<
Comp0, Comp1, Comp2, Comp3
>;
// Store all signatures in a compile-time type list.
using MySigs = ecs::SignatureList
<
Sig0, Sig1, Sig2
>;
// Final type of the entity manager.
using MyManager = ecs::Manager<MyComps, MySigs>;
void example()
{
MyManager m;
// Create an entity and add components to it at runtime.
auto e0 = m.createEntity();
m.add<Comp0>(e0);
m.add<Comp1>(e0);
m.add<Comp3>(e0);
// Matches.
assert(m.matches<Sig0>(e0));
// Matches.
assert(m.matches<Sig1>(e0));
// Doesn't match. (`Comp2` missing)
assert(!m.matches<Sig2>(e0));
// Do something with all entities matching `Sig0`.
m.forEntitiesMatching<Sig0>([](/*...*/){/*...*/});
}
我目前正在检查实体是否使用std::bitset
操作匹配签名。然而,一旦签名数量和实体数量增加,性能就会迅速下降。
伪代码:
// m.forEntitiesMatching<Sig0>
// ...gets transformed into...
for(auto& e : entities)
if((e.bitset & getBitset<Sig0>()) == getBitset<Sig0>())
callUserFunction(e);
forEntitiesMatching
这可行,但如果用户多次调用相同的签名,则必须再次匹配所有实体。
也可能有更好的方法在缓存友好的容器中预缓存实体。
我尝试使用某种缓存来创建编译时映射(实现为std::tuple<std::vector<EntityIndex>, std::vector<EntityIndex>, ...>
),其中键是签名类型(由于 ,每个签名类型都有唯一的增量索引SignatureList
),值是实体索引的向量。
我用类似的东西填充了缓存元组:
// Compile-time list iterations a-la `boost::hana`.
forEveryType<SignatureList>([](auto t)
{
using Type = decltype(t)::Type;
for(auto entityIndex : entities)
if(matchesSignature<Type>(e))
std::get<idx<Type>()>(cache).emplace_back(e);
});
并在每个经理更新周期后清除它。
不幸的是,在我的所有测试中,它的执行速度都比上面显示的“原始”循环慢。它还会有一个更大的问题:如果调用forEntitiesMatching
实际删除或添加组件到实体怎么办?缓存必须失效并为后续forEntitiesMatching
调用重新计算。
有没有更快的方法将实体与签名匹配?
很多事情在编译时是已知的(组件类型列表,签名类型列表,......) -是否有任何可以在编译时生成的辅助数据结构有助于“类似位集”的匹配?