web-dev-qa-db-ja.com

コンポーネントエンティティシステムアーキテクチャを使用して(ゲームではなく)アプリケーションを構築することは妥当ですか?

Apple AppStoreやGoogle Playアプリストア)などのアプリケーション(ネイティブまたはWeb)を構築する場合、Model-View-Controllerアーキテクチャを使用するのが非常に一般的であることを知っています。

ただし、ゲームエンジンで一般的なComponent-Entity-Systemアーキテクチャを使用してアプリケーションを作成することも妥当ですか?

25

ただし、ゲームエンジンで一般的なComponent-Entity-Systemアーキテクチャを使用してアプリケーションを作成することも妥当ですか?

私には、絶対に。私はビジュアルFXで作業し、この分野のさまざまなシステム、それらのアーキテクチャ(CAD/CAMを含む)、SDKに飢えていること、そして無限に見えるアーキテクチャ上の決定の賛否両論を感じさせるあらゆる論文を研究しました。最も微妙なものでさえ、常に微妙な影響を与えるとは限りません。

VFXは「シーン」という中心的な概念が1つあり、レンダリングされた結果を表示するビューポートがあるという点で、ゲームにかなり似ています。また、アニメーションコンテキストでは、このシーンを中心に常に中心的なループ処理が頻繁に行われる傾向があります。物理コンテキストの発生、パーティクルエミッターによるパーティクルの発生、メッシュのアニメーション化とレンダリング、モーションアニメーションなどがあり、最終的にはそれらをレンダリングします。最後にユーザーにすべて。

少なくとも非常に複雑なゲームエンジンに似たもう1つの概念は、独自の軽量スクリプト(スクリプトとノード)を実行する機能など、デザイナーがシーンを柔軟に設計できる「デザイナー」の側面の必要性でした。

私は何年にもわたって、ECSが最適であることがわかりました。もちろん、それが主観性から完全に離脱することは決してありませんが、私はそれが最も少ない問題を与えるように強く見えたと思います。それは私たちがいつも苦労していたより多くの大きな問題を解決しましたが、代わりに私たちにいくつかの新しい小さな問題を与えました。

従来のOOP

より伝統的なOOPアプローチは、実装要件ではなく、設計要件を事前にしっかりと把握している場合に非常に強力になる可能性があります。フラットな複数インターフェースアプローチでも、ネストされた階層ABCアプローチでも、設計を固定し、変更をより困難にしながら、実装をより簡単かつ安全に変更する傾向があります。単一バージョンを通過する製品には常に不安定性が必要であるため、OOPアプローチ安定性(変更の難しさと変更理由の欠如)を設計レベルに偏らせ、不安定性(変更の容易さと変更理由)を実装レベルに偏らせる傾向があります。

ただし、進化するユーザーエンド要件に対して、設計と実装の両方を頻繁に変更する必要がある場合があります。同時に植物と動物の両方である必要がある類似の生き物に対するユーザー側の強いニーズのような奇妙なものを見つけて、構築した概念モデル全体を完全に無効にする場合があります。通常のオブジェクト指向のアプローチはここではあなたを保護せず、そのような予期せぬ、概念を壊す変更をさらに困難にすることがあります。パフォーマンスが非常に重要な領域が関係する場合、設計変更の理由はさらに増大します。

複数の細かいインターフェースを組み合わせてオブジェクトの適合インターフェースを形成すると、クライアントコードを安定させるのに役立ちますが、クライアントの依存関係の数を小さくする可能性のあるサブタイプの安定には役立ちません。たとえば、システムの一部でのみ使用されている1つのインターフェースを持つことができますが、そのインターフェースを実装する1000の異なるサブタイプがあります。その場合、複雑なサブタイプ(インターフェースの責任が非常に多くあるため複雑になる)を維持することは、コードをインターフェース経由で使用するのではなく、悪夢になる可能性があります。 OOPは、複雑さをオブジェクトレベルに転送する傾向がありますが、ECSはそれをクライアント(「システム」)レベルに転送します。これは、システムが非常に少ないが準拠している全体がたくさんある場合に理想的です。 「オブジェクト」(「エンティティ」)。

enter image description here

クラスもデータをプライベートに所有しているため、不変条件をすべて独自に維持できます。それにもかかわらず、オブジェクトが相互に作用するときに実際に維持するのが難しい「粗い」不変条件があります。複雑なシステム全体が有効な状態になるには、個々の不変条件が適切に維持されていても、オブジェクトの複雑なグラフを考慮する必要があることがよくあります。従来のOOPスタイルのアプローチは、細かい不変量を維持するのに役立ちますが、オブジェクトがシステムの小さなファセットに焦点を当てている場合、実際には広く粗い不変量を維持することが困難になる可能性があります。

このようなレゴブロック構築ECSアプローチやバリアントが非常に役立ちます。また、通常のオブジェクトよりもシステムの設計が粗いため、システムの鳥瞰図でこれらの種類の粗い不変条件を維持することが容易になります。小さなオブジェクトの相互作用の多くは、1キロの紙をカバーする依存関係グラフを持つ小さなタスクに焦点を当てた小さなオブジェクトではなく、1つの広範なタスクに焦点を合わせた1つの大きなシステムに変わります。

それでも、私はECSについて学ぶために、ゲーム業界で自分の分野の外を見なければなりませんでしたが、私は常にデータ指向の考え方の1つでした。また、おかしなことに、私は自分自身でECSに向けて自分の道を進んで、ただ繰り返して、より良いデザインを考え出そうとしました。私はそれを完全にはしませんでした、そして、「システム」の部分の形式化である非常に重要な詳細、および生データに至るまでコンポーネントを押しつぶすことを逃しました。

ECSに落ち着くまでの経緯と、それが以前の設計の反復で発生したすべての問題を解決するまでの経緯について説明します。ここでの答えが非常に強力な「はい」である理由を正確に強調するのに役立つと思います。ECSはゲーム業界以外にも適用できる可能性があるということです。

1980年代のブルートフォースアーキテクチャ

私がVFX業界で最初に取り組んだアーキテクチャには、私が入社してからすでに10年を超える長い歴史があります。それは、総当たりの粗野なCコーディングでした(私がCを愛しているので、Cで傾斜しているわけではありませんが、ここで使用されている方法は本当に粗野でした)。ミニチュアで単純すぎるスライスは、次のような依存関係に似ています。

enter image description here

これは、システムのごく一部を非常に簡略化した図です。図内のこれらの各クライアント(「レンダリング」、「物理」、「モーション」)は、次のようにタイプフィールドをチェックするための「汎用」オブジェクトを取得します。

void transform(struct Object* obj, const float mat[16])
{
    switch (obj->type)
    {
        case camera:
            // cast to camera and do something with camera fields
            break;
        case light:
            // cast to light and do something with light fields
            break;
        ...
    }
}

もちろん、これよりもかなり醜く、より複雑なコードを使用しています。多くの場合、これらの切り替えケースから追​​加の関数が呼び出され、繰り返し何度も切り替えを行います。この図とコードはほとんどECS-liteのように見えるかもしれませんが、エンティティコンポーネントの明確な区別はありませんでした( "is this object a camera?"ではなく、 "does this object provide =モーション? ")、および"システム "の形式化はありません(ネストされた関数の集まりがいたるところに行き渡り、責任が混同されます)。その場合、ほぼすべてが複雑であり、どんな機能も、起こるのを待つ災害の可能性でした。

ここでのテスト手順では、ここでコーディングのブルートフォースの性質(多くの場合、多くのコピーと貼り付けを伴う)が頻繁に行われるため、メッシュのようなものを他のタイプのアイテムから分離する必要があることがよくあります。それ以外の場合はまったく同じロジックがアイテムタイプ間で失敗する可能性が非常に高いです。新しいタイプのアイテムを処理するようにシステムを拡張しようとすることは、既存のタイプのアイテムを処理するためだけに非常に苦労していたので非常に困難であったため、強く表現されたユーザーエンドのニーズがあったとしても、かなり絶望的でした。

一部の長所:

  • ええと...エンジニアリングの経験は必要ないでしょうね?このシステムは、ポリモーフィズムのような基本的な概念の知識さえも必要とせず、それは完全に力ずくなので、デバッグのプロがほとんど維持できなくても、初心者でもコードの一部を理解できるかもしれません。

いくつかの短所:

  • メンテナンスの悪夢。私たちのマーケティングチームは実際に、3年間のサイクルで2000以上のバグを修正したことを自慢する必要性を感じていました。私にとってそれは恥ずかしいことですが、そもそも非常に多くのバグがあり、そのプロセスはおそらく、常に増え続けるバグの合計の約10%しか修正していません。
  • 可能な限り最も柔軟性のないソリューションについて。

1990年代のCOMアーキテクチャ

ほとんどのVFX業界は、私が収集したものからこのスタイルのアーキテクチャを使用し、設計の決定に関するドキュメントを読み、ソフトウェア開発キットをちらっと見ています。

ABIレベルでのCOMとは厳密には一致しない場合があります(これらのアーキテクチャの一部は、同じコンパイラを使用して記述されたプラグインしか持つことができません)が、コンポーネントがサポートするインターフェイスを確認するためにオブジェクトに対して行われたインターフェイスクエリと多くの同様の特性を共有します。

enter image description here

このようなアプローチにより、上記のtransform関数は次のような形になります。

void transform(Object obj, const Matrix& mat)
{
    // Wrapper that performs an interface query to see if the 
    // object implements the IMotion interface.
    MotionRef motion(obj);

    // If the object supported the IMotion interface:
    if (motion.valid())
    {
        // Transform the item through the IMotion interface.
        motion->transform(mat);
        ...
    }
}

これは、古いコードベースの新しいチームが最終的にリファクタリングするために決着したアプローチです。また、柔軟性と保守性の点で元のバージョンよりも劇的に改善されましたが、次のセクションで取り上げるいくつかの問題がまだありました。

一部の長所:

  • 以前のブルートフォースソリューションよりも劇的に柔軟/拡張可能/保守可能です。
  • SOLIDの多くの原則への強い準拠を促進します。すべてのインターフェースを完全に抽象化します(ステートレス、実装なし、純粋なインターフェースのみ)。

いくつかの短所:

  • 定型文がたくさん。オブジェクトをインスタンス化するには、コンポーネントをレジストリ経由で公開する必要がありました。コンポーネントがサポートするインターフェースは、インターフェースの継承(Javaでの「実装」)と、クエリで使用可能なインターフェースを示すコードの提供の両方が必要でした。
  • 純粋なインターフェースの結果として、あらゆる場所で複製されたロジックが促進されました。たとえば、IMotionを実装したすべてのコンポーネントは、すべての関数に対して常にまったく同じ状態とまったく同じ実装を持ちます。これを緩和するために、同じクラスに対して同じ方法で同じ方法で冗長的に実装される傾向があり、場合によっては複数の継承が背後で行われる可能性があるものについて、システム全体で基本クラスとヘルパー機能を集中化しますが、クライアントのコードは簡単でしたが、内部的には面倒です。
  • 非効率:vtuneセッションでは、ほとんどの場合、基本的なQueryInterface関数がほとんど常に中間から上のホットスポットとして表示され、場合によっては#1ホットスポットでさえ表示されました。これを軽減するために、コードベースキャッシュの一部をレンダリングして、IRenderableをサポートすることが既にわかっているオブジェクトのリストをレンダリングするなどの処理を行いますが、複雑さとメンテナンスコストが大幅に増大しました。同様に、これを測定することはより困難でしたが、すべての単一のインターフェースが動的ディスパッチを必要としたときに以前に行っていたCスタイルのコーディングと比較して、いくつかの明確なスローダウンに気づきました。ブランチの予測ミスや最適化の障壁などは、コードの小さな側面以外では測定が困難ですが、ユーザーは通常、ソフトウェアの以前のバージョンと新しいバージョンを並べて比較することで、ユーザーインターフェイスの応答性などが悪化していることに気付きましたアルゴリズムの複雑さが変わらなかった領域の側、定数のみ。
  • より広いシステムレベルでの正しさについて推論するのは依然として困難でした。以前のアプローチよりもはるかに簡単でしたが、このシステム全体でオブジェクト間の複雑な相互作用を把握することは依然として困難でした。特に、最適化が必要になったためです。
  • インターフェイスを正しく設定するのに問題がありました。インターフェースを使用するシステム内の広い場所は1つしかないかもしれませんが、ユーザーエンドの要件はバージョンによって異なり、追加された新しい関数に対応するためにインターフェースを実装するすべてのクラスにカスケード変更を行わなければなりません。たとえば、内部ですでにロジックを集中化している抽象基本クラスがない場合(これらのいくつかは、これを何度も繰り返し行わないことを期待して、これらのカスケード変更の真ん中に現れます)。

enter image description here

実用的な応答:構成

以前に(または少なくとも私は)問題を引き起こしていたことに気づいたことの1つは、IMotionが100の異なるクラスによって実装されている可能性があることですが、まったく同じ実装と状態が関連付けられています。さらに、レンダリング、キーフレームモーション、物理学など、ほんの一握りのシステムでのみ使用されます。

したがって、このような場合、インターフェースへのインターフェースを使用するシステム間で3対1の関係があり、インターフェースへのインターフェースを実装するサブタイプ間で100対1の関係になる可能性があります。

その場合、複雑さと保守は、IMotionに依存する3つのクライアントシステムではなく、100のサブタイプの実装と保守に大幅に偏ります。これにより、メンテナンスの問題はすべて、インターフェースを使用する3箇所ではなく、これらの100のサブタイプのメンテナンスに移行しました。 「間接遠心性カップリング」がほとんどまたはまったくないコードの3つの場所を更新します(依存関係のように、直接依存関係ではなく、インターフェースを介して間接的に)、大した問題ではありません。「間接遠心性カップリング」のボートロードで100のサブタイプの場所を更新します。 、かなり重要*。

*実装の観点からこの意味で「遠心性結合」の定義をねじ込むのは奇妙で間違っていることに気づきました。 100のサブタイプのインターフェースと対応する実装の両方を変更する必要がある場合に関連するメンテナンスの複雑さ。

だから私は一生懸命プッシュしなければなりませんでしたが、もう少し実用的なものにして、「純粋なインターフェース」のアイデア全体を緩和しようとすることを提案しました。 IMotionのようなものを完全に抽象的でステートレスにすることは、さまざまな実装があることのメリットが見られない限り、私には意味がありませんでした。私たちの場合、IMotionにさまざまな実装を含めることは、実際にはwant多様性ではなかったので、メンテナンスの悪夢になってしまいます。代わりに、クライアントの要件の変更に対して本当に優れた単一のモーション実装を作成することを目指して繰り返し、多くの場合、IMotionのすべての実装者に同じ実装と関連付けられた状態を使用するように強制することで、純粋なインターフェイスのアイデアを回避していました。目標を複製しないでください。

したがって、インターフェースはエンティティに関連付けられた広範なBehaviorsのようになりました。 IMotionは単にMotion "コンポーネント"になります( "コンポーネント"の定義方法をCOMから通常の定義に近いものに変更し、 "完全な"エンティティを構成するピースにしました)。

これの代わりに:

class IMotion
{
public:
    virtual ~IMotion() {}
    virtual void transform(const Matrix& mat) = 0;
    ...
};

私たちはそれを次のようなものに進化させました:

class Motion
{
public:
    void transform(const Matrix& mat)
    {
        ...
    }
    ...

private:
    Matrix transformation;
    ...
};

これは依存関係の逆転原理の露骨な違反であり、アブストラクトから具象に戻ろうとしますが、私にとってこのようなレベルのアブストラクションは、合理的な疑いを超えて、将来的に真の必要性を予測できる場合にのみ役立ちます。このような柔軟性のために、ユーザーエクスペリエンスから完全に切り離されたばかげた "what if"シナリオ(おそらく、デザインの変更が必要になる可能性があります)を実行します。

そこで、私たちはこのデザインに進化し始めました。 QueryInterfaceは、QueryBehaviorのようになりました。さらに、ここで継承を使用することは無意味に思われるようになりました。代わりに構成を使用しました。オブジェクトは、実行時に可用性を照会および挿入できるコンポーネントのコレクションに変わりました。

enter image description here

一部の長所:

  • 私たちの場合、以前の純粋なインターフェイスのCOMスタイルのシステムよりも、メンテナンスがはるかに簡単でした。要件の変更やワークフローの苦情などの予期しない驚きは、1つの非常に中心的で明白なMotion実装でより簡単に対処できます。たとえば、100のサブタイプに分散する必要はありません。
  • 私たちが実際に必要とする類のまったく新しいレベルの柔軟性をもたらしました。以前のシステムでは、継承は静的な関係をモデル化していたため、C++ではコンパイル時に新しいエンティティを効果的に定義することしかできませんでした。スクリプト言語からそれを行うことはできませんでした。構成アプローチを使用すると、コンポーネントを接続してリストに追加するだけで、実行時に新しいエンティティをオンザフライで連結できます。 「エンティティ」は空白のキャンバスに変わり、その上で必要なもののコラージュをその場で一緒に投げることができ、結果として関連システムがこれらのエンティティを自動的に認識して処理しました。

いくつかの短所:

  • 効率部門ではまだ苦労しており、パフォーマンスが重要な領域では保守性がありませんでした。各システムは、これらの動作を提供するエンティティのコンポーネントをキャッシュして、それらすべてを繰り返しループして、何が利用可能であるかをチェックすることを避けたいと思っています。パフォーマンスを要求する各システムは、これを少しずつ異なる方法で実行し、このキャッシュリストとデータ構造(フラストラムカリングやレイトレーシングなどの検索形式が含まれている場合)の更新に失敗すると、異なる一連のバグが発生する傾向がありました。あいまいなシーン変更イベント。
  • これらの細かい小さな行動的な単純なオブジェクトすべてに関連して、指を置くことができなかった厄介で複雑なものがまだありました。時々必要だったこれらの「振る舞い」オブジェクト間の相互作用を処理するために、私たちはまだ多くのイベントを生成し、結果は非常に分散されたコードでした。それぞれの小さなオブジェクトは、正確さをテストするのが簡単で、個別に見た場合、多くの場合完全に正確でした。それでも、小さな村で構成された大規模なエコシステムを維持し、それぞれが個別に何をして全体として作り上げているのかを推理しようとしているように感じました。 Cスタイルの80年代のコードベースは、1つの壮大な人口過多のメガロポリスのように感じられました。すべての間の相互作用の複雑さ。
  • 抽象化の欠如による柔軟性の喪失。しかし、私たちが実際にそれに対する真の必要性に実際に遭遇したことのない領域では、実用的な詐欺はほとんどありません(確かに少なくとも理論的なものです)。
  • ABI互換性を維持することは常に困難でした。これにより、「動作」に関連付けられた安定したインターフェイスだけでなく、安定したデータが必要になるため、処理が難しくなりました。ただし、状態の変更が必要な場合は、新しい動作を簡単に追加し、既存の動作を単に廃止することができます。これは、バージョン管理の問題を処理するためにサブタイプレベルのインターフェイスの下でバックフリップを実行するよりも間違いなく簡単でした。

発生した現象の1つは、これらの動作コンポーネントの抽象化を失ったため、それらの多くが存在することでした。たとえば、抽象的なIRenderableコンポーネントの代わりに、具体的なMeshまたはPointSpritesコンポーネントを使用してオブジェクトをアタッチします。レンダリングシステムは、MeshおよびPointSpritesコンポーネントをレンダリングする方法を認識し、そのようなコンポーネントを提供して描画するエンティティを見つけます。他の場合には、後から必要となるSceneLabelなどのさまざまなレンダリング可能要素があり、その場合はSceneLabelを関連エンティティに(おそらくMeshに加えて)アタッチします。次に、レンダリングシステムの実装を更新して、それらを提供するエンティティをレンダリングする方法を把握します。これは、変更が非常に簡単です。

この場合、コンポーネントで構成されるエンティティを別のエンティティのコンポーネントとして使用することもできます。レゴブロックを接続することで、そのように物事を構築します。

ECS:システムと生データコンポーネント

その最後のシステムは私が自分で作成した限りであり、私たちはまだCOMでそれを粗野化していました。エンティティー・コンポーネント・システムになりたいと思っていたようですが、当時は私はそれに慣れていませんでした。建築のインスピレーションを得るためにAAAゲームエンジンを検討するべきだったときに、自分の分野を飽和させるCOMスタイルの例を見回していました。ようやくそれを始めました。

私が欠けていたのは、いくつかの重要なアイデアでした。

  1. 「コンポーネント」を処理するための「システム」の形式化。
  2. 「コンポーネント」は、大きなオブジェクトにまとめられた動作オブジェクトではなく、生データです。
  3. エンティティは、コンポーネントのコレクションに関連付けられた厳密なIDにすぎません。

私は最終的にその会社を去り、インディとしてECSに取り組み始めました(それでも私の貯蓄を使い果たしている間それで取り組んでいます)、それははるかに管理するのが最も簡単なシステムでした。

ECSのアプローチで気付いたのは、それでもまだ上で苦労していた問題が解決されたことです。私にとって最も重要なのは、複雑な相互作用を持つ小さな村ではなく、健全なサイズの「都市」を管理しているように感じたということです。モノリシックな「メガロポリス」のように維持することは難しくありませんでした。人口が多すぎて効果的に管理できませんでしたが、貿易ルートについて考えているだけの小さな村同士が相互作用している世界ほど混沌としていませんでした。それらの間の悪夢のようなグラフを形成しました。 ECSは、レンダリングシステムのようなかさばる「システム」に向けて、すべての複雑さを抽出しました。「人口過多のメガロポリス」ではなく、健全なサイズの「都市」です。

生データになるコンポーネントは、最初はOOPの基本的な情報非表示の原理さえも壊してしまうので、[本当におかしいと感じました。それは、カプセル化と情報の隠蔽を必要とする不変量を維持する能力であるOOPについて私が最も大切にした価値の1つに挑戦するようなものでした。しかし、そのようなロジックがインターフェースの組み合わせを実装する数百から数千のサブタイプに分散されるのではなく、わずか数十の広範なシステムがデータを変換することで何が起こっているのかがすぐに明らかになるため、問題はなくなり始めました。私は、システムがデータにアクセスする機能と実装を提供し、コンポーネントがデータを提供し、エンティティがコンポーネントを提供するという点を除いて、まだOOPスタイルの方法でそれを考える傾向があります。

広いパスでデータを変換するかさばるシステムがほんの一握りであったときに、システムによって引き起こされる副作用について推論するために、easierになりました。システムはかなり「フラット」になり、コールスタックはスレッドごとにこれまでよりも浅くなりました。私はその監督レベルでシステムについて考えることができ、奇妙な驚きに遭遇しませんでした。

同様に、これらのクエリを排除することに関して、パフォーマンスが重要な領域でさえ簡単になりました。 「システム」のアイデアが非常に形式化されたので、システムは関心のあるコンポーネントをサブスクライブし、その基準を満たすエンティティのキ​​ャッシュリストを受け取るだけで済みます。キャッシュの最適化を個別に管理する必要はなく、1つの場所に集中化されました。

一部の長所:

  • 予期しないニーズに遭遇したときにデザインコーナーに閉じ込められることなく、自分のキャリアで遭遇したほとんどすべての主要な建築上の問題を解決するようです。

いくつかの短所:

  • それでも頭を抱えるのに苦労することがあり、ゲーム業界においても、最も成熟した、または確立されたパラダイムではありません。 COMスタイルのマインドセットまたは1980年代の元のコードベースのCスタイルのマインドセットに深く関わっているメンバーで構成されていた、以前のチームと一緒にできることは間違いありません。ときどき混乱するのは、コンポーネント間のグラフスタイルの関係をモデル化する方法のようなものですが、コンポーネントを別のコンポーネントに依存させるだけで、後で恐ろしいことがわからない解決策を常に見つけてきました(「このモーションコンポーネントは親としてこの他のコンポーネントに依存しており、システムはメモ化を使用して、同じ再帰的なモーション計算を繰り返し行うことを回避します。
  • ABIはまだ難しいですが、これまでのところ、純粋なインターフェースアプローチよりも簡単だと言ってさえ思います。これは考え方の転換です。データの安定性がインターフェイスの安定性ではなくABIの唯一の焦点になり、インターフェイスの安定性よりもデータの安定性を達成する方が簡単な場合があります(たとえば、新しいパラメーターが必要なだけで関数を変更するという誘惑はありません)。この種のものは、ABIを壊さない粗いシステム実装の内部で発生します。

enter image description here

ただし、ゲームエンジンで一般的なComponent-Entity-Systemアーキテクチャを使用してアプリケーションを作成することも妥当ですか?

とにかく、私は絶対に「はい」と言います。個人的なVFXの例は有力な候補です。しかし、それはまだゲームのニーズとかなり似ています。

ゲームエンジンの懸念から完全に切り離された(VFXは非常によく似ている)より離れた地域でそれを実践することはしていませんが、ECSアプローチの候補としてははるかに多くの地域があるようです。たぶん、GUIシステムでさえも適しているかもしれませんが、私はまだより多くのOOP=アプローチを使用しています(ただし、Qtのような深い継承はありません)。

それは広く未踏の領域ですが、エンティティが「特性」の豊富な組み合わせで構成できる場合はいつでも、そしてそれらが提供する特性の組み合わせが変更される可能性があるときはいつでも、そして一般化された少数の場所があるので、私には適しているようです必要な特性を持つエンティティを処理するシステム。

これは、複数の継承やコンセプトのエミュレーション(ミックスインなど)を使用して、深い継承階層または数百のコンボで数百以上のコンボを生成したくなるようなシナリオの非常に実用的な代替手段になります。特定の組み合わせのインターフェイスを実装するフラットな階層のクラスの数。ただし、システムの数は少ない(たとえば、数十)。

これらの場合、コードベースの複雑さは、タイプの組み合わせの数ではなく、システムの数に比例するように感じ始めます。これは、各タイプが、生データにすぎないコンポーネントを構成するエンティティにすぎないためです。 GUIシステムは、これらの種類の仕様に自然に適合し、他の基本タイプまたはインターフェースから何百ものウィジェットタイプを組み合わせることができますが、それらを処理するシステム(レイアウトシステム、レンダリングシステムなど)はほんの一握りです。 GUIシステムでECSを使用する場合、継承されたインターフェイスや基本クラスを持つ数百の異なるオブジェクトタイプではなく、すべての機能が少数のシステムによって提供される場合、システムの正確さを推測するのははるかに簡単です。 GUIシステムでECSが使用されている場合、ウィジェットには機能がなく、データのみがあります。ウィジェットエンティティを処理する少数のシステムのみが機能します。ウィジェットのオーバーライド可能なイベントがどのように処理されるかは私を超えていますが、これまでの私の限られた経験に基づくと、そのタイプのロジックを特定のシステムに集中的に転送できなかったケースは見つかりませんでした。後知恵は、私がこれまでに期待したよりはるかにエレガントなソリューションをもたらしました。

それは私の命の恩人だったので、もっと多くの分野で採用されるのを見てみたいです。もちろん、コンポーネントを集約するエンティティから、それらのコンポーネントを処理する大まかなシステムまで、設計がこのように機能しない場合は不適切ですが、この種のモデルに自然に適合する場合、これは私が今まで遭遇した中で最も素晴らしいものです。

39
user204677

ゲームエンジンのComponent-Entity-Systemアーキテクチャは、ゲームソフトウェアの性質、およびその独自の特性と品質要件のために、ゲームで機能します。たとえば、エンティティは、ゲーム内のものに対処して操作するための統一された手段を提供します。これは、目的と使用法が大幅に異なる場合がありますが、システムによって統一された方法でレンダリング、更新、またはシリアル化/逆シリアル化する必要があります。このアーキテクチャにコンポーネントモデルを組み込むことにより、コードカップリングを低く抑えながら、必要に応じて機能を追加しながら、シンプルなコア構造を維持することができます。 CADアプリケーション、A/Vコーデック、または多様な構造化パイプラインの作成を中心とするその他のシステムなど、この設計の特性から恩恵を受けることができるさまざまなソフトウェアシステムがいくつかあります。他のタイプのオブジェクトと複雑な方法で相互作用する必要があるが、開発チームが簡単に変更可能なままであるコンテンツ。

TL; DR-問題の領域が設計に課す機能と欠点に十分適している場合にのみ、設計パターンはうまく機能します。

16
Shotgun Ninja

問題の領域がそれに適している場合は確かです。

私の現在の仕事には、一連のランタイム要因に応じてさまざまな機能をサポートする必要があるアプリが含まれます。コンポーネントベースのエンティティを使用してこれらの機能をすべて分離し、拡張性とテスト容易性を分離して実現することは、私たちにとってのどかなものです。

編集:私の仕事には、(C#で)専用ハードウェアへの接続を提供することが含まれます。ハードウェアのフォームファクター、ハードウェアにインストールされているファームウェア、クライアントが購入したサービスのレベルなどに応じて、デバイスにさまざまなレベルの機能を提供する必要があります。同じインターフェイスを持ついくつかの機能でも、デバイスのバージョンに応じて実装が異なります。

ここの以前のコードベースには、実装されていない非常に幅広いインターフェースがありました。多くのシンインターフェイスがあり、1つの獣のクラスで静的に構成されたものもあります。単純にstring-> string辞書を使用してモデル化したものもあります。 (私たち全員がもっと上手くできると思っている多くの部門があります)

これらすべてに欠陥があります。広いインターフェースは、効果的に模擬/テストするのに苦労し、半分です。新しい機能を追加することは、パブリックインターフェイス(およびすべての既存の実装)を変更することを意味します。多くのシンインターフェイスは非常に醜いコードの消費につながりましたが、最終的に大きな脂肪のオブジェクトを通過するようになったので、テストはまだ苦労しました。さらに、シンインターフェイスは依存関係をうまく管理できませんでした。文字列ディクショナリには、通常の構文解析と存在の問題、およびパフォーマンス、読みやすさ、保守性の問題があります。

現在使用しているのは、ランタイム情報に基づいてコンポーネントが検出および構成される非常にスリムなエンティティです。依存関係は宣言的に行われ、コアコンポーネントフレームワークによって自動解決されます。コンポーネント自体は、依存関係を直接操作するため、独立してテストできます。依存関係の欠落に関する問題は、最初に依存関係を使用するのではなく、1つの場所で見つかります。新しい(またはテスト)コンポーネントをドロップインすることができ、既存のコードは影響を受けません。コンシューマーはエンティティにコンポーネントへのインターフェースを要求するため、さまざまな実装(および実装がランタイムデータにどのようにマップされるか)を比較的自由に自由に変更できます。

オブジェクトとそのインターフェースの構成に共通コンポーネントの(非常に多様な)サブセットを含めることができるこのような状況では、veryがうまく機能します。

8
Telastyn