Tratemos de hablar sobre máquinas de estado jerárquicas en general y su soporte en SObjectizer-5 en particular

Las máquinas de estados finitos son quizás uno de los conceptos más fundamentales y ampliamente utilizados en la programación. Las máquinas de estado finito (KA) se utilizan activamente en muchos nichos aplicados. En particular, en nichos como APCS y telecomunicaciones, con los que fue posible tratar, las naves espaciales se encuentran con un poco menos de frecuencia que en cada paso.

Por lo tanto, en este artículo intentaremos hablar sobre naves espaciales, principalmente sobre máquinas de estados finitos jerárquicos y sus capacidades avanzadas. Y cuente un poco sobre el soporte para naves espaciales en SObjectizer-5 , el marco "actor" para C ++. Uno de esos dos pocos que están abiertos, libres, multiplataforma y aún vivos.

Incluso si no está interesado en SObjectizer, pero nunca ha oído hablar de máquinas de estados finitos jerárquicos o de lo útiles que son las funciones avanzadas de la nave espacial como manejadores de entrada / salida para estados o la historia del estado, entonces puede estar interesado en mirar debajo del gato y lea al menos la primera parte del artículo.

Palabras generales sobre máquinas de estados finitos


No trataremos de llevar a cabo un programa educativo completo en el artículo sobre el tema de autómatas y una variedad tal como máquinas de estados finitos . El lector debe tener al menos una comprensión básica de este tipo de entidades.

Máquinas avanzadas de estados finitos y sus capacidades.


La nave espacial tiene varias características "avanzadas" que aumentan en gran medida la usabilidad de la nave espacial en el programa. Echemos un vistazo rápido a estas funciones "avanzadas".

Descargo de responsabilidad: si el lector conoce bien los diagramas de estado de UML, entonces no encontrará nada nuevo para él aquí.

Máquinas de estado jerárquico


Quizás la oportunidad más importante y valiosa es la organización de una jerarquía / anidación de estados. Dado que es precisamente la capacidad de poner estados entre sí lo que elimina la "explosión" del número de transiciones de un estado a otro a medida que aumenta la complejidad de la nave espacial.

Es más difícil explicar esto con palabras que mostrar con el ejemplo. Por lo tanto, imaginemos que tenemos un infokiosk en la pantalla del cual se muestra primero un mensaje de bienvenida. El usuario puede seleccionar el elemento "Servicios" e ir a la sección para seleccionar los servicios que necesita. O puede seleccionar el elemento "Cuenta personal" e ir a la sección sobre cómo trabajar con sus datos y servicios personales. O puede seleccionar la sección de Ayuda. Hasta ahora, todo parece ser simple y puede representarse mediante el siguiente diagrama de estado (lo más simplificado posible):



Pero tratemos de asegurarnos de que al hacer clic en el botón "Cancelar", el usuario pueda regresar de cualquier sección a la página de inicio con un mensaje de bienvenida:



El esquema se está volviendo complicado, pero aún está bajo control. Sin embargo, recordemos que en la sección "Servicios" podemos tener varias subsecciones más, por ejemplo, "Servicios populares", "Nuevos servicios" y "Lista completa". Y de cada una de estas secciones también debe volver a la página de inicio. Nuestra nave espacial simple se está volviendo cada vez más difícil:



Pero esto está lejos de todo. Todavía no hemos tenido en cuenta el botón "Atrás", por el cual debemos volver a la sección anterior. Agreguemos una reacción al botón "Atrás" y veamos qué obtenemos:



Sí, ahora vemos el camino hacia la verdadera diversión. Pero ni siquiera hemos considerado las subsecciones en las secciones "Mi cuenta" y "Ayuda" ... Si comenzamos, entonces casi de inmediato, nuestra simple nave espacial, al principio, se convertirá en algo inimaginable.

Aquí la anidación de estados viene en nuestra ayuda. Imaginemos que solo tenemos dos estados de nivel superior: WelcomeScreen y UserSelection. Todas nuestras secciones (es decir, "Servicios", "Mi cuenta" y "Ayuda") estarán "anidadas" en el estado de Selección de usuario. Puede decir que los estados de ServicesScreen, ProfileScreen y HelpScreen serán hijos de UserSelection. Y como son niños, heredarán la reacción a algunas señales de su estado parental. Por lo tanto, podemos definir la respuesta al botón Cancelar en UserSelection. Pero no necesitamos determinar esta reacción en todos los subestados subsidiarios. Lo que hace que nuestra nave espacial sea más concisa y comprensible:



Aquí puede observar que la reacción para "Cancelar" y "Atrás" la definimos en UserSelection. Y esta reacción al botón Cancelar funciona para todos sin excepción de los subestados de UserSelection (incluido otro subestado de ServicesSelection compuesto). Pero en el subestado de ServicesSelection, la reacción al botón Atrás ya es diferente: el retorno no está en WelcomScreen, sino en ServicesScreen.

Las CA que usan una jerarquía / anidamiento de estados se denominan máquinas jerárquicas de estado finito (ICA).

Reacción a la entrada / salida hacia / desde el estado


Una característica muy útil es la capacidad de asignar una respuesta al entrar en un estado particular, así como una reacción al salir de un estado. Entonces, en el ejemplo anterior con un infokiosk, se puede colgar un controlador para ingresar a cada uno de los estados, lo que cambiará el contenido de la pantalla del infokiosk.

El ejemplo anterior se puede ampliar un poco. Supongamos que tenemos dos subestados en WelcomScreen: BrightWelcomScreen, en el que la pantalla se resaltará normalmente, y DarkWelcomScreen, en el que se reducirá el brillo de la pantalla. Podemos hacer un controlador de entrada DarkWelcomScreen que atenuará la pantalla. Y un controlador de salida DarkWelcomScreen que restaurará el brillo normal.



Cambio automático de estado después de un tiempo establecido


A veces, puede ser necesario limitar la permanencia de la nave espacial en un estado particular. Entonces, en el ejemplo anterior, podemos limitar el tiempo que nuestro ICA permanece en el estado de BrightWelcomScreen a un minuto. Tan pronto como expire el minuto, el ICA cambia automáticamente al estado DarkWelcomScreen.

Historia de naves espaciales


Otra característica muy útil de ICA es la historia del estado de la nave espacial.

Imaginemos que tenemos algún tipo de ICA abstracto de este tipo:



Este nuestro ICA puede ir de TopLevelState1 a TopLevelState2 y viceversa. Pero dentro de TopLevelState1 hay varios estados anidados. Si el ICA simplemente se mueve de TopLevelState2 a TopLevelState1, se activan dos estados inmediatamente: TopLevelState1 y NestedState1. NestedState1 se activa porque es el subestado inicial del estado TopLevelState1.

Ahora imagine que aún más nuestro ICA cambió su estado de NestedState1 a NestedState2. Dentro de NestedState2, se activó el SubState InternalState1 (ya que es el subestado inicial de NestedState2). Y desde InternalState1 fuimos a InternalState2. Por lo tanto, simultáneamente tenemos activos los siguientes estados: TopLevelState1, NestedState2 e InternalState2. Y aquí vamos a TopLevelState2 (es decir, generalmente dejamos TopLevelState1).

Activo se convierte en TopLevelState2. Después de lo cual queremos volver a TopLevelState1. Está en TopLevelState1, y no en ningún subestado particular en TopLevelState1.

Entonces, desde TopLevelState2 vamos a TopLevelState1 y ¿a dónde llegamos?

Si TopLevelState1 no tiene historial, llegaremos a TopLevelState1 y NestedState1 (ya que NestedState1 es el subestado inicial de TopLevelState1). Es decir toda la historia sobre las transiciones dentro de TopLevelState1, que tuvo lugar antes de abandonar TopLevelState2, se perdió por completo.

Si TopLevelState1 tiene un llamado historial poco profundo, luego, al regresar de TopLevelState2 a TopLevelState1, ingresamos en NestedState2 e InternalState1. Entramos en NestedState2 porque está registrado en el historial de estado de TopLevelState1. Y llegamos a InternalState1 porque es el inicio de NestedState2. Resulta que en la historia superficial de TopLevelState1, la información se almacena solo sobre los subestados del primer nivel. La historia de los estados integrados en estos subestados no se conserva.

Pero si TopLevelState1 tiene un historial profundo, entonces cuando volvemos de TopLevelState2 a TopLevelState1 nos metemos en NestedState2 e InternalState2. Porque en una historia profunda, se almacena información completa sobre los subestados activos, independientemente de su profundidad.

Estados ortogonales


Hasta ahora, hemos examinado ICA en el que solo uno de los subestados podría estar activo dentro del estado. Pero a veces puede haber situaciones en las que en un estado particular de ICA debería haber varios subestados activos simultáneamente. Dichos subestados se denominan estados ortogonales.

Un ejemplo clásico que demuestra estados ortogonales es el teclado de computadora familiar y sus modos NumLock, CapsLock y ScrollLock. Podemos decir que el trabajo con NumLock / CapsLock / ScrollLock se describe en subestados ortogonales dentro del estado Activo:



Todo lo que querías saber sobre las máquinas de estados finitos, pero ...


En general, hay un artículo fundamental sobre notación formal para diagramas de estado de David Harel: Statecharts: A Visual Formalism For Complex Systems (1987) .

Allí, se examinan varias situaciones que se pueden encontrar al trabajar con máquinas de estados finitos utilizando el ejemplo de controlar un reloj electrónico ordinario. Si alguien no lo ha leído, lo recomiendo encarecidamente. Básicamente, todo lo que Harel describió luego entró en notación UML. Pero cuando lee la descripción de los diagramas de estado del UML, no siempre comprende qué, por qué y cuándo lo necesita. Pero en el artículo de Harel, la presentación va desde situaciones simples a situaciones más complejas. Y conoce mejor toda la potencia que las máquinas de estados finitos esconden en sí mismas.

Máquinas de estados finitos en SObjectizer


Además hablaremos sobre SObjectizer y sus detalles. Si no comprende los ejemplos a continuación, podría tener sentido aprender más sobre SObjectizer. Por ejemplo, de nuestro artículo de revisión sobre SObjecizer y varios posteriores que presentan a los lectores SObjectizer, pasando de simple a complejo ( primer artículo, segundo y tercero ).

Los agentes en SObjectizer son máquinas de estado


Los agentes en SObjectizer desde el principio eran máquinas de estados con estados explícitos. Incluso si el desarrollador del agente no describió ninguno de sus propios estados en su clase de agente, el agente todavía tenía un estado predeterminado, que se utilizó de manera predeterminada. Por ejemplo, si un desarrollador hizo un agente tan trivial:
class simple_demo final : public so_5::agent_t { public: //   ,       . struct how_are_you final : public so_5::signal_t {}; //   ,     . struct quit final : public so_5::signal_t {}; // ..   ,      . simple_demo(context_t ctx) : so_5::agent_t{std::move(ctx)} { so_subscribe_self() .event<how_are_you>([]{ std::cout << "I'm fine!" << std::endl; }) .event<quit>([this]{ so_deregister_agent_coop_normally(); }); } }; 

entonces puede que ni siquiera sospeche que en realidad todas las suscripciones que hizo están hechas para el estado predeterminado. Pero si el desarrollador agrega sus propios estados al agente, entonces ya debe pensar en firmar correctamente el agente en el estado correcto. Aquí, digamos, una modificación incorrecta simple (y, como de costumbre) del agente que se muestra arriba:
 class simple_demo final : public so_5::agent_t { // ,  ,   . state_t st_free{this}; // ,  ,   . state_t st_busy{this}; public: //   ,       . struct how_are_you final : public so_5::signal_t {}; //   ,     . struct quit final : public so_5::signal_t {}; // ..   ,      . simple_demo(context_t ctx) : so_5::agent_t{std::move(ctx)} { so_subscribe_self() .event<quit>([this]{ so_deregister_agent_coop_normally(); }); //   how_are_you   ,    . st_free.event([]{ std::cout << "I'm free" << std::endl; }); st_busy.event([]{ std::cout << "I'm busy" << std::endl; }); //     st_free. this >>= st_free; } }; 

Establecemos dos manejadores diferentes para la señal how_are_you, cada uno para su propio estado.

Y el error en esta modificación del agente simple_demo es que al estar en st_free o st_busy el agente no responderá para dejar de fumar, porque Dejamos la suscripción de abandono en el estado predeterminado, pero no realizamos las suscripciones correspondientes para st_free y st_busy. Una forma simple y obvia de solucionar este problema es agregar las suscripciones apropiadas a st_free y st_busy:
  simple_demo(context_t ctx) : so_5::agent_t{std::move(ctx)} { //   how_are_you   ,    . st_free .event([]{ std::cout << "I'm free" << std::endl; }) .event<quit>([this]{ so_deregister_agent_coop_normally(); }); st_busy .event([]{ std::cout << "I'm busy" << std::endl; }) .event<quit>([this]{ so_deregister_agent_coop_normally(); }); //     st_free. this >>= st_free; } 

Es cierto que este método huele a copiar y pegar, lo que no es bueno. Puede deshacerse de copiar y pegar ingresando un estado padre común para st_free y st_busy:
 class simple_demo final : public so_5::agent_t { //      . state_t st_basic{this}; // ,  ,   . //      st_basic. state_t st_free{initial_substate_of{st_basic}}; // ,  ,   . //     st_basic. state_t st_busy{substate_of{st_basic}}; public: //   ,       . struct how_are_you final : public so_5::signal_t {}; //   ,     . struct quit final : public so_5::signal_t {}; // ..   ,      . simple_demo(context_t ctx) : so_5::agent_t{std::move(ctx)} { //   quit   st_basic    //  ""  . st_basic.event<quit>([this]{ so_deregister_agent_coop_normally(); }); //   how_are_you   ,    . st_free.event([]{ std::cout << "I'm free" << std::endl; }); st_busy.event([]{ std::cout << "I'm busy" << std::endl; }); //     st_free. this >>= st_free; } }; 

En aras de la justicia, debe agregarse que inicialmente en SObjectizer los agentes solo podrían ser máquinas de estado simples. El apoyo a las naves espaciales jerárquicas apareció relativamente recientemente, en enero de 2016.

¿Por qué los agentes SObjectizer son máquinas de estados finitos?


Esta pregunta tiene una respuesta muy simple: sucedió que las raíces de SObjectizer crecen del mundo de los sistemas de control de procesos, y allí las máquinas de estado finito se usan con mucha frecuencia. Por lo tanto, consideramos necesario que los agentes en SObjectizer también sean máquinas de estado. Esto es muy conveniente si en la aplicación para qué SObjectizer están tratando de aplicar, se utilizan CAs. Y el estado predeterminado, que tienen todos los agentes, nos permite no pensar en naves espaciales si no se requiere el uso de naves espaciales.

En principio, si observa el modelo de actores en sí y los principios sobre los que se basa este modelo:

  • un actor es una entidad con comportamiento;
  • los actores responden a los mensajes entrantes;
  • Una vez recibido el mensaje, el actor puede:
    • enviar un cierto número de mensajes a otros actores;
    • crear una serie de nuevos actores;
    • Defina un nuevo comportamiento para procesar mensajes posteriores.

Uno puede encontrar una fuerte similitud entre una nave espacial simple y actores. Incluso se podría decir que los actores son simples máquinas de estados finitos.

¿Qué características de las máquinas de estado avanzadas admite SObjectizer?


De las características anteriores de las máquinas de estados finitos avanzados, SObjectizer admite todo excepto los estados ortogonales. Se admiten otras ventajas, como estados anidados, manejadores de entrada / salida, restricciones en el tiempo pasado en el estado, historial de los estados.

Con el apoyo de los estados ortogonales, la primera vez no creció juntos. Por un lado, la arquitectura interna de SObjectizer no estaba destinada a admitir varios estados independientes y simultáneamente activos del agente. Por otro lado, hay preguntas ideológicas sobre cómo debe comportarse un agente que tiene estados ortogonales. La maraña de estas preguntas resultó ser demasiado complicada, y el escape útil fue demasiado pequeño para resolver este problema. Sí, y en nuestra práctica, todavía no ha habido situaciones en las que se hubieran requerido estados ortogonales, pero habría sido imposible hacerlo, por ejemplo, dividiendo el trabajo entre varios agentes vinculados a un contexto de trabajo común.

Sin embargo, si alguien necesita una función, como los estados ortogonales, y tiene ejemplos del mundo real de tareas en las que esto está en demanda, hablemos. Quizás, teniendo ejemplos concretos ante nuestros ojos, podamos agregar esta función a SObjectizer.

Cómo se ve en el código el soporte para funciones avanzadas de ICA


En esta parte de la historia, intentaremos revisar rápidamente la API SObjectizer-5 para trabajar con ICA. Sin profundizar en los detalles, solo para que el lector tenga una idea de qué es y cómo se ve. Si lo desea, puede encontrar información más detallada en la documentación oficial .

Estados anidados


Para declarar un estado anidado, debe pasar la expresión initial_substate_of o substate_of al constructor del objeto state_t correspondiente:
 class demo : public so_5::agent_t { state_t st_parent{this}; //  . state_t st_first_child{initial_substate_of{st_parent}}; //   . //    . state_t st_second_child{substate_of{st_parent}}; //   . state_t st_third_child{substate_of{st_parent}}; //   . state_t st_first_grandchild{initial_substate_of{st_third_child}}; //    . state_t st_second_grandchild{substate_of{st_third_child]}; ... }; 

Si el estado S tiene varios subestados C1, C2, ..., Cn, entonces uno de ellos (y solo uno) debe marcarse como initial_substate_of. La violación de esta regla se diagnostica en tiempo de ejecución.

La profundidad máxima de anidación de estado en SObjectizer-5 es limitada. En las versiones 5.5, estos son 16 niveles. La violación de esta regla se diagnostica en tiempo de ejecución.

El truco más importante con los estados anidados es que cuando se activa un estado que tiene estados anidados, se activan varios estados a la vez. Supongamos que hay un estado A que tiene los subestados B y C, y en el subestado B hay los subestados D y E:



Cuando se activa el estado A, entonces, de hecho, se activan tres estados inmediatamente: A, AB y ABD

El hecho de que varios estados puedan estar activos a la vez tiene el efecto más grave en dos cosas de archivo. En primer lugar, buscar un controlador para el próximo mensaje entrante. Entonces, en el ejemplo que se muestra, primero se buscará el controlador de mensajes en el estado ABD. Si no hay un controlador adecuado allí, la búsqueda continuará en su estado principal, es decir, en AB Y ya herido, si es necesario, la búsqueda continuará en el estado A.

En segundo lugar, la presencia de varios estados activos afecta el orden de invocación de los manejadores de entrada / salida para los estados. Pero esto se discutirá a continuación.

Manejadores de E / S estatales


Para un estado, se pueden especificar controladores de estado de entrada y salida de estado. Esto se hace usando los métodos state_t :: on_enter y state_t :: on_exit. Por lo general, estos métodos se invocan en el método so_define_agent () (o directamente en el constructor del agente si el agente es trivial y no se proporciona la herencia del mismo).
 class demo : public so_5::agent_t { state_t st_free{this}; state_t st_busy{this}; ... void so_define_agent() override { // :       , //     . st_free.on_enter([]{ ... }); st_busy.on_exit([]{ ...}); ... this >>= st_free; } ... }; 

Probablemente el momento más difícil con los controladores on_enter / on_exit es usarlos para estados anidados. Volvamos al ejemplo con los estados A, B, C, D y E.



Supongamos que cada estado tiene un controlador on_enter y on_exit.

Deje que A. se convierta en el estado actual del agente. los estados A, AB y ABD se activan Durante el cambio de estado de un agente, se llamará a A.on_enter, ABon_enter y ABDon_enter. Y en ese orden.

Supongamos que hay una transición a ABE. Se llamará a ABDon_exit y ABEon_enter.

Si luego ponemos al agente en estado de CA, se llamará a ABEon_exit, ABon_exit, ACon_enter.

Si el agente, que se encuentra en el estado de CA, se da de baja, inmediatamente después de completar el método so_evt_finish (), se llamarán los controladores ACon_exit y A.on_exit.

Plazos


El límite de tiempo para que el agente permanezca en un estado particular se establece utilizando el método state_t :: time_limit. Al igual que con on_enter / on_exit, los métodos time_limit generalmente se llaman donde el agente está configurado para funcionar dentro del SObjectizer:
 class led_indicator : public so_5::agent_t { state_t inactive{this}; state_t active{this}; ... void so_define_agent() override { //        15s. //        inactive. active.time_limit(15s, inactive); ... } ... }; 

Si se establece el límite de tiempo para el estado, tan pronto como el agente ingrese a este estado, SObjectizer comienza a contar el tiempo pasado en el estado. Si el agente abandona el estado y luego vuelve a este estado nuevamente, la cuenta regresiva comienza nuevamente.

Si se establecen límites de tiempo para los estados incrustados, entonces debe tener cuidado, porque Son posibles trucos curiosos:
 class demo : public so_5::agent_t { //   . state_t A{this}, B{this}; //   first . state_t C{initial_substate_of{A}}, st_D{substate_of{A}}; ... void so_define_agent() override { A.time_limit(15s, B); C.time_limit(10s, D); D.time_limit(20s, C); ... } ... }; 

Supongamos que un agente entra en el estado A. I.e. los estados A y C se activan tanto para A como para C. Anteriormente, finalizará para el estado C y el agente cambiará al estado D. Esto comenzará la cuenta regresiva para permanecer en el estado D. ¡Pero la cuenta regresiva continuará para permanecer en A! Como durante la transición de C a D, el agente continuó en el estado A. Y cinco segundos después de la transición forzada de C a D, el agente pasará al estado B.

Historia de fortuna


Por defecto, los estados del agente no tienen un historial. Para activar el ahorro de historial para un estado, pase la constante shallow_history (el estado tendrá un historial superficial) o deep_history (el estado tendrá un historial profundo) al constructor state_t. Por ejemplo:
 class demo : public so_5::agent_t { state_t A{this, shallow_history}; state_t B{this, deep_history}; ... }; 

La historia de los estados es un tema difícil, especialmente cuando se utiliza una profundidad de anidación decente de los estados y los subestados tienen su propia historia. Por lo tanto, para obtener información más completa sobre este tema, es mejor consultar la documentación y experimentar. Bueno, para preguntarnos si no puede resolverlo usted mismo;)

just_switch_to, transfer_to_state, suprimir


La clase state_t tiene varios de los métodos más comúnmente utilizados que ya se han mostrado anteriormente: evento () para suscribir eventos a un mensaje, on_enter () y on_exit () para configurar controladores de entrada / salida, time_limit () para establecer un límite para el tiempo pasado en un estado.

Junto con estos métodos, cuando se trabaja con ICA, los siguientes métodos de la clase state_t son muy útiles:

Método just_switch_to (), que está diseñado para el caso en que la única reacción a un mensaje entrante es transferir el agente a un nuevo estado. Puedes escribir:
 some_state.just_switch_to<some_msg>(another_state); 

en lugar de:
 some_state.event([this](mhood_t<some_msg>) { this >>= another_state; }); 

El método transfer_to_state () es muy útil cuando tenemos algún mensaje M procesado de la misma manera en dos o más estados S1, S2, ..., Sn. Pero, si estamos en los estados S2, ..., Sn, primero tenemos que volver a S1, y solo entonces hacemos el procesamiento M.

Si esto suena complicado, entonces tal vez en un ejemplo de código se entenderá mejor esta situación:
 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); ... //           S1, //      M  . S2.event([this](mhood_t<M> cmd) { this >>= S1; actual_M_handler(cmd); }); ... //      . Sn.event([this](mhood_t<M> cmd) { this >>= S1; actual_M_handler(cmd); }); } ... }; 

Pero en lugar de definir controladores de eventos muy similares para S2, ..., Sn, use 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); ... //           S1, //      M  . S2.transfer_to_state<M>(S1); ... //      . Sn.transfer_to_state<M>(Sn); } ... }; 

El método suppress () suprime una búsqueda de controlador de eventos para el subestado actual y todos sus subestados principales. Supongamos que tenemos un estado primario A en el que se llama std :: abort () en el mensaje M. Y hay un estado secundario de B en el que M puede ignorarse con seguridad. Debemos determinar la reacción a M en el subestado B, porque si no lo hacemos, entonces el controlador para B se encontrará en A. Por lo tanto, necesitaremos escribir algo como:
 void so_define_agent() override { A.event([](mhood_t<M>) { std::abort(); }); ... B.event([](mhood_t<M>) {}); //    ,      //   M   . ... } 

El método suppress () le permite escribir esta situación en código de manera más explícita y gráfica:
 void so_define_agent() override { A.event([](mhood_t<M>) { std::abort(); }); ... B.suppress<M>(); //    ,      //   M   . ... } 

Ejemplo muy simple


Los ejemplos estándar de SObjectizer v.5.5 incluyen un ejemplo simple, parpadeando_led , que simula el funcionamiento de un indicador LED parpadeante. El diagrama de estado del agente de este ejemplo es el siguiente:



Y aquí está el código de agente completo de este ejemplo:
 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 ); } }; 

Aquí, todo el trabajo real se realiza dentro de los manejadores de E / S para el subestado blink_on. Bueno, además, limita la duración de la estadía en el trabajo de los subestaciones blink_on y blink_off.

No es un ejemplo muy simple.


Los ejemplos estándar de SObjectizer v.5.5 también incluyen un ejemplo mucho más complejo, intercom_statechart , que imita el comportamiento del panel del interfono. Y el diagrama de estado del agente principal en este ejemplo se ve así:



Todo es tan duro porque esta imitación admite no solo llamar a un departamento por número, sino también cosas como un código secreto único para cada departamento, así como un código de servicio especial. Estos códigos le permiten abrir la cerradura de la puerta sin marcar en ningún lado.

Todavía hay cosas interesantes en este ejemplo. Pero es demasiado grande para ser descrito en detalle (incluso un artículo separado puede no ser suficiente para esto). Entonces, si está interesado en cómo se ven los ICA realmente complejos en SObjectizer, puede ver en este ejemplo. Y si algo no está claro, puede hacernos una pregunta. Por ejemplo, en los comentarios a este artículo.

¿Es posible no usar el soporte para naves espaciales integradas en SObjectizer-5?


Entonces, SObjectizer-5 tiene soporte integrado para ICA con una amplia gama de características compatibles. Este soporte se hace, por supuesto, para usarlo. En particular, los mecanismos de depuración de SObjectizer, como el rastreo de entrega de mensajes , conocen el estado del agente y muestran el estado actual en sus respectivos mensajes de depuración.

Sin embargo, si el desarrollador no quiere, por algún motivo, utilizar las herramientas integradas SObjectizer-5, entonces puede que no lo haga.

Por ejemplo, puede negarse a utilizar SObjectizer state_t y otros similares porque state_t es un objeto bastante pesado con std :: string, un par de funciones std ::, varios contadores como std :: size_t, cinco punteros a varios objetos y alguna otra bagatela. Juntos, esto en Linux de 64 bits y GCC-5.5, por ejemplo, proporciona 160 bytes por state_t (aparte de lo que se puede asignar en la memoria dinámica).

Si necesita, por ejemplo, un millón de agentes en la aplicación, cada uno de los cuales tendrá 10 estados, entonces la sobrecarga de SObjectizer state_t puede no ser aceptable. En este caso, puede usar algún otro mecanismo para trabajar con máquinas de estado, delegando manualmente el procesamiento de mensajes a este mecanismo. Algo como:
 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); }); ... } ... }; 

En este caso, está pagando por la eficiencia al aumentar la cantidad de trabajo manual y la falta de ayuda de los mecanismos de depuración de SObjectizer. Pero aquí depende del desarrollador decidir.

Conclusión


El artículo resultó ser voluminoso, mucho más de lo planeado originalmente. Gracias a todos los que leyeron a este lugar. Si uno de los lectores considera que es posible dejar sus comentarios en los comentarios al artículo, entonces será genial.

Si algo no está claro, entonces haga preguntas, le responderemos con gusto.

Además, aprovechando esta oportunidad, quiero llamar la atención de aquellos interesados ​​en SObjectizer, que el trabajo ha comenzado en la próxima versión de SObjectizer en el marco de la rama 5.5. Brevemente sobre lo que se considera para la implementación en 5.5.23, descrito aquí . Más completamente, pero en inglés, aquí . Puede dejar su opinión sobre cualquiera de las características propuestas para la implementación u ofrecer algo más. Es decirExiste una oportunidad real de influir en el desarrollo de SObjectizer. Además, después del lanzamiento de v.5.5.23, puede haber una pausa en el trabajo en SObjectizer y la próxima oportunidad de incluir algo útil en SObjectizer 2018 puede no ser posible.

Source: https://habr.com/ru/post/es423497/


All Articles