=================================================================================== Возможное объединение идей о жесткой проверке состояний агентов и доменов сообщений =================================================================================== :Author: Евгений Охотников :Contact: eao197 at intervale dot ru; eao197 at yahoo dot com :Version: 0.2 :Date: 2009.05.22 .. _SObjectizer: http://sobjectizer.sourceforge.net .. _agent_states_in_so5: http://groups.google.com/group/sobjectizer/web/sobjectizer-6 .. _rfc_strict_state_event_checking: http://eao197.narod.ru/sobjectizer/rfc/rfc_strict_state_event_checking.html .. contents:: **Оглавление** Предисловие =========== Описываемые ниже соображения являются результатом поиска возможности реализации идей: - о поддержке состояний агентов в SObjectizer_-5 (agent_states_in_so5_); - о жестком контроле за доступностью событий в состояниях агентов (rfc_strict_state_event_checking_); - о доменах сообщений. Главной целью во время этого поиска было достижение следующей цели: состояния агентов в SObjectizer-5 должны присутствовать, нужен жесткий контроль за тем, чтобы реация на конкретное сообщение была определена в каждом состоянии, не требовалось бы использование внешних DSL для достижения этой цели. Однако, как можно увидеть в разделе `Сравнение данной нотации с нотацией SObjectizer-4`_, данное предложение пока ведет к очень многословным описаниям агента и это большой недостаток. Слоты сообщений и состояния агента ================================== Для каждого сообщения, которое агент получает, в классе агента должен быть определен слот. Например: :: class a_my_agent_t : public so_5::agent_t { // Вот слоты агента. so_5::message_slot_t< msg_start > slot_msg_start; so_5::message_slot_t< msg_shutdown > slot_msg_shutdown; so_5::message_slot_t< msg_work > slot_msg_work; public : a_my_agent_t( ... ) // Все слоты агента должны быть специальным образом // связаны с агентом в конструкторе... : slot_msg_start( so_self() ) // so_self() -- это псевдоним для *this. , slot_msg_shutdown( so_self() ) , slot_msg_work( so_self() ) ... {} ... }; В базовом классе, ``so_5::agent_t`` находится специальный объект -- ``message_slot_collector``. Этот объект собирает информацию о всех слотах агента. Для каждого состояния агента в классе агента должен быть определен атрибут специального типа. Например: :: class a_my_agent_t : public so_5::agent_t { protected : // Вот слоты агента. so_5::message_slot_t< msg_start > slot_msg_start; so_5::message_slot_t< msg_shutdown > slot_msg_shutdown; so_5::message_slot_t< msg_work > slot_msg_work; // Вот состояния агента. so_5::state_t st_not_started; so_5::state_t st_normal; so_5::state_t st_shutting_down; public : a_my_agent_t( ... ) // Все слоты агента должны быть специальным образом // связаны с агентом в конструкторе... : slot_msg_start( so_self() ) // so_self() -- это псевдоним для *this. , slot_msg_shutdown( so_self() ) , slot_msg_work( so_self() ) // Все состояния агента так же должны быть специальным образом // связаны с агентом в конструкторе... , st_not_started( so_self() ) , st_normal( so_self() ) , st_shutting_down( so_self() ) ... {} ... }; В базовом классе, ``so_5::agent_t``, находится специальный объект -- ``state_collector``. Этот объект собирает информацию обо всех состояниях агентов. Для класса агента должен быть переопределен виртуальный метод ``so_define_agent_class``, в котором необходимо определить соотношения между сообщениями, событиями и состояниями. Например: :: void a_my_agent_t::so_define_agent_class() { st_not_started .on( slot_msg_start, &a_my_agent_t::evt_start ) .on( slot_msg_shutdown, &a_my_agent_t::evt_shutdown ) .on( slot_msg_work, SO_IGNORE_MESSAGE ); st_normal .on( slot_msg_start, SO_UNEXPECTED_MESSAGE ) .on( slot_msg_shutdown, &a_my_agent_t::evt_shutdown ) .on( slot_msg_work, &a_my_agent_t::evt_work ); st_shutdown .on( slot_msg_start, SO_UNEXPECTED_MESSAGE ) .on( slot_msg_shutdown, SO_IGNORE_MESSAGE ) .on( slot_msg_work, SO_IGNORE_MESSAGE ); } После выхода из ``so_define_agent_class`` SObjectizer сможет проверить, все ли сообщения определены во всех событиях. Если какое-то сообщение не имеет заданной реакции в каком-то состоянии, то SObjectizer породит исключение и не позволит такому агенту работать в составе приложения. **Примечание.** Выше предполагалось, что метод ``so_define_agent_class`` будет вызываться всего лишь один раз -- при регистрации первого агента, тип которого не известен SObjectizer-у. Но, в принципе, можно вызывать ``so_define_agent_class`` для каждого агента. Тогда агенты одного типа смогут иметь разное распределение событий по состояниям. Только не очень понятно, зачем это надо. Какая еще практическая польза от слотов сообщений и атрибутов-состояний? ------------------------------------------------------------------------ Кроме того, что SObjectizer будет способен проверить, определена ли реакция на сообщение в конкретном состоянии, существует еще одна важная штука. Объекты ``message_slot_collector`` и ``state_collector`` смогут назначать сообщениям и событиям уникальные целочисленные идентификаторы. Причем, эти идентификаторы будут иметь небольшие значения в диапазоне [0,Mmax) и [0,Smax) (где Mmax -- это общее количество слотов сообщений в агенте, а Smax -- это общее количество состояний в агенте). После чего реакция на сообщение может быть выражена матрицей размера [Smax,Mmax]. И для того, чтобы определить, какое событие нужно вызвать в текущем состоянии при получении сообщения M достаточно обратиться к ячейке [i,j] этой матрицы (где i будет соответствовать идентификактору текущего состояния, а j -- идентификатору сообщения M). Смена состояния агента и получение текущего состояния агента ------------------------------------------------------------ Поскольку состояния агента представляются атрибутами, то смена состояния может выглядеть следующим образом: :: void a_my_agent_t::evt_start( ... ) { so_change_state( st_normal ); } А проверка текущего состояния вот так: :: void a_my_agent_t::somewhere_in_the_dark() { if( st_normal == so_current_state() ) ... else if( st_shutting_down == so_current_state() ) ... } Переопределение состояний в производных классах ----------------------------------------------- Поскольку состояния агента являются обычными атрибутами, то появляется возможность легко модифицировать состояния агента в производных классах. Например, пусть класс ``a_your_agent_t`` производен от ``a_my_agent_t`` и он в состоянии ``st_normal`` хочет обрабатывать еще одно сообщение. Вот как это будет выглядеть: :: class a_your_agent_t : public a_my_agent_t { protected : so_5::message_slot_t< msg_query_stat > slot_msg_query_stat; public : a_your_agent_t( ... ) : a_my_agent_t( ... ) , slot_msg_query_stat( so_self() ) {} void so_define_agent_class() { a_my_agent_t::so_define_agent_class(); // Поскольку появляется новое сообщение, все состояния // должны быть расширены реакцией на него. st_not_started .on( slot_msg_query_stat, SO_UNEXPECTED_MESSAGE ); st_normal .on( slot_msg_query_stat, &a_your_agent_t::evt_query_stat ); st_shutting_down .on( slot_msg_query_stat, SO_IGNORE_MESSAGE ); } ... }; Так же не сложно ввести новое состояние. Допустим, пусть агент ``a_slow_agent_t`` производен от ``a_your_agent`` и вводит новое состояние ``st_starting``, в которое агент должен перейти после получения ``msg_start`` :: class a_slow_agent_t : public a_your_agent_t { protected : so_5::state_t st_starting; public : a_slow_agent_t( ... ) : a_your_agent_t( ... ) , st_starting( so_self() ) {} void so_define_agent_class() { a_your_agent_t::so_define_agent_class(); // Поскольку новых сообщений не вводится, то и старые // состояния не нуждаются в модификации. За исключением // состояния st_not_started, в котором нужно предусмотреть // новую реакцию на msg_start. st_not_started .on( slot_msg_start, &a_slow_agent_t::evt_new_msg_start_action ); // Но новое состояние должно быть определено полностью. st_starting .on( slot_msg_start, SO_UNEXPECTED_MESSAGE ) .on( slot_msg_shutdown, SO_IGNORE_MESSAGE ) .on( slot_msg_work, SO_IGNORE_MESSAGE ) .on( slot_msg_query_stat, &a_slow_agent_t::evt_query_stat_when_starting ); } }; Так же в классе ``so_5::state_t`` могут быть методы ``clear`` для полной очистки сообщения (с его помощью производный класс сможет полностью переопределять состояние, описанное в базовом классе) и ``merge`` для слияния содержимого состояний (аналог сегодняшнего макроса ``SOL4_STATE_MERGE``). Огранизация доменов сообщений в виде атрибутов агентов ====================================================== В `обсуждении SObjectizer-5 всплыло понятие домена сообщений `__. При этом домены сообщений воспринимались мной как: - альтернатива понятию "владения сообщением"; - как объект, за которым будет спрятана информация о подписчиках данного сообщения; - как объект, который будет использоваться для отсылки экземпляров данного сообщения. При этом домен сообщения оказывался глобальным, тогда как понятие владения сообщением в SObjectizer-4 позволяло выделять из всего множества однотипных сообщений в приложении только те сообщения, которые касаются конкретного агента. В принципе, можно совместить понятия "владения сообщениями" и домена сообщения. Для этого необходимо, чтобы домены сообщений были атрибутами класса агента. Например, пусть агент ``a_my_agent_t`` владеет двумя сообщениями: :: class a_my_agent_t : public so_5::agent_t { ... // Слоты сообщений, состояния, ... public : // Домены сообщений, которыми владеет данный агент. so_5::message_domain_t< msg_shutdown > msg_shutdown; so_5::message_domain_t< msg_work > msg_work; a_my_agent_t() ... // Инициализация слотов и состояний. , msg_shutdown( so_self() ) , msg_work( so_self() ) {} ... }; Если адресация агентов в SObjectizer-5 будет идти через умные указатели, то отсылка сообщений агентов будет похожа, на вызовы нестатических методов у агентов-владельцев сообщений: :: so_5::agent_ref_t< a_my_agent_t > my_agent( lockup_agent( ... ) ); my_agent->msg_work.send( ... ); Похожим образом будет выглядеть и подписка агента на сообщения: :: void a_my_agent_t::so_on_subscription( so_5::runtime_t & runtime ) { // Сообщение msg_start принадлежит агенту a_sobjectizer. slot_msg_start.connect( runtime.sobjectizer_agent()->msg_start ); // Остальные сообщения наши собственные. slot_msg_shutdown.connect( msg_shutdown ); slot_msg_work.connect( msg_work ); } Т.е. получается, что остается понятие владения сообщениями. И при этом нельзя подписаться на сообщение, не имея ссылки на агента-владельца этого сообщения. А так же, нельзя подписаться, если типы инцидентов не совпадают. Сравнение данной нотации с нотацией SObjectizer-4 ================================================= `Здесь `__ приведено сравнение описания одного и того же агента из реального проекта в описанной выше нотации и в существующей нотации SObjectizer-4. Из сравнения можно увидеть, что предлагаемая нотация не дает выигрыша в количестве кода, который нужно написать программисту для определения агента. Это означает, что либо данная нотация должна быть серьезно усовершенствована, либо имеет смысл использовать внешние описания, из которых будет генерироваться C++ный код. .. vim:ts=2:sw=2:sts=2:expandtab:tw=78:ft=rst: