Menu

[ru] Как можно обойтись без определения собственных типов сообщений?

2015-04-10
2015-04-11
  • Yauheni Akhotnikau

    До версии 5.5.4 включительно пользователю всегда нужно было определять свое сообщение. Даже если он в нем передает всего одно единственное поле. Что не удобно при написании мелких программ на выброс (в том числе и программ, которые используются для сравнения SO с другими фреймворками).

    Похоже, избежать этого можно за счет вот такого простого фокуса:

    template< typename TAG, typename... TYPES >
    struct tagged_tuple_msg
        :   public std::tuple< TYPES... >
        ,   public so_5::rt::message_t
    {
        using base_tuple_type = std::tuple< TYPES... >;
    
        tagged_tuple_msg() 
            {}
    
        tagged_tuple_msg( const tagged_tuple_msg & ) = default;
    
        tagged_tuple_msg &
        operator=( const tagged_tuple_msg & ) = default;
    
    #if !defined( _MSC_VER )
        tagged_tuple_msg( tagged_tuple_msg && ) = default;
    
        tagged_tuple_msg &
        operator=( tagged_tuple_msg && ) = default;
    #endif
    
        explicit tagged_tuple_msg( const TYPES &... args )
            :   base_tuple_type( args... )
        {}
    
        template< typename... UTYPES >
        tagged_tuple_msg( UTYPES &&... args )
            :   base_tuple_type( std::forward< UTYPES >( args )... )
        {}
    };
    

    Что позволяет определять простые сообщения одним из двух нижеследующих способов. Первый, требует дополнительного пользовательского типа:

    struct process_range_tag {};
    using process_range = tagged_tuple_msg< process_range_tag, password, password >;
    
    struct found_tag {};
    using found = tagged_tuple_msg< found_tag, password >;
    

    Второй вообще позволяет обойтись однострочником за счет std::integral_constant:

    using process_range = tagged_tuple_msg< std::integral_constant<int, 0>, password, password >;
    
    using found = tagged_tuple_msg< std::integral_constant<int, 1>, password >;
    

    Используются такие сообщения в точности как обычные:

    // Отсылка:
    so_5::send< process_range >( channel, start, last );
    so_5::send< found >( channel, pw );
    
    // Получение и использование:
    coop->define_agent().event( channel,
        [channel, hash]( const process_range & evt ) {
            worker( channel, hash, std::get<0>(evt), std::get<1>(evt) );
        },
        so_5::thread_safe );
    
    coop->define_agent().event( channel,
        [&env]( const found & evt ) {
            cout << "password: " << std::get<0>( evt ) << endl;
            env.stop();
        } );
    

    Ну и просто для сравнения объема:

    // Существующий вариант, который рекомендуется для больших проектов:
    struct process_range : public so_5::rt::message_t
    {
        const password m_start;
        const password m_end;
    
        process_range( const password & start, const password & end )
            :   m_start( start )
            ,   m_end( end )
        {}
    };
    
    struct found : public so_5::rt::message_t
    {
        const password m_result;
    
        found( const password & result ) : m_result( result ) {}
    };
    
    // ---
    
    // Вариант с tagged_tuple_msg:
    using process_range = tagged_tuple_msg< std::integral_constant<int, 0>, password, password >;
    
    using found = tagged_tuple_msg< std::integral_constant<int, 1>, password >;
    
     
  • Yauheni Akhotnikau

    Блин, во что превращается SO5 и программы на нем... Просто страшно подумать :)))

    void
    create_coop(
        so_5::rt::agent_coop_t & coop )
    {
        using namespace so_5;
        using namespace so_5::rt;
        using namespace std;
    
        using hello = tuple_as_message_t< mtag< 0 >, string >;
        using bye = tuple_as_message_t< mtag< 1 >, string, string >;
        using repeat = tuple_as_message_t< mtag< 2 >, int, int >;
    
        auto & env = coop.environment();
        auto agent = coop.define_agent();
        auto mb = agent.direct_mbox();
        agent.on_start( [mb] {
                send< hello >( mb, "Hello" );
            } )
            .event( mb, [mb]( const hello & evt ) {
                cout << "hello: " << get<0>( evt ) << endl;
                send< repeat >( mb, 0, 3 );
            } )
            .event( mb, [mb]( const repeat & evt ) {
                cout << "repetition: " << get<0>( evt ) << endl;
                auto next = get<0>( evt ) + 1;
                if( next < get<1>( evt ) )
                    send< repeat >( mb, next, get<1>( evt ) );
                else
                    send< bye >( mb, "Good", "Bye" );
            } )
            .event( mb, [&env]( const bye & evt ) {
                cout << "bye: " << get<0>( evt ) << " " << get<1>( evt ) << endl;
                env.stop();
            } );
    }
    
     

    Last edit: Yauheni Akhotnikau 2015-04-11
  • Yauheni Akhotnikau

    После появления tuple_as_message_t и mtag нужно бы сделать возможнось писать так:

    using process_range = tuple_as_message_t< mtag<0>, string, string, string>;
    using result_found = tuple_as_message_t< mtag<1>, string, string>;
    
    void processor::so_define_agent() override
    {
      so_subscribe_self().event< process_range >( &processor::evt_process );
      ...
    }
    void processor::evt_process(
      const string & md5, const string & left, const string & right )
    { ... }
    
    void manager::so_define_agent() override
    {
      so_subscribe_self().event< result_found >( &manager::evt_result_found );
    }
    void manager::evt_result_found(
      const string & worker_id, const string & result )
    { ... }
    

    Вроде как есть возможность использовать std::tuple при вызове функции. Но нужно придумать, как встроить все это в уже существующие механизмы подписки событий в SO-5.

     

    Last edit: Yauheni Akhotnikau 2015-04-11

Log in to post a comment.