Apakah ada pola implementasi mesin negara yang khas?

118

Kita perlu menerapkan mesin negara sederhana dalam C .
Apakah pernyataan sakelar standar cara terbaik untuk pergi?
Kami memiliki keadaan saat ini (keadaan) dan pemicu untuk transisi.


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

Apakah ada cara yang lebih baik untuk mesin status sederhana

EDIT: Untuk C ++, saya pikir perpustakaan Boost Statechart mungkin cara yang tepat. Namun, itu tidak membantu dengan C. Mari kita berkonsentrasi pada kasus penggunaan C.

Benoit
sumber

Jawaban:

134

Saya lebih suka menggunakan pendekatan berbasis tabel untuk sebagian besar mesin negara:

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

Ini tentu saja dapat diperluas untuk mendukung beberapa mesin keadaan, dll. Tindakan transisi juga dapat diakomodasi:

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

Pendekatan berbasis tabel lebih mudah dipelihara dan diperluas dan lebih sederhana untuk dipetakan ke diagram status.

Frank Szczerba
sumber
Cara yang sangat bagus untuk memulai, setidaknya titik awal bagi saya. Satu komentar, baris pertama run_state () memiliki "." itu seharusnya tidak ada.
Atilla Filiz
2
akan lebih baik jika jawaban ini juga akan setidaknya mengatakan 2 kata tentang dua pendekatan lainnya: metode "global" dengan kasus sakelar besar, dan memisahkan status dengan Pola Desain Status dan membiarkan setiap status menangani transisinya sendiri.
erikbwork
Hai, Saya tahu posting ini sudah tua tapi saya harap saya akan mendapatkan jawaban saya :) Apa yang harus dilakukan dengan variabel instance_data_t? Saya bertanya-tanya bagaimana cara mengubah status dalam interupsi ... apakah ini cara yang baik untuk menyimpan informasi tentang interupsi yang diproses dalam variabel ini? Misalnya menyimpan informasi yang tombolnya ditekan sehingga keadaan harus diubah.
grongor
@GRoNGoR Kedengarannya seperti Anda berurusan dengan mesin negara yang digerakkan oleh peristiwa. Saya pikir Anda memang bisa menggunakannya untuk menyimpan data acara.
Zimano
3
Sentuhan yang sangat bagus bagaimana NUM_STATES didefinisikan.
Albin Stigo
25

Anda mungkin telah melihat jawaban saya untuk pertanyaan C lainnya di mana saya menyebutkan FSM! Inilah cara saya melakukannya:

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

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

Dengan makro berikut ditentukan

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

Ini dapat dimodifikasi agar sesuai dengan kasus tertentu. Misalnya, Anda mungkin memiliki file FSMFILEyang Anda inginkan untuk menjalankan FSM Anda, sehingga Anda dapat menggabungkan tindakan membaca karakter berikutnya ke dalam makro itu sendiri:

#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

sekarang Anda memiliki dua jenis transisi: satu beralih ke status dan membaca karakter baru, yang lain beralih ke status tanpa mengonsumsi input apa pun.

Anda juga dapat mengotomatiskan penanganan EOF dengan sesuatu seperti:

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

#define ENDFSM    sx_endfsm:

Hal yang baik dari pendekatan ini adalah Anda dapat langsung menerjemahkan diagram status yang Anda gambar menjadi kode yang berfungsi dan, sebaliknya, Anda dapat dengan mudah menggambar diagram status dari kode.

Dalam teknik lain untuk mengimplementasikan FSM, struktur transisi terkubur dalam struktur kontrol (sementara, jika, beralih ...) dan dikendalikan oleh nilai variabel (biasanya statevariabel) dan mungkin tugas yang kompleks untuk mengaitkan diagram yang bagus dengan a kode berbelit-belit.

Saya mempelajari teknik ini dari sebuah artikel yang muncul di majalah besar "Bahasa Komputer" yang sayangnya tidak lagi diterbitkan.

Remo.D
sumber
1
Pada dasarnya, FSM yang baik adalah tentang keterbacaan. Ini menyediakan antarmuka yang baik, dan implementasinya sebaik yang didapat. Sayang sekali tidak ada struktur FSM asli dalam bahasa tersebut. Saya bisa melihatnya sekarang sebagai tambahan terlambat untuk C1X!
Kelden Cowan
3
Saya suka pendekatan ini untuk aplikasi yang disematkan. Apakah ada cara untuk menggunakan pendekatan ini dengan mesin status berbasis peristiwa?
ARF
13

Saya juga telah menggunakan pendekatan tabel. Namun, ada biaya tambahan. Mengapa menyimpan daftar petunjuk kedua? Sebuah fungsi di C tanpa () adalah pointer const. Jadi Anda bisa melakukan:

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

Tentu saja tergantung pada faktor ketakutan Anda (yaitu keamanan vs kecepatan), Anda mungkin ingin memeriksa petunjuk yang valid. Untuk mesin status yang lebih besar dari tiga atau lebih status, pendekatan di atas harus lebih sedikit instruksi daripada pendekatan sakelar atau tabel yang setara. Anda bahkan dapat membuat makro sebagai:

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

Juga, saya merasa dari contoh OP, bahwa ada penyederhanaan yang harus dilakukan ketika memikirkan / mendesain mesin negara. Saya tidak berpikir bahwa keadaan transisi harus digunakan untuk logika. Setiap fungsi keadaan harus dapat menjalankan peran yang diberikan tanpa pengetahuan eksplisit tentang keadaan sebelumnya. Pada dasarnya Anda merancang bagaimana transisi dari keadaan Anda saat ini ke keadaan lain.

Terakhir, jangan memulai desain mesin status berdasarkan batas "fungsional", gunakan subfungsi untuk itu. Sebaliknya bagi negara bagian berdasarkan kapan Anda harus menunggu sesuatu terjadi sebelum Anda dapat melanjutkan. Ini akan membantu meminimalkan berapa kali Anda harus menjalankan mesin status sebelum Anda mendapatkan hasil. Ini bisa menjadi penting saat menulis fungsi I / O, atau penangan interupsi.

Juga, beberapa pro dan kontra dari pernyataan saklar klasik:

Kelebihan:

  • itu dalam bahasa, sehingga didokumentasikan dan jelas
  • negara ditentukan di mana mereka dipanggil
  • dapat menjalankan beberapa status dalam satu pemanggilan fungsi
  • kode umum untuk semua negara bagian dapat dijalankan sebelum dan sesudah pernyataan switch

Kekurangan:

  • dapat menjalankan beberapa status dalam satu pemanggilan fungsi
  • kode umum untuk semua negara bagian dapat dijalankan sebelum dan sesudah pernyataan switch
  • implementasi sakelar bisa lambat

Perhatikan dua atribut yang merupakan pro dan kontra. Saya pikir peralihan memungkinkan kesempatan untuk terlalu banyak berbagi antar negara bagian, dan saling ketergantungan antar negara bagian bisa menjadi tidak terkelola. Namun untuk sejumlah kecil negara bagian, ini mungkin yang paling mudah dibaca dan dipelihara.

Josh Petitt
sumber
10

Untuk mesin status sederhana cukup gunakan pernyataan switch dan tipe enum untuk negara Anda. Lakukan transisi Anda di dalam pernyataan sakelar berdasarkan masukan Anda. Dalam program nyata Anda jelas akan mengubah "jika (masukan)" untuk memeriksa titik transisi Anda. Semoga ini membantu.

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;
    }
    ...
}
jsl4980.dll
sumber
1
Mungkin ada baiknya meletakkan "status" di dalam fungsi, dan membuatnya statis.
Steve Melnikoff
2
@ Steve Melnikoff: hanya jika Anda hanya punya satu mesin negara. Simpan di luar fungsi dan Anda dapat memiliki array mesin status dengan statusnya sendiri.
Vicky
@Vicky: Satu fungsi dapat berisi mesin status sebanyak yang Anda suka, dengan larik variabel status jika diperlukan, yang dapat hidup di dalam fungsi (sebagai variabel statis) jika tidak digunakan di tempat lain.
Steve Melnikoff
10

Dalam Distilled UML Martin Fowler , dia menyatakan (tidak ada permainan kata-kata) di Bab 10 Diagram Mesin Negara (penekanan milik saya):

Diagram status dapat diimplementasikan dalam tiga cara utama: sakelar bersarang , pola Status , dan tabel status .

Mari kita gunakan contoh sederhana dari status tampilan ponsel:

masukkan deskripsi gambar di sini

Sakelar bersarang

Fowler memberikan contoh kode C #, tetapi saya telah menyesuaikannya dengan contoh saya.

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

Pola negara bagian

Berikut implementasi contoh saya dengan pola GoF State:

masukkan deskripsi gambar di sini

Tabel Status

Mengambil inspirasi dari Fowler, inilah tabel untuk contoh saya:

Tindakan Penjaga Acara Negara Sasaran Negara Sumber
-------------------------------------------------- ------------------------------------
ScreenOff ScreenOff tekan tombol powerLow displayLowPowerMessage  
ScreenOff ScreenOn pressButton! PowerLow
ScreenOn ScreenOff tekan Tombol
ScreenOff ScreenCharging plugPower
ScreenOn ScreenCharging plugPower
ScreenCharging ScreenOff unplugPower

Perbandingan

Sakelar bersarang menyimpan semua logika di satu tempat, tetapi kodenya bisa sulit dibaca ketika ada banyak status dan transisi. Ini mungkin lebih aman dan lebih mudah untuk divalidasi daripada pendekatan lainnya (tanpa polimorfisme atau interpretasi).

Implementasi pola State berpotensi menyebarkan logika ke beberapa kelas terpisah, yang mungkin membuat pemahaman itu secara keseluruhan menjadi masalah. Di sisi lain, kelas kecil mudah dipahami secara terpisah. Desainnya sangat rapuh jika Anda mengubah perilakunya dengan menambahkan atau menghapus transisi, karena keduanya merupakan metode dalam hierarki dan mungkin ada banyak perubahan pada kode. Jika Anda hidup dengan prinsip desain antarmuka kecil, Anda akan melihat pola ini tidak bekerja dengan baik. Namun, jika mesin status stabil, perubahan seperti itu tidak diperlukan.

Pendekatan tabel status memerlukan penulisan semacam penerjemah untuk konten (ini mungkin lebih mudah jika Anda memiliki refleksi dalam bahasa yang Anda gunakan), yang bisa menjadi banyak pekerjaan yang harus dilakukan di depan. Seperti yang ditunjukkan Fowler, jika tabel Anda terpisah dari kode Anda, Anda dapat mengubah perilaku perangkat lunak Anda tanpa kompilasi ulang. Namun, ini memiliki beberapa implikasi keamanan; perangkat lunak berperilaku berdasarkan konten file eksternal.

Edit (tidak benar-benar untuk bahasa C)

Ada juga pendekatan antarmuka yang lancar (alias Bahasa Spesifik Domain internal), yang mungkin difasilitasi oleh bahasa yang memiliki fungsi kelas satu . The Stateless perpustakaan ada dan blog menunjukkan contoh sederhana dengan kode. Sebuah implementasi Java (pra Java8) dibahas. Saya ditunjukkan contoh Python di GitHub juga.

Fuhrmanator
sumber
Perangkat lunak apa yang Anda gunakan untuk membuat gambar?
sjas
1
Saya menduga ini mungkin telah dibuat melalui PlantUML plantuml.com/state-diagram
Seidleroni
9

ada juga grid logika yang lebih dapat dipelihara saat mesin negara semakin besar

geocoin
sumber
4

Untuk kasus sederhana, Anda dapat menggunakan metode gaya sakelar. Apa yang saya temukan yang berfungsi dengan baik di masa lalu adalah menangani transisi juga:

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
}

Saya tidak tahu apa-apa tentang pustaka dorongan, tetapi jenis pendekatan ini sangat sederhana, tidak memerlukan ketergantungan eksternal, dan mudah diterapkan.

Menandai
sumber
4

switch () adalah cara yang andal dan standar untuk mengimplementasikan mesin status di C, tetapi cara ini dapat menurunkan daya rawat jika Anda memiliki banyak status. Metode umum lainnya adalah menggunakan pointer fungsi untuk menyimpan status berikutnya. Contoh sederhana ini mengimplementasikan set / reset flip-flop:

/* 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;
}
Komodor Jaeger
sumber
4

Saya menemukan implementasi C yang sangat apik dari Moore FSM pada kursus edx.org Sistem Tertanam - Bentuk Dunia UTAustinX - UT.6.02x, bab 10, oleh Jonathan Valvano dan Ramesh Yerraballi ....

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];  
  }
}
pengguna153222
sumber
2

Anda mungkin ingin melihat-lihat perangkat lunak generator FSM libero . Dari bahasa deskripsi negara dan / atau editor diagram status (windows) Anda dapat menghasilkan kode untuk C, C ++, java dan banyak lainnya ... ditambah dokumentasi dan diagram yang bagus. Sumber dan biner dari iMatix

pklausner.dll
sumber
2

Artikel ini ini bagus untuk pola negara (meskipun C ++, tidak secara khusus C).

Jika Anda dapat meletakkan tangan Anda pada buku " Pola Desain Kepala Pertama ", penjelasan dan contohnya sangat jelas.

pmlarocque.dll
sumber
2

Salah satu pola favorit saya adalah pola desain negara bagian. Menanggapi atau berperilaku berbeda untuk sekumpulan input yang sama.
Salah satu masalah dengan menggunakan pernyataan sakelar / kasus untuk mesin status adalah saat Anda membuat lebih banyak status, sakelar / kasing menjadi lebih sulit / berat untuk dibaca / dipelihara, mempromosikan kode spaghetti yang tidak terorganisir, dan semakin sulit untuk diubah tanpa merusak sesuatu. Saya menemukan penggunaan pola desain membantu saya mengatur data saya dengan lebih baik, yang merupakan inti dari abstraksi. Alih-alih mendesain kode negara Anda di sekitar negara bagian mana Anda berasal, alih-alih susun kode Anda sehingga ia mencatat keadaan saat Anda memasuki negara bagian baru. Dengan begitu, Anda secara efektif mendapatkan catatan tentang keadaan Anda sebelumnya. Saya menyukai jawaban @ JoshPetit, dan telah mengambil solusinya selangkah lebih maju, diambil langsung dari buku 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;
}

Untuk sebagian besar Mesin Negara, khususnya. Mesin keadaan terbatas, setiap keadaan akan tahu apa keadaan selanjutnya, dan kriteria untuk transisi ke keadaan berikutnya. Untuk desain status longgar, ini mungkin tidak terjadi, oleh karena itu opsi untuk mengekspos API untuk status transisi. Jika Anda menginginkan lebih banyak abstraksi, setiap penangan status dapat dipisahkan menjadi filenya sendiri, yang setara dengan penangan status konkret di buku GoF. Jika desain Anda sederhana dengan hanya beberapa status, maka stateCtxt.c dan statehandlers.c dapat digabungkan menjadi satu file untuk kesederhanaan.

Phileo99
sumber
State3 dan State2 memiliki nilai kembali meskipun dinyatakan kosong.
Semut
1

Dalam pengalaman saya, menggunakan pernyataan 'switch' adalah cara standar untuk menangani beberapa kemungkinan status. Meskipun saya terkejut bahwa Anda meneruskan nilai transisi ke pemrosesan per status. Saya pikir inti dari mesin negara adalah bahwa setiap negara bagian melakukan satu tindakan. Kemudian tindakan / masukan berikutnya menentukan status baru yang akan ditransisikan. Jadi saya akan mengharapkan setiap fungsi pemrosesan status untuk segera melakukan apa pun yang diperbaiki untuk memasuki status dan kemudian memutuskan apakah transisi diperlukan ke status lain.

Phil Wright
sumber
2
Ada beberapa model dasar yang berbeda: Mesin Mealy dan mesin Moore. Tindakan Mealy bergantung pada transisi, tindakan Moore bergantung pada negara bagian.
xmjx
1

Ada sebuah buku berjudul Practical Statecharts in C / C ++ . Namun, cara yang terlalu berat untuk apa yang kita butuhkan.

Benoit
sumber
2
Saya memiliki reaksi yang sama persis dengan buku itu. Bagaimana 700+ halaman diperlukan untuk menjelaskan & menerapkan sesuatu yang menurut saya cukup intuitif & mudah?!?!?
Dan
1

Untuk compiler yang mendukung __COUNTER__, Anda bisa menggunakannya untuk state mashine yang sederhana (tapi besar).

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

Keuntungan menggunakan __COUNTER__alih-alih nomor kode keras adalah Anda dapat menambahkan status di tengah negara bagian lain, tanpa menomori ulang semuanya. Jika kompilator tidak mendukung __COUNTER__, dengan cara yang terbatas mungkin untuk digunakan dengan hati-hati__LINE__

Seb
sumber
Bisakah Anda menjelaskan lebih lanjut jawaban Anda?
abarisone
Dalam mashine status "sakelar" normal, Anda memiliki misalnya kasus 0, kasus 1, kasus 2, ... kasus 100. Jika sekarang Anda ingin menambahkan 3 kasus antara 5 dan 6, Anda harus menomori ulang sisanya menjadi 100, yang mana sekarang akan menjadi 103. Penggunaan dari __COUNTER__menghilangkan kebutuhan untuk menomori ulang, karena precompiler melakukan penomoran selama kompilasi.
Seb
1

Anda dapat menggunakan kerangka kerja mesin status UML minimalis di c. https://github.com/kiishor/UML-State-Machine-in-C

Ini mendukung mesin negara hingga dan hierarkis. Ini hanya memiliki 3 API, 2 struktur dan 1 pencacahan.

Mesin Negara diwakili oleh state_machine_tstruktur. Ini adalah struktur abstrak yang dapat diwariskan untuk membuat mesin negara.

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

Status diwakili oleh penunjuk ke state_tstruktur dalam kerangka kerja.

Jika kerangka dikonfigurasi untuk mesin negara hingga maka state_tberisi,

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;

Kerangka kerja menyediakan API dispatch_eventuntuk mengirimkan kejadian ke mesin negara dan dua API untuk traversal negara.

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*);

Untuk detail lebih lanjut tentang cara mengimplementasikan mesin status hierarki, lihat repositori GitHub.

contoh kode
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

Nandkishor Biradar
sumber
dapatkah Anda juga menambahkan contoh kode yang sesuai dengan pertanyaan?
Giulio Caccin
1
Folder demo di repositori memiliki satu contoh. github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/… . Saat ini saya sedang mengerjakan satu contoh sistem tertanam yang melibatkan key, led dan timer, masih belum lengkap. Akan memberi tahu Anda setelah siap.
Nandkishor Biradar
0

Pertanyaan Anda mirip dengan "apakah ada pola implementasi Data Base yang khas"? Jawabannya tergantung pada apa yang ingin Anda capai? Jika Anda ingin menerapkan mesin keadaan deterministik yang lebih besar, Anda dapat menggunakan model dan generator mesin keadaan. Contohnya dapat dilihat di www.StateSoft.org - SM Gallery. Janusz Dobrowolski

Janusz Dobrowolski
sumber