IT

일반적인 상태 머신 구현

lottoking 2020. 7. 29. 07:33
반응형

일반적인 상태 머신 구현


C 에서 간단한 상태 머신을 구현해야합니다 .
표준 스위치 문이 가장 좋은 방법입니까?
현재 상태 (상태)와 전환 트리거가 있습니다.


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 에는 도움 되지 않습니다 . C 사용 사례에 집중할 수 있습니다.


대부분의 상태 머신에 테이블 기반 접근 방식을 사용하는 것을 선호합니다.

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

테이블 중심 접근 방식은 유지 관리 및 확장이 부인 상태 다이어그램에 매핑하기가 더 간단합니다.


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

특정 상황에 맞게 배치 할 수 있습니다. 예를 들어, FSMFILEFSM을 구동하려는 파일에 다음 문자를 읽을 수 있으므로 작업을 매크로 자체에 통합 할 수 있습니다.

#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

이제 두 가지 유형의 전환이 있습니다. 하나는 상태로 이동하고 새 문자를 읽고 다른 하나는 입력을 소비하지 않고 상태로갑니다.

다음과 같은 방법으로 EOF 처리를 자동화 할 수도 있습니다.

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

#define ENDFSM    sx_endfsm:

이 방법의 좋은 점은 상태 다이어그램을 코드로 직접 변환 할 수있는 곳에서 작업 코드에서 상태 다이어그램을 쉽게 그릴 수있는 것입니다.

FSM을 구현하기위한 다른 기술들에서, 전이의 구조는 제어 구조 (매개, 스위치 ...)에 묻히고 변수 값 (종종 state변수)에 의해 제어 완료 , 멋진 다이어그램을 복잡한 코드.

불행히도 더 이상 출판되지 않는 위대한 "컴퓨터 언어"잡지에 실린 기사 에서이 기술을 배웠습니다.


나는 또한 테이블을 사용했습니다. 그러나 오버 헤드가 있습니다. 두 번째 포인터 목록을 저장해야하는 이유 ()가없는 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 함수 또는 중요 할 수 있습니다.

또한 고전적인 스위치 문의 몇 가지 장단점이 있습니다.

장점 :

  • 언어로되어 있으므로 문서화되고 명확합니다.
  • 상태는 호출되는 곳에서 정의됩니다
  • 하나의 함수 호출에서 여러 상태를 사용할 수 있습니다.
  • 스위치 문 전후에 모든 상태에 공통적 인 코드를 사용할 수 있습니다.

단점 :

  • 하나의 함수 호출에서 여러 상태를 사용할 수 있습니다.
  • 스위치 문 전후에 모든 상태에 공통적 인 코드를 사용할 수 있습니다.
  • 스위치 구현이 느려질 수 있습니다.

pro와 con의 두 가지 특성에 유의하십시오. 스위치를 사용하면 상태를 너무 많이 공유 할 수 있고 상태 그대로 상호 채팅을 관리 할 수 ​​없게 될 수 있습니다. 그러나 소수의 상태에서는 가장 읽기 유지 관리가 쉬운 상태 일 수 있습니다.


간단한 상태 머신의 경우 상태에 대해 스위치 문과 열거 형을 사용하십시오. 입력을 기반으로 스위치 문 내에서 전환을 수행하십시오. 실제 프로그램에서는 전환점을 확인하기 위해 "if (입력)"을 반드시 변경해야합니다. 도움이 되셨기를 바랍니다.

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

상태 머신이 커질수록 유지 관리 하기 쉬운 관리 그리드 도 있습니다.


에서 마틴 파울러의 UML 증류 , 그는 (웃기 의도 없음) 제 10 장 상태 머신 다이어그램 (강조 광산)의 상태 :

상태 다이어그램은 세 가지 주요 방법 , 즉 중첩 스위치 , 상태 패턴상태 테이블 로 구현할 수 있습니다 .

휴대 전화의 상태의 간단한 예를 사용하겠습니다.

여기에 이미지 설명을 입력하십시오

중첩 스위치

Fowler는 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 패턴을 예제 구현입니다.

여기에 이미지 설명을 입력하십시오

상태 테이블

Fowler에서 영감을 얻은 예는 다음과 같습니다.

소스 상태 대상 상태 이벤트 가드 조치
-------------------------------------------------- ------------------------------------
화면 꺼짐 화면 꺼짐 누름 버튼 전원 낮음 표시 낮음 전원 메시지  
ScreenOff ScreenOn 누름 버튼! powerLow
ScreenOn ScreenOff 누름 버튼
화면 꺼짐 화면 충전 플러그 전원
ScreenOn 화면 충전 플러그 전원
충전 중 화면 꺼짐

비교

중첩 스위치는 모든 논리를 한 곳에 유지하지만 상태 및 전환이 많은 경우 코드를 읽을 수 있습니다. 다형성이나 해석이없는 다른 접근 방식보다 더 안전하고 독립적 인 접근 방식.

상태 패턴 구현은 대규모 논리를 여러 클래스에서 분산 데 시키 전체적으로 이해하는 논리를 있습니다. 반면에 작은 수업은 개별적으로 이해하기 위해서입니다. 트랜지션은 계층 구조의 방식으로 코드가 많이 변경 될 수 있으므로 전환을 추가하거나 제거하여 동작을 변경하면 부담합니다. 작은 인터페이스의 디자인 원칙에 따라 생활하면 패턴이 잘 수행되지 않는 것을 알 수 있습니다. 그러나 상태 머신이 필요하지 않습니다.

상태 테이블 접근 방식에는 컨텐츠에 대한 예방 통역사가 필요합니다 (사용중인 언어로 반영하는 것이 더 쉬울 수 있음). Fowler가 지적했듯이 테이블이 코드와 분리되어 있고 다시 준비 할 소프트웨어의 동작을 할 수 있습니다. 그러나 이것은 보안에 영향을 미칩니다. 소프트웨어가 외부 파일의 내용을 기반으로 작동합니다.

편집 (실제로 C 언어는 아님)

유창한 인터페이스 (일명 내부 도메인 특정 언어) 접근 방식도 가능하며 이는 아마도 일류 함수 를 언어에 의해 촉진 될 것입니다 . 상태 비 라이브러리가 존재하고 그 블로그 프로그램 코드와 간단한 예제. 자바 구현 (Java8 사전) 논의된다. GitHub 라고 Python 예제 가 표시 했습니다.


간단한 경우에는 스위치 스타일 방법을 사용할 수 있습니다. 내가 과거에 잘 작동하는 것은 전환을 처리하는 것입니다.

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
}

부스트 라이브러리에는 아무것도 모르지만이 유형의 접근 방식은 간단하지만 외부에서 필요하지 않습니다.


스위치 ()는 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;
}

edx.org 과정 Embedded Systems-Shape the World UTAustinX-UT.6.02x, 10 장, Jonathan Valvano와 Ramesh Yerraballi에서 Moore 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];  
  }
}

libero FSM 생성기 소프트웨어 를 볼 수 있습니다 . 상태 설명 언어 및 / 또는 (윈도우) 상태 다이어그램 편집기에서 C, C ++, java 및 기타 여러 가지 코드와 멋진 문서 및 다이어그램을 생성 할 수 있습니다. iMatix의 소스 및 바이너리


이 기사 는 상태 패턴에 적합합니다 (C가 아니라 C ++ 임).

" Head First Design Patterns " 책에 손을 대면 설명과 예제가 매우 명확합니다.


내가 가장 좋아하는 패턴 중 하나는 상태 디자인 패턴입니다. 주어진 동일한 입력에 응답하거나 다르게 행동하십시오.
상태 머신에 스위치 / 케이스 문을 사용할 때 발생하는 문제 중 하나는 더 많은 상태를 만들수록 스위치 / 케이스가 읽기 / 유지하기가 어렵거나 다루기 어려워지고 조직화되지 않았으며 코드를 활성화하며, 깨뜨리지 않고 변경하기 가 점점 더 어렵다는 것입니다. 디자인 패턴을 사용하면 데이터를 더 잘 정리할 수 있으며 이는 추상화의 핵심입니다. 출신 국가를 중심으로 상태 코드를 설계하는 대신 새 상태에 구성 할 때 상태를 기록하도록 코드를하십시오. 이렇게하면 이전 상태를 관리 기록 할 수 있습니다. 나는 @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;
}

대부분의 상태 머신의 경우 esp. 유한 상태 머신, 각 상태는 다음 상태가 무엇인지, 다음 상태로 전환하기위한 기준을 알게됩니다. 않을 상태 디자인의 경우에는 않을 수 있으므로 상태 전이를 위해 API를 노출하는 옵션이 있습니다. 더 고유 한 경우를 원하는 경우 각 상태를 선택할 수 있습니다.이 파일은 GoF 서적의 파일로 분리 할 수 ​​있습니다. 몇 개의 상태만으로 디자인이 단순하면 stateCtxt.c와 statehandlers.c를 모두 단일 파일로 결합하여 간단하게 만들 수 있습니다.


내 경험상 'switch'문을 사용하는 것은 가능한 여러 상태를 처리하는 표준 방법입니다. 상태 별 처리로 전환 값을 전달하고 사실에 놀랐습니다. 상태 머신의 요점은 각 상태가 단일 작업을 수행 한 생각했습니다. 그런 다음 다음 조치 / 입력으로 전환 할 새 상태를 결정합니다. 따라서 각 상태 처리 기능을 입력하기 위해 모든 것을 즉시 수행 한 다음 다른 상태로 전환 해야할지 여부를 결정해야합니다.


C / C ++ 에는 Practical Statecharts 라는 제목의 책이 있습니다 . 그러나,이다 그것은 방법 우리가 필요로하는 것을 너무 헤비급.


을 지원하는 컴파일러의 경우 단순 __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__


C ++에서 State 패턴을 고려하십시오 .


귀하의 질문은 "일반적인 데이터베이스 구현 패턴이 있습니까?" 답은 달성하고자하는 것에 달려 있습니다. 더 큰 결정적 상태 머신을 구현할 수 있습니다. 예는 www.StateSoft.org-SM Gallery에서 볼 수 있습니다. 야누스 도브 롤롤 스키


C에서 간단한 기계의 경우 이와 같은 것이 일반적으로 사용됩니다.

이벤트 중심 FSM은 동작 (FsmAction)과 관련된 상태 개체 (FsmState)와 현재 상태 및 이벤트에 의해 정의 된 전환 (FsmEdge)으로 설명됩니다.

또한 FSM 바운드 및 사용자 바운드 정보를 분리하고 동일한 FSM의 여러 인스턴스 (예 : 동일한 설명을 사용하지만 다른 사용자 데이터를 전달)를 허용하기 위해 FSM 및 사용자 데이터를 모두 저장하고 전달합니다.

이벤트는 정수 유형 (FsmEvent)으로 표시됩니다. 구현에서는 특수한 공통 이벤트 (재설정, 없음, 모두, 비어 있음, 끝)를 허용하기 위해 음수 값을 예약합니다. 음이 아닌 이벤트는 사용자 정의됩니다.

단순화를 위해 전이가 배열에 배열 순서로 일치가 시도되어 있음으로 전이 우선 순위를 제공합니다. 옵션 가드 기능이 있습니다. 다음 상태는 전환 목록에 직접 표시되거나 점프 기능으로 표시 될 수 있으므로 동적 FSM 동작을 가능하게하는 유연성이 있습니다.

전이 설명에서 NULL 현재 상태는 모든 상태와 일치하고 와일드 카드 이벤트 (Any)는 모든 이벤트와 일치합니다. 어쨌든 전환을 트리거 한 이벤트의 실제 값은 점프 및 가드 기능으로 전달됩니다.

복잡한 FSM의 경우 단순 에지 어레이 솔루션이 너무 비효율적입니다. 이 경우, 엣지 어레이와 전이 매트릭스 또는 상태 구현리스트로 변환 된 이벤트 항목을 사용하여 점프 기능을 사용할 수 있습니다.

상태 조치는 상태 입력 (Enter), 상태 종료 (Leave) 및 상태 내 (State) 조작을 구분하는 재진입 기능으로 구현됩니다. 대신 방식으로 로컬 상태 정보를 정적 함수 변수로 캡슐화하고 보존 할 수 있습니다.

일반적으로 상태 입력 종료 조치는 현저하게 실행되고 새 이벤트를 리턴하지 않습니다 (없음). 그렇지 않은 경우 새 이벤트가 트랩되어 즉시 리턴됩니다. 이렇게하면 현재 상태를 종료 할 때 발생하는 전환을 관리 할 수 ​​없습니다.

FSM 단계 기능 (fsmStep)은 새 이벤트를 사용하여 전환을 트리거하거나 현재 상태의 상태 내 동작을 실행하는 이벤트 없음 (없음)을 사용하여 FSM의 단일 단계를 수행합니다. step 함수는 처리하거나 FSM으로 다시 공급할 수있는 새로 생성 된 이벤트를 리턴합니다. 또는 없음, 비어 있음 및 종료 이벤트가 설치되어 종료 상태에 도달했습니다.

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

c에서 미니멀리스트 UML 상태 머신 프레임 워크를 사용할 수 있습니다. https://github.com/kiishor/UML-State-Machine-in-C

유한 상태 시스템과 계층 상태 시스템을 모두 지원합니다. 3 개의 API, 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;

프레임 워크는 dispatch_event이벤트를 상태 머신에 전달 하는 API 와 상태 순회를위한 두 개의 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 / 마스터 / 데모 / simple_state_machine_enhanced / readme.md


Boost에는 상태 차트 라이브러리가 있습니다. http://www.boost.org/doc/libs/1_36_0/libs/statechart/doc/index.html

그래도 사용할 수 없습니다. 직접 사용하여 폭발 (아직)

참고 URL : https://stackoverflow.com/questions/133214/is-there-a-typical-state-machine-implementation-pattern

반응형