[sobjectizer] Релиз версии 5.8.1: реализация пожеланий пользователей и исправление недочетов

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Пару дней назад мы зафиксировали версию 5.8.1 открытого проекта SObjectizer. В данной статье поговорим о новых возможностях, которые появились в SObjectizer благодаря пожеланиям пользователей, и упомянем исправление не выявленного ранее недочета. Кому интересно, милости прошу под кат.

Для тех же, кто ни разу не слышал про SObjectizer, очень кратко: это относительно небольшой C++17 фреймворк, который позволяет использовать в С++ программах такие подходы, как Actor Model, Publish-Subscribe и Communicating Sequential Processes (CSP). Основная идея, лежащая в основе SObjectizer, — это построение приложения из мелких сущностей-агентов, которые взаимодействуют между собой через обмен сообщениями. SObjectizer при этом берет на себя ответственность за:

  • доставку сообщений агентам-получателям внутри одного процесса;

  • управление рабочими нитями, на которых агенты обрабатывают адресованные им сообщения;

  • механизм таймеров (в виде отложенных и периодических сообщений);

  • возможности настройки параметров работы перечисленных выше механизмов.

Составить впечатление о этом инструменте можно ознакомившись вот с этой обзорной статьей.

Новый тип message sink-а: transform_then_redirect

Принципиальным нововведением релиза 5.8.0 были message sink-и: если раньше подписчиком для сообщения мог быть только агент, то сейчас можно сделать реализацию интерфейса abstract_message_sink_t и подписать на сообщение кого угодно. Подробнее об этой функциональности рассказывалось в предыдущей статье. Там же говорилось и о том, что со временем могут появиться неочевидные для нас применения message sink-ов...

Одно из таких применений не заставило себя ждать: issue #67: bind_and_transform. Пользователь хотел иметь возможность преобразовать одно сообщение в другое прямо в момент его отправки получателю. Допустим, у нас есть сообщение:

struct compound_data
{
   first_data_part m_first;
   second_data_part m_second;
};

Оно отсылается в mbox_A.

И есть агент F, который хочет получить не всё сообщение compound_data целиком, а только compound_data::m_first. Т.е. агент F хочет получить сообщение типа first_data_part:

class F : public agent_t
{
   ...
   void so_define_agent() override {
      so_subscribe_self().event([](const first_data_part & msg) {...});
   }
};

И вот тут возникает вопрос: как же сделать так, чтобы при отсылке сообщения compound_data в mbox_A произошло формирование сообщения first_data_part и его отправка напрямую агенту F?

Связать mbox_A с собственным mbox-ом агента F не сложно, для этого есть вспомогательные классы single_sink_binding_t и multi_sink_binding_t:

const so_5::mbox_t mbox_A = ... // Получение mbox-а для compound_data.
const so_5::mbox_t mbox_F = ... // Получение mbox-а агента F.

so_5::multi_sink_binding_t<> binding;
binding.bind<compound_data>(mbox_A, so_5::wrap_to_msink(mbox_F));

Но такая связь доставляет агенту F исходное сообщение compound_data, тогда как нужно доставить только compound_data::m_first.

Т.е. нужно трансформировать исходное сообщение в новое сообщение другого типа.

И тут можно вспомнить, что в одном месте в SObjectizer подобная трансформация уже есть. Она является частью механизма защиты агентов от перегрузки:

class message_limits_demo final : public so_5::agent_t
{
public:
   message_limits_demo(context_t ctx)
      : so_5::agent_t{ ctx +
            // Говорим SObjectizer-у, что если количество
            // ждущих в очереди сообщений типа compound_data
            // будет больше 3-х, то новые сообщения нужно
            // преобразовать и переслать на другой mbox.
            limit_then_transform(3u, [this](const compound_data & msg) {
               return so_5::make_transformed<first_data_part>(
                  // Куда отсылать.
                  new_destination_mbox(),
                  // А это параметры для конструирования нового
                  // экземпляра сообщения first_data_part.
                  msg.m_first);
            })
            + ... }
       {}
...
};

Т.е. у нас в SObjectizer уже есть специальный тип so_5::transformed_message_t<Msg> и вспомогательная функция so_5::make_transformed<Msg, ...Args> предназначенные для преобразования сообщений с их последующей переадресацией. Так почему бы этим не воспользоваться?

В результате появилась вспомогательная функция so_5::bind_transformer, которая позволяет связать лямбду-трансформатор с сообщением из конкретного mbox-а. Благодаря bind_transformer наша задача решается следующим образом:

const so_5::mbox_t mbox_A = ... // Получение mbox-а для compound_data.
const so_5::mbox_t mbox_F = ... // Получение mbox-а агента F.

so_5::multi_sink_binding_t<> binding;
so_5::bind_transformer(binding, mbox_A,
   [mbox_F](const compound_data & msg) {
      return so_5::make_transformed<first_data_part>(mbox_F, msg.m_first);
   });

Интересное впечатление оставила реализация этой фичи: по субъективным ощущениям (учет времени, понятное дело, не велся) проектирование и реализация заняла всего лишь около 1/10 от всех затрат. Т.е. на тестирование и документирование полученной реализации ушло чуть ли не на порядок больше времени. Да еще и при разработке тестов удалось свалить VC++ в internal compiler error, чего уже давненько на нашем коде видеть не приходилось

Источник: https://habr.com/ru/articles/768438/


Интересные статьи

Интересные статьи

Всем привет! Я Head of QA в Scalable Solutions. Так как компания разрабатывает высоконагруженную платформу для управления цифровыми активами и онбордит преимущественно middle+ и senior специалист...
После того, как вы освоите это руководство, в вашем распоряжении окажется минимальная реализация Lua (парсер, компилятор, виртуальная машина), написанная на Rust с чистого листа. Этот проект получил н...
За последние 14 дней я разработал мессенджер, у которого отличительная черта от остальных мессенджеров — это то, что серверную программы вы контролируете сами. Вы можете скачать исходные коды серверн...
Команда Rust рада сообщить о выпуске новой версии, 1.45.0. Rust — это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение. Если вы уст...
Привет! Меня зовут Лёша Руцкой, и я — продуктовый менеджер в компании Wrike. До этого работал в Adform и PandaDoc. Последние пять лет я занимаюсь всем, что связано с интеграциями и API. W...