Возможное объединение идей о жесткой проверке состояний агентов и доменов сообщений

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

Здесь приведено сравнение описания одного и того же агента из реального проекта в описанной выше нотации и в существующей нотации SObjectizer-4.

Из сравнения можно увидеть, что предлагаемая нотация не дает выигрыша в количестве кода, который нужно написать программисту для определения агента. Это означает, что либо данная нотация должна быть серьезно усовершенствована, либо имеет смысл использовать внешние описания, из которых будет генерироваться C++ный код.

Hosted by uCoz