2016-09-06 17 views
-1

現在、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 reference I've been readingはそうのように、異なる配列にし、それをループにそれぞれ異なるコンポーネントタイプを置くことを示唆しています配列と配列ループを手作業で作成する必要があります。

私はずっとそうのような単一のフィールドを希望:これは私が私のinit(で動的に新しいシステムを追加し続けることができるでしょう

// 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); 
    } 
} 

)、そのよう:

AddComponent(entityId, RenderComponent()); 

をどの自動的うcomponentPoolを展開して新しいRenderComponentスロットを追加します。新しいRenderComponentスロットは新しく作成されたベクターを指し、それを効率的に反復することができます。

問題は、どうやってこれを行うのか、それとも最適に行うのかわかりません。ベクタにアクセスする前に、私が必要とするタイプは何かを知っているが、それ以外には手がかりがないと思う。それを想定し

+2

今読んでいるのと同じC++の本を読んでください。ある時点で、継承とサブクラス化について十分に学習し、自分でこれを把握することができます。これは広範なテーマであり、stackoverflow.comの簡単な回答では完全にはカバーできません。 –

+0

あなたが何を意味するか分かりませんが、多態性を使うことはできません。なぜなら、私はキャッシュフレンドリにしようとする目的を打ち破るポインタのベクトルを持たなければならないからです。 – Alex

+0

このような種類のエキゾチックな環境については、すべてのフェムト秒が価値があると言われていませんでした。その場合、要件を満たす完全にカスタムのコンテナを作成する必要があります。これはまだあなたのC++の本でカバーされていますが、はるか後の、高度な章でカバーされます。間違いなくstack overflow.comの一般的な答えに絞り込むことはできません。標準のC++ライブラリコンテナは要件を満たしません。 –

答えて

1

  • あなたの質問はan XY problemで、あなただけのリストを知っている様々なタイプのベクトルに格納し、ポインタが解決策ではなく、解決策になるかもしれないと思う、と

コンパイル時にコンポーネントタイプを指定すると、これは比較的簡単ですそれはタプルとそれを反復する何らかの仕組みによって実現されます。

まず、我々は、コンポーネントタイプのリストを指定して、適切なタプル型を製造するためのメタ関数をしたい:

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; } 
}; 

Online Demo
(N.b.デモでprocess()は、任意の実装要件に起因するだけでなく、博覧会のためのパラメータを、与えられた。)

は今、あなただけのMAX_NUMComponentTypesを管理するために必要な他のすべての場所に落ちます。あなたが別のコンポーネントタイプごとに異なるアクセス・ポイント(問題のようRenderComponentためAIComponentPhysicsComponentしかしrender()ため、例えばupdate()、)を可能にする場合は

しかし、我々は明らかに仕事のビットを行うには残っています。 1つのアプローチは、アクセスポイントを呼び出すためのレベルのインダイレクションを追加することです。最小限のオーバーヘッド(構文と実行時の両方)できちんと実行する方法の1つは、コンポーネント処理を自明に行うために、 - タイプ別。 overloadの必要に応じて、より堅牢な実装がオンラインで見つけることができる場合

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)...}; 
} 

、それでもこの単純な実装で、私たちは今、できること:ここではなく、関数ポインタのすべてのファンクタ(INC。ラムダ)のために働く基本的な実装であります次の手順を実行します。今すぐ

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; } 
}; 

Online Demo

あなたが必要MAX_NUMComponentTypesを管理し、processに新しいオーバーロードを追加することもできます(ただし、忘れた場合はコンパイラエラーが発生します)。

0

あなたはオブジェクトから必要なインターフェイスを定義しているので、インターフェイスと複数の継承を使用するだけですが、ここではコンポーネントリストを所有するポインタにすることはできません。

関連する問題