Author: | Евгений Охотников |
---|---|
Contact: | eao197 at intervale dot ru; eao197 at yahoo dot com |
Version: | 0.2 |
Date: | 2009.05.22 |
Описываемые ниже соображения являются результатом поиска возможности реализации идей:
Главной целью во время этого поиска было достижение следующей цели: состояния агентов в 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.
Из сравнения можно увидеть, что предлагаемая нотация не дает выигрыша в количестве кода, который нужно написать программисту для определения агента. Это означает, что либо данная нотация должна быть серьезно усовершенствована, либо имеет смысл использовать внешние описания, из которых будет генерироваться C++ный код.