sample/qt_ring/main.cpp

/******************************************************************************
 *
 * Qt 3 Ring Example.
 *
 * В данном примере производится замер производительности регистрации
 * кооперации гентов-виджетов и обмена сообщениями агентов-виджетов.
 *
 * При старте примере появляется главное окно приложения, в котором можно
 * выбрать размер кольца агентов (ширина х высота) и количество циклов движения
 * сообщения msg_take_token по кольцу агентов. При нажатии на кнопку "Старт"
 * производится регистрация кооперации кольца агентов с заданными параметрами
 * и запуск теста. При кадом полном кольце прохода сообщения msg_take_token
 * агент a_starter_shutdowner_t (стартер) получает сообщение msg_ring_complete
 * и подсчитывает количество завершенных колец. При достижении счетчика колец
 * заданного параметра агенты-виджеты закрываются и производится дерегистрация
 * кооперации кольца агентов. Агент стартер подписан на сообщение о регистрации
 * кооперации, при получении которого стартер разрешает главному окну
 * приложения запустить следующий тест. При заврешении теста агент-стартер
 * сообщает главному окну приложения о результатах теста, которые выводятся
 * в текстовое поле главного окна приложения.
 *
 * После начала теста главное окно приложения скрывается и запрещается
 * использование кнопки "Старт". При завершении теста главное окно появится
 * на экране, однако кнопка "Старт" будет разрешена лишь после получения
 * сообщения стартером о полной дерегистрации кооперации кольца агентов.
 *
 * ПРЕДОСТЕРЕЖЕНИЕ!!! Не запускайте тест со слишком большим кольцом агентов и
 * количеством прохода сообщения по кольцу. Поскольку в данном случае менеджер
 * сессий или окон операционной системы будет отбирать слишком много ресурсов.
 *
 *****************************************************************************/


// STL include.
#include <iostream>
#include <vector>
#include <set>

// ACE include.
#include <ace/OS.h>
#include <ace/OS_main.h>
#include <ace/Time_Value.h>
#include <ace/Thread_Manager.h>

// cpp_util_2 include.
#include <cpp_util_2/h/lexcast.hpp>

// SObjectizer include.
#include <so_4/api/h/api.hpp>
#include <so_4/rt/h/rt.hpp>

#include <so_4/timer_thread/simple/h/pub.hpp>
#include <so_4/disp/active_obj/h/pub.hpp>
#include <so_4/disp/qt_ui/h/pub.hpp>

// Qt 3 include.
#include <qapplication.h>
#include <qwidget.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qpushbutton.h>
#include <qspinbox.h>
#include <qtextedit.h>


// Вспомогательная функция для вывода в строку результатов замеров.
std::string
show_time_and_price(
  const std::string & what,
  int item_count,
  const ACE_Time_Value start,
  const ACE_Time_Value finish )
{
  std::string result;
  unsigned long diff = finish.msec() - start.msec();
  if( diff )
  {
    double total = diff / 1000.0;
    double price = total / item_count;
    double throughput = 1.0 / price;

    char msg[ 128 ];
    ACE_OS::snprintf( msg, sizeof msg,
      "total: %f sec, price: %f sec; troughput: %f",
      total, price, throughput );

    result = what + "=> " + msg + "\n";
  }
  else
    result = what + ": too small time\n";

  return result;
}// std::string show_time_and_price()


// Предварительное объявление.
class a_starter_shutdowner_t;


//
// main_widget_t
//

// Главное окно приложения.
class main_widget_t
  : public QWidget
{
  Q_OBJECT

public:
  main_widget_t( QWidget * parent = 0 );
  virtual ~main_widget_t();

  // Проинициализировать указатель на стартер тестов.
  void init( a_starter_shutdowner_t * starter );
  // Сигнализировать о завершении теста.
  void test_finished( const std::string & msg );
  // Включаем конпку "Start".
  void enable_start();

private slots:
  // Старт теста.
  void slot_start();

protected:
  // Закрытие приложения.
  void closeEvent( QCloseEvent * e );

private:
  // Кнопка старта.
  QPushButton * m_ok;
  // Ширина кольца.
  QSpinBox * m_width;
  // Высота кольца.
  QSpinBox * m_height;
  // Количество колец.
  QSpinBox * m_rings;
  // Текстовое поле.
  QTextEdit * m_text;
  // Стартер.
  a_starter_shutdowner_t * m_starter;
};// class main_widget_t

#include "moc/main.moc"


//
// a_starter_shutdowner_t
//

/*
 * Агент, который отвечает за регистрацию кольца агентов, за фиксацию
 * времени теста и за дерегистрацию кольца агентов после того, как
 * тест завершится.
 */
class a_starter_shutdowner_t
  : public so_4::rt::agent_t
{
  typedef so_4::rt::agent_t base_type_t;

public :
  a_starter_shutdowner_t( main_widget_t * widget );
  ~a_starter_shutdowner_t();

  // Запустить тест на выполнение. Вызывается из главного окна приложения.
  void
  start_test(
    // Кооперация с кольцом агентов, которую нужно будет
    // зарегистрировать для начала теста.
    std::auto_ptr< so_4::rt::dyn_agent_coop_t > customer_ring,
    int size,
    int tokens );

  static const std::string
  agent_name();

  // Это сообщение будет отсылаться агента после того, как один
  // токен завершит полный круг по кольцу.
  struct msg_ring_complete {};

  virtual const char *
  so_query_type() const;

  virtual void
  so_on_subscription();

  /*
   * Обработка завершения очередного полного круга.
   * Если все токены совершили полный круг, то работа теста
   * завершается.
   */
  void
  evt_ring_complete();

  // Когда будет дерегистрирована кооперация кольца нужно
  // включить кнопку "Start".
  void
  evt_deregistered( const so_4::rt::msg_coop_deregistered & msg );

private :
  /*
   * Количество полученых потверждений о завершении круга.
   * Когда это значение достигнет m_token_count, то отсчет времени
   * теста прекращается, а работа SObjectizer завершается.
   */
  int m_rings_completed;

  /*
   * Время начала движения токенов по кругу.
   * Начальное значение принимает только после завершения
   * регистрации агентов customer_ring.
   */
  ACE_Time_Value m_start_time;
  // Главное окно приложения.
  main_widget_t * m_widget;
  // Размер кольца (ширина * высота).
  int m_size;
  // Количество колец.
  int m_tokens;
  // Результат теста.
  std::string m_result;
};// class a_starter_shutdowner_t

SOL4_CLASS_START( a_starter_shutdowner_t )
  SOL4_MSG_START(
      msg_ring_complete,
      a_starter_shutdowner_t::msg_ring_complete )
  SOL4_MSG_FINISH()

  SOL4_EVENT( evt_ring_complete )
  SOL4_EVENT_STC( evt_deregistered,
    so_4::rt::msg_coop_deregistered )

  SOL4_STATE_START( st_normal )
    SOL4_STATE_EVENT( evt_ring_complete )
  SOL4_STATE_FINISH()

  SOL4_STATE_START( st_finish )
    SOL4_STATE_EVENT( evt_deregistered )
  SOL4_STATE_FINISH()
SOL4_CLASS_FINISH( /* a_starter_shutdowner_t */ )

a_starter_shutdowner_t::a_starter_shutdowner_t( main_widget_t * widget )
  : base_type_t( agent_name() )
  , m_rings_completed( 0 )
  , m_widget( widget )
{
  so_add_traits( so_4::disp::qt_ui::query_gui_thread_traits() );
  if( m_widget )
    // Инициализируем главное окно приложения.
    m_widget->init( this );
}

a_starter_shutdowner_t::~a_starter_shutdowner_t()
{
}

const std::string
a_starter_shutdowner_t::agent_name()
{
  return "a_starter_shutdowner";
}

void
a_starter_shutdowner_t::so_on_subscription()
{
  so_subscribe( "evt_ring_complete", "msg_ring_complete" );
  so_subscribe( "evt_deregistered", 
    so_4::rt::sobjectizer_agent_name(),
    "msg_coop_deregistered" );
}

void
a_starter_shutdowner_t::start_test(
  std::auto_ptr< so_4::rt::dyn_agent_coop_t > customer_ring,
  int size, int tokens )
{
  // Инициализируем необходимые для теста переменные.
  m_size = size;
  m_tokens = tokens;
  m_rings_completed = 0;
  m_result.clear();
  // Переключаемся в нормальное состояние.
  so_change_state( "st_normal" );

  // Фиксируем так же время регистрации.
  ACE_Time_Value reg_start = ACE_OS::gettimeofday();

  // Регистрируем кооперацию кольца.
  so_4::rt::dyn_agent_coop_helper_t helper(
      customer_ring.release() );
  if( helper.result() ) 
  {
    if( m_widget )
      // Закрывем главное окно приложения.
      m_widget->close();
    // Все плохо, тест пора завершать.
    std::cerr << "Unable to register customer_ring coop: "
        << helper.result() << std::endl;
    // Говорим SObjectizer-у завершиться с кодом возврата ошибки.
    so_4::api::send_msg(
        so_4::rt::sobjectizer_agent_name(),
        "msg_alarm_shutdown" );
  }
  else
  {
    // Время окончания регистрации будет так же и
    // временем начала теста.
    m_start_time = ACE_OS::gettimeofday();

    m_result += show_time_and_price(
      "registration",
      m_size,
      reg_start,
      m_start_time );
  }
}

void
a_starter_shutdowner_t::evt_deregistered(
  const so_4::rt::msg_coop_deregistered & msg )
{
  // Если была дерегистрирована ооперация кольца, то включаем кнопку.
  if( "customer_ring" == msg.m_coop_name )
    if( m_widget )
      m_widget->enable_start();
}

void
a_starter_shutdowner_t::evt_ring_complete()
{
  if( ++m_rings_completed == m_tokens )
  {
    // Переключаемся в состояние завершенного теста.
    so_change_state( "st_finish" );
    // Тест завершен.
    ACE_Time_Value finish_time = ACE_OS::gettimeofday();
    m_result += show_time_and_price(
      "ringing",
      m_size * m_tokens,
      m_start_time,
      finish_time );

    if( m_widget )
      // Тест завершился. Говорим об этом главному окну.
      m_widget->test_finished( m_result );
  }
}


/*
 * Агент, который отвечает за движение токена.
 */
class a_customer_t
  : public QWidget
  , public so_4::rt::agent_t
{
  typedef so_4::rt::agent_t base_type_t;

public :
  a_customer_t(
    // Собственное имя агента.
    const std::string & self_name,
    // Имя его соседа по кольцу.
    const std::string & next_name,
    int tokens );
  ~a_customer_t();

  // Сообщение о необходимости принять токен от соседа.
  struct msg_take_token {};
  // Сообщение о необходимости закрыть виджет.
  struct msg_shutdown {};

  virtual const char *
  so_query_type() const;

  virtual void
  so_on_subscription();

  /*
   * Событие о начале работы получают только те агенты, которые
   * владеют токеном. Они начинают движение своего токена по
   * кругу.
   */
  void
  evt_start();

  /*
   * При получении токена проверяется, пришел ли наш токен обратно.
   * Если да, то shutdowner-у отсылается уведомление о завершении
   * очередного цикла.
   */
  void
  evt_take_token();

  void
  evt_shutdown();

private :
  /*
   * Имя следующего агента в цепочке.
   */
  const std::string m_next_name;

  // Отсылка токена следующему в кольце агенту.
  void send_token_to_next();
  /*
   * Количество полученых потверждений о завершении круга.
   * Когда это значение достигнет m_tokens, то отсчет времени
   * теста прекращается.
   */
  int m_rings_completed;
  // Количество колец
  int m_tokens;

  QLabel * m_label;
};// class a_customer_t

SOL4_CLASS_START( a_customer_t )
  SOL4_MSG_START( msg_take_token, a_customer_t::msg_take_token )
  SOL4_MSG_FINISH()

  SOL4_MSG_START( msg_shutdown, a_customer_t::msg_shutdown )
  SOL4_MSG_FINISH()

  SOL4_EVENT( evt_start )
  SOL4_EVENT( evt_take_token )
  SOL4_EVENT( evt_shutdown )

  SOL4_STATE_START( st_normal )
    SOL4_STATE_EVENT( evt_start )
    SOL4_STATE_EVENT( evt_take_token )
    SOL4_STATE_EVENT( evt_shutdown )
  SOL4_STATE_FINISH()

SOL4_CLASS_FINISH( /* a_customer_t */ )

a_customer_t::a_customer_t(
  const std::string & self_name,
  const std::string & next_name,
  int tokens )
  : base_type_t( self_name )
  , QWidget()
  , m_next_name( next_name )
  , m_label( 0 )
  , m_rings_completed( 0 )
  , m_tokens( tokens )
{
  so_add_traits( so_4::disp::qt_ui::query_gui_thread_traits() );

  QVBoxLayout * layout = new QVBoxLayout( this );
  m_label = new QLabel( this );
  m_label->setText( "Start" );
  layout->addWidget( m_label );
}

a_customer_t::~a_customer_t()
{}

void
a_customer_t::so_on_subscription()
{
  if( "a_customer_0" == so_query_name() )
    so_subscribe(
      "evt_start",
      so_4::rt::sobjectizer_agent_name(),
      "msg_start" );

  so_subscribe( "evt_take_token", "msg_take_token" );

  so_subscribe( "evt_shutdown", "msg_shutdown" );
}

void
a_customer_t::evt_start()
{
  send_token_to_next();
}

void
a_customer_t::evt_shutdown()
{
  // Если агент не является первым агентом кольца,
  // то говорим следующему, что пора закрываться.
  if( "a_customer_0" != so_query_name() )
    so_4::api::send_msg_safely(
      m_next_name,
      "msg_shutdown",
      new msg_shutdown() );
  else
    so_4::api::deregister_coop( "customer_ring" );

  // Закрываем виджет.
  close();
}

void
a_customer_t::evt_take_token()
{
  static int num = 0;
  m_label->setNum( ++num );

  // Если это первый агент кольца, значит уже пройден круг.
  // И нужно об этом сказать стратеру.
  if( "a_customer_0" == so_query_name() )
  {
    so_4::api::send_msg(
      a_starter_shutdowner_t::agent_name(),
      "msg_ring_complete" );

    // Если не все круги пройдены.
    if( ++m_rings_completed != m_tokens )
      // Передаем его следующему.
      send_token_to_next();
    // Иначе говорим, что пора закрываться.
    else
      so_4::api::send_msg_safely(
        m_next_name,
        "msg_shutdown",
        new msg_shutdown() );
  }
  else
    // Передаем его следующему.
    send_token_to_next();
}

void
a_customer_t::send_token_to_next()
{
  so_4::api::send_msg_safely(
    m_next_name,
    "msg_take_token",
    new msg_take_token() );
}

/*
 * Тип хранилища агентов a_customer_t.
 */
typedef std::vector< a_customer_t * > agents_ptr_storage_t;


/*
 * Создание кольца агентов в виде динамической кооперации.
 */
std::auto_ptr< so_4::disp::qt_ui::dyn_coop_t >
create_customer_ring( QApplication * app, int width, int height, int rings )
{
  std::vector< so_4::rt::agent_t* > customers;
  customers.reserve( width * height );

  QDesktopWidget * d = QApplication::desktop();
  int w = double( d->width() ) / double( width );
  int h = double( d->height() ) / double( height );

  // Создаем агентов для кольца.
  for( int i = 0; i != height; ++i )
    for( int j = 0; j != width; ++j )
    {
      const std::string self_name = "a_customer_" +
        cpp_util_2::slexcast( i * width + j );
      const std::string next_name = "a_customer_" +
        cpp_util_2::slexcast(
          i * width + j + 1 == ( width * height ) ? 0 : i * width + j + 1 );

      a_customer_t * customer =
        new a_customer_t( self_name, next_name, rings );

      customer->setGeometry( j * w, i * h, w, h );
      customer->show();

      customers.push_back( customer );
    }

  // Осталось оформить все это в кооперацию.
  return std::auto_ptr< so_4::disp::qt_ui::dyn_coop_t >(
    new so_4::disp::qt_ui::dyn_coop_t(
      "customer_ring",
      &customers[ 0 ],
      customers.size() ) );
}


//
// sobj_thread_t
//

class sobj_thread_t
{
private :
  so_4::rt::agent_coop_t * m_start_coop;

public :
  sobj_thread_t( so_4::rt::agent_coop_t * start_coop = 0 )
  : m_start_coop( start_coop )
  {}

  virtual ~sobj_thread_t()
  {}

  virtual void
  start()
  {
    if( -1 == ACE_Thread_Manager::instance()->spawn(
      entry_point,
      this ) )
    {
      ACE_ERROR(( LM_ERROR, "unable to spawn work thread\n" ));
    }
  }

  virtual void
  wait()
  {
    ACE_Thread_Manager::instance()->wait(); 
  }

protected :
  virtual void
  body()
  {
    so_4::ret_code_t rc = so_4::api::start(
      so_4::disp::qt_ui::create_disp(
        so_4::disp::active_obj::create_disp(
          so_4::timer_thread::simple::create_timer_thread(),
          so_4::auto_destroy_timer ) ),
      so_4::auto_destroy_disp, 
      m_start_coop
      );
    if( rc )
    {
      std::cerr << "start: " << rc << std::endl;
    }
  }

  static ACE_THR_FUNC_RETURN
  entry_point( void * self_object )
  {
    sobj_thread_t * sobj_thread = ACE_reinterpret_cast(
      sobj_thread_t *,
      self_object );
    sobj_thread->body();

    return 0;
  }
};// class sobj_thread_t


//
// main_widget_t
//

main_widget_t::main_widget_t( QWidget * parent )
  : QWidget( parent )
  , m_ok( 0 )
  , m_width( 0 )
  , m_height( 0 )
  , m_rings( 0 )
  , m_starter( 0 )
  , m_text( 0 )
{
  QVBoxLayout * frame = new QVBoxLayout( this );
  QHBoxLayout * hframe = new QHBoxLayout( frame );
  QVBoxLayout * vframe_1 = new QVBoxLayout( hframe );
  QVBoxLayout * vframe_2 = new QVBoxLayout( hframe );
  QVBoxLayout * vframe_3 = new QVBoxLayout( hframe );
  QLabel * width_label = new QLabel( "Width", this );
  width_label->setAlignment( Qt::AlignCenter );
  vframe_1->addWidget( width_label );
  QLabel * height_label = new QLabel( "Height", this );
  height_label->setAlignment( Qt::AlignCenter );
  vframe_2->addWidget( height_label );
  QLabel * rings_label = new QLabel( "Rings", this );
  rings_label->setAlignment( Qt::AlignCenter );
  vframe_3->addWidget( rings_label );
  m_width = new QSpinBox( 2, 150, 1, this );
  vframe_1->addWidget( m_width );
  m_height = new QSpinBox( 1, 150, 1, this );
  vframe_2->addWidget( m_height );
  m_rings = new QSpinBox( 1, 30000, 1, this );
  vframe_3->addWidget( m_rings );
  m_width->setValue( 10 );
  m_height->setValue( 10 );
  m_rings->setValue( 100 );
  m_ok = new QPushButton( "Start Test", this );
  frame->addWidget( m_ok );
  m_text =  new QTextEdit( this );
  frame->addWidget( m_text );

  connect( m_ok, SIGNAL( clicked() ), this, SLOT( slot_start() ) );
};

main_widget_t::~main_widget_t()
{
}

void
main_widget_t::enable_start()
{
  m_ok->setEnabled( TRUE );
}

void
main_widget_t::init( a_starter_shutdowner_t * starter )
{
  m_starter = starter;
}

void
main_widget_t::test_finished( const std::string & msg )
{
  // Показываем главное окно.
  show();
  m_text->setText( msg.c_str() );
}

void
main_widget_t::slot_start()
{
  // Отключаем кнопку.
  m_ok->setEnabled( FALSE );
  // Прячем главное окно.
  hide();
  // Нужно передать стартеру кооперацию нового кольца.
  m_starter->start_test(
    create_customer_ring(
      qApp,
      m_width->value(),
      m_height->value(),
      m_rings->value() ),
    m_width->value() * m_height->value(),
    m_rings->value() );
}

void
main_widget_t::closeEvent( QCloseEvent * e )
{
  // Если главное окно закрыто, то нужно остановить SObjectizer.
  so_4::api::send_msg(
    so_4::rt::sobjectizer_agent_name(),
    "msg_normal_shutdown" );
  e->accept();
}

int
main( int argc, char ** argv )
{
  QApplication app( argc, argv );
  app.connect( &app, SIGNAL( lastWindowClosed() ),
    &app, SLOT( quit() ) );

  main_widget_t * main_widget = new main_widget_t();
  app.setMainWidget( main_widget );
  main_widget->show();
  a_starter_shutdowner_t a_starter_shutdowner( main_widget );
  so_4::rt::agent_coop_t starter_coop( a_starter_shutdowner );

  // Запускаем SObjectizer.
  sobj_thread_t sobj_thread( &starter_coop );
  sobj_thread.start();

  int q_result = app.exec();

  so_4::api::shutdown();
  // Ожидаем завершения SObjectizer.
  sobj_thread.wait();

  return q_result;
}

Документация по SObjectizer v.4.4 'Тебуломста'. Последние изменения: Thu Sep 18 10:26:45 2008. Создано системой  doxygen1.5.6 Intervale SourceForge.net Logo