Pergunta

Eu tenho um StateMachine em um sistema em tempo real, com muito poucos (3) estados.

typedef enum {
    STATE1,
    STATE2,
    STATE3
} state_t;

No entanto, as transições entre esses estados precisam de tempo considerável e têm suas próprias subdivisões. Então, eu tenho duas opções, ou eu estender o StateMachine principal de tal forma que todos os estados intermediários são representados:

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;

ou eu criar um StateMachine nested para os estados principais relevantes:

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

As duas possibilidades têm suas vantagens e desvantagens. A grande StateMachine fica confuso e complicado muito facilmente. No entanto, ter todos os estados consistentes no segundo caso, não é trivial, quer e muitas funções que precisam de informações sobre tanto o estado global e os subestados.

Eu gostaria de evitar código complicado que tem que lidar com vários estados paralelos, como:

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

O que é a melhor abordagem geral para esse problema? A: muitas statemachines pequenas e aninhados (com muitas combinações inválidas), um grande StateMachine ou qualquer outra coisa

Foi útil?

Solução

Primeiro, eu quero elogiá-lo para reconhecer o que está acontecendo e fazer esses estados explícita (uma vez que eles são de fato estados adicionais no seu modelo, não realmente transições com uma ação). Demasiadas vezes vejo máquinas de estado que acabam como o seu último exemplo (que você quer evitar). Quando você tem testes de variáveis ??'adicionais' estado dentro de seus manipuladores de eventos, é um sinal de que a sua máquina de estado tem mais estados que você realmente colocar no projeto - aqueles show de ser reflectido no design, não encravado em evento do estado existente manipuladores com um grupo do espaguete codificados cheques para 'estado' adicional codificados em variáveis ??globais.

Existem vários quadros para C ++ que modelo hierárquicos máquinas de estado - HSMs - (que é o que seus aninhados estaduais ideia máquina de sons similares), mas o único que eu estou ciente de que os apoios reta C é Quadro Quantum , e eu acho que a compra em que provavelmente significaria um nível decente de compromisso (ou seja, é provavelmente não é uma simples mudança). No entanto, se você quiser olhar para esta possibilidade, Samek tem escrito uma série de artigos ( e livro) sobre como apoiar HSMs em C.

No entanto, se você não precisa de algumas das peças mais sofisticadas dos modelos de HSM (tais como eventos que não são tratados pelo Estado 'mais íntimo' se borbulhava, possivelmente manipulados por estados pai, cheio entrar e suporte de saída para toda a hierarquia do estado), então é muito fácil para suportar máquinas de estados aninhados máquinas de estado tão completamente independentes que acontecem para iniciar e parar quando um estado pai é inserido / que acabou.

O modelo de grande máquina de estado é provavelmente um pouco mais fácil de implementar (é apenas mais alguns estados em seu quadro existente). Eu sugiro que, se adicionando os estados para o seu modo de máquina de estado atual não tornar o modelo muito complexo, em seguida, basta ir com isso.

Em outras palavras, deixar que o que funciona melhor para sua modelo unidade como você implementar a máquina de estado no software.

Outras dicas

Muitas máquinas de estados pequenos vão dar-lhe mais flexibilidade código abaixo da estrada, especialmente se você precisar de redesenhar nada. Então você deve (espero) ser capaz de mudar uma máquina de estados aninhada sem ter que alterar qualquer uma das outras máquinas de estados aninhados.

Tendo uma tabela de transição maior não deve resultar em pesquisas mais longas, desde que eu supor que você colocar para fora da mesa de forma sensata na memória. Se qualquer coisa, você deve realmente ser capaz de obter um pouco mais de velocidade fora da grande máquina, simplesmente porque você não tem o extra ou dois passos que você pode precisar com as pequenas máquinas de estado para a transição limpa entre eles. Mas, dada a complexidade deste método, eu sugiro o seguinte:. Projeto com máquinas de estados aninhados, em seguida, uma vez que tudo funciona, refatorar em uma única máquina de estado se necessário, para ganhar um pouco de impulso de velocidade

Como você mencionou, grande máquina de estado fica confuso por isso é muito difícil de manter. Vários SMs menores são sempre mais fáceis de entender e manter.

Outra desvantagem do grande SM -. Tabela de transição maior, toma assim lookup mais

Eu não acho que há uma única abordagem, em geral. Como já foi dito, isso depende do que você está tentando fazer.

De um modo geral, eu gostaria de evitar pequenas máquinas de estado de nidificação dentro maiores, como não só você está adicionando mais estados - e, portanto, a complexidade - quando você está tentando simplificar as coisas, agora você tem duas variáveis ??de estado para acompanhar .

Em particular, a variável de estado "interior" tem de ser devidamente inicializado quando atravessando estados na máquina de estado "externo". Por exemplo, o que acontece se, devido a um bug, há uma transição na máquina de estado exterior que não consegue repor a variável de estado para as máquinas de estado interior?

A única possível exceção a isso é onde todas as máquinas de estados internos fazer a mesma coisa. Se é possível parametrizar os dados (por exemplo, usando uma matriz), então você pode ter uma única implementação da máquina de estado interno, e isso pode ser possível substituir a máquina de estados exterior com um contador ou similar.

Para dar um exemplo simplista:

#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() */

Por que você não use o estado padrão ?

Eu voto para a máquina de estado maior, assumindo que uma única máquina só pode estar em um dos grandes estados da máquina estatal, deve logicamente estar lá.

Ao usar uma máquina grande que você está usando uma característica do ambiente para evitar um estado no qual existem dois estados ao mesmo tempo, tornando o programa mais seguro e mais legível.

Também uma máquina de estado grande tem a vantagem de qualquer outro programador pode facilmente compreender todos os estados, olhando em um único lugar (isto é, obter o retrato grande), vs procurando em um único lugar, espero estar ciente do sub -Divisão, em seguida, ter de olhar para cada sub-divisão.

Além disso, como você sugeriu trabalhar com várias máquinas de estado irá forçá-lo a enviar mais parâmetros, realizando mais de um teste para cada estado, etc' ...

Quanto a expectativas futuras Eu sou um crente no YAGNI .

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top