3

状態が非常に少ないリアルタイムシステムのステートマシンがあります。

typedef enum {
    STATE1,
    STATE2,
    STATE3
} state_t;

ただし、これらの州間の移行にはかなりの時間が必要であり、独自の細分化があります。したがって、2つの選択肢があります。メインのステートマシンを拡張して、すべての中間状態が表されるようにします。

typedef enum {
    STATE1,
    STATE1_PREPARE_TRANSITION_TO_STATE2,
    STATE1_DO_TRANSITION_TO_STATE2,
    STATE1_PREPARE_TRANSITION_TO_STATE3,
    STATE1_DO_TRANSITION_TO_STATE3,
    STATE2,
    ...
} state_t;

または、関連するメイン状態のネストされたステートマシンを作成します。

typedef enum {
    STATE1_NOT_ACTIVE,
    STATE1_NORMAL,
    STATE1_PREPARE_TRANSITION_TO_STATE2,
    STATE1_DO_TRANSITION_TO_STATE2,
    STATE1_PREPARE_TRANSITION_TO_STATE3,
    STATE1_DO_TRANSITION_TO_STATE3
} sub_state1_t;
...

どちらの可能性にも長所と短所があります。大きなステートマシンは非常に簡単に乱雑で複雑になります。ただし、2番目のケースですべての状態を一貫させることも簡単ではなく、多くの関数はグローバル状態とサブ状態の両方に関する情報を必要とします。

次のようないくつかの並列状態を処理する必要がある複雑なコードは避けたいと思います。

if ((global_state == STATE1) &&
    (sub_state_1 == STATE1_DO_TRANSITION_TO_STATE2))
{
    ...
    if (transition_xy_done(...))
    {
        global_state = STATE2;
        sub_state_1 = STATE1_NOT_ACTIVE;
        sub_state_2 = STATE2_NORMAL;
    }
}

このような問題に対する一般的な最善のアプローチは何ですか:多くの小さくネストされたステートマシン(多くの無効な組み合わせを持つ)、1つの大きなステートマシンまたは他のもの?

4

6 に答える 6

5

多くの小さなステートマシンは、特に何かを再設計する必要がある場合に、将来的にコードの柔軟性を高めるでしょう。次に、(うまくいけば)他のネストされたステートマシンを変更せずに、ネストされたステートマシンを変更できるはずです。

遷移テーブルを大きくしても、ルックアップが長くなることはありません。テーブルを適切にメモリに配置すると想定しているからです。どちらかといえば、小さなステートマシン間でクリーンに移行するために必要な余分な1つまたは2つのステップがないという理由だけで、実際には大きなマシンからもう少し速度を上げることができるはずです。ただし、この方法がさらに複雑になることを考えると、次のことをお勧めします。ネストされたステートマシンを使用して設計し、すべてが機能したら、必要に応じて単一のステートマシンにリファクタリングして、速度を少し上げます。

于 2009-09-10T10:55:21.780 に答える
4

まず、何が起こっているのかを認識し、これらの状態を明示的にしたことを称賛したいと思います(実際には、これらはモデル内の追加の状態であり、実際にはアクションによる遷移ではないため)。最後の例のようになってしまう(避けたい)ステートマシンをよく目にします。イベントハンドラー内に「追加の」状態変数のテストがある場合、それは、ステートマシンに、実際にデザインに入れた状態がさらにあることを示しています。これらの状態は、既存の状態のイベントに詰まることなく、デザインに反映されます。スパゲッティコード化された一連のハンドラーは、グローバル変数にエンコードされた追加の「状態」をチェックします。

階層型ステートマシンをモデル化するC++のフレームワークはいくつかあります-HSM-(ネストされたステートマシンのアイデアはどのように聞こえますか)が、ストレートCをサポートすることを私が知っている唯一のフレームワークはQuantum Frameworkであり、それはおそらくまともなレベルのコミットメントを意味するでしょう(つまり、それはおそらく単純な変更ではありません)。ただし、この可能性を調べたい場合は、SamekがCでHSMをサポートする方法について多くの記事(および本)を書いています。

ただし、HSMモデルのより洗練された部分の一部が必要ない場合(「最も内側の」状態で処理されないイベントがバブルアップして親状態で処理される可能性があるなど)、完全な開始および終了のサポート状態階層全体)、親状態が開始/終了したときにたまたま開始および停止する完全に独立したステートマシンと同じように、ネストされたステートマシンをサポートするのは非常に簡単です。

ビッグステートマシンモデルは、おそらく実装が少し簡単です(既存のフレームワークにはさらにいくつかの状態があります)。現在のステートマシンモードに状態を追加してもモデルが複雑になりすぎない場合は、それを使用することをお勧めします。

言い換えれば、モデルに最適なものが、ソフトウェアでステートマシンを実装する方法を推進するようにします。

于 2009-09-10T21:11:22.590 に答える
1

あなたが言ったように、大きなステートマシンは乱雑になるので、メンテナンスが非常に困難です。いくつかの小さなSMは、常に理解と保守が容易です。

大きなSMのもう1つの欠点は、遷移テーブルが大きいため、ルックアップに時間がかかることです。

于 2009-09-10T10:46:37.017 に答える
1

単一の一般的なアプローチはないと思います。他の人が言っているように、それはあなたがやろうとしていることに依存します。

大まかに言えば、小さな状態のマシンを大きな状態のマシンの中にネストすることは避けたいと思います。単純化しようとしているときに状態を追加するだけでなく、複雑さを増すだけでなく、追跡する2つの状態変数があるからです。

特に、「外部」ステートマシンで状態をトラバースするときは、「内部」状態変数を適切に初期化する必要があります。たとえば、バグが原因で、外部ステートマシンに遷移があり、内部ステートマシンの状態変数をリセットできない場合はどうなりますか?

これに対する1つの考えられる例外は、すべての内部ステートマシンが同じことを行う場合です。データをパラメーター化できる場合(たとえば、配列を使用して)、内部ステートマシンの単一の実装を持つことができ、外部ステートマシンをカウンターなどに置き換えることができる場合があります。

簡単な例を挙げます。

#define MyDataSIZE 10

void UpdateStateMachine(void)
{
    static enum {BeginSTATE, DoStuffSTATE, EndSTATE} State = BeginSTATE;
    static unsigned int Counter = 0;
    static unsigned int MyData[MyDataSIZE];

    switch(State)
    {
        default:
        case BeginSTATE:
            /* Some code */
            if(/* Some condition*/)
                {State = DoStuffSTATE;}
            break;
        case DoStuffSTATE:
            /* Some actions on MyData[Counter] */
            if(/* Some condition*/)
                {State = EndSTATE;}
            break;
        case EndSTATE:
            /* Some code */
            if(/* Some condition*/)
            {
                Counter++;
                if(Counter >= MyDataSIZE)
                    {Counter = 0;}
                State = BeginSTATE;
            } /* if */
            break;
    } /* switch */
} /* UpdateStateMachine() */
于 2009-09-12T15:50:01.647 に答える
0

状態パターンを使ってみませんか?

于 2009-09-10T11:02:14.637 に答える
0

私はより大きなステートマシンに投票します。単一のマシンが大きなステートマシンの状態の1つにしか存在できないと仮定すると、論理的にはそこにあるはずです。

1台の大きなマシンを使用することで、環境の特性を使用して、2つの状態が同時に存在する状態を防ぎ、プログラムをより安全で読みやすくします。

また、1つの大きなステートマシンには、他のプログラマーが1つの場所を見る(つまり、全体像を把握する)ことですべての状態を簡単に理解できるという利点があります。次に、各サブディビジョンを確認する必要があります。

また、複数のステートマシンで作業することを提案したように、より多くのパラメーターを送信したり、状態ごとに複数のテストを実行したりする必要があります。

今後の期待はYAGNIを信じています。

于 2009-09-13T16:13:41.770 に答える