Projet

Général

Profil

Framework State Machine

Ce framework fournit des classes pour créer et exécuter des graphiques d'états.
Les concepts et notations sont basés sur les diagrammes d'états (inventés par Harel).

Les diagrammes d'états sont une solution graphique à la modélisation des réactions d'un système à certains stimuli.
Cela se fait en définissant les états possibles dans lesquels le système peut se trouver, et comment le système peut passer d'un état à un autre (les transitions entre états).
Une caractéristique clé des applications incluant une machine à états est que le comportement dépend du dernier événement ou de celui en cours, mais aussi des événements qui l'ont précédés.
Avec le diagramme d'états, cette information est facile à exprimer.

Légende

point noir -> état initial
point noir cerclé -> état final
boite rectangulaire -> état
flèche avec texte -> transition

Explications du diagramme

État initial : C'est le point de départ. Dans une partie d'échec, c'est lorsque toutes les pièces sont posées sur l'échiquier. Puis on passe à l'état Tour Blanc.
État Tour Blanc : Cet état est automatique aux échecs (c'est toujours les blancs qui commencent).
Dans cet état, seules les pièces blanches peuvent se déplacer, et seules les pièces noires peuvent être mangées.
On dit que ce sont les propriétés de cet état. Puis on passe à l'état Tour Noire.
État Tour Noire : On passe à cet état dès que le joueur blanc a effectué son mouvement. C'est la transition.
Il est important de noter que l'état Tour Noire dépend de l'état précédent, à savoir l'état Tour Blanc.
En effet, les différentes pièces que le joueur blanc a déplacé doivent être "le point de départ" pour l'autre joueur.
Cet enchainement d'états se poursuit jusqu'à qu'un des joueurs est mat, ou qu'il y a égalité.

Le framework State Machine (littéralement machine à états) fournit une bibliothèque et un modèle d'exécution qui peut être utilisé efficacement pour intégrer les éléments et la sémantique des diagrammes d'états dans les applications Qt.
Le cadre est étroitement intégré à Qt.
Par exemple, les transitions entre états peuvent être déclenchés par des signaux, et les états peuvent être configurés pour définir les propriétés et appeler des méthodes sur QObject.
En d'autres termes, le système d'événements de Qt (signaux/slots) est utilisé pour piloter les machines à état.
Le graphe d'état dans ce framework est hiérarchique.
Les états peuvent être imbriquées à l'intérieur d'autres états (cf le diagramme AbulEduStateMachine ou l'état exercice est un sous-état de séquence), et la configuration actuelle de la machine d'état se compose de l'ensemble des états qui sont actuellement actifs.
Ainsi, tous les états dans une configuration valide de la machine d'état ont un ancêtre commun.

Un petit exemple

Pour démontrer la fonctionnalité de base du framework State Machine, regardons un petit exemple: Soit une machine d'état à trois états, s1, s2 et s3.
La machine d'état est contrôlé par un QPushButton (un simple bouton) unique.
Lorsque le bouton est cliqué, la machine transite d'un état vers un autre état.
Initialement, la machine d'état est en état s1.
Le diagramme d'états pour cette machine à états est le suivant :

L'extrait suivant montre le code nécessaire pour créer une telle machine d'état.
Tout d'abord, nous créons la machine à états et les états :

     QStateMachine machine;
     QState *s1 = new QState();
     QState *s2 = new QState();
     QState *s3 = new QState();

Ensuite, nous créons les transitions à l'aide de la la fonction QState::addTransition() :

     s1->addTransition(button, SIGNAL(clicked()), s2);
     s2->addTransition(button, SIGNAL(clicked()), s3);
     s3->addTransition(button, SIGNAL(clicked()), s1);

Ensuite, nous ajoutons les états à la machine et nous définissons son état initial :

     machine.addState(s1);
     machine.addState(s2);
     machine.addState(s3);
     machine.setInitialState(s1);

Finalement on démarre la machine :

     machine.start();

La machine à états s'exécute de façon asynchrone, c'est à dire qu'elle devient une partie de la boucle d'événement de votre application.

Aller un peu plus loin...

La machine à états que nous venons de voir se contente juste de transiter d'un état à un autre (s1, s2 et s3), mais n'exécute pas d'opérations.
La fonction QState::assignProperty() peut être utilisée pour qu'un état définisse une propriété à un QObject (un objet au sens Qt).
Dans l'extrait suivant, la valeur qui devrait être assignée à la propriété de texte d'un QLabel (un label au sens Qt) est spécifié pour chaque état :

     s1->assignProperty(label, "text1", "In state s1");
     s2->assignProperty(label, "text2", "In state s2");
     s3->assignProperty(label, "text3", "In state s3");

Lorsque l'un des états est assigné, le texte de l'étiquette (le texte du QLabel) sera modifié en conséquence.

Le signal QState::entered() est émis quand on entre dans l'état, et le signal QState::exited() est émis quand on sort de l'état.
Dans l'extrait suivant, le slot du bouton showMaximized() sera appelée lorsqu'on entre dans l'état s3 , et le slot du bouton showMinimized() sera appelé quand on sort de l'état s3 :

     QObject::connect(s3, SIGNAL(entered()), button, SLOT(showMaximized()));
     QObject::connect(s3, SIGNAL(exited()), button, SLOT(showMinimized()));

Des états personnalisés peuvent être ré-implémentés dans les méthodes QABSTRACTSTATE::onEntry() et QABSTRACTSTATE::onExit().

Fin d'une machine à états

La machine à état définie dans la section précédente ne s'arrête jamais.
Pour qu'une machine à état puisse se terminer, elle doit avoir un état final au plus haut niveau (l'objet QFinalState).
Quand la machine à état entre dans un état final au plus haut niveau, la machine émettra le signal QSTATEMACHINE::finished() et se terminera.

Tout que vous devez faire pour présenter un état final dans le graphique est de créer un objet QFINALSTATE et l'utiliser comme cible d'une ou de plusieurs transitions.

Partage de transitions par groupement d'états

Supposons que nous voulions que l'utilisateur soit en mesure de quitter l'application à tout moment en cliquant sur un bouton Quitter.
Pour atteindre cet objectif, nous devons créer un état final et en faire la cible d'une transition associée avec le signal clicked() du bouton Quitter.
Nous pourrions ajouter une transition à partir de chacun des états s1, s2, et s3.
Cependant, cela semble redondant, et il faudrait aussi se rappeler d'ajouter cette transition à chaque nouvel état qui sera ajouté à l'avenir.

Nous pouvons atteindre le même comportement (à savoir que le clic sur le bouton Quitter quitte la machine à états, et ce quelque soit l'état dans lequel la machine se trouve) en regroupant les états s1, s2 et s3.
Cela peut se faire en créant un nouvel état de haut niveau et en indiquant que les trois états d'origine (à savoir s1, s2 et s3) sont maintenant des enfants (des sous-états) de ce nouvel état.
Le schéma suivant montre la nouvelle machine d'état.

Les trois états originaux ont été renommés S11, S12 et S13 pour indiquer que ce sont maintenant les enfants du nouvel état de haut niveau, s1.
Les états enfants héritent implicitement des transitions de leur parent (c'est beau l'hérédité).
Cela signifie qu'il est maintenant suffisant d'ajouter une transition unique de s1 à l'état final s2.
Les sous-états de s1 (s11, s12 et s13) vont automatiquement hériter de cette transition.

     QState *s1 = new QState();
     QState *s11 = new QState(s1);
     QState *s12 = new QState(s1);
     QState *s13 = new QState(s1);
     s1->setInitialState(s11);
     machine.addState(s1);
     QFinalState *s2 = new QFinalState();
     s1->addTransition(quitButton, SIGNAL(clicked()), s2);
     machine.addState(s2);

     QObject::connect(&machine, SIGNAL(finished()), QApplication::instance(), SLOT(quit()));

Dans l'extrait décrit ci-dessus, nous voulons quitter l'application lorsque la machine à états se termine.
Nous avons donc connecter le signal finished() de la machine à états au slot quitter de l'application.

Un état enfant peut surcharger une transition héritée.
Une transition peut avoir n'importe quel état comme objectif, cependant l'état cible ne doit pas être au même niveau hiérarchique que l'état source.

Aller encore plus loin...

Il existe des états "historiques", des états parallèles...
Si la demande est faite, je continuerai ces sections.

DiagrammeEtatExemple.png (27,4 ko) Icham, 19/06/2012 10:40

EtatHautNiveau.png (20,2 ko) Icham, 19/06/2012 11:28

EchecDiagrammeEtatsTransitions.png (19,1 ko) Icham, 19/06/2012 14:21

Redmine Appliance - Powered by TurnKey Linux