Les machines à états finis sont peut-être l'un des concepts les plus fondamentaux et les plus largement utilisés en programmation. Les machines à états finis (KA) sont activement utilisées dans de nombreuses niches appliquées. En particulier, dans des créneaux tels que l'APCS et les télécommunications, avec lesquels il était possible de faire face, les engins spatiaux se retrouvent un peu moins souvent qu'à chaque étape.
Par conséquent, dans cet article, nous essaierons de parler des engins spatiaux, principalement des machines à états finis hiérarchiques et de leurs capacités avancées. Et parlez un peu de la prise en charge des vaisseaux spatiaux dans
SObjectizer-5 , le framework «acteur» pour C ++. Un de ces
deux rares qui sont ouverts, gratuits, multiplateformes et encore en vie.
Même si vous n'êtes pas intéressé par SObjectizer, mais que vous n'avez jamais entendu parler de machines à états finis hiérarchiques ou de l'utilité des fonctionnalités avancées d'un vaisseau spatial comme les gestionnaires d'entrée / sortie pour les états ou l'historique des états, alors vous pourriez être intéressé à regarder sous le chat et lire au moins la première partie de l'article.
Mots généraux sur les machines à états finis
Nous n'essaierons pas de mener un programme éducatif complet dans l'article sur le sujet des
automates et une telle variété que
les machines à états finis . Le lecteur doit avoir au moins une compréhension de base de ces types d'entités.
Machines avancées à états finis et leurs capacités
Le vaisseau spatial a plusieurs fonctionnalités "avancées" qui augmentent considérablement l'utilisabilité du vaisseau spatial dans le programme. Jetons un coup d'œil à ces fonctionnalités «avancées».
Avertissement: si le lecteur connaît bien les diagrammes d'état d'UML, il ne trouvera rien de nouveau pour lui ici.
Machines à états hiérarchiques
L'opportunité peut-être la plus importante et la plus précieuse est l'organisation d'une hiérarchie / imbrication d'états. Étant donné que c'est précisément la capacité de mettre des états les uns dans les autres qui élimine «l'explosion» du nombre de transitions d'un état à l'autre à mesure que la complexité de l'engin spatial augmente.
Il est plus difficile d'expliquer cela avec des mots que de montrer par l'exemple. Par conséquent, imaginons que nous ayons un infokiosque sur l'écran dont un message de bienvenue est d'abord affiché. L'utilisateur peut sélectionner l'élément «Services» et aller à la section pour sélectionner les services dont il a besoin. Ou il peut sélectionner l'élément «Compte personnel» et aller à la section sur l'utilisation de ses données et services personnels. Ou il peut sélectionner la section Aide. Jusqu'à présent, tout semble simple et peut être représenté par le diagramme d'état suivant (aussi simplifié que possible):

Mais essayons de nous assurer qu'en cliquant sur le bouton "Annuler", l'utilisateur peut revenir de n'importe quelle section à la page de démarrage avec un message de bienvenue:

Le schéma devient compliqué, mais toujours sous contrôle. Cependant, rappelons que dans la section «Services», nous pouvons avoir plusieurs sous-sections supplémentaires, par exemple, «Services populaires», «Nouveaux services» et «Liste complète». Et à partir de chacune de ces sections, vous devez également revenir à la page de démarrage. Notre vaisseau spatial simple devient de plus en plus difficile:

Mais c'est loin d'être tout. Nous n'avons pas encore pris en compte le bouton "Retour", par lequel nous devons revenir à la section précédente. Ajoutons une réaction au bouton "Retour" et voyons ce que nous obtenons:

Oui, nous voyons maintenant le chemin du vrai plaisir. Mais nous n'avons même pas considéré les sous-sections dans les sections "Mon compte" et "Aide" ... Si nous commençons, alors presque immédiatement notre vaisseau spatial simple, au début, se transformera en quelque chose d'inimaginable.
Ici l'imbrication des États vient à notre secours. Imaginons que nous n'avons que deux états de niveau supérieur: WelcomeScreen et UserSelection. Toutes nos sections (c'est-à-dire «Services», «Mon compte» et «Aide») seront «imbriquées» dans l'état UserSelection. Vous pouvez dire que les états ServicesScreen, ProfileScreen et HelpScreen seront des enfants de UserSelection. Et comme ils sont enfants, ils hériteront de la réaction à certains signaux de leur état parental. Par conséquent, nous pouvons définir la réponse au bouton Annuler dans UserSelection. Mais nous n'avons pas besoin de déterminer cette réaction dans tous les sous-états subsidiaires. Ce qui rend notre vaisseau spatial plus concis et compréhensible:

Ici, vous pouvez noter que la réaction pour "Annuler" et "Retour" nous avons défini dans UserSelection. Et cette réaction au bouton Annuler fonctionne pour tous sans exception les sous-états UserSelection (y compris un autre sous-état composite ServicesSelection). Mais dans le sous-état ServicesSelection, la réaction au bouton Retour est déjà différente - le retour n'est pas dans WelcomScreen, mais dans ServicesScreen.
Les autorités de certification qui utilisent une hiérarchie / imbrication d'états sont appelées machines à états finis hiérarchiques (ICA).
Réaction à l'entrée / sortie vers / depuis l'État
Une caractéristique très utile est la capacité d'attribuer une réponse à l'entrée dans un état particulier, ainsi qu'une réaction à la sortie d'un état. Ainsi, dans l'exemple ci-dessus avec un infokiosque, un gestionnaire peut être raccroché pour entrer dans chacun des états, ce qui changera le contenu de l'écran de l'infokiosque.
L'exemple précédent peut être développé un peu. Supposons que nous avons deux sous-états dans WelcomScreen: BrightWelcomScreen, dans lequel l'écran sera mis en surbrillance normalement, et DarkWelcomScreen, dans lequel la luminosité de l'écran sera réduite. Nous pouvons créer un gestionnaire d'entrée DarkWelcomScreen qui atténuera l'écran. Et un gestionnaire de sortie DarkWelcomScreen qui rétablira une luminosité normale.

Changement d'état automatique après un temps défini
Parfois, il peut être nécessaire de limiter le séjour du vaisseau spatial dans un état particulier. Ainsi, dans l'exemple ci-dessus, nous pouvons limiter le temps que notre ICA reste à l'état BrightWelcomScreen à une minute. Dès que la minute expire, l'ICA passe automatiquement à l'état DarkWelcomScreen.
Histoire du vaisseau spatial
Une autre caractéristique très utile de l'ICA est l'histoire de l'état du vaisseau spatial.
Imaginons que nous ayons une sorte d'ICA abstraite de ce type:

Notre ICA peut aller de TopLevelState1 à TopLevelState2 et vice versa. Mais à l'intérieur de TopLevelState1, il existe plusieurs états imbriqués. Si l'ICA passe simplement de TopLevelState2 à TopLevelState1, alors deux états sont immédiatement activés: TopLevelState1 et NestedState1. NestedState1 est activé car il s'agit du sous-état initial de l'état TopLevelState1.
Imaginez maintenant que notre ICA change son état de NestedState1 à NestedState2. Dans NestedState2, le SubState InternalState1 a été activé (car il s'agit du sous-état initial pour NestedState2). Et de InternalState1, nous sommes allés à InternalState2. Ainsi, nous avons simultanément les états suivants actifs: TopLevelState1, NestedState2 et InternalState2. Et ici, nous allons à TopLevelState2 (c'est-à-dire que nous avons généralement quitté TopLevelState1).
Active devient TopLevelState2. Après quoi, nous voulons revenir à TopLevelState1. Il se trouve dans TopLevelState1 et non dans un sous-état particulier de TopLevelState1.
Donc, de TopLevelState2, nous allons à TopLevelState1 et où allons-nous?
Si TopLevelState1 n'a pas d'historique, nous arriverons à TopLevelState1 et NestedState1 (puisque NestedState1 est le sous-état initial de TopLevelState1). C'est-à-dire toute l'histoire des transitions à l'intérieur de TopLevelState1, qui a eu lieu avant de quitter TopLevelState2, a été complètement perdue.
Si TopLevelState1 a un soi-disant historique peu profond, puis lors du retour de TopLevelState2 à TopLevelState1, nous entrons dans NestedState2 et InternalState1. Nous entrons dans NestedState2 car il est enregistré dans l'historique de statut de TopLevelState1. Et nous arrivons à InternalState1 parce que c'est celui de départ pour NestedState2. Il s'avère que dans l'historique superficiel de TopLevelState1, les informations ne sont stockées que sur les sous-états du tout premier niveau. L'historique des états intégrés dans ces sous-états n'est pas conservé.
Mais si TopLevelState1 a une histoire profonde, alors lorsque nous revenons de TopLevelState2 à TopLevelState1, nous entrons dans NestedState2 et InternalState2. Parce que dans une histoire profonde, des informations complètes sur les sous-états actifs sont stockées, quelle que soit leur profondeur.
États orthogonaux
Jusqu'à présent, nous avons examiné l'ICA dans lequel un seul des sous-états pouvait être actif à l'intérieur de l'État. Mais parfois, il peut y avoir des situations où, dans un état particulier de l'ICA, il devrait y avoir plusieurs sous-états simultanément actifs. Ces sous-états sont appelés états orthogonaux.
Un exemple classique qui illustre des états orthogonaux est le clavier d'ordinateur familier et ses modes NumLock, CapsLock et ScrollLock. Nous pouvons dire que travailler avec NumLock / CapsLock / ScrollLock est décrit par des sous-états orthogonaux à l'intérieur de l'état Actif:

Tout ce que vous vouliez savoir sur les machines à états finis, mais ...
En général, il existe un article fondamental sur la notation formelle pour les diagrammes d'état de David Harel:
Statecharts: A Visual Formalism For Complex Systems (1987) .
Là, diverses situations qui peuvent être rencontrées lorsque l'on travaille avec des machines à états finis sont examinées en utilisant l'exemple de la commande d'une horloge électronique ordinaire. Si quelqu'un ne l'a pas lu, je le recommande vivement. Fondamentalement, tout ce que Harel a décrit est ensuite entré dans la notation UML. Mais lorsque vous lisez la description des diagrammes d'état de l'UML, vous ne comprenez pas toujours quoi, pourquoi et quand vous en avez besoin. Mais dans l'article de Harel, la présentation passe de situations simples à des situations plus complexes. Et vous êtes mieux conscient de tout le pouvoir que les machines à états finis cachent en elles-mêmes.
Machines à états finis dans SObjectizer
Plus loin, nous parlerons de SObjectizer et de ses spécificités. Si vous ne comprenez pas bien les exemples ci-dessous, il peut être judicieux d'en savoir plus sur SObjectizer. Par exemple, à partir de notre
article de synthèse sur SObjecizer et de plusieurs autres qui introduisent les lecteurs à SObjectizer, passant du simple au complexe (
premier article,
deuxième et
troisième ).
Les agents dans SObjectizer sont des machines à états
Les agents de SObjectizer depuis le début étaient des machines à états avec des états explicites. Même si le développeur de l'agent n'a décrit aucun de ses propres états dans sa classe d'agent, l'agent avait toujours un état par défaut, qui était utilisé par défaut. Par exemple, si un développeur a créé un agent aussi trivial:
class simple_demo final : public so_5::agent_t { public:
alors il peut même ne pas soupçonner qu'en réalité tous les abonnements qu'il a faits sont faits pour l'état par défaut. Mais si le développeur ajoute ses propres états à l'agent, vous devez déjà penser à signer correctement l'agent dans l'état correct. Ici, disons, une modification simple (et, comme d'habitude) incorrecte de l'agent montré ci-dessus:
class simple_demo final : public so_5::agent_t {
Nous avons défini deux gestionnaires différents pour le signal how_are_you, chacun pour son propre état.
Et l'erreur dans cette modification d'agent simple_demo est qu'étant en st_free ou st_busy, l'agent ne répondra pas du tout pour quitter, car nous avons laissé l'abonnement de sortie dans l'état par défaut, mais n'avons pas effectué les abonnements correspondants pour st_free et st_busy. Un moyen simple et évident de résoudre ce problème consiste à ajouter les abonnements appropriés à st_free et st_busy:
simple_demo(context_t ctx) : so_5::agent_t{std::move(ctx)} {
Certes, cette méthode sent le copier-coller, ce qui n'est pas bon. Vous pouvez vous débarrasser du copier-coller en entrant un état parent commun pour st_free et st_busy:
class simple_demo final : public so_5::agent_t {
Par souci de justice, il convient d'ajouter qu'au départ, dans SObjectizer, les agents ne pouvaient être que de simples machines à états. La prise en charge des engins spatiaux hiérarchiques est apparue relativement récemment, en janvier 2016.
Pourquoi les agents SObjectizer sont-ils des machines à états finis?
Cette question a une réponse très simple: il
se trouve que les racines de SObjectizer se développent dans le monde des systèmes de contrôle de processus, et que des machines à états finis sont utilisées très souvent. Par conséquent, nous avons jugé nécessaire que les agents dans SObjectizer soient également des machines à états. Ceci est très pratique si dans l'application pour quel SObjectizer ils essaient d'appliquer, des autorités de certification sont utilisées. Et l'état par défaut, que tous les agents ont, nous permet de ne pas penser aux vaisseaux spatiaux si l'utilisation de vaisseaux spatiaux n'est pas requise.
En principe, si vous regardez le modèle des acteurs lui-même et les principes sur lesquels ce modèle est construit:
- un acteur est une entité avec un comportement;
- les acteurs répondent aux messages entrants;
- Après avoir reçu le message, l'acteur peut:
- envoyer un certain nombre de messages à d'autres acteurs;
- créer un certain nombre de nouveaux acteurs;
- Définissez un nouveau comportement pour le traitement des messages suivants.
On peut trouver une forte similitude entre un vaisseau spatial simple et des acteurs. On pourrait même dire que les acteurs sont de simples machines à états finis.
Quelles sont les fonctionnalités des machines à états avancées prises en charge par SObjectizer?
Parmi les fonctionnalités ci-dessus des machines avancées à états finis, SObjectizer prend en charge tout sauf les états orthogonaux. D'autres avantages, tels que les états imbriqués, les gestionnaires d'entrée / sortie, les restrictions sur le temps passé dans l'état, l'historique des états, sont pris en charge.
Avec le soutien des états orthogonaux, la première fois n'a pas grandi ensemble. D'une part, l'architecture interne de SObjectizer n'était pas destinée à prendre en charge plusieurs états indépendants et simultanément actifs de l'agent. D'un autre côté, il existe des questions idéologiques sur le comportement d'un agent qui a des états orthogonaux. L'enchevêtrement de ces questions s'est avéré trop compliqué et l'échappement utile était trop petit pour résoudre ce problème. Oui, et dans notre pratique, il n'y a pas encore eu de situations où des états orthogonaux auraient été requis, mais cela n'aurait pas été possible, par exemple, en divisant le travail entre plusieurs agents liés à un contexte de travail commun.
Cependant, si quelqu'un a besoin d'une fonctionnalité telle que des états orthogonaux et que vous avez des exemples concrets de tâches où cela est demandé, parlons-en. Peut-être, ayant des exemples concrets sous nos yeux, nous pouvons ajouter cette fonctionnalité à SObjectizer.
A quoi ressemble la prise en charge des fonctionnalités avancées d'ICA dans le code
Dans cette partie de l'histoire, nous allons essayer de passer rapidement en revue l'API SObjectizer-5 pour travailler avec ICA. Sans entrer dans les détails, juste pour que le lecteur ait une idée de ce qu'est et à quoi il ressemble. Des informations plus détaillées, si vous le souhaitez, peuvent être trouvées
dans la documentation officielle .
États imbriqués
Pour déclarer un état imbriqué, vous devez transmettre l'expression initial_substate_of ou substate_of au constructeur de l'objet state_t correspondant:
class demo : public so_5::agent_t { state_t st_parent{this};
Si l'état S a plusieurs sous-états C1, C2, ..., Cn, alors l'un d'entre eux (et un seul) doit être marqué comme initial_substate_of. La violation de cette règle est diagnostiquée au moment de l'exécution.
La profondeur d'état maximale imbriquée dans SObjectizer-5 est limitée. Dans les versions 5.5, ce sont 16 niveaux. La violation de cette règle est diagnostiquée au moment de l'exécution.
L'astuce la plus importante avec les états imbriqués est que lorsqu'un état qui a des états imbriqués est activé, plusieurs états sont activés à la fois. Supposons qu'il existe un état A qui a les sous-états B et C, et dans le sous-état B qu'il y ait des sous-états D et E:

Lorsque l'état A est activé, alors, en fait, trois états sont activés immédiatement: A, AB et ABD
Le fait que plusieurs états puissent être actifs à la fois a l'effet le plus grave sur deux choses d'archives. Tout d'abord, pour rechercher un gestionnaire pour le prochain message entrant. Ainsi, dans l'exemple qui vient d'être montré, le gestionnaire de messages sera d'abord recherché dans l'état ABD. S'il n'y a pas de gestionnaire approprié, la recherche continuera dans son état parent, c'est-à-dire en AB Et déjà blessé, si nécessaire, la recherche se poursuivra dans l'état A.
Deuxièmement, la présence de plusieurs états actifs affecte l'ordre d'invocation des gestionnaires d'entrée / sortie pour les états. Mais cela sera discuté ci-dessous.
Gestionnaires d'E / S d'état
Pour un état, des gestionnaires d'état d'entrée et de sortie d'état peuvent être spécifiés. Cela se fait à l'aide des méthodes state_t :: on_enter et state_t :: on_exit. En règle générale, ces méthodes sont appelées dans la méthode so_define_agent () (ou directement dans le constructeur de l'agent si l'agent est trivial et que l'héritage n'est pas fourni).
class demo : public so_5::agent_t { state_t st_free{this}; state_t st_busy{this}; ... void so_define_agent() override {
Le moment le plus difficile avec les gestionnaires on_enter / on_exit est probablement de les utiliser pour les états imbriqués. Revenons à l'exemple avec les états A, B, C, D et E.

Supposons que chaque état possède un gestionnaire on_enter et on_exit.
Soit A. devenir l'état actuel de l'agent. les états A, AB et ABD sont activés Lors du changement d'état d'un agent, A.on_enter, ABon_enter et ABDon_enter seront appelés. Et dans cet ordre.
Supposons alors qu'il y ait une transition vers ABE. ABDon_exit et ABEon_enter seront appelés.
Si nous mettons alors l'agent en état AC, alors ABEon_exit, ABon_exit, ACon_enter seront appelés.
Si l'agent, étant à l'état AC, est radié, immédiatement après la fin de la méthode so_evt_finish (), les gestionnaires ACon_exit et A.on_exit seront appelés.
Délais
La limite de temps pour que l'agent reste dans un état particulier est définie à l'aide de la méthode state_t :: time_limit. Comme avec on_enter / on_exit, les méthodes time_limit sont généralement appelées lorsque l'agent est configuré pour fonctionner à l'intérieur de SObjectizer:
class led_indicator : public so_5::agent_t { state_t inactive{this}; state_t active{this}; ... void so_define_agent() override {
Si la limite de temps pour l'état est définie, dès que l'agent entre dans cet état, SObjectizer commence à compter le temps passé dans l'état. Si l'agent quitte l'état, puis revient à cet état, le compte à rebours recommence.
Si des délais sont définis pour les états intégrés, vous devez être prudent, car des trucs curieux sont possibles:
class demo : public so_5::agent_t {
Supposons qu'un agent entre dans l'état A. I.e. les états A et C sont activés pour A et C. Auparavant, il se terminait pour l'état C et l'agent passait à l'état D. Cela commencerait le compte à rebours pour rester dans l'état D. Mais le compte à rebours continuera pour rester dans A! Étant donné que pendant la transition de C à D, l'agent est resté dans l'état A. Et cinq secondes après la transition forcée de C à D, l'agent passe à l'état B.
Histoire de fortune
Par défaut, les états d'agent n'ont pas d'historique. Pour activer la sauvegarde de l'historique d'un état, transmettez la constante shallow_history (l'état aura un historique superficiel) ou deep_history (l'état aura un historique profond) au constructeur state_t. Par exemple:
class demo : public so_5::agent_t { state_t A{this, shallow_history}; state_t B{this, deep_history}; ... };
L'histoire des états est un sujet difficile, surtout lorsqu'une profondeur d'imbrication décente des états est utilisée et que les sous-états ont leur propre histoire. Par conséquent, pour des informations plus complètes sur ce sujet, il est préférable de se référer
à la documentation , pour expérimenter. Eh bien, pour nous demander si vous ne pouvez pas le découvrir vous-même
just_switch_to, transfer_to_state, supprimer
La classe state_t possède un certain nombre des méthodes les plus couramment utilisées qui ont déjà été présentées ci-dessus: event () pour abonner des événements à un message, on_enter () et on_exit () pour définir les gestionnaires d'entrée / sortie, time_limit () pour définir une limite pour le temps passé dans un état.
Parallèlement à ces méthodes, lorsque vous travaillez avec ICA, les méthodes suivantes de la classe state_t sont très utiles:
Méthode just_switch_to (), conçue pour le cas où la seule réaction à un message entrant est de transférer l'agent dans un nouvel état. Vous pouvez écrire:
some_state.just_switch_to<some_msg>(another_state);
au lieu de:
some_state.event([this](mhood_t<some_msg>) { this >>= another_state; });
La méthode transfer_to_state () est très utile lorsque nous avons un message M traité de la même manière dans deux ou plusieurs états S1, S2, ..., Sn. Mais, si nous sommes dans les états S2, ..., Sn, alors nous devons d'abord revenir à S1, et ensuite seulement faire le traitement M.
Si cela semble délicat, alors peut-être que dans un exemple de code, cette situation sera mieux comprise:
class demo : public so_5::agent_t { state_t S1{this}, S2{this}, ..., Sn{this}; ... void actual_M_handler(mhood_t<M> cmd) {...} ... void so_define_agent() override { S1.event(&demo::actual_M_handler); ...
Mais au lieu de définir des gestionnaires d'événements très similaires pour S2, ..., Sn, utilisez transfer_to_state:
class demo : public so_5::agent_t { state_t S1{this}, S2{this}, ..., Sn{this}; ... void actual_M_handler(mhood_t<M> cmd) {...} ... void so_define_agent() override { S1.event(&demo::actual_M_handler); ...
La méthode suppress () supprime une recherche de gestionnaire d'événements pour le sous-état actuel et tous ses sous-états parents. Supposons que nous ayons un état parent A dans lequel std :: abort () est appelé sur le message M. Et il y a un état enfant de B dans lequel M peut être ignoré en toute sécurité. Nous devons déterminer la réaction à M dans le sous-état B, car si nous ne le faisons pas, le gestionnaire de B se trouvera dans A. Par conséquent, nous devrons écrire quelque chose comme:
void so_define_agent() override { A.event([](mhood_t<M>) { std::abort(); }); ... B.event([](mhood_t<M>) {});
La méthode suppress () vous permet d'écrire cette situation dans le code de manière plus explicite et graphique:
void so_define_agent() override { A.event([](mhood_t<M>) { std::abort(); }); ... B.suppress<M>();
Exemple très simple
Les exemples standard de SObjectizer v.5.5 incluent un exemple simple,
blinking_led , qui simule le fonctionnement d'un indicateur LED clignotant. Le diagramme d'état de l'agent de cet exemple est le suivant:

Et voici le code d'agent complet de cet exemple:
class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } }; public : struct turn_on_off : public so_5::signal_t {}; blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { this >>= off; off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off ); blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1250}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); } };
Ici, tout le travail réel est effectué à l'intérieur des gestionnaires d'E / S pour le sous-état blink_on. De plus, les limites de la durée de séjour dans les sous-états blink_on et blink_off fonctionnent.
Pas un exemple très simple
Les exemples standard de SObjectizer v.5.5 incluent également un exemple beaucoup plus complexe,
intercom_statechart , qui imite le comportement du panneau d'interphone. Et le diagramme d'état de l'agent principal dans cet exemple ressemble à ceci:

Tout est si dur car cette imitation permet non seulement d'appeler un appartement par numéro, mais aussi des choses comme un code secret unique pour chaque appartement, ainsi qu'un code de service spécial. Ces codes vous permettent d'ouvrir la serrure de porte sans composer de numéro.
Il y a encore des choses intéressantes dans cet exemple. Mais il est trop volumineux pour être décrit en détail (même un article séparé peut ne pas être suffisant pour cela). Donc, si vous êtes intéressé par l'apparence vraiment complexe des ICA dans SObjectizer, vous pouvez le voir dans cet exemple. Et si quelque chose n'est pas clair, vous pouvez nous poser une question. Par exemple, dans les commentaires de cet article.
Est-il possible de ne pas utiliser le support de vaisseau spatial intégré à SObjectizer-5?
Ainsi, SObjectizer-5 a un support intégré pour ICA avec une très large gamme de fonctionnalités prises en charge. Ce support est fait, bien sûr, pour l'utiliser. En particulier, les mécanismes de débogage de SObjectizer, comme
le traçage de remise des messages , connaissent l'état de l'agent et affichent l'état actuel dans leurs messages de débogage respectifs.
Néanmoins, si le développeur ne souhaite pas, pour une raison quelconque, utiliser les outils intégrés de SObjectizer-5, il peut ne pas le faire.
Par exemple, vous pouvez refuser d'utiliser SObjectizer state_t et d'autres comme ça parce que state_t est un objet assez lourd avec à l'intérieur std :: string, quelques std :: function, plusieurs compteurs comme std :: size_t, cinq pointeurs vers divers objets et quelques autres bagatelles. Ensemble, cela sur Linux 64 bits et GCC-5.5, par exemple, donne 160 octets par state_t (à part ce qui peut être alloué dans la mémoire dynamique).
Si vous avez besoin, par exemple, d'un million d'agents dans l'application, dont chacun aura 10 états, la surcharge de SObjectizer state_t peut ne pas être acceptable. Dans ce cas, vous pouvez utiliser un autre mécanisme pour travailler avec des machines d'état, en déléguant manuellement le traitement des messages à ce mécanisme. Quelque chose comme:
class external_fsm_demo : public so_5::agent_t { some_fsm_type my_fsm_; ... void so_define_agent() override { so_subscribe_self() .event([this](mhood_t<msg_one> cmd) { my_fsm_.handle(*cmd); }) .event([this](mhood_t<msg_two> cmd) { my_fsm_.handle(*cmd); }) .event([this](mhood_t<msg_three> cmd) { my_fsm_.handle(*cmd); }); ... } ... };
Dans ce cas, vous payez pour l'efficacité en augmentant la quantité de travail manuel et le manque d'aide des mécanismes de débogage de SObjectizer. Mais ici, c'est au développeur de décider.
Conclusion
L'article s'est avéré volumineux, bien plus que prévu initialement. Merci à tous ceux qui ont lu cet endroit. Si l'un des lecteurs estime possible de laisser vos commentaires dans les commentaires de l'article, ce sera parfait.
Si quelque chose n'est pas clair, posez des questions, nous vous répondrons avec plaisir.
Aussi, saisissant cette opportunité, je veux attirer l'attention de ceux qui sont intéressés par SObjectizer, que les travaux ont commencé sur la prochaine version de SObjectizer dans le cadre de la branche 5.5. Brièvement sur ce qui est envisagé pour l'implémentation dans 5.5.23, décrit ici . Plus complètement, mais en anglais, ici . Vous pouvez laisser votre avis sur l'une des fonctionnalités proposées pour la mise en œuvre, ou proposer autre chose. C'est-à-dire
il existe une réelle opportunité d'influencer le développement de SObjectizer. De plus, après la sortie de la v.5.5.23, il pourrait y avoir une pause dans le travail sur le SObjectizer et la prochaine opportunité d'inclure quelque chose d'utile dans le SObjectizer 2018 pourrait ne pas être possible.