Author: | Евгений Охотников |
---|---|
Contact: | eao197 at intervale dot ru; eao197 at yahoo dot com |
Date: | 2007.07.22 |
SObjectizer-4 не использовал внешних языков для описания классов агентов поскольку первая версия SObjectizer-4 создавалась в очень сжатые сроки и на разработку специализированного языка (DSL -- Domain Specific Language) просто не было времени. Вероятно, на тот момент это было правильное решение, которое позволило получить работающий SObjectizer и применить его в реальных проектах.
По прошествии пяти лет использования SObjectizer-4 возникает вопрос о то, каким будет SObjectizer-5. Возможно, следующие версии SObjectizer либо будут серьезно переделаны по сравнению с SObjectizer-4, либо вообще будут созданы для других языков программирования (D/C#/Java/Scala/Eiffel, конкретный язык еще не выбран). Поэтому есть смысл вернуться к идее внешнего DSL для SObjectizer.
Сейчас DSL может использоваться для описания класса агента. Так, вместо макросов:
SOL4_CLASS_START( a_creature_t ) SOL4_EVENT( evt_start ) SOL4_EVENT_STC( evt_meet_completed, a_meeting_place_t::msg_meet_completed ) SOL4_STATE_START( st_normal ) SOL4_STATE_EVENT( evt_start ) SOL4_STATE_EVENT( evt_meet_completed ) SOL4_STATE_FINISH() SOL4_CLASS_FINISH()
возможно более компактное описание:
{agent_class a_creature_t {event evt_start} {event evt_meet_completed {by msg_meet_completed {of a_meeting_place_t} } } } {state st_normal {events evt_start evt_meet_completed } } }
Это описание помещается в отдельный файл (например, a_creature.sol5) и обрабатывается специальным транслятором DSL. На выходе транслятора получается набор hpp/cpp файлов, которые подключаются в основной исходный текст через директивы #include. (В случае отличных от C++ языков программирования на выходе могут быть какие-нибудь классы/интерфейсы, которые содержат вспомогательный код для SObjectizer-а).
Сейчас SObjectizer Run-Time вынужден обрабатывать динамическое изменение состава классов агентов и их взаимосвязей. Поскольку за макросами SOL4_* скрываются глобальные переменные, которые в собственных конструкторах передают в системный словарь SObjectizer-а описания классов агентов. Возможность динамической загрузки/выгрузки DLL с агентами приводит к ситуации, когда SObjectizer должен реагировать на появление и исчезновения классов агентов. Что особенно сложно при обработке наследования для агентов и мержинге состояний агентов.
При наличии же DSL эту работу может выполнять транслятор DSL. Так, если агент использует наследование и мержинг состояний, то все конфликты будут выявлены еще при компиляции, а не при запуске приложения. Для этого при описании в DSL класса-наследника нужно будет сделать для транслятора доступными описания базовых классов. Например, такое описание:
SOL4_CLASS_START( a_derived_t ) SOL4_SUPER_CLASS( a_base_t ) SOL4_INITIAL_STATE( st_normal ) SOL4_MSG_START( msg_2, a_derived_t::msg_2 ) SOL4_MSG_FINISH() SOL4_EVENT( evt_msg_2 ) SOL4_STATE_START( st_normal ) SOL4_STATE_MERGE( a_base_t, st_normal ) SOL4_STATE_EVENT( evt_msg_2 ) SOL4_STATE_FINISH() SOL4_CLASS_FINISH()
в DSL может выглядеть как:
{include "a_base.sol5" } {agent_class a_derived_t {super a_base_t } {initial_state st_normal } {message msg_2 {of a_derived_t::msg_2} } {event evt_msg_2} {state st_normal {merge {with st_normal} {from a_base_t}} {events evt_msg_2} } }
Поскольку транслятор DSL будет иметь всю необходимую информацию, то в сгенерированном вспомогательном коде будет находиться полностью готовое описание класса агента для SObjectizer. Это описание уже не нужно будет изменять во время работы SObjectizer.
Таким образом из SObjectizer Run-Time изрядный кусок работы будет перемещен в транслятор DSL. Что благоприятно скажется на объеме и сложности ядра SObjectizer. Например, транслятор DSL может быть написан на одном языке программирования (Ruby, к примеру), а ядро SObjectizer может быть реализовано для ряда других языков (C++, D, Scala).
Справедливую критику в SObjectizer вызывает то, что для идентификации сообщений, событий и состояний в SObjectizer используются строки. Хотя строки зарекомендовали себя как очень удобный подход (в частности, имена состояний и сообщений можно формировать во время исполнения, имена сообщений можно передавать через SOP из-вне приложения), все же хочется иметь статический контроль компилятора. Достичь этого можно, если транслятор DSL будет генерировать специальные вспомогательные классы/методы/объекты, являющиеся, по сути, обертками над текстовыми именами.
Например, если в DSL описании указано:
{agent_class a_meeting_place_t {message msg_creature_in {of ... } } {message msg_meet_completed {of ...} } ... {event evt_first_in {by msg_creature_in {of {self}}} } {event evt_second_in {by msg_creature_in {of {self}}} } ... {state st_empty {events evt_first_in}} {state st_first_in {events evt_second_in}} ... }
то в коде агента a_meeting_place_t можно будет использовать следующие имена:
void a_meeting_place_t::so_on_subscription() { // Подписка событий на сообщения. so_events().evt_first_in().subscribe( so_messages().msg_creature_in() ); so_events().evt_second_in().subscribe( so_messages().msg_creature_in() ); } void a_meeting_place_t::evt_first_in( const msg_creature_in & cmd ) { ... // Переход в состояние. so_states().st_first_in().switch_to(); } void a_meeting_place_t::evt_second_in( const msg_creature_in & cmd ) { ... // Отсылка сообщения. auto_ptr< msg_meet_completed > msg1 = so_messages().msg_meet_complete().make(); msg1->... // Инициализация. msg1->send(); ... // Переход в состояние. so_states().st_empty().switch_to; }
в грядущем C++09 или в D/Scala (в общем, если доступен вывод типов) работа с сообщениями может стать еще компактнее:
// Например, в D. auto msg1 = soMessages.msgMeetComplete.make; ... msg.send;
Главное достоинство этого подхода в том, что изменение имени сообщения/события/состояния в DSL автоматически сделает исходный код некомпилирируемым. И компилятор сам будет указывать разработчику, какие фрагменты необходимо переделать.
В подавляющем количестве случаев заранее известно, на чьи сообщения агент будет подписываться (в многих случаях это его собственные сообщения). И подписка не изменяется в течении жизни агента. Поэтому, если предоставить возможность в DSL перечислить имена инцидентов событий, то код so_on_subscription может быть сгенерирован транслятором DSL:
{agent_class a_meeting_place_t {message msg_creature_in {of ... } } {message msg_meet_completed {of ...} } {message msg_creature_report {of ...} } ... {event evt_first_in {by msg_creature_in {of {self}}} } {event evt_second_in {by msg_creature_in {of {self}} } {event evt_creature_report {by msg_creature_report {of {self}}} } ... } {agent_class a_creature_t {event evt_start {by msg_start {of {sobjectizer_agent}}}} {event evt_meet_completed {by msg_meet_completed {of a_meeting_place_t} {owner_name_from_expression "m_meeting_place_name"}}} ... }
что приведет к:
void a_meeting_place_t::so_on_subscription() { so_events().evt_first_in().subscribe( so_query_name(), so_messages().msg_creature_in() ); so_events().evt_second_in().subscribe( so_query_name(), so_messages().msg_creature_in() ); so_events().evt_creature_report().subscribe( so_query_name(), so_messages().msg_creature_report() ); } void a_creature_t::so_on_subscription() { so_events().evt_start().subscribe( so_4::rt::sobjectizer_agent_name(), so_4::rt::sobjectizer_agent_t::so_messages().msg_start() ); so_events().evt_meet_completed.subscribe( m_meeting_place_name, a_meeting_place_t::so_messages().msg_meet_completed() ); }
Естественно, что будет предложен какой-то способ разбавить автоматически генерируемый so_on_subscription кодом разработчика для случаев, когда детали подписки выясняются только в процессе работы приложения.
Сейчас для агента нужно сделать полностью корректное описание C++ класса агента, после чего нужно продублировать часть этого описания в макросах SOL4_*. Но часть этой работы может сделать транслятор DSL. Так, если все события описаны в DSL, то вместо ручного описания прототипов обработчиков событий в hpp-файле можно будет в тело C++ класса подставить инструкцию #include:
class a_meeting_place_t : public so_4::rt::agent_t { #include "a_meeting_place.sol5.hpp" ... };
В C++ можно было скрывать довольно сложные конструкции за макросами. В D/Java/Scala/C#/Eiffel макросов нет. Поэтому для них DSL представляется чуть ли не единственным удобным способом описания деталей агентов для SObjectizer-а.
Например, для D транслятор DSL может генерировать фрагмент кода, который разработчик подключает в тело класса агента через mixin:
class AMeetingPlace : so5.rt.Agent { // Подключение как шаблона. mixin AMeetingPlace_SObjPart; // Или подключение как текстового mixin-а. mixin( import( "a_meeting_place.sol5.d" ) ); }
Для Scala можно генерировать trait-ы, который будет подмешиваться к классу агента:
class AMeetingPlace extends so5.rt.Agent with AMeetingPlace_SObjPart { ... }
Аналогично для Eiffel-я можно будет генерировать вспомогательный класс, который будет наследоваться классом агента:
class A_MEETING_PLACE inherit AGENT A_MEETING_PLACE_SOBJ_PART feature ... end
Для C# 2.0, может быть, получится использовать т.н. partial classes.
В тексте использованы фрагметы реализации теста chameneos для SObjectizer v.4.4.