复杂的状态转换 : best practices

标签 c state state-machine state-machine-workflow

我使用嵌入式东西,并且我有一些管理硬件的软件模块。该模块具有状态,并且状态转换很复杂:根据事件,模块可能从状态 A 转到状态 B 或可能转到 C 。但是,当它退出某种状态时,它应该对硬件执行一些操作,以使其也保持在正确的状态。

对于相当简单的模块,我只有几个这样的函数:

enum state_e {
    MY_STATE__A,
    MY_STATE__B,
};

static enum state_e _cur_state;

void state_on_off(enum state_e state, bool on)
{
    switch (state){
        case MY_STATE__A:
            if (on){
                //-- entering the state A
                prepare_hardware_1(for_state_a);
                prepare_hardware_2(for_state_a);
            } else {
                //-- exiting the state A
                finalize_hardware_2(for_state_a);
                finalize_hardware_1(for_state_a);
            }
            break;
        case MY_STATE__B:
            if (on){
                //-- entering the state B
                prepare_hardware_1(for_state_b);
                prepare_hardware_2(for_state_b);
            } else {
                //-- exiting the state B
                finalize_hardware_2(for_state_b);
                finalize_hardware_1(for_state_b);
            }
            break;
    }
}

void state_set(enum state_e new_state)
{
    state_on_off(_cur_state, false);
    _cur_state = new_state;
    state_on_off(_cur_state, true);
}

显然,我们需要在_state_on_off()函数中保留所有状态的所有必要操作,当我们需要移动到另一个状态时,我们只需调用_state_set(new_state) 状态转换顺利进行,与方向无关:执行所有需要的操作。

但它仅适用于简单的情况。如果状态 MY_STATE__BMY_STATE__C 之间有一些共同点,那么当状态从 MY_STATE__B 更改为 MY_STATE__C 时会怎样? > 然后我们应该只执行缩短的破坏/构造?但是当我们进入其他状态(例如,MY_STATE__A)时,我们应该执行完全销毁。

我想到的是子状态。因此,我们有一个状态 MY_STATE__BC,以及诸如 MY_BC_SUBSTATE__BMY_BC_SUBSTATE__C 之类的子状态;当然,我们有自己的函数,例如 _state_bc_on_off()。即使这已经很痛苦了,但想象一下更复杂的情况:它会变得可怕。

那么,解决此类问题的最佳实践是什么?

最佳答案

稍微更通用的状态机具有

  • 原语——在特定硬件上执行特定操作的子例程
  • 序列——以特定顺序调用的一个或多个原语
  • 转换——以特定顺序执行的一个或多个序列

转换被编码在结构数组中。序列由switch语句选择,每个序列调用一个或多个原语。

#define stA    0x00000001  // bit mask for state A
#define stB    0x00000002  // bit mask for state B
#define stC    0x00000004  // bit mask for state C
#define stAny  0xffffffff  // matches any state

enum { seqXtoY, seqError, seqEnterA, seqExitA, seqEnterB, seqExitB, seqEnableC, seqDisableC, seqEnd };

typedef struct
{
    int oldState;     // bit mask that represents one or more states that we're transitioning from
    int newState;     // bit mask that represents one or more states that we're transitioning to
    int seqList[10];  // an array of sequences that need to be executed
}
stTransition;

static stTransition transition[] =
{
    // transitions from state A to B or C
    { stA, stB, { seqExitA, seqXtoY, seqEnterB, seqEnd } },
    { stA, stC, { seqExitA, seqXtoY, seqEnableC, seqEnterB, seqEnd } },

    // transitions from state B to A or C
    { stB, stA, { seqExitB, seqXtoY, seqEnterA, seqEnd } },
    { stB, stC, { seqXtoY, seqEnableC, seqEnd } },

    // transitions from states C to A or B
    { stC, stA, { seqDisableC, seqExitB, seqXtoY, seqEnterA, seqEnd } },
    { stC, stB, { seqDisableC, seqXtoY, seqEnd } },

    // any other transition (should never get here)
    { stAny, stAny, { seqError, seqEnd } }
};

static int currentState = stA;

void executeSequence( int sequence )
{
    switch ( sequence )
    {
        case seqEnterA:
            prepare_hardware_1(for_state_a);
            prepare_hardware_2(for_state_a);
            break;

        case seqExitA:
            finalize_hardware_2(for_state_a);
            finalize_hardware_1(for_state_a);
            break;

        case seqEnterB:
            prepare_hardware_1(for_state_b);
            prepare_hardware_2(for_state_b);
            break;

        case seqExitB:
            finalize_hardware_2(for_state_b);
            finalize_hardware_1(for_state_b);
            break;

        case seqEnableC:
            enable_hardware_3();
            break;

        case seqDisableC:
            disable_hardware_3();
            break;
    }
}

void executeTransition( int newState )
{
    if ( newState == currentState )
        return;

    // search the transition table to find the entry that matches the old and new state
    stTransition *tptr;
    for ( tptr = transition; tptr->seqList[0] != seqError; tptr++ )
        if ( (tptr->oldState & currentState) && (tptr->newState & newState) )
            break;

    // execute the sequence list
    int *seqptr;
    for ( seqptr = tptr->seqList; *seqptr != seqEnd; seqptr++ )
    {
        if ( *seqptr == seqXtoY )
            currentState = newState;
        else if ( *seqptr == seqError )
            printf( "The state table is missing the transition from %d to %d\n", currentState, newState );
        else
            executeSequence( *seqptr );
    }

    // if the seqList doesn't have an explicit update, then we update at the end
    currentState = newState;
}

关于复杂的状态转换 : best practices,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28356569/

相关文章:

c - 下面的代码片段在c中返回什么

python - Python 中的简单状态机

state-machine - 工作流引擎的用例

c - 如何在调用 atoi 之前将指针设置为 NULL

c - 多个相同文字的内存使用与 const

c - 如何在 C 中的 switch 中使用格式说明符

javascript - 状态更新后不重新渲染表组件

javascript - 在组件之间传递状态

function - 在不使用类的情况下在 OCaml 中隐藏函数参数

python - 高尔夫代码: Finite-state machine!