私は、コンパイル時にコンポーネント タイプとシステム シグネチャが認識される、データ指向のエンティティ コンポーネント システムに取り組んでいます。
エンティティはコンポーネントの集合体です。コンポーネントは、実行時にエンティティから追加/削除できます。
コンポーネントは、ロジックのない小さなクラスです。
シグネチャは、コンパイル時のコンポーネント タイプのリストです。署名に必要なすべてのコンポーネント タイプがエンティティに含まれている場合、そのエンティティは署名に一致すると見なされます。
短いコード サンプルは、ユーザー構文がどのように見えるか、および意図された使用法が何であるかを示します。
// 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
後続の呼び出しでは、キャッシュを無効にして再計算する必要があります。
エンティティを署名に一致させるより速い方法はありますか?
コンパイル時に多くのことがわかっています(コンポーネント タイプのリスト、シグネチャ タイプのリストなど) -コンパイル時に生成できる、「ビットセットのような」マッチングに役立つ補助データ構造はありますか? ?