État des machines que j'ai conçu avant (C, pas du C++) ont tous descendent d'un struct
tableau et une boucle. La structure se compose essentiellement d'un état et de l'événement (pour la recherche) et 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 les événements avec de simples définit ( 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
Ensuite, vous définissez toutes les fonctions qui sont appelées par les transitions:
static int GotKey (void) { ... };
static int FsmError (void) { ... };
Toutes ces fonctions sont écrites de ne pas prendre de variables et retour le nouvel état de la machine d'état. Parce qu'ils suivent tous la même forme et ne pas prendre les paramètres, de l'utilisation qui est faite de "global", les variables de l'information en passant, si nécessaire. Ce n'est pas aussi mauvais que cela puisse paraître, depuis le FSM est généralement enfermé à l'intérieur d'une seule unité de compilation et toutes les variables sont statiques à l'unité (c'est pourquoi j'ai utilisé des guillemets autour de "global" ci - dessus, ils ne sont plus partagées que véritablement mondiale). Comme avec toutes les variables globales, il nécessite des soins.
Les transitions tableau définit ensuite toutes les transitions possibles et les fonctions get appelée pour ces transitions (y compris le fourre-tout dernier):
tTransition trans[] = {
{ ST_INIT, EV_KEYPRESS, &GotKey},
: :
{ ST_ANY, EV_ANY, &FsmError}
};
#define TRANS_COUNT (sizeof(trans)/sizeof(*trans))
Le fonctionnement de la FSM devient alors relativement simple boucle:
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 mentionné ci-dessus, note l'utilisation de l' ST_ANY
et EV_ANY
comme des caractères génériques, permettant à une manifestation pour appeler une fonction peu importe l'état actuel, et de garantir que, si vous atteignez la fin de la transition tableau, vous obtenez un message d'erreur indiquant votre FSM n'a pas été correctement construit.
J'ai utilisé un code similaire pour cela sur un grand nombre de projets de communication, comme un début de mise en œuvre de l'OSI modèle en couches et protocoles pour les systèmes embarqués. C'est gros avantage est sa simplicité et la facilité relative à la modification de la transitions tableau.
Je n'ai aucun doute il y aura des abstractions de niveau supérieur qui peut être plus approprié de nos jours, mais je soupçonne qu'ils vont tous se résument à ce même genre de structure.
En tant que ldog
états dans un commentaire, vous pouvez éviter les variables globales tout en passant un pointeur de structure à toutes les fonctions (et les utiliser que dans le cas de la boucle). 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 tient la machine les données spécifiques de l'etat (au minimum) et d'utiliser à la place de la globals.
La raison pour laquelle j'ai rarement fait c'est simplement parce que la plupart des machines d'état que j'ai écrits ont été singleton types (one-off, les processus de démarrage, le fichier de configuration de la lecture par exemple), 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.