web-dev-qa-db-ja.com

典型的なステートマシンの実装パターンはありますか?

[〜#〜] c [〜#〜]に単純な状態マシンを実装する必要があります。
標準のswitchステートメントは最適な方法ですか?
現在の状態(状態)と遷移のトリガーがあります。


switch(state)
{
  case STATE_1:
     state = DoState1(transition);
     break;
  case STATE_2:
     state = DoState2(transition);
     break;
}
...
DoState2(int transition)
{
   // Do State Work
   ...
   if(transition == FROM_STATE_2) {
     // New state when doing STATE 2 -> STATE 2
   }
   if(transition == FROM_STATE_1) {
    // New State when moving STATE 1 -> STATE 2
   }
   return new_state;
}

簡単な状態マシンのためのより良い方法がありますか

編集:C++の場合、Boost Statechart ライブラリが道のりかもしれません。ただし、Cのnotには役立ちません。Cのユースケースに集中しましょう。

110
Benoit

私は、ほとんどの状態マシンにテーブル駆動のアプローチを使用することを好みます。

typedef enum { STATE_INITIAL, STATE_FOO, STATE_BAR, NUM_STATES } state_t;
typedef struct instance_data instance_data_t;
typedef state_t state_func_t( instance_data_t *data );

state_t do_state_initial( instance_data_t *data );
state_t do_state_foo( instance_data_t *data );
state_t do_state_bar( instance_data_t *data );

state_func_t* const state_table[ NUM_STATES ] = {
    do_state_initial, do_state_foo, do_state_bar
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    return state_table[ cur_state ]( data );
};

int main( void ) {
    state_t cur_state = STATE_INITIAL;
    instance_data_t data;

    while ( 1 ) {
        cur_state = run_state( cur_state, &data );

        // do other program logic, run other state machines, etc
    }
}

これはもちろん、複数のステートマシンなどをサポートするために拡張できます。遷移アクションも同様に対応できます。

typedef void transition_func_t( instance_data_t *data );

void do_initial_to_foo( instance_data_t *data );
void do_foo_to_bar( instance_data_t *data );
void do_bar_to_initial( instance_data_t *data );
void do_bar_to_foo( instance_data_t *data );
void do_bar_to_bar( instance_data_t *data );

transition_func_t * const transition_table[ NUM_STATES ][ NUM_STATES ] = {
    { NULL,              do_initial_to_foo, NULL },
    { NULL,              NULL,              do_foo_to_bar },
    { do_bar_to_initial, do_bar_to_foo,     do_bar_to_bar }
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    state_t new_state = state_table[ cur_state ]( data );
    transition_func_t *transition =
               transition_table[ cur_state ][ new_state ];

    if ( transition ) {
        transition( data );
    }

    return new_state;
};

テーブル駆動のアプローチは、維持および拡張がより簡単で、状態図へのマッピングがより簡単です。

125
Frank Szczerba

FSMに言及した別のCの質問に対する私の答えを見たことがあるかもしれません!ここに私がそれをする方法があります:

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

次のマクロが定義されている

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

これは、特定のケースに合わせて変更できます。たとえば、FSMを駆動するファイルFSMFILEがある場合、次の文字を読み込むアクションをマクロ自体に組み込むことができます。

#define FSM
#define STATE(x)         s_##x : FSMCHR = fgetc(FSMFILE); sn_##x :
#define NEXTSTATE(x)     goto s_##x
#define NEXTSTATE_NR(x)  goto sn_##x

現在、2つのタイプの遷移があります。1つは状態に移行して新しい文字を読み取り、もう1つは入力をまったく消費せずに状態に移行します。

次のようなものでEOFの処理を自動化することもできます。

#define STATE(x)  s_##x  : if ((FSMCHR = fgetc(FSMFILE) == EOF)\
                             goto sx_endfsm;\
                  sn_##x :

#define ENDFSM    sx_endfsm:

このアプローチの良い点は、描画する状態図を作業コードに直接変換でき、逆に、コードから状態図を簡単に描画できることです。

FSMを実装する他の手法では、遷移の構造は制御構造に埋め込まれ(while、if、switch ...)、変数値(通常はstate変数)によって制御され、複雑なタスクになる場合がありますニース図を複雑なコードに関連付けます。

このテクニックは、残念ながら、もはや出版されていない偉大な「コンピュータ言語」誌に掲載された記事から学びました。

25
Remo.D

また、テーブルアプローチも使用しました。ただし、オーバーヘッドがあります。ポインターの2番目のリストを保存する理由()のないCの関数はconstポインターです。だからあなたができる:

struct state;
typedef void (*state_func_t)( struct state* );

typedef struct state
{
  state_func_t function;

  // other stateful data

} state_t;

void do_state_initial( state_t* );
void do_state_foo( state_t* );
void do_state_bar( state_t* );

void run_state( state_t* i ) {
    i->function(i);
};

int main( void ) {
    state_t state = { do_state_initial };

    while ( 1 ) {
        run_state( state );

        // do other program logic, run other state machines, etc
    }
}

もちろん、あなたの恐怖要因(すなわち、安全性と速度)に応じて、あなたは有効なポインターをチェックしたいかもしれません。 3つ以上の状態よりも大きい状態マシンの場合、上記のアプローチは、同等のスイッチまたはテーブルアプローチよりも少ない命令である必要があります。次のようにマクロ化することもできます。

#define RUN_STATE(state_ptr_) ((state_ptr_)->function(state_ptr_))

また、私はOPの例から、ステートマシンを考えたり設計したりするときに行うべき単純化があると感じています。遷移状態がロジックに使用されるべきではない。各状態関数は、過去の状態を明示的に知らなくても、指定された役割を実行できる必要があります。基本的に、現在の状態から別の状態に移行する方法を設計します。

最後に、「機能」境界に基づいてステートマシンの設計を開始せず、そのためにサブ関数を使用します。代わりに、続行する前に何かが発生するのを待つ必要がある場合に基づいて、状態を分割します。これにより、結果を得るまでにステートマシンを実行する必要がある回数を最小限に抑えることができます。これは、I/O関数または割り込みハンドラーを作成するときに重要になります。

また、古典的なswitchステートメントのいくつかの長所と短所:

長所:

  • それは言語で書かれているので、文書化され明確になっています
  • 状態は呼び出される場所で定義されます
  • 1つの関数呼び出しで複数の状態を実行できます
  • すべての状態に共通のコードは、switchステートメントの前後に実行できます

短所:

  • 1つの関数呼び出しで複数の状態を実行できます
  • すべての状態に共通のコードは、switchステートメントの前後に実行できます
  • スイッチの実装は遅くなる可能性があります

Proとconの両方である2つの属性に注意してください。この切り替えにより、状態間の共有が過剰になり、状態間の相互依存が管理不能になる可能性があると思います。ただし、少数の状態では、最も読みやすく保守しやすい場合があります。

13
Josh Petitt

単純な状態マシンの場合は、状態にswitchステートメントと列挙型を使用するだけです。入力に基づいてswitchステートメント内で遷移を行います。実際のプログラムでは、明らかに「if(input)」を変更して移行ポイントを確認します。お役に立てれば。

typedef enum
{
    STATE_1 = 0,
    STATE_2,
    STATE_3
} my_state_t;

my_state_t state = STATE_1;

void foo(char input)
{
    ...
    switch(state)
    {
        case STATE_1:
            if(input)
                state = STATE_2;
            break;
        case STATE_2:
            if(input)
                state = STATE_3;
            else
                state = STATE_1;
            break;
        case STATE_3:
            ...
            break;
    }
    ...
}
10
jsl4980

ロジックグリッド もあります。これは、ステートマシンが大きくなるにつれて保守しやすくなります

8
geocoin

Martin FowlerのUML Distilled で、彼は第10章State Machine Diagrams(emphasis mine)に次のように述べています(しゃれはありません):

状態図は、3つの主な方法で実装できます。入れ子スイッチ状態パターン、およびstate tables

携帯電話のディスプレイの状態の簡単な例を使用してみましょう。

enter image description here

入れ子スイッチ

ファウラーはC#コードの例を提供しましたが、私はそれを私の例に適合させました。

public void HandleEvent(PhoneEvent anEvent) {
    switch (CurrentState) {
    case PhoneState.ScreenOff:
        switch (anEvent) {
        case PhoneEvent.PressButton:
            if (powerLow) { // guard condition
                DisplayLowPowerMessage(); // action
                // CurrentState = PhoneState.ScreenOff;
            } else {
                CurrentState = PhoneState.ScreenOn;
            }
            break;
        case PhoneEvent.PlugPower:
            CurrentState = PhoneState.ScreenCharging;
            break;
        }
        break;
    case PhoneState.ScreenOn:
        switch (anEvent) {
        case PhoneEvent.PressButton:
            CurrentState = PhoneState.ScreenOff;
            break;
        case PhoneEvent.PlugPower:
            CurrentState = PhoneState.ScreenCharging;
            break;
        }
        break;
    case PhoneState.ScreenCharging:
        switch (anEvent) {
        case PhoneEvent.UnplugPower:
            CurrentState = PhoneState.ScreenOff;
            break;
        }
        break;
    }
}

状態パターン

GoF Stateパターンを使用した私の例の実装を次に示します。

enter image description here

状態テーブル

ファウラーからインスピレーションを得て、ここに私の例の表があります:

ソース状態ターゲット状態イベントガードアクション
 --------------------------------- -------------------------------------------------- --- 
 ScreenOff ScreenOff pressButton powerLow displayLowPowerMessage 
 ScreenOff ScreenOn pressButton!powerLow 
 ScreenOn ScreenOff pressButton 
 ScreenOff ScreenCharging plugPower 
 ScreenOn ScreenCharging plugPower 
 ScreenCharging ScreenOff unplugPower 

比較

ネストされたスイッチは、すべてのロジックを1つの場所に保持しますが、状態と遷移が多い場合、コードを読みにくくすることができます。他のアプローチよりも安全で検証しやすい可能性があります(ポリモーフィズムや解釈なし)。

状態パターンの実装は、ロジックをいくつかの別個のクラスに分散させる可能性があり、全体としての理解が問題になる場合があります。一方、少人数クラスは個別に理解しやすいです。トランジションは階層内のメソッドであり、コードに多くの変更がある可能性があるため、トランジションを追加または削除して動作を変更する場合、デザインは特に脆弱です。小さなインターフェイスの設計原則に従っている場合、このパターンは実際にはうまくいかないことがわかります。ただし、ステートマシンが安定している場合、このような変更は必要ありません。

状態テーブルのアプローチでは、コンテンツに対して何らかの種類のインタープリターを作成する必要があります(使用している言語に反映している場合はこれが簡単になる可能性があります)。 Fowlerが指摘しているように、テーブルがコードから分離されている場合、再コンパイルせずにソフトウェアの動作を変更できます。ただし、これにはセキュリティ上の意味があります。ソフトウェアは外部ファイルの内容に基づいて動作しています。

編集(実際にはC言語用ではありません)

流なインターフェイス(別名、内部ドメイン固有言語)アプローチもあります。これは、おそらく ファーストクラス関数 を持つ言語によって促進されます。 Stateless library が存在し、そのブログはコードを使用した簡単な例を示しています。 Java実装(Java8以前) について説明します。 GitHubでのPythonの例 も示されました。

6
Fuhrmanator

Jonathan ValvanoとRamesh Yerraballiによるedx.orgコースEmbedded Systems-Shape the World UTAustinX-UT.6.02x、第10章で、ムーアFSMの非常に滑らかなC実装を見つけました。

struct State {
  unsigned long Out;  // 6-bit pattern to output
  unsigned long Time; // delay in 10ms units 
  unsigned long Next[4]; // next state for inputs 0,1,2,3
}; 

typedef const struct State STyp;

//this example has 4 states, defining constants/symbols using #define
#define goN   0
#define waitN 1
#define goE   2
#define waitE 3


//this is the full FSM logic coded into one large array of output values, delays, 
//and next states (indexed by values of the inputs)
STyp FSM[4]={
 {0x21,3000,{goN,waitN,goN,waitN}}, 
 {0x22, 500,{goE,goE,goE,goE}},
 {0x0C,3000,{goE,goE,waitE,waitE}},
 {0x14, 500,{goN,goN,goN,goN}}};
unsigned long currentState;  // index to the current state 

//super simple controller follows
int main(void){ volatile unsigned long delay;
//embedded micro-controller configuration omitteed [...]
  currentState = goN;  
  while(1){
    LIGHTS = FSM[currentState].Out;  // set outputs lines (from FSM table)
    SysTick_Wait10ms(FSM[currentState].Time);
    currentState = FSM[currentState].Next[INPUT_SENSORS];  
  }
}
4
user153222

switch()はCでステートマシンを実装する強力で標準的な方法ですが、多数のステートがある場合は保守性が低下する可能性があります。別の一般的な方法は、関数ポインターを使用して次の状態を保存することです。この単純な例では、セット/リセットフリップフロップを実装しています。

/* Implement each state as a function with the same prototype */
void state_one(int set, int reset);
void state_two(int set, int reset);

/* Store a pointer to the next state */
void (*next_state)(int set, int reset) = state_one;

/* Users should call next_state(set, reset). This could
   also be wrapped by a real function that validated input
   and dealt with output rather than calling the function
   pointer directly. */

/* State one transitions to state one if set is true */
void state_one(int set, int reset) {
    if(set)
        next_state = state_two;
}

/* State two transitions to state one if reset is true */
void state_two(int set, int reset) {
    if(reset)
        next_state = state_one;
}
4

単純な場合には、スイッチスタイルメソッドを使用できます。過去にうまくいくことがわかったのは、移行にも対処することです。

static int current_state;    // should always hold current state -- and probably be an enum or something

void state_leave(int new_state) {
    // do processing on what it means to enter the new state
    // which might be dependent on the current state
}

void state_enter(int new_state) {
    // do processing on what is means to leave the current atate
    // might be dependent on the new state

    current_state = new_state;
}

void state_process() {
    // switch statement to handle current state
}

Boostライブラリについては何も知りませんが、このタイプのアプローチは非常にシンプルで、外部の依存関係を必要とせず、実装も簡単です。

4
Mark

私のお気に入りのパターンの1つは、状態設計パターンです。与えられた同じ入力セットに対して異なる応答または動作をします。
[。何かを壊します。設計パターンを使用すると、データを整理しやすくなります。これが抽象化のポイントです。あなたがどの状態から来たのかについて状態コードを設計する代わりに、代わりに新しい状態に入るときに状態を記録するようにコードを構造化します。これにより、以前の状態の記録を効果的に取得できます。私は@JoshPetitの答えが好きで、彼の解決策をさらに一歩進め、GoFの本から直接取った。

stateCtxt.h:

#define STATE (void *)
typedef enum fsmSignal
{
   eEnter =0,
   eNormal,
   eExit
}FsmSignalT;

typedef struct fsm 
{
   FsmSignalT signal;
   // StateT is an enum that you can define any which way you want
   StateT currentState;
}FsmT;
extern int STATECTXT_Init(void);
/* optionally allow client context to set the target state */
extern STATECTXT_Set(StateT  stateID);
extern void STATECTXT_Handle(void *pvEvent);

stateCtxt.c:

#include "stateCtxt.h"
#include "statehandlers.h"

typedef STATE (*pfnStateT)(FsmSignalT signal, void *pvEvent);

static FsmT      fsm;
static pfnStateT UsbState ;

int STATECTXT_Init(void)
{    
    UsbState = State1;
    fsm.signal = eEnter;
    // use an enum for better maintainability
    fsm.currentState = '1';
    (*UsbState)( &fsm, pvEvent);
    return 0;
}

static void ChangeState( FsmT *pFsm, pfnStateT targetState )
{
    // Check to see if the state has changed
    if (targetState  != NULL)
    {
        // Call current state's exit event
        pFsm->signal = eExit;
        STATE dummyState = (*UsbState)( pFsm, pvEvent);

        // Update the State Machine structure
        UsbState = targetState ;

        // Call the new state's enter event
        pFsm->signal = eEnter;            
        dummyState = (*UsbState)( pFsm, pvEvent);
    }
}

void STATECTXT_Handle(void *pvEvent)
{
    pfnStateT newState;

    if (UsbState != NULL)
    {
         fsm.signal = eNormal;
         newState = (*UsbState)( &fsm, pvEvent );
         ChangeState( &fsm, newState );
    }        
}


void STATECTXT_Set(StateT  stateID)
{
     prevState = UsbState;
     switch (stateID) 
     {
         case '1':               
            ChangeState( State1 );
            break;
          case '2':
            ChangeState( State2);
            break;
          case '3':
            ChangeState( State3);
            break;
     }
}

statehandlers.h:

/* define state handlers */
extern STATE State1(void);
extern STATE State2(void);
extern STATE State3(void);

statehandlers.c:

#include "stateCtxt.h:"

/* Define behaviour to given set of inputs */
STATE State1(FsmT *fsm, void *pvEvent)
{   
    STATE nextState;
    /* do some state specific behaviours 
     * here
     */
    /* fsm->currentState currently contains the previous state
     * just before it gets updated, so you can implement behaviours 
     * which depend on previous state here
     */
    fsm->currentState = '1';
    /* Now, specify the next state
     * to transition to, or return null if you're still waiting for 
     * more stuff to process.  
     */
    switch (fsm->signal)
    {
        case eEnter:
            nextState = State2;
            break;
        case eNormal:
            nextState = null;
            break;
        case eExit:
            nextState = State2;
            break;
    }

    return nextState;
}

STATE  State3(FsmT *fsm, void *pvEvent)
{
    /* do some state specific behaviours 
     * here
     */
    fsm->currentState = '2';
    /* Now, specify the next state
     * to transition to
     */
     return State1;
}

STATE   State2(FsmT *fsm, void *pvEvent)
{   
    /* do some state specific behaviours 
     * here
     */
    fsm->currentState = '3';
    /* Now, specify the next state
     * to transition to
     */
     return State3;
}

ほとんどのステートマシンでは、特に有限状態マシン。各状態は、次の状態がどうあるべきか、および次の状態に移行するための基準を知っています。緩い状態の設計の場合、これは当てはまらない可能性があるため、状態を遷移させるためのAPIを公開するオプションがあります。さらに抽象化する場合は、各状態ハンドラーを独自のファイルに分離できます。これは、GoFブックの具体的な状態ハンドラーと同等です。設計がいくつかの状態のみでシンプルな場合、stateCtxt.cとstatehandlers.cの両方を単一のファイルに結合して簡単にすることができます。

2
Phileo99

この記事 は、状態パターンに適したものです(ただし、CではなくC++です)。

Head First Design Patterns 」という本に手を加えることができれば、説明と例は非常に明確です。

2
pmlarocque

liberoFSMジェネレータソフトウェアを調べてください。状態記述言語および/または(ウィンドウ)状態図エディタから、C、C++、Javaおよびその他多くの...に加えて、ニースのドキュメントと図のコードを生成できます。ソースおよびバイナリ iMatix

2
pklausner

__COUNTER__をサポートするコンパイラーでは、単純な(ただし大規模な)状態マシンにそれらを使用できます。

  #define START 0      
  #define END 1000

  int run = 1;
  state = START;    
  while(run)
  {
    switch (state)
    {
        case __COUNTER__:
            //do something
            state++;
            break;
        case __COUNTER__:
            //do something
            if (input)
               state = END;
            else
               state++;
            break;
            .
            .
            .
        case __COUNTER__:
            //do something
            if (input)
               state = START;
            else
               state++;
            break;
        case __COUNTER__:
            //do something
            state++;
            break;
        case END:
            //do something
            run = 0;
            state = START;
            break;
        default:
            state++;
            break;
     } 
  } 

ハードコードされた数字の代わりに__COUNTER__を使用する利点は、毎回すべての番号を付け直すことなく、他の状態の中間に状態を追加できることです。コンパイラが__COUNTER__をサポートしていない場合、限定的な方法で注意して使用できます__LINE__

1
Seb

私の経験では、「switch」ステートメントを使用することは、複数の可能な状態を処理する標準的な方法です。状態ごとの処理に遷移値を渡すことに驚いていますが。ステートマシンのポイントは、各ステートが単一のアクションを実行することだと思いました。次に、次のアクション/入力により、遷移する新しい状態が決定されます。したがって、各状態処理機能は、状態に入るために修正されたものをすぐに実行し、その後、別の状態への移行が必要かどうかを決定することを期待していました。

1
Phil Wright

C/C++の実用的なステートチャート というタイトルの本があります。しかし、それはwayであり、私たちが必要とするものには重すぎます。

1
Benoit

Cでは、最小限のUMLステートマシンフレームワークを使用できます。 https://github.com/kiishor/UML-State-Machine-in-C

有限状態マシンと階層状態マシンの両方をサポートしています。 APIは3つ、構造は2つ、列挙は1つのみです。

ステートマシンは、state_machine_t構造で表されます。これは、ステートマシンを作成するために継承できる抽象的な構造です。

//! Abstract state machine structure
struct state_machine_t
{
   uint32_t Event;          //!< Pending Event for state machine
   const state_t* State;    //!< State of state machine.
};

状態は、フレームワークのstate_t構造体へのポインターによって表されます。

フレームワークが有限状態マシン用に構成されている場合、state_tには、

typedef struct finite_state_t state_t;

// finite state structure
typedef struct finite_state_t{
  state_handler Handler;        //!< State handler function (function pointer)
  state_handler Entry;          //!< Entry action for state (function pointer)
  state_handler Exit;           //!< Exit action for state (function pointer)
}finite_state_t;

このフレームワークは、イベントをステートマシンにディスパッチするためのAPI dispatch_eventと、ステートトラバーサル用の2つのAPIを提供します。

state_machine_result_t dispatch_event(state_machine_t* const pState_Machine[], uint32_t quantity);
state_machine_result_t switch_state(state_machine_t* const, const state_t*);

state_machine_result_t traverse_state(state_machine_t* const, const state_t*);

階層状態マシンを実装する方法の詳細については、GitHubリポジトリを参照してください。

コード例
https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine/readme.md
https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine_enhanced/readme.md

0

Cでは、単純なマシンの場合、通常このようなものが使用されます。

イベント駆動型FSMは、アクション(FsmAction)に関連付けられた状態オブジェクト(FsmState)、および現在の状態とイベントによって定義された遷移(FsmEdge)によって記述されます。

また、FSMとユーザーの両方の情報を格納および渡すことにより、FSMにバインドされた情報とユーザーにバインドされた情報を分離し、同じFSMの複数のインスタンスを許可します(つまり、同じ説明を使用して異なるユーザーデータを渡します)。

イベントは整数型(FsmEvent)で表されます。負の値は、特別な一般的なイベント(Reset、None、Any、Empty、End)を許可するために実装によって予約されています。負でないイベントはユーザー定義です。

簡単にするために、遷移は配列にリストされ、配列の順序でマッチングが試行され、本質的に遷移の優先順位が提供されます。オプションのガード機能があります。次の状態は、遷移リストに直接表示するか、ジャンプ機能によって表示できます。これにより、動的FSMの動作をより柔軟に実現できます。

遷移の説明では、NULLの現在の状態は任意の状態と一致し、ワイルドカードイベント(任意)は任意のイベントと一致します。とにかく、遷移をトリガーしたイベントの実際の値は、ジャンプ関数とガード関数に渡されます。

複雑なFSMの場合、単純なEdgeアレイソリューションは非効率になりすぎる可能性があります。その場合、Edge配列と遷移行列または状態隣接リストに変換されたイベントエントリを使用して、適切なジャンプ機能を実装できます。

状態アクションは、状態エントリ(Enter)、状態出口(Leave)、および状態内(State)操作を区別するリエントラント関数で実装することを意図しています。この方法で、ローカル状態情報をカプセル化し、静的関数変数で保存できます。

通常、状態の​​開始および終了アクションは目立たないように実行され、新しいイベントを返しません(なし)。そうでない場合、新しいイベントはトラップされ、すぐに返されます。これにより、現在の状態を終了するときに発生した場合の移行を効果的に防止できます。

FSMステップ関数(fsmStep)は、新しいイベントを使用してトランジションをトリガーするか、イベントなし(なし)を使用して現在の状態のインステートアクションを実行し、FSMの単一ステップを実行します。 step関数は、処理またはFSMに再供給できる新しい発行イベントを返します。または、イベントなし、遷移が見つからない場合、または終了状態に達した場合は、それぞれNone、EmptyおよびEndです。

#ifndef FSM_H_
#define FSM_H_

#include <stdbool.h>
#include <stdint.h>

/** FSM enum type */
typedef enum
{
    // Events and return values
    fsm_User = 0, ///< User events start with this id
    fsm_Reset = -1, ///< Reset event
    fsm_None = -2, ///< No event
    fsm_Any = -3, ///< Any event, used as a wildcard
    fsm_Empty = -4, ///< No transition found for event
    fsm_End = -5, ///< Final state event generated when FSM reaches end state, or stop processing when used in transition

    // Action types
    fsm_Enter = 0, ///< Entry action
    fsm_State, ///< In-state action
    fsm_Leave ///< Exit action
} fsm_e;

typedef int FsmEvent; ///< Type for events
typedef struct FsmState FsmState; ///< Type for states
typedef struct FsmEdge FsmEdge; ///< Type for edges (transitions)

/** State action functor
    @param state Pointer to this state
    @param type Action type (Enter/State/Leave)
    @param frto Pointer to from(Enter)/to(Leave) state, NULL otherwise
    @param data User data
    @return Event id in case of a new triggered event, fsm_None otherwise
*/
typedef FsmEvent (*FsmAction)(FsmState *state, fsm_e type, FsmState *frto, void *data);

/** FSM state object */
struct FsmState
{
    FsmAction action; ///< Per-state action
    void *data; ///< Per-state data
};

/** State jump functor
    @param Edge Pointer to this Edge
    @param state Pointer to the actual current state
    @param event Event id that triggered the transition
    @param data User data
    @return Pointer to the next state and NULL for end state
*/
typedef FsmState *(*FsmJump)(FsmEdge *Edge, FsmState *state, FsmEvent event, void *data);

/** Guard function
    @param Edge Pointer to this Edge
    @param state Pointer to the actual current state
    @param event Event id that triggered the transition
    @param data User data
    @return Guard result
*/
typedef bool (*FsmGuard)(FsmEdge *Edge, FsmState *state, FsmEvent event, void *data);

/** FSM Edge transition */
struct FsmEdge
{
    FsmState *state; ///< Matching current state pointer, or NULL to match any state
    FsmEvent event; ///< Matching event id or fsm_Any for wildcard
    void *next; ///< Next state pointer (FsmState *) or jump function (FsmJump)
    FsmGuard guard; ///< Transition guard function
    void *data; ///< Per-Edge data
};

typedef struct Fsm Fsm;
struct Fsm
{
    FsmState *state; ///< Pointer to the state array
    size_t states; ///< Number of states
    void **stateData; ///< Pointer to user state data array

    FsmEdge *Edge; ///< Pointer to the Edge transitions array
    size_t edges; ///< Number of edges
    void **edgeData; ///< Pointer to user Edge data array

    FsmEvent event; ///< Current/last event
    fsm_e type; ///< Current/last action type

    FsmState *current; ///< Pointer to the current state
    void *data; ///< Per-fsm data
};

#define fsm_INIT { 0 }

static inline FsmEvent
fsmStep(Fsm *f, FsmEvent e)
{
    FsmState *cp = f->current; // Store current state
    FsmEvent ne = fsm_None; // Next event

    // User state data
    void *us = (f->stateData && cp) ? f->stateData[cp - f->state] : NULL;

    if (!cp && e == fsm_None)
        e = fsm_Reset; // Inject reset into uninitialized FSM

    f->event = e;

    switch (e)
    {
    case fsm_Reset:
        {
            // Exit current state
            if (cp && cp->action)
            {
                f->type = fsm_Leave, ne = cp->action(cp, fsm_Leave, f->state, us);

                if (ne != fsm_None)
                    return ne; // Leave action emitted event
            }

            FsmState *ps = cp;
            cp = f->current = f->state; // First state in array is entry state

            if (!cp)
                return fsm_End; // Null state machine

            if (cp->action)
            {
                us = f->stateData ? f->stateData[0] : NULL;
                f->type = fsm_Enter, ne = cp->action(cp, fsm_Enter, ps, us);
            }
        }
        break;

    case fsm_None: // No event, run in-state action
        if (cp->action)
            f->type = fsm_State, ne = cp->action(cp, fsm_State, NULL, us);
        break;

    default: // Process user transition event
        ne = fsm_Empty; // Default return in case no transition is found

        // Search transition in listing order
        for (size_t i = 0; i < f->edges; ++i)
        {
            FsmEdge *ep = &f->Edge[i];

            // Check for state match (null Edge state matches any state)
            if (ep->state && ep->state != cp)
                continue; // Not a match

            // Check for stop processing filter
            if (ep->event == fsm_End)
                break;

            ne = fsm_None; // Default return for a transition

            // Check for event match
            if (ep->event == e || ep->event == fsm_Any)
            {
                // User Edge data
                void *ue = f->edgeData ? f->edgeData[i] : NULL;

                // Check transition guard
                if (!ep->guard || ep->guard(ep, cp, e, ue))
                {
                    // Resolve next state pointer (NULL, new state pointer or jump function)
                    FsmState *np = (!ep->next) ? NULL
                        : ((FsmState *)(ep->next) >= f->state && (FsmState *)(ep->next) < (f->state + f->states)) ? (FsmState *)(ep->next)
                        : ((FsmJump)(ep->next))(ep, cp, e, ue);

                    if (cp->action)
                    {
                        f->type = fsm_Leave, ne = cp->action(cp, fsm_Leave, np, us);

                        if (ne != fsm_None)
                            return ne; // Leave action emitted event
                    }

                    if (!np) // Final state reached if next state is NULL
                        ne = fsm_End;
                    else if (np->action)
                    {
                        us = f->stateData ? f->stateData[np - f->state] : NULL;
                        f->type = fsm_Enter, ne = np->action(np, fsm_Enter, cp, us);
                    }

                    cp = np; // New state value

                    // Transition executed, stop
                    break;
                }
            }
        }
    }

    f->current = cp; // Commit current state

    return ne; // Last event value
}

static inline FsmEvent
fsmReset(Fsm *f)
{
    return fsmStep(f, fsm_Reset);
}

#endif // FSM_H_

以下に、FSM実装を定義および使用する方法に関するアイデアを得るための非常に簡単なテストがあります。ユーザー定義のイベントはなく、FSMデータ(文字列)、すべての状態に対する同じ状態アクション、および共有ジャンプ機能のみがあります。

#include <stdio.h>

#include "fsm.h"

// State action example
static FsmEvent
state_action(FsmState *s, fsm_e t, FsmState *ft, void *us)
{
    FsmEvent e = fsm_None; // State event
    const char *q = "?";

    switch (t)
    {
    case fsm_Enter:
        q = "enter";
        break;

    case fsm_Leave:
        q = "leave";
        break;

    default /* fsm_State */:
        q = "state";
    }

    printf("%s %s\n", (const char *)s->data, q);

    return e;
}

// States
FsmState fs[] =
{
    { state_action, "S0" },
    { state_action, "S1" },
    { state_action, "S2" },
};

// Transition jump example
static FsmState *
state_jump(FsmEdge *t, FsmState *s, FsmEvent e, void *ue)
{
    if (s == &fs[0])
        return &fs[1];

    if (s == &fs[2])
        return NULL; // End state

    return NULL; // Trap
}

// Transition guard example
static bool
count_attempt(FsmEdge *t, FsmState *s, FsmEvent e, void *ue)
{
    static int c = 0;
    ++c;
    bool b = c == 3;
    printf("guard is %s\n", b ? "true" : "false");
    return b;
}

// Transitions
FsmEdge fe[] =
{
    { &fs[0], fsm_Any, state_jump }, // Using jump function, no guard
    { &fs[1], fsm_Any, &fs[2], count_attempt }, // Using direct state and guard
    { &fs[2], fsm_Any, state_jump  }, // Using jump function, no guard
};

int
main(int argc, char **argv)
{
    Fsm f = { fs, 3, NULL, fe, 3, NULL, };

    fsmReset(&f);

    do 
    {
        fsmStep(&f, fsm_None);
    } while (fsmStep(&f, fsm_Any) != fsm_End);

    return 0;
}
0
pbk

C++では、 状態パターン を考慮してください。

0
Bruno De Fraine

あなたの質問は「典型的なデータベース実装パターンはありますか」に似ていますか?答えは何を達成したいかによって異なりますか?より大きな確定的状態マシンを実装する場合は、モデルと状態マシンジェネレーターを使用できます。サンプルはwww.StateSoft.org-SM Galleryで見ることができます。ヤヌシュ・ドブロウルスキ

0