Как получить boost.msm для правильного изменения состояния при использовании обработчика сигнала для запуска событий?

Мой (boost.msm) конечный автомат выглядит как "откат" при использовании обработчиков сигналов для запуска событий. Однако, когда я использую прямые вызовы для запуска событий, конечный автомат ведет себя правильно.

Я посмотрел в документации по бусту и искал в Интернете, но кажется, что во всех примерах используются прямые вызовы для запуска событий. Я также искал SO, но не смог найти ничего, касающегося этой темы.

Я нахожусь в процессе изучения библиотеки автомата мета-состояний наддува, чтобы выяснить, было бы полезно заменить существующую библиотеку автоматов состояния, которая в настоящее время используется моей командой разработчиков.

Чтобы это работало, мне нужно иметь возможность запускать события конечного автомата из обработчиков сигналов (обработки сигналов из boost.signals2).

Я создал простой, но надуманный пример для его тестирования и был сбит с толку, когда увидел, что после запуска первого события конечный автомат правильно (но временно) изменил состояния (находясь в обработчике сигналов), но, по-видимому, "покатился" назад после возвращения на главную.

Когда я обошел обработчики сигналов (используя прямые вызовы process_event), все работало правильно.

Предположительно изобретенный тестовый конечный автомат предназначен для этого:

[state_a]--event_a-->[state_b]--event_b-->[state_c]--event_c-->{back-to-state_a}

Я хотел бы знать, как я могу заставить этот дизайн (или что-то подобное) работать, используя обработчики сигналов для правильного запуска событий конечного автомата. Использование прямых вызовов для меня не вариант, так как я получаю только сигналы для работы.

Я включил тестовый код ниже. Обратите внимание, что первая половина основной функции выполняет запуск обработчика сигнала, а вторая половина основной функции - прямой вызов вызова (скомпилировано с использованием g++ main.cpp -omain' or 'clang++ main.cpp -omain):

#include <iostream>
#include <boost/signals2.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/back/tools.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>

typedef boost::signals2::signal<void()> sig1_t;

//================================================================================
// ------- Sensors section

struct sensor_a {
  sig1_t& get_sig() { return sig; }
  void emit() { sig(); }

private:
  sig1_t sig;
};

struct sensor_b {
  sig1_t& get_sig() { return sig; }
  void emit() { sig(); }

private:
  sig1_t sig;
};

struct sensor_c {
  sig1_t& get_sig() { return sig; }
  void emit() { sig(); }

private:
  sig1_t sig;
};

//========================================
// Sensors class
struct Sensors {
  sensor_a& get_sa() {
    return sa;
  }

  sensor_b& get_sb() {
    return sb;
  }

  sensor_c& get_sc() {
    return sc;
  }

private:
  sensor_a sa;
  sensor_b sb;
  sensor_c sc;
};

// ----- Events
struct event_a {
  std::string name() const { return "event_a"; }
};
struct event_b {
  std::string name() const { return "event_b"; }
};
struct event_c {
  std::string name() const { return "event_c"; }
};
struct exit {
  std::string name() const { return "exit"; }
};

//================================================================================
// ----- State machine section

namespace msm = boost::msm;
namespace msmf = boost::msm::front;
namespace mpl = boost::mpl;

class Controller;  // forward declaration

//========================================
// testmachine class (the state machine)
struct testmachine : msmf::state_machine_def<testmachine>
{
  testmachine(Controller& c) : controller(c) {}

  template <class Fsm,class Event>
  void no_transition(Event const& e, Fsm& ,int state) {
    std::cout << "testmachine::no_transition -- No transition for event: '"
              << e.name() << "'" << " on state: " << state << std::endl;
  }

  //---------
  struct state_a : msmf::state<> {
    template <class Event,class Fsm>
    void on_entry(Event const&, Fsm&) const {
      std::cout << "state_a::on_entry() " << std::endl;
    }

    template <class Event,class Fsm>
    void on_exit(Event const&, Fsm&) const {
      std::cout << "state_a::on_exit()" << std::endl;
    }
  };

  //---------
  struct state_b : msmf::state<> {
    template <class Event,class Fsm>
    void on_entry(Event const& e, Fsm&) const {
      std::cout << "state_b::on_entry() -- event: " << e.name() << std::endl;
    }

    template <class Event,class Fsm>
    void on_exit(Event const& e, Fsm&) const {
      std::cout << "state_b::on_exit() -- event: " << e.name() << std::endl;
    }
  };

  //---------
  struct state_c : msmf::state<> {
    template <class Event,class Fsm>
    void on_entry(Event const& e, Fsm&) const {
      std::cout << "state_c::on_entry() -- event: " << e.name() << std::endl;
    }

    template <class Event,class Fsm>
    void on_exit(Event const& e, Fsm&) const {
      std::cout << "state_c::on_exit() -- event: " << e.name() << std::endl;
    }
  };

  //---------
  // Set initial state
  typedef mpl::vector<state_a> initial_state;

  //---------
  // Transition table
  struct transition_table:mpl::vector<
    //          Start      Event           Next       Action      Guard
    msmf::Row < state_a,   event_a,        state_b,   msmf::none, msmf::none >,
    msmf::Row < state_b,   event_b,        state_c,   msmf::none, msmf::none >,
    msmf::Row < state_c,   event_c,        state_a,   msmf::none, msmf::none >
    > {};

private:
  Controller& controller;
};

// state-machine back-end
typedef msm::back::state_machine<testmachine> TestMachine;

//================================================================================
// --------- controller section

namespace msm = boost::msm;
namespace mpl = boost::mpl;

// debug print helper:
std::string demangle(const std::string& mangled) {
  int status;
  char* c_name = abi::__cxa_demangle(mangled.c_str(), 0, 0, &status);

  if(c_name){
    std::string retval(c_name);
    free((void*)c_name);
    return retval;
  }

  return mangled;
}

// debug print helper (from boost msm documentation):
void pstate(TestMachine const& sm) {
  typedef TestMachine::stt Stt;
  typedef msm::back::generate_state_set<Stt>::type all_states;
  static char const* state_names[mpl::size<all_states>::value];
  mpl::for_each<all_states,boost::msm::wrap<mpl::placeholders::_1> >
    (msm::back::fill_state_names<Stt>(state_names));

  for (unsigned int i=0;i<TestMachine::nr_regions::value;++i){
    std::cout << " -> " << demangle(state_names[sm.current_state()[i]])
              << std::endl;
  }
}

//========================================
// Controller class
struct Controller {
  Controller(Sensors& s) :
    sensors(s),
    tm(boost::ref(*this)) {
    s.get_sa().get_sig().connect(boost::bind(&Controller::on_sa_event, *this));
    s.get_sb().get_sig().connect(boost::bind(&Controller::on_sb_event, *this));
    s.get_sc().get_sig().connect(boost::bind(&Controller::on_sc_event, *this));
    tm.start();
  }

  void on_sa_event() {
    std::cout << "Controller::on_sa_event function entered ++++++++" << std::endl;
    current_state(__FUNCTION__);
    trigger_event_a();
    current_state(__FUNCTION__);
    std::cout << "Controller::on_sa_event function exiting --------" << std::endl;
  };

  void on_sb_event() {
    std::cout << "Controller::on_sb_event function entered ++++++++" << std::endl;
    current_state(__FUNCTION__);
    trigger_event_b();
    current_state(__FUNCTION__);
    std::cout << "Controller::on_sb_event function exiting --------" << std::endl;
  };

  void on_sc_event() {
    std::cout << "Controller::on_sc_event function entered ++++++++" << std::endl;
    current_state(__FUNCTION__);
    trigger_event_c();
    current_state(__FUNCTION__);
    std::cout << "Controller::on_sc_event function exiting --------" << std::endl;
  };

  // debug print function
  void current_state(const std::string& f) {
    std::cout << "\nController::current_state ("
              << "called from function: " << f
              <<")" << std::endl;
    pstate(tm);
    std::cout << std::endl;
  }

  void trigger_event_a() {
    std::cout << "Controller::trigger_event_a" << std::endl;
    tm.process_event(event_a());
    current_state(__FUNCTION__);
  }

  void trigger_event_b() {
    std::cout << "Controller::trigger_event_b" << std::endl;
    tm.process_event(event_b());
    current_state(__FUNCTION__);
  }

  void trigger_event_c() {
    std::cout << "Controller::trigger_event_c" << std::endl;
    tm.process_event(event_c());
    current_state(__FUNCTION__);
  }

private:
  Sensors& sensors;
  TestMachine tm;
};

//================================================================================
// --------- main
int main() {
  Sensors sensors;
  Controller controller(sensors);

  std::cout << "Exercise state machine using signal handlers (fails):" << std::endl;
  controller.current_state("***** main");
  sensors.get_sa().emit();

  controller.current_state("***** main");
  sensors.get_sb().emit();

  controller.current_state("***** main");
  sensors.get_sc().emit();

  controller.current_state("***** main");

  std::cout << "\nExercise state machine using direct calls (works):" << std::endl;
  controller.current_state("***** main");
  controller.trigger_event_a();

  controller.current_state("***** main");
  controller.trigger_event_b();

  controller.current_state("***** main");
  controller.trigger_event_c();

  controller.current_state("***** main");
}

Вот вывод:

 1  state_a::on_entry()
 2  Exercise state machine using signal handlers (fails):

 3  Controller::current_state (called from function: ***** main)
 4   -> testmachine::state_a

 5  Controller::on_sa_event function entered ++++++++

 6  Controller::current_state (called from function: on_sa_event)
 7   -> testmachine::state_a

 8  Controller::trigger_event_a
 9  state_a::on_exit()
10  state_b::on_entry() -- event: event_a

11  Controller::current_state (called from function: trigger_event_a)
12   -> testmachine::state_b

13  Controller::current_state (called from function: on_sa_event)
14   -> testmachine::state_b

15  Controller::on_sa_event function exiting --------

16  Controller::current_state (called from function: ***** main)
17   -> testmachine::state_a

18  Controller::on_sb_event function entered ++++++++

19  Controller::current_state (called from function: on_sb_event)
20   -> testmachine::state_a

21  Controller::trigger_event_b
22  testmachine::no_transition -- No transition for event: 'event_b' on state: 0

23  Controller::current_state (called from function: trigger_event_b)
24   -> testmachine::state_a

25  Controller::current_state (called from function: on_sb_event)
26   -> testmachine::state_a

27  Controller::on_sb_event function exiting --------

28  Controller::current_state (called from function: ***** main)
29   -> testmachine::state_a

30  Controller::on_sc_event function entered ++++++++

31  Controller::current_state (called from function: on_sc_event)
32   -> testmachine::state_a

33  Controller::trigger_event_c
34  testmachine::no_transition -- No transition for event: 'event_c' on state: 0

35  Controller::current_state (called from function: trigger_event_c)
36   -> testmachine::state_a

37  Controller::current_state (called from function: on_sc_event)
38   -> testmachine::state_a

39  Controller::on_sc_event function exiting --------

40  Controller::current_state (called from function: ***** main)
41   -> testmachine::state_a

42  Exercise state machine using direct calls (works):

43  Controller::current_state (called from function: ***** main)
44   -> testmachine::state_a

45  Controller::trigger_event_a
46  state_a::on_exit()
47  state_b::on_entry() -- event: event_a

48  Controller::current_state (called from function: trigger_event_a)
49   -> testmachine::state_b

50  Controller::current_state (called from function: ***** main)
51   -> testmachine::state_b

52  Controller::trigger_event_b
53  state_b::on_exit() -- event: event_b
54  state_c::on_entry() -- event: event_b

55  Controller::current_state (called from function: trigger_event_b)
56   -> testmachine::state_c

57  Controller::current_state (called from function: ***** main)
58   -> testmachine::state_c

59  Controller::trigger_event_c
60  state_c::on_exit() -- event: event_c
61  state_a::on_entry() 

62  Controller::current_state (called from function: trigger_event_c)
63   -> testmachine::state_a

64  Controller::current_state (called from function: ***** main)
65   -> testmachine::state_a

Я добавил номера строк путем пост-обработки выходного файла для удобства.

Строка 01 вывода показывает, что конечный автомат правильно перешел из исходного псевдосостояния в состояние_a.

Строка 14 вывода показывает, что конечный автомат правильно перешел из state_a в state_b внутри функции on_sa_event.

Тем не менее, строка 17 показывает конечный автомат, возвращенный в state_a при тестировании из main (!)

Конечный автомат остается в state_a для остальных переходов тестов обработчика сигналов (строки 18-41), что приводит к нескольким сообщениям об ошибках "Нет перехода".

Для упражнения с прямым вызовом (выходные строки 42-65) конечный автомат корректно проходит через все состояния, и нет разницы в его "текущем состоянии" изнутри функции запуска и когда в основном (после вызова функции запуска).

Окружающая среда: ОС: "Ubuntu 16.04 LTS"

Версия g++: (Ubuntu 5.3.1-14ubuntu2) 5.3.1 20160413

буст версия: boost_1_60_0

1 ответ

Решение

Проблема вызвана копированием * этого. Смотрите следующий код. Boost:: Bind копии * это. Каждый скопированный * это в начальном состоянии (state_a). Вот почему вы испытали откат.

s.get_sa().get_sig().connect(boost::bind(&Controller::on_sa_event, *this));
s.get_sb().get_sig().connect(boost::bind(&Controller::on_sb_event, *this));
s.get_sc().get_sig().connect(boost::bind(&Controller::on_sc_event, *this));

Если вы скопируете указатель this следующим образом, ваш код будет работать так, как вы ожидали.

s.get_sa().get_sig().connect(boost::bind(&Controller::on_sa_event, this));
s.get_sb().get_sig().connect(boost::bind(&Controller::on_sb_event, this));
s.get_sc().get_sig().connect(boost::bind(&Controller::on_sc_event, this));

Вы также можете привязать ссылку * this следующим образом:

s.get_sa().get_sig().connect(boost::bind(&Controller::on_sa_event, boost::ref(*this)));
s.get_sb().get_sig().connect(boost::bind(&Controller::on_sb_event, boost::ref(*this)));
s.get_sc().get_sig().connect(boost::bind(&Controller::on_sc_event, boost::ref(*this)));
Другие вопросы по тегам