5

ステートマシンの実装方法について少し混乱しています。
一部の州が同じアクションを共有しているため、階層的であることはすでに知っています。
私はこれらのパラメータによって何をする必要があるかを決定します:

  • クラス(値は次のとおりです:BaseDerivedSpecific
  • OpCode
  • パラメータ1-オプション
  • パラメータ2-オプション

私の階層はクラスによって決定され、OpCodeはアクションを表します。
DerivedはBaseのOpCodeを使用でき、SpecificはBaseDerivedの両方のOpCodeを使用できます。 素朴な実装は次のとおりです。

void (*const state_table [MAX_CLASSES][MAX_OPCODES]) (state *) {
  {base_state1, base_state2, NULL, NULL},
  {base_state1, base_state2, derived_state1, NULL},
  {base_state1,base_state2, derived_state1, specific_state3},
};

void dispatch(state *s)
{
  if (state_table[s->Class][s->OpCode] != NULL)
    state_table[s->Class][s->OpCode](s);
}

これにより、メンテナンスが非常に迅速になります。
状態をスーパークラスにマップする別の方法はありますか?

編集:さらに計算すると、すべてではない
してもほとんどのOpCodeを使用するだろうと思うようになりますが、使用可能なすべてのクラスを使用するわけではありません。 別の説明: 一部のOpCodeは、複数の派生クラスと基本クラスを介して共有される場合があります。 例えば:


  • 基本クラス であるAnyというクラスがあります。オペコードがあり ます:STATE_ONSTATE_OFFSTATE_SET
  • 派生クラスであるMyGroupという 別のクラスがあります。オペコードがあります: STATE_FLIPSTATE_FLOP

  • 3番目のクラスは、OpCodeSTATE_FLIP_FLOP_AND_FLOOPを持つThingInMyGroupと 呼ばれる特定のクラスです

したがって、クラスAnyのメッセージがサーバーから送信され、すべてのクライアントで受信されて処理されます。

クラスMyGroupのメッセージはサーバーから送信され、すべてのクライアントで受信され、 MyGroupに属するクライアントでのみ処理されます。Anyクラスに有効なすべてのOpCodeは、 MyGroupクラスに有効です。

クラスThingInMyGroupのメッセージはサーバーから送信され、すべてのクライアントで受信され、 MyGroupに属し、ThingInMyGroup*であるクライアントでのみ処理されます。AnyクラスおよびMyGroupクラスに有効な**OpCodesはThingInMyGroupクラスに有効です。 。

メッセージが受信された後、クライアントはそれに応じてACK/NACKを実行します。

スイッチケースやconst配列は大きくなると保守できなくなるため、使用しない方がいいです。
次のような柔軟な設計が必要です。

  1. クラスで使用可能なOpCodeを指定します。
  2. 各クラスのスーパークラスを指定し、その指定を通じて、現在のOpCodeで表される関数ポインターを呼び出せるようにします。
4

2 に答える 2

4

これに対処する方法はいくつかあります。これが1つです:

編集-汎用階層が追加されました

typedef unsigned op_code_type;
typedef void (*dispatch_type)(op_code_type);
typedef struct hierarchy_stack hierarchy_stack;
struct hierarchy_stack {
       dispatch_type func;
       hierarchy_stack *tail;
};

void dispatch(state *s, hierarchy_stack *stk) {
    if (!stk) {
          printf("this shouldn't have happened");
    } else {
          stk->func(s, stk->tail);
    }
}

void Base(state *s, hierarchy_stack *stk ) {
    switch (s->OpCode) {
          case bstate1:
               base_state1(s);
               break;
          case bstate2:
               base_state(2);
               break;
          default:
               dispatch(s, stk);
    }
}
void Derived(state *s, hierarchy_stack *stk ) {
    switch(s->opcode) {
           case dstate1:
                deriveds_state1(s);
                break;
           default:
                dispatch(s, stk);
    }
}
... 

注:すべての関数呼び出しは末尾呼び出しです。

これにより、「クラス」が適切にローカライズされるため、Derivedにさらに100個のメソッド/オペコードが必要であると判断した場合は、オペコードの定義に使用するメソッドと列挙型(またはその他)を編集するだけで済みます。

これに対処するためのもう1つのより動的な方法は、各「クラス」内に、処理できないものを処理する「クラス」を指す親ポインターを作成することです。

2Dテーブルアプローチは高速で柔軟性がありますが(Derivedはオペコード0のBaseとは異なるハンドラーを持つ可能性があります)、急速に成長します。

于 2010-08-18T00:40:07.043 に答える
1

ミニ言語に基づいて、単純な実装に似たコードを生成する小さなツールを作成しました。言語はstate-opcode-actionの関係を指定したばかりで、すべてのアクションはtypedefに準拠したC関数でした。

HSMの側面は処理しませんでしたが、これは言語に比較的簡単に追加できます。

このアプローチを取ることをお勧めします-ステートマシンを説明するためのクリーンな方法を提供する小さな言語を作成し、そのマシンの説明に基づいてコードを生成します。そうすれば、1か月後に新しい状態を挿入する必要がある場合でも、編集するのに混乱することはありません。

コードが必要な場合はお知らせください。まだどこかで利用できることを確認します。

于 2010-08-17T23:36:37.800 に答える