C État-conception de la machine
je réalise un petit projet en mixed c/" class="blnk">C et C++. Je construis une petite machine d'état au cœur d'un de mes fils ouvriers.
je me demandais si vous, les gourous, partageriez vos techniques de conception de machines d'état.
NOTE: je suis principalement après des techniques de mise en œuvre éprouvées.
mise à JOUR: Basé sur tous les grands commentaires recueillis sur DONC, j'ai fixé sur cette architecture:
24 réponses
les machines D'État que j'ai conçues avant (C, Pas C++) sont toutes descendues à un tableau struct
et une boucle. La structure se compose essentiellement d'un état et d'un événement (pour la recherche) et d'une fonction qui renvoie le nouvel état, quelque chose comme:
typedef struct {
int st;
int ev;
int (*fn)(void);
} tTransition;
ensuite vous définissez vos états et événements avec des définitions simples (les ANY
sont des marqueurs spéciaux, voir ci-dessous):
#define ST_ANY -1
#define ST_INIT 0
#define ST_ERROR 1
#define ST_TERM 2
: :
#define EV_ANY -1
#define EV_KEYPRESS 5000
#define EV_MOUSEMOVE 5001
Puis vous définissez toutes les fonctions qui sont appelé par les transitions:
static int GotKey (void) { ... };
static int FsmError (void) { ... };
toutes ces fonctions sont écrites pour ne pas prendre de variables et retourner le nouvel État pour la machine d'état. Dans cet exemple, les variables globales sont utilisées pour transmettre n'importe quelle information dans les fonctions d'état si nécessaire.
utiliser des globals n'est pas aussi mauvais qu'il n'y paraît puisque le FSM est habituellement enfermé à l'intérieur d'une seule unité de compilation et toutes les variables sont statiques à cette unité (c'est pourquoi j'ai utilisé des guillemets autour "global" ci - dessus-ils sont plus partagés au sein de la FSM, que vraiment global). Comme pour tous les globals, il nécessite des soins.
le tableau de transitions définit alors toutes les transitions possibles et les fonctions qui sont appelées pour ces transitions (y compris la dernière):
tTransition trans[] = {
{ ST_INIT, EV_KEYPRESS, &GotKey},
: :
{ ST_ANY, EV_ANY, &FsmError}
};
#define TRANS_COUNT (sizeof(trans)/sizeof(*trans))
cela signifie que si vous êtes dans l'état ST_INIT
et que vous recevez l'événement EV_KEYPRESS
, faites un appel à GotKey
.
le le fonctionnement du FSM devient alors une boucle relativement simple:
state = ST_INIT;
while (state != ST_TERM) {
event = GetNextEvent();
for (i = 0; i < TRANS_COUNT; i++) {
if ((state == trans[i].st) || (ST_ANY == trans[i].st)) {
if ((event == trans[i].ev) || (EV_ANY == trans[i].ev)) {
state = (trans[i].fn)();
break;
}
}
}
}
comme indiqué ci-dessus, notez l'utilisation de ST_ANY
comme jokers, permettant à un événement d'appeler une fonction quel que soit l'état actuel. EV_ANY
fonctionne également de manière similaire, permettant à tout événement à un état spécifique d'appeler une fonction.
il peut également garantir que, si vous atteignez la fin du tableau de transitions, vous obtenez une erreur indiquant que votre FSM n'a pas été construit correctement (en utilisant la combinaison ST_ANY/EV_ANY
.
j'ai utilisé un code similaire pour cela sur un grand nombre de projets de communication, tels que la mise en œuvre précoce de piles de communications et de protocoles pour les systèmes intégrés. Le grand avantage était sa simplicité et sa facilité relative à changer le tableau des transitions.
Je n'ai aucun doute qu'il y aura des abstractions de plus haut niveau qui pourraient être plus appropriées de nos jours, mais je pense qu'elles se réduiront toutes à cette même une sorte de structure.
et, comme ldog
l'indique dans un commentaire, vous pouvez éviter les globals tout à fait en passant un pointeur de structure à toutes les fonctions (et en utilisant cela dans la boucle d'événement). Cela permettra à plusieurs machines d'état à exécuter côte à côte, sans interférence.
il suffit de créer un type de structure qui contient les données spécifiques à la machine (État à un strict minimum) et l'utiliser à la place des globals.
la raison pour laquelle j'ai rarement fait cela est tout simplement parce que la plupart des machines d'état que j'ai écrites étaient des types uniques (lecture de fichier de configuration, par exemple), n'ayant pas besoin d'exécuter plus d'une instance. Mais il a de la valeur si vous avez besoin d'exécuter plus d'un.
les autres réponses sont bonnes, mais une implémentation Très "légère" que j'ai utilisée quand la machine d'état est très simple ressemble à:
enum state { ST_NEW, ST_OPEN, ST_SHIFT, ST_END };
enum state current_state = ST_NEW;
while (current_state != ST_END)
{
input = get_input();
switch (current_state)
{
case ST_NEW:
/* Do something with input and set current_state */
break;
case ST_OPEN:
/* Do something different and set current_state */
break;
/* ... etc ... */
}
}
Je l'utiliserais quand la machine d'état est assez simple que l'approche de table de transition de pointeur de fonction et d'état est exagérée. Cela est souvent utile pour l'analyse de chaque caractère ou mot par mot.
Pardonnez-moi d'avoir enfreint toutes les règles de l'informatique, mais une machine d'état est l'un des rares (Je ne peux en compter que deux) endroits où une déclaration goto
est non seulement plus efficace, mais aussi rend votre code plus propre et plus facile à lire. Parce que les déclarations goto
sont basées sur des lables, vous pouvez nommer vos états au lieu d'avoir à garder la trace d'un désordre de nombres ou d'utiliser un enum. Il rend également le code beaucoup plus propre puisque vous n'avez pas besoin de tous les croûtes supplémentaires de la fonction pointeurs ou énormes déclarations de commutateur et pendant les boucles. Ai-je mentionné que c'est plus efficace?
voici à quoi pourrait ressembler une machine d'état:
void state_machine() {
first_state:
// Do some stuff here
switch(some_var) {
case 0:
goto first_state;
case 1:
goto second_state;
default:
return;
}
second_state:
// Do some stuff here
switch(some_var) {
case 0:
goto first_state;
case 1:
goto second_state;
default:
return;
}
}
Vous avez l'idée générale. Le point est que vous pouvez mettre en œuvre la machine d'état d'une manière efficace et un qui est relativement facile à lire et crie au lecteur qu'ils regardent une machine d'état. Notez que si vous utilisez goto
, vous devez toujours être prudent, car il est très facile de se tirer dans le pied tout en faisant ainsi.
vous pourriez considérer le compilateur de Machine D'État http://smc.sourceforge.net/
Ce splendide utilitaire open source accepte une description d'une machine d'état dans un langage simple et compile à quelqu'un d'une douzaine de langues - dont le C et le C++. L'utilitaire lui-même est écrit en Java, et peut être inclus dans une construction.
la raison de faire ceci, plutôt que le codage à la main en utilisant le modèle D'État GoF ou n'importe quel une autre approche, c'est qu'une fois que votre machine d'état est exprimée sous forme de code, la structure sous-jacente a tendance à disparaître sous le poids de boilerplate qui doit être généré pour le supporter. L'utilisation de cette approche vous donne une excellente séparation des préoccupations, et vous gardez la structure de votre machine d'état "visibles". Le code généré automatiquement va dans des modules que vous n'avez pas besoin de toucher, de sorte que vous pouvez retourner et jouer avec la structure de la machine d'état sans impact sur le code de soutien que vous avez écrit.
Désolé, je suis trop enthousiaste, et sans doute repousser tout le monde. Mais il est un excellent utilitaire, et bien documenté.
assurez-vous de vérifier le travail de Miro Samek (blog State Space , site State Machines & Tools ), dont les articles au C/C++ Users Journal étaient grands.
le site web contient une implémentation complète (C/C++) à la fois en open source et sous licence commerciale d'un state machine framework (QP Framework) , un event handler (QEP) , un modélisation de base de l'outil (QM) et un outil de traçage (QSpy) , qui permettent de faire l'état des machines, créer un code et déboguer.
le livre contient une explication détaillée sur le quoi/pourquoi de la mise en œuvre et comment l'utiliser et est également un grand matériel pour acquérir la compréhension des principes de base de machines hiérachiques et d'état fini.
le site web contient également des liens vers plusieurs packages de support de cartes pour l'utilisation du logiciel avec les plateformes embarquées.
j'ai fait quelque chose de similaire à ce que paxdiablo décrit, seulement au lieu d'un tableau de transitions état/événement, j'ai mis en place un tableau bidimensionnel de pointeurs de fonction, avec la valeur d'événement comme index d'un axe et la valeur d'état courant comme l'autre. Puis j'appelle juste state = state_table[event][state](params)
et la bonne chose arrive. Les cellules représentant des combinaisons d'état/événement invalides reçoivent un pointeur vers une fonction qui le dit, Bien sûr.
Évidemment, cela ne fonctionne que si l'état et les valeurs d'événement sont toutes les deux des plages contiguës et commencent à 0 ou assez près.
un très joli" framework "de machine D'état C++ à base de gabarits est donné par Stefan Heinzmann dans son article .
comme il n'y a pas de lien vers un téléchargement de code complet dans l'article, j'ai pris la liberté de coller le code dans un projet et de le vérifier. Le matériel ci-dessous est testé et comprend les quelques pièces manquantes mineures mais assez évidentes.
la principale innovation ici est que le compilateur génère très code efficace. Les actions vides d'entrée/sortie n'ont aucun coût. Les actions d'entrée/sortie Non vides sont insérées. Le compilateur vérifie également l'exhaustivité de statechart. Les actions manquantes génèrent des erreurs de liaison. La seule chose qui n'est pas attrapée est le Top::init
manquant .
c'est une très bonne alternative à L'implémentation de Miro Samek, si vous pouvez vivre sans ce qui manque -- c'est loin d'une implémentation complète de Statechart D'UML, bien qu'il implémente correctement la La sémantique UML, alors que le code de Samek par conception ne gère pas les actions de sortie/transition/entrée dans le bon ordre.
si ce code fonctionne pour ce que vous devez faire, et que vous avez un compilateur C++ décent pour votre système, il fonctionnera probablement mieux que L'implémentation C/C++ de Miro. Le compilateur génère une implémentation de machine d'état de transition aplatie, O(1) pour vous. Si l'audit de la production de l'assemblage confirme que les optimisations fonctionnent comme souhaité, vous vous rapprochez de la théorie performance. La meilleure partie: c'est relativement petit, facile à comprendre le code.
#ifndef HSM_HPP
#define HSM_HPP
// This code is from:
// Yet Another Hierarchical State Machine
// by Stefan Heinzmann
// Overload issue 64 december 2004
// http://accu.org/index.php/journals/252
/* This is a basic implementation of UML Statecharts.
* The key observation is that the machine can only
* be in a leaf state at any given time. The composite
* states are only traversed, never final.
* Only the leaf states are ever instantiated. The composite
* states are only mechanisms used to generate code. They are
* never instantiated.
*/
// Helpers
// A gadget from Herb Sutter's GotW #71 -- depends on SFINAE
template<class D, class B>
class IsDerivedFrom {
class Yes { char a[1]; };
class No { char a[10]; };
static Yes Test(B*); // undefined
static No Test(...); // undefined
public:
enum { Res = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) ? 1 : 0 };
};
template<bool> class Bool {};
// Top State, Composite State and Leaf State
template <typename H>
struct TopState {
typedef H Host;
typedef void Base;
virtual void handler(Host&) const = 0;
virtual unsigned getId() const = 0;
};
template <typename H, unsigned id, typename B>
struct CompState;
template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct CompState : B {
typedef B Base;
typedef CompState<H, id, Base> This;
template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
static void init(H&); // no implementation
static void entry(H&) {}
static void exit(H&) {}
};
template <typename H>
struct CompState<H, 0, TopState<H> > : TopState<H> {
typedef TopState<H> Base;
typedef CompState<H, 0, Base> This;
template <typename X> void handle(H&, const X&) const {}
static void init(H&); // no implementation
static void entry(H&) {}
static void exit(H&) {}
};
template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct LeafState : B {
typedef H Host;
typedef B Base;
typedef LeafState<H, id, Base> This;
template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
virtual void handler(H& h) const { handle(h, *this); }
virtual unsigned getId() const { return id; }
static void init(H& h) { h.next(obj); } // don't specialize this
static void entry(H&) {}
static void exit(H&) {}
static const LeafState obj; // only the leaf states have instances
};
template <typename H, unsigned id, typename B>
const LeafState<H, id, B> LeafState<H, id, B>::obj;
// Transition Object
template <typename C, typename S, typename T>
// Current, Source, Target
struct Tran {
typedef typename C::Host Host;
typedef typename C::Base CurrentBase;
typedef typename S::Base SourceBase;
typedef typename T::Base TargetBase;
enum { // work out when to terminate template recursion
eTB_CB = IsDerivedFrom<TargetBase, CurrentBase>::Res,
eS_CB = IsDerivedFrom<S, CurrentBase>::Res,
eS_C = IsDerivedFrom<S, C>::Res,
eC_S = IsDerivedFrom<C, S>::Res,
exitStop = eTB_CB && eS_C,
entryStop = eS_C || eS_CB && !eC_S
};
// We use overloading to stop recursion.
// The more natural template specialization
// method would require to specialize the inner
// template without specializing the outer one,
// which is forbidden.
static void exitActions(Host&, Bool<true>) {}
static void exitActions(Host&h, Bool<false>) {
C::exit(h);
Tran<CurrentBase, S, T>::exitActions(h, Bool<exitStop>());
}
static void entryActions(Host&, Bool<true>) {}
static void entryActions(Host& h, Bool<false>) {
Tran<CurrentBase, S, T>::entryActions(h, Bool<entryStop>());
C::entry(h);
}
Tran(Host & h) : host_(h) {
exitActions(host_, Bool<false>());
}
~Tran() {
Tran<T, S, T>::entryActions(host_, Bool<false>());
T::init(host_);
}
Host& host_;
};
// Initializer for Compound States
template <typename T>
struct Init {
typedef typename T::Host Host;
Init(Host& h) : host_(h) {}
~Init() {
T::entry(host_);
T::init(host_);
}
Host& host_;
};
#endif // HSM_HPP
Le code D'essai suit.
#include <cstdio>
#include "hsm.hpp"
#include "hsmtest.hpp"
/* Implements the following state machine from Miro Samek's
* Practical Statecharts in C/C++
*
* |-init-----------------------------------------------------|
* | s0 |
* |----------------------------------------------------------|
* | |
* | |-init-----------| |-------------------------| |
* | | s1 |---c--->| s2 | |
* | |----------------|<--c----|-------------------------| |
* | | | | | |
* |<-d-| |-init-------| | | |-init----------------| | |
* | | | s11 |<----f----| | s21 | | |
* | /--| |------------| | | |---------------------| | |
* | a | | | | | | | | |
* | \->| | |------g--------->|-init------| | | |
* | | |____________| | | |-b->| s211 |---g--->|
* | |----b---^ |------f------->| | | | |
* | |________________| | |<-d-|___________|<--e----|
* | | |_____________________| | |
* | |_________________________| |
* |__________________________________________________________|
*/
class TestHSM;
typedef CompState<TestHSM,0> Top;
typedef CompState<TestHSM,1,Top> S0;
typedef CompState<TestHSM,2,S0> S1;
typedef LeafState<TestHSM,3,S1> S11;
typedef CompState<TestHSM,4,S0> S2;
typedef CompState<TestHSM,5,S2> S21;
typedef LeafState<TestHSM,6,S21> S211;
enum Signal { A_SIG, B_SIG, C_SIG, D_SIG, E_SIG, F_SIG, G_SIG, H_SIG };
class TestHSM {
public:
TestHSM() { Top::init(*this); }
~TestHSM() {}
void next(const TopState<TestHSM>& state) {
state_ = &state;
}
Signal getSig() const { return sig_; }
void dispatch(Signal sig) {
sig_ = sig;
state_->handler(*this);
}
void foo(int i) {
foo_ = i;
}
int foo() const {
return foo_;
}
private:
const TopState<TestHSM>* state_;
Signal sig_;
int foo_;
};
bool testDispatch(char c) {
static TestHSM test;
if (c<'a' || 'h'<c) {
return false;
}
printf("Signal<-%c", c);
test.dispatch((Signal)(c-'a'));
printf("\n");
return true;
}
int main(int, char**) {
testDispatch('a');
testDispatch('e');
testDispatch('e');
testDispatch('a');
testDispatch('h');
testDispatch('h');
return 0;
}
#define HSMHANDLER(State) \
template<> template<typename X> inline void State::handle(TestHSM& h, const X& x) const
HSMHANDLER(S0) {
switch (h.getSig()) {
case E_SIG: { Tran<X, This, S211> t(h);
printf("s0-E;");
return; }
default:
break;
}
return Base::handle(h, x);
}
HSMHANDLER(S1) {
switch (h.getSig()) {
case A_SIG: { Tran<X, This, S1> t(h);
printf("s1-A;"); return; }
case B_SIG: { Tran<X, This, S11> t(h);
printf("s1-B;"); return; }
case C_SIG: { Tran<X, This, S2> t(h);
printf("s1-C;"); return; }
case D_SIG: { Tran<X, This, S0> t(h);
printf("s1-D;"); return; }
case F_SIG: { Tran<X, This, S211> t(h);
printf("s1-F;"); return; }
default: break;
}
return Base::handle(h, x);
}
HSMHANDLER(S11) {
switch (h.getSig()) {
case G_SIG: { Tran<X, This, S211> t(h);
printf("s11-G;"); return; }
case H_SIG: if (h.foo()) {
printf("s11-H");
h.foo(0); return;
} break;
default: break;
}
return Base::handle(h, x);
}
HSMHANDLER(S2) {
switch (h.getSig()) {
case C_SIG: { Tran<X, This, S1> t(h);
printf("s2-C"); return; }
case F_SIG: { Tran<X, This, S11> t(h);
printf("s2-F"); return; }
default: break;
}
return Base::handle(h, x);
}
HSMHANDLER(S21) {
switch (h.getSig()) {
case B_SIG: { Tran<X, This, S211> t(h);
printf("s21-B;"); return; }
case H_SIG: if (!h.foo()) {
Tran<X, This, S21> t(h);
printf("s21-H;"); h.foo(1);
return;
} break;
default: break;
}
return Base::handle(h, x);
}
HSMHANDLER(S211) {
switch (h.getSig()) {
case D_SIG: { Tran<X, This, S21> t(h);
printf("s211-D;"); return; }
case G_SIG: { Tran<X, This, S0> t(h);
printf("s211-G;"); return; }
}
return Base::handle(h, x);
}
#define HSMENTRY(State) \
template<> inline void State::entry(TestHSM&) { \
printf(#State "-ENTRY;"); \
}
HSMENTRY(S0)
HSMENTRY(S1)
HSMENTRY(S11)
HSMENTRY(S2)
HSMENTRY(S21)
HSMENTRY(S211)
#define HSMEXIT(State) \
template<> inline void State::exit(TestHSM&) { \
printf(#State "-EXIT;"); \
}
HSMEXIT(S0)
HSMEXIT(S1)
HSMEXIT(S11)
HSMEXIT(S2)
HSMEXIT(S21)
HSMEXIT(S211)
#define HSMINIT(State, InitState) \
template<> inline void State::init(TestHSM& h) { \
Init<InitState> i(h); \
printf(#State "-INIT;"); \
}
HSMINIT(Top, S0)
HSMINIT(S0, S1)
HSMINIT(S1, S11)
HSMINIT(S2, S21)
HSMINIT(S21, S211)
la technique que j'aime pour les machines d'état (au moins pour le contrôle de programme) est d'utiliser des pointeurs de fonction. Chaque état est représenté par une fonction différente. La fonction prend un symbole d'entrée et retourne le pointeur de fonction pour l'état suivant. Les moniteurs de boucle centrale de régulation prennent la prochaine entrée, l'alimentent à l'état actuel, et traitent le résultat.
le fait de taper dessus devient un peu bizarre, puisque C n'a pas de façon d'indiquer les types de fonctions pointeurs retournant eux-mêmes, ainsi les fonctions d'état renvoient void*
. Mais vous pouvez faire quelque chose comme ceci:
typedef void* (*state_handler)(input_symbol_t);
void dispatch_fsm()
{
state_handler current = initial_handler;
/* Let's assume returning null indicates end-of-machine */
while (current) {
current = current(get_input);
}
}
alors vos fonctions d'état individuelles peuvent activer leur entrée pour traiter et retourner la valeur appropriée.
cas le plus simple
enum event_type { ET_THIS, ET_THAT };
union event_parm { uint8_t this; uint16_t that; }
static void handle_event(enum event_type event, union event_parm parm)
{
static enum { THIS, THAT } state;
switch (state)
{
case THIS:
switch (event)
{
case ET_THIS:
// Handle event.
break;
default:
// Unhandled events in this state.
break;
}
break;
case THAT:
// Handle state.
break;
}
}
Points: L'état est privé, non seulement à l'unité de compilation, mais aussi à la event_handler. Les cas spéciaux peuvent être traités séparément de l'interrupteur principal en utilisant n'importe quelle construction jugée nécessaire.
affaire plus complexe
quand le commutateur est plus grand que quelques écrans pleins, divisez-le en fonctions qui gèrent chaque État, utiliser une table d'État pour chercher la fonction directement. L'état est toujours privé pour le gestionnaire d'événements. Les fonctions du gestionnaire d'état renvoient l'état suivant. Si nécessaire, certains événements peuvent encore recevoir un traitement spécial dans le handler Main event. J'aime lancer des pseudo-événements pour l'entrée et la sortie d'état et peut-être machine d'état de départ:
enum state_type { THIS, THAT, FOO, NA };
enum event_type { ET_START, ET_ENTER, ET_EXIT, ET_THIS, ET_THAT, ET_WHATEVER, ET_TIMEOUT };
union event_parm { uint8_t this; uint16_t that; };
static void handle_event(enum event_type event, union event_parm parm)
{
static enum state_type state;
static void (* const state_handler[])(enum event_type event, union event_parm parm) = { handle_this, handle_that };
enum state_type next_state = state_handler[state](event, parm);
if (NA != next_state && state != next_state)
{
(void)state_handler[state](ET_EXIT, 0);
state = next_state;
(void)state_handler[state](ET_ENTER, 0);
}
}
Je ne suis pas sûr si j'ai cloué la syntaxe, surtout en ce qui concerne le tableau des pointeurs de fonction. Je n'ai pas couru de tout cela par le biais de compilateur. Après examen, j'ai remarqué que j'avais oublié de rejeter explicitement l'état suivant lors de la gestion des pseudo événements (la parenthèse (vide) avant l'appel à state_handler()). C'est quelque chose que j'aime faire, même si les compilateurs d'accepter l'omission silencieusement. Il indique aux lecteurs du code que "oui, je voulais en effet appeler la fonction sans utiliser la valeur de retour", et il peut arrêter les outils d'analyse statique d'avertissement à ce sujet. Il peut être idiosyncrasique parce que je ne me souviens pas avoir vu quelqu'un d'autre pour le faire.
Points: ajouter un petit peu de complexité (vérifier si l'état suivant est différent de l'état actuel), peut éviter le code dupliqué ailleurs, parce que les fonctions du gestionnaire d'état peuvent profiter des pseudo événements qui se produisent lorsqu'un État est entré et laissé. Rappelez-vous que l'état ne peut pas changer lors de la manipulation des pseudo événements, parce que le résultat du gestionnaire d'état est écarté après ces événements. Vous pouvez bien sûr choisir de modifier le comportement.
Un état gestionnaire ressemble à:
static enum state_type handle_this(enum event_type event, union event_parm parm)
{
enum state_type next_state = NA;
switch (event)
{
case ET_ENTER:
// Start a timer to do whatever.
// Do other stuff necessary when entering this state.
break;
case ET_WHATEVER:
// Switch state.
next_state = THAT;
break;
case ET_TIMEOUT:
// Switch state.
next_state = FOO;
break;
case ET_EXIT:
// Stop the timer.
// Generally clean up this state.
break;
}
return next_state;
}
plus de complexité
lorsque l'Unité de compilation devient trop grande (quoi que vous pensiez de cela, je dirais environ 1000 lignes), mettez chaque gestionnaire d'état dans un fichier séparé. Lorsque chaque gestionnaire d'état devient plus long qu'un couple d'écrans, séparez chaque événement dans une fonction séparée, semblable à la façon dont le commutateur d'État a été séparé. Vous pouvez le faire cela de plusieurs façons, séparément de l'état ou en utilisant un tableau commun, ou en combinant divers régimes. Certains d'entre eux ont été couverts ici par d'autres. Classez vos tables et utilisez la recherche binaire si la vitesse est une exigence.
programmation générique
je voudrais que le préprocesseur s'occupe de problèmes tels que le tri de tables ou même la génération de machines d'État à partir de descriptions, vous permettant d' "écrire des programmes sur des programmes". Je crois que C'est pour cela que les gens de Boost exploitent les modèles C++, mais je trouve la syntaxe cryptique.
tables bidimensionnelles
j'ai utilisé des tables d'état/d'événement dans le passé mais je dois dire que pour les cas les plus simples Je ne les trouve pas nécessaires et je préfère la clarté et la lisibilité de l'instruction de commutateur même si elle s'étend au-delà d'un écran plein. Pour les cas plus complexes, les tables deviennent rapidement hors de contrôle les autres l'ont mentionné. Les idiomes que je présente ici vous permettent d'ajouter un grand nombre d'événements et d'États quand vous le souhaitez, sans avoir à maintenir une table de consommation de mémoire (même si elle peut être mémoire de programme).
Avertissement
les besoins spéciaux peuvent rendre ces idiomes moins utiles, mais je les ai trouvés très clairs et maintenables.
Extrêmement pas testé, mais le plaisir de code, maintenant dans une version plus raffinée que ma réponse originale à cette question; à jour les versions peuvent être trouvées à mercurial.intuxication.org :
sm.h
#ifndef SM_ARGS
#error "SM_ARGS undefined: " \
"use '#define SM_ARGS (void)' to get an empty argument list"
#endif
#ifndef SM_STATES
#error "SM_STATES undefined: " \
"you must provide a list of comma-separated states"
#endif
typedef void (*sm_state) SM_ARGS;
static const sm_state SM_STATES;
#define sm_transit(STATE) ((sm_state (*) SM_ARGS)STATE)
#define sm_def(NAME) \
static sm_state NAME ## _fn SM_ARGS; \
static const sm_state NAME = (sm_state)NAME ## _fn; \
static sm_state NAME ## _fn SM_ARGS
exemple.c
#include <stdio.h>
#define SM_ARGS (int i)
#define SM_STATES EVEN, ODD
#include "sm.h"
sm_def(EVEN)
{
printf("even %i\n", i);
return ODD;
}
sm_def(ODD)
{
printf("odd %i\n", i);
return EVEN;
}
int main(void)
{
int i = 0;
sm_state state = EVEN;
for(; i < 10; ++i)
state = sm_transit(state)(i);
return 0;
}
j'ai vraiment aimé la réponse de paxdiable et j'ai décidé d'implémenter toutes les fonctionnalités manquantes pour mon application comme les variables de garde et les données spécifiques à la machine.
j'ai téléchargé mon implémentation sur ce site pour la partager avec la communauté. Il a été testé en utilisant IAR Embedded Workbench for ARM.
un autre outil open source intéressant est Yakindu Statechart Tools on statecharts.org . Il utilise Harel statecharts et fournit donc des États hiérarchiques et parallèles et génère du code C et C++ (ainsi que du code Java). Il n'utilise pas de bibliothèques, mais suit une approche de "code simple". Le code s'applique essentiellement aux structures à boîtiers de commutation. Les générateurs de code peuvent également être personnalisés. En outre, l'outil offre de nombreuses autres fonctionnalités.
venir à cette fin (comme d'habitude) mais en parcourant les réponses à ce jour, je pense que quelque chose d'important manque;
j'ai trouvé dans mes propres projets qu'il peut être très utile de pas ont une fonction pour chaque état valide/événement combinaison. J'aime l'idée d'avoir un tableau 2D de membres/événements. Mais j'aime que les éléments de la table soient plus qu'un simple pointeur de fonction. Au lieu de cela j'essaie d'organiser mon dessin donc à son cœur, c' comprend un tas d'éléments ou d'actions atomiques simples. De cette façon, je peux lister ces éléments atomiques simples à chaque intersection de ma table d'état/événement. L'idée est que vous ne doivent définir une masse de n au carré (généralement très simple) fonctions. Pourquoi quelque chose d'aussi sujettes à l'erreur, beaucoup de temps, difficile à écrire, difficile à lire, vous le nom ?
j'ai aussi inclure l'option d'un nouvel état, et en option un pointeur de fonction pour chaque cellule du tableau. Le le pointeur de fonction est là pour ces cas exceptionnels où vous ne voulez pas juste lancer une liste d'actions atomiques.
vous savez que vous le faites bien quand vous pouvez exprimer beaucoup de fonctionnalités différentes, juste en éditant votre table, sans nouveau code à écrire.
je pense que le mien est un peu différent de celui des autres. Un peu plus de séparation du code et des données que ce que je vois dans les autres réponses. J'ai vraiment lu sur la théorie pour écrire ceci, qui met en œuvre une langue régulière complète (sans expressions régulières, malheureusement). Ullman, Minsky, Chomsky. Je ne peux pas dire que j'ai tout compris, mais j'ai tiré des vieux maîtres aussi directement que possible: à travers leurs mots.
j'utilise un pointeur de fonction à un prédicat qui détermine la transition vers un État " oui "ou un état "non". Cela facilite la création d'un état fini accepteur un langage régulier que vous programme en langage assembleur. Ne sois pas rebuté par mes stupides choix de nom. "czek" = = "check". 'connaît' == [aller le chercher dans le Dictionnaire Hacker].
donc pour chaque itération, czek appelle une fonction de prédicat avec le caractère courant comme argument. Si le prédicat retourne true, le caractère est consommé (le pointeur avancé) et nous suivons la transition 'y' pour sélectionner l'état suivant. Si le prédicat retourne false, le caractère n'est pas consommé et nous suivons la transition 'n'. Chaque instruction est donc une branche à double sens! J'ai dû lire L'Histoire de Mel à l'époque.
ce code vient tout droit de mon interpréteur postscript , et a évolué dans sa forme actuelle avec beaucoup de conseils des boursiers sur comp.lang.C. Depuis postscript essentiellement n'a pas de syntaxe (n'exigeant que des crochets équilibrés), un accepteur de langage régulier comme celui-ci fonctionne aussi comme l'analyseur.
/* currentstr is set to the start of string by czek
and used by setrad (called by israd) to set currentrad
which is used by israddig to determine if the character
in question is valid for the specified radix
--
a little semantic checking in the syntax!
*/
char *currentstr;
int currentrad;
void setrad(void) {
char *end;
currentrad = strtol(currentstr, &end, 10);
if (*end != '#' /* just a sanity check,
the automaton should already have determined this */
|| currentrad > 36
|| currentrad < 2)
fatal("bad radix"); /* should probably be a simple syntaxerror */
}
/*
character classes
used as tests by automatons under control of czek
*/
char *alpha = "0123456789" "ABCDE" "FGHIJ" "KLMNO" "PQRST" "UVWXYZ";
#define EQ(a,b) a==b
#define WITHIN(a,b) strchr(a,b)!=NULL
int israd (int c) {
if (EQ('#',c)) { setrad(); return true; }
return false;
}
int israddig(int c) {
return strchrnul(alpha,toupper(c))-alpha <= currentrad;
}
int isdot (int c) {return EQ('.',c);}
int ise (int c) {return WITHIN("eE",c);}
int issign (int c) {return WITHIN("+-",c);}
int isdel (int c) {return WITHIN("()<>[]{}/%",c);}
int isreg (int c) {return c!=EOF && !isspace(c) && !isdel(c);}
#undef WITHIN
#undef EQ
/*
the automaton type
*/
typedef struct { int (*pred)(int); int y, n; } test;
/*
automaton to match a simple decimal number
*/
/* /^[+-]?[0-9]+$/ */
test fsm_dec[] = {
/* 0*/ { issign, 1, 1 },
/* 1*/ { isdigit, 2, -1 },
/* 2*/ { isdigit, 2, -1 },
};
int acc_dec(int i) { return i==2; }
/*
automaton to match a radix number
*/
/* /^[0-9]+[#][a-Z0-9]+$/ */
test fsm_rad[] = {
/* 0*/ { isdigit, 1, -1 },
/* 1*/ { isdigit, 1, 2 },
/* 2*/ { israd, 3, -1 },
/* 3*/ { israddig, 4, -1 },
/* 4*/ { israddig, 4, -1 },
};
int acc_rad(int i) { return i==4; }
/*
automaton to match a real number
*/
/* /^[+-]?(d+(.d*)?)|(d*.d+)([eE][+-]?d+)?$/ */
/* represents the merge of these (simpler) expressions
[+-]?[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?
[+-]?[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?
The complexity comes from ensuring at least one
digit in the integer or the fraction with optional
sign and optional optionally-signed exponent.
So passing isdot in state 3 means at least one integer digit has been found
but passing isdot in state 4 means we must find at least one fraction digit
via state 5 or the whole thing is a bust.
*/
test fsm_real[] = {
/* 0*/ { issign, 1, 1 },
/* 1*/ { isdigit, 2, 4 },
/* 2*/ { isdigit, 2, 3 },
/* 3*/ { isdot, 6, 7 },
/* 4*/ { isdot, 5, -1 },
/* 5*/ { isdigit, 6, -1 },
/* 6*/ { isdigit, 6, 7 },
/* 7*/ { ise, 8, -1 },
/* 8*/ { issign, 9, 9 },
/* 9*/ { isdigit, 10, -1 },
/*10*/ { isdigit, 10, -1 },
};
int acc_real(int i) {
switch(i) {
case 2: /* integer */
case 6: /* real */
case 10: /* real with exponent */
return true;
}
return false;
}
/*
Helper function for grok.
Execute automaton against the buffer,
applying test to each character:
on success, consume character and follow 'y' transition.
on failure, do not consume but follow 'n' transition.
Call yes function to determine if the ending state
is considered an acceptable final state.
A transition to -1 represents rejection by the automaton
*/
int czek (char *s, test *fsm, int (*yes)(int)) {
int sta = 0;
currentstr = s;
while (sta!=-1 && *s) {
if (fsm[sta].pred((int)*s)) {
sta=fsm[sta].y;
s++;
} else {
sta=fsm[sta].n;
}
}
return yes(sta);
}
/*
Helper function for toke.
Interpret the contents of the buffer,
trying automatons to match number formats;
and falling through to a switch for special characters.
Any token consisting of all regular characters
that cannot be interpreted as a number is an executable name
*/
object grok (state *st, char *s, int ns,
object *src,
int (*next)(state *,object *),
void (*back)(state *,int, object *)) {
if (czek(s, fsm_dec, acc_dec)) {
long num;
num = strtol(s,NULL,10);
if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
error(st,limitcheck);
/* } else if (num > INT_MAX || num < INT_MIN) { */
/* error(limitcheck, OP_token); */
} else {
return consint(num);
}
}
else if (czek(s, fsm_rad, acc_rad)) {
long ra,num;
ra = (int)strtol(s,NULL,10);
if (ra > 36 || ra < 2) {
error(st,limitcheck);
}
num = strtol(strchr(s,'#')+1, NULL, (int)ra);
if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
error(st,limitcheck);
/* } else if (num > INT_MAX || num < INT_MAX) { */
/* error(limitcheck, OP_token); */
} else {
return consint(num);
}
}
else if (czek(s, fsm_real, acc_real)) {
double num;
num = strtod(s,NULL);
if ((num==HUGE_VAL || num==-HUGE_VAL) && errno==ERANGE) {
error(st,limitcheck);
} else {
return consreal(num);
}
}
else switch(*s) {
case '(': {
int c, defer=1;
char *sp = s;
while (defer && (c=next(st,src)) != EOF ) {
switch(c) {
case '(': defer++; break;
case ')': defer--;
if (!defer) goto endstring;
break;
case '\': c=next(st,src);
switch(c) {
case '\n': continue;
case 'a': c = '\a'; break;
case 'b': c = '\b'; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'v': c = '\v'; break;
case '\'': case '\"':
case '(': case ')':
default: break;
}
}
if (sp-s>ns) error(st,limitcheck);
else *sp++ = c;
}
endstring: *sp=0;
return cvlit(consstring(st,s,sp-s));
}
case '<': {
int c;
char d, *x = "0123456789abcdef", *sp = s;
while (c=next(st,src), c!='>' && c!=EOF) {
if (isspace(c)) continue;
if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
else error(st,syntaxerror);
d = (char)c << 4;
while (isspace(c=next(st,src))) /*loop*/;
if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
else error(st,syntaxerror);
d |= (char)c;
if (sp-s>ns) error(st,limitcheck);
*sp++ = d;
}
*sp = 0;
return cvlit(consstring(st,s,sp-s));
}
case '{': {
object *a;
size_t na = 100;
size_t i;
object proc;
object fin;
fin = consname(st,"}");
(a = malloc(na * sizeof(object))) || (fatal("failure to malloc"),0);
for (i=0 ; objcmp(st,a[i]=toke(st,src,next,back),fin) != 0; i++) {
if (i == na-1)
(a = realloc(a, (na+=100) * sizeof(object))) || (fatal("failure to malloc"),0);
}
proc = consarray(st,i);
{ size_t j;
for (j=0; j<i; j++) {
a_put(st, proc, j, a[j]);
}
}
free(a);
return proc;
}
case '/': {
s[1] = (char)next(st,src);
puff(st, s+2, ns-2, src, next, back);
if (s[1] == '/') {
push(consname(st,s+2));
opexec(st, op_cuts.load);
return pop();
}
return cvlit(consname(st,s+1));
}
default: return consname(st,s);
}
return null; /* should be unreachable */
}
/*
Helper function for toke.
Read into buffer any regular characters.
If we read one too many characters, put it back
unless it's whitespace.
*/
int puff (state *st, char *buf, int nbuf,
object *src,
int (*next)(state *,object *),
void (*back)(state *,int, object *)) {
int c;
char *s = buf;
while (isreg(c=next(st,src))) {
if (s-buf >= nbuf-1) return false;
*s++ = c;
}
*s = 0;
if (!isspace(c) && c != EOF) back(st,c,src); /* eat interstice */
return true;
}
/*
Helper function for Stoken Ftoken.
Read a token from src using next and back.
Loop until having read a bona-fide non-whitespace non-comment character.
Call puff to read into buffer up to next delimiter or space.
Call grok to figure out what it is.
*/
#define NBUF MAXLINE
object toke (state *st, object *src,
int (*next)(state *, object *),
void (*back)(state *, int, object *)) {
char buf[NBUF] = "", *s=buf;
int c,sta = 1;
object o;
do {
c=next(st,src);
//if (c==EOF) return null;
if (c=='%') {
if (DUMPCOMMENTS) fputc(c, stdout);
do {
c=next(st,src);
if (DUMPCOMMENTS) fputc(c, stdout);
} while (c!='\n' && c!='\f' && c!=EOF);
}
} while (c!=EOF && isspace(c));
if (c==EOF) return null;
*s++ = c;
*s = 0;
if (!isdel(c)) sta=puff(st, s,NBUF-1,src,next,back);
if (sta) {
o=grok(st,buf,NBUF-1,src,next,back);
return o;
} else {
return null;
}
}
boost.org livré avec 2 implémentations de cartes d'état différentes:
comme toujours, boost vous téléportera dans l'enfer des gabarits.
la première bibliothèque est pour plus de performances-machines d'état critiques. La seconde bibliothèque vous donne un chemin de transition direct d'un Statechart UML vers le code.
voici le donc question demandant une comparaison entre les deux où les deux auteurs répondent.
Ce série d'Ars OpenForum postes sur un un peu compliqué, peu de logique de contrôle comprend un très facile-à-suivre la mise en œuvre d'une machine d'état dans C.
vu quelque part
#define FSM
#define STATE(x) s_##x :
#define NEXTSTATE(x) goto s_##x
FSM {
STATE(x) {
...
NEXTSTATE(y);
}
STATE(y) {
...
if (x == 0)
NEXTSTATE(y);
else
NEXTSTATE(x);
}
}
étant donné que vous sous-entendez que vous pouvez utiliser le code C++ et donc le code OO, je suggérerais d'évaluer le modèle 'GoF' (Gof = Gang of Four, les gars qui ont écrit le livre design patterns qui a mis le design pattern au premier plan).
Il n'est pas particulièrement complexe, et il est largement utilisé et discuté de sorte qu'il est facile de voir des exemples et des explications sur la ligne.
il sera également très probablement reconnaissable par toute autre personne maintenant votre code à un date ultérieure.
si l'efficacité est la préoccupation, il serait intéressant de faire une analyse comparative pour s'assurer qu'une approche non OO est plus efficace car beaucoup de facteurs affectent la performance et il n'est pas toujours tout simplement oo mauvais, code fonctionnel bon. De même, si l'utilisation de la mémoire est une contrainte pour vous, il est de nouveau intéressant de faire des tests ou des calculs pour voir si cela sera réellement un problème pour votre application particulière si vous utilisez le modèle d'état.
le voici quelques liens vers le modèle d'état "Gof", comme le suggère Craig:
votre question est assez générique,
Voici deux articles de référence qui pourraient être utiles,
-
Incorporé Machine D'État De La Mise En Œuvre
cet article décrit une approche simple pour implémenter une machine d'État pour un système embarqué. Aux fins de cet article, une machine d'état est défini comme un algorithme qui peut être dans un petit nombre des etats. Un État est une condition qui provoque un rapport prescrit des entrées aux sorties, et des entrées aux États suivants.
Un lecteur averti remarquera rapidement que les machines d'état décrites dans cet article sont des machines farfelues. Une machine Mealy est une machine d'état où les sorties sont une fonction à la fois de l'état présent et de l'entrée, par opposition à une machine Moore, dans laquelle les sorties sont une fonction uniquement de l'état.- machines D'État codantes en C et C++
ma préoccupation dans cet article est avec les principes de base de la machine d'état et quelques lignes directrices de programmation simples pour coder les machines d'état en C ou C++. J'espère que ces techniques simples pourront devenir plus courantes, afin que vous (et d'autres) puissiez facilement voir la structure état-machine à partir du code source.
- machines D'État codantes en C et C++
j'ai utilisé State Machine Compiler dans des projets Java et Python avec succès.
C'est un vieux post avec beaucoup de réponses, mais j'ai pensé que je voudrais ajouter ma propre approche à la machine d'état fini en C. j'ai fait un script Python pour produire le code squelette C pour un certain nombre d'États. Ce script est documenté sur GituHub à FsmTemplateC
cet exemple est basé sur d'autres approches que j'ai lues. Il n'utilise pas les instructions goto ou switch mais a des fonctions de transition dans une matrice de pointeur (table de recherche). Le le code s'appuie sur une macro multi-ligne initialiseur macro et C99 traits (initialiseurs désignés et alphabets composés) donc si vous n'aimez pas ces choses, vous pourriez ne pas aimer cette approche.
voici un script Python d'un exemple de tourniquet qui génère un code squelette c en utilisant FsmTemplateC :
# dict parameter for generating FSM
fsm_param = {
# main FSM struct type string
'type': 'FsmTurnstile',
# struct type and name for passing data to state machine functions
# by pointer (these custom names are optional)
'fopts': {
'type': 'FsmTurnstileFopts',
'name': 'fopts'
},
# list of states
'states': ['locked', 'unlocked'],
# list of inputs (can be any length > 0)
'inputs': ['coin', 'push'],
# map inputs to commands (next desired state) using a transition table
# index of array corresponds to 'inputs' array
# for this example, index 0 is 'coin', index 1 is 'push'
'transitiontable': {
# current state | 'coin' | 'push' |
'locked': ['unlocked', ''],
'unlocked': [ '', 'locked']
}
}
# folder to contain generated code
folder = 'turnstile_example'
# function prefix
prefix = 'fsm_turnstile'
# generate FSM code
code = fsm.Fsm(fsm_param).genccode(folder, prefix)
l'en-tête généré contient les typedefs:
/* function options (EDIT) */
typedef struct FsmTurnstileFopts {
/* define your options struct here */
} FsmTurnstileFopts;
/* transition check */
typedef enum eFsmTurnstileCheck {
EFSM_TURNSTILE_TR_RETREAT,
EFSM_TURNSTILE_TR_ADVANCE,
EFSM_TURNSTILE_TR_CONTINUE,
EFSM_TURNSTILE_TR_BADINPUT
} eFsmTurnstileCheck;
/* states (enum) */
typedef enum eFsmTurnstileState {
EFSM_TURNSTILE_ST_LOCKED,
EFSM_TURNSTILE_ST_UNLOCKED,
EFSM_TURNSTILE_NUM_STATES
} eFsmTurnstileState;
/* inputs (enum) */
typedef enum eFsmTurnstileInput {
EFSM_TURNSTILE_IN_COIN,
EFSM_TURNSTILE_IN_PUSH,
EFSM_TURNSTILE_NUM_INPUTS,
EFSM_TURNSTILE_NOINPUT
} eFsmTurnstileInput;
/* finite state machine struct */
typedef struct FsmTurnstile {
eFsmTurnstileInput input;
eFsmTurnstileCheck check;
eFsmTurnstileState cur;
eFsmTurnstileState cmd;
eFsmTurnstileState **transition_table;
void (***state_transitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
void (*run)(struct FsmTurnstile *, FsmTurnstileFopts *, const eFsmTurnstileInput);
} FsmTurnstile;
/* transition functions */
typedef void (*pFsmTurnstileStateTransitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
- enum
eFsmTurnstileCheck
est utilisé pour déterminer si une transition a été bloquée avecEFSM_TURNSTILE_TR_RETREAT
, a permis de progresser avecEFSM_TURNSTILE_TR_ADVANCE
, ou l'appel de fonction n'a pas été précédé d'une transition avecEFSM_TURNSTILE_TR_CONTINUE
. - enum
eFsmTurnstileState
est simplement la liste des États. - enum
eFsmTurnstileInput
est simplement la liste des entrées. - la structure
FsmTurnstile
est le cœur de la machine d'état avec la transition vérifier, en fonction de la table de choix, état actuel, sous le commandement de l'état, et un alias de la fonction principale qui exécute la machine. - chaque pointeur de fonction (alias) dans
FsmTurnstile
ne doit être appelé que depuis la structure et doit avoir sa première entrée comme pointeur vers lui-même afin de maintenir un état persistant, Style orienté objet.
maintenant pour les déclarations de fonction dans l'en-tête:
/* fsm declarations */
void fsm_turnstile_locked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_locked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_run (FsmTurnstile *fsm, FsmTurnstileFopts *fopts, const eFsmTurnstileInput input);
fonction les noms sont dans le format {prefix}_{from}_{to}
, où {from}
est l'état précédent (courant) et {to}
est l'état suivant. Notez que si la table de transition ne permet pas certaines transitions, un pointeur NULL à la place d'un pointeur de fonction sera défini. Enfin, la magie se produit avec une macro. Nous construisons ici la table de transition (matrice des énums d'état) et la table de recherche des fonctions de transition d'état (une matrice de pointeurs de fonction):
/* creation macro */
#define FSM_TURNSTILE_CREATE() \
{ \
.input = EFSM_TURNSTILE_NOINPUT, \
.check = EFSM_TURNSTILE_TR_CONTINUE, \
.cur = EFSM_TURNSTILE_ST_LOCKED, \
.cmd = EFSM_TURNSTILE_ST_LOCKED, \
.transition_table = (eFsmTurnstileState * [EFSM_TURNSTILE_NUM_STATES]) { \
(eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
EFSM_TURNSTILE_ST_UNLOCKED, \
EFSM_TURNSTILE_ST_LOCKED \
}, \
(eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
EFSM_TURNSTILE_ST_UNLOCKED, \
EFSM_TURNSTILE_ST_LOCKED \
} \
}, \
.state_transitions = (pFsmTurnstileStateTransitions * [EFSM_TURNSTILE_NUM_STATES]) { \
(pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
fsm_turnstile_locked_locked, \
fsm_turnstile_locked_unlocked \
}, \
(pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
fsm_turnstile_unlocked_locked, \
fsm_turnstile_unlocked_unlocked \
} \
}, \
.run = fsm_turnstile_run \
}
quand la création de la FSM, la macro FSM_EXAMPLE_CREATE()
doit être utilisée.
maintenant, dans le code source, chaque fonction de transition d'état déclarée ci-dessus doit être peuplée. La structure FsmTurnstileFopts
peut être utilisée pour transmettre des données à/depuis la machine d'état. Chaque transition doit définir fsm->check
pour être égale à EFSM_EXAMPLE_TR_RETREAT
pour l'empêcher de passer ou EFSM_EXAMPLE_TR_ADVANCE
pour lui permettre de passer à l'état commandé.
Un exemple pratique peut être trouvé à (FsmTemplateC))[ https://github.com/ChisholmKyle/FsmTemplateC] .
Voici l'usage réel très simple dans votre code:
/* create fsm */
FsmTurnstile fsm = FSM_TURNSTILE_CREATE();
/* create fopts */
FsmTurnstileFopts fopts = {
.msg = ""
};
/* initialize input */
eFsmTurnstileInput input = EFSM_TURNSTILE_NOINPUT;
/* main loop */
for (;;) {
/* wait for timer signal, inputs, interrupts, whatever */
/* optionally set the input (my_input = EFSM_TURNSTILE_IN_PUSH for example) */
/* run state machine */
my_fsm.run(&my_fsm, &my_fopts, my_input);
}
Tout ce commerce d'en-tête et toutes ces fonctions juste pour avoir une interface simple et rapide en vaut la peine dans mon esprit.
vous pouvez utiliser la bibliothèque open source OpenFST .
OpenFst est une bibliothèque pour la construction, la combinaison, l'optimisation et la recherche de transducteurs d'états finis pondérés (FST). Les transducteurs à états finis pondérés sont des automates où chaque transition a une étiquette d'entrée, une étiquette de sortie et un poids. L'accepteur d'état fini le plus familier est représenté comme un transducteur avec les étiquettes d'entrée et de sortie de chaque transition égales. Les accepteurs d'états finis sont utilisés pour représenter les ensembles de cordes (spécifiquement, les ensembles réguliers ou rationnels); les transducteurs d'états finis sont utilisés pour représenter les relations binaires entre les paires de cordes (spécifiquement, les transductions rationnelles). Les pondérations peuvent être utilisées pour représenter le coût d'une transition particulière.
void (* StateController)(void);
void state1(void);
void state2(void);
void main()
{
StateController=&state1;
while(1)
{
(* StateController)();
}
}
void state1(void)
{
//do something in state1
StateController=&state2;
}
void state2(void)
{
//do something in state2
//Keep changing function direction based on state transition
StateController=&state1;
}
j'utilise personnellement des structures d'auto-référencement en combinaison avec des tableaux de pointeurs. J'ai téléchargé un tutoriel sur githuba il y a quelque temps, lien:
https://github.com/mmelchger/polling_state_machine_c
Note: je me rends compte que ce fil est assez ancien, mais j'espère obtenir des informations et des réflexions sur la conception de la machine d'état ainsi que d'être en mesure de fournir un exemple pour une conception possible de machine D'état en C.