Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -303,20 +303,14 @@ If the `fsm_parameter` is set to `root_sm`, then also the `root_sm` must be set.

=== Generic support for serializers

The `state_machine` allows access to its private members for serialization purposes with a friend:
The `backmp11` namespace contains a `serialize` function for serialization purposes:

```cpp
// Class boost::msm::backmp11::state_machine
template<typename T, typename A0, typename A1, typename A2>
friend void serialize(T&, state_machine<A0, A1, A2>&);
void serialize(T&, state_machine<A0, A1, A2>&);
```

A similar friend declaration is available in the `history_impl` classes.

[IMPORTANT]
====
This design allows you to provide any serializer implementation, but due to the need to access private members there is no guarantee that your implementation breaks in a new version of the back-end.
====
Serializers passed to this function need to provide `operator&` overloads for serialization and deserialization. Boost.Serialization supports such overloads out-of-the-box, an example integration of it is shown in the state machine adapter.

=== Unified event pool for queued and deferred events

Expand Down Expand Up @@ -471,6 +465,13 @@ class state_machine_adapter
this->get_event_pool().events.clear();
}

template <class Archive>
void serialize(Archive& ar,
const unsigned int /*version*/)
{
backmp11::serialize(ar, *this);
}

// No adapter.
// Superseded by the visitor API.
// void visit_current_states(...) {...}
Expand Down
5 changes: 5 additions & 0 deletions doc/modules/ROOT/pages/version-history.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

= Version history

== Boost 1.92

* New features (`backmp11`):
** Improve serializer interface (https://github.com/boostorg/msm/issues/197[#197])

== Boost 1.91

* New features (`backmp11`):
Expand Down
31 changes: 19 additions & 12 deletions include/boost/msm/backmp11/detail/history_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ class history_impl<front::no_history, NumberOfRegions>
return true;
}

private:
// Allow access to private members for serialization.
template<typename T, int N>
friend void serialize(T&, history_impl<front::no_history, N>&);
template<typename T>
void serialize(T& archive)
{
archive & m_initial_state_ids;
}

private:
std::array<int, NumberOfRegions> m_initial_state_ids;
};

Expand Down Expand Up @@ -83,11 +85,13 @@ class history_impl<front::always_shallow_history, NumberOfRegions>
return false;
}

private:
// Allow access to private members for serialization.
template<typename T, int N>
friend void serialize(T&, history_impl<front::always_shallow_history, N>&);
template<typename T>
void serialize(T& archive)
{
archive & m_last_active_state_ids;
}

private:
std::array<int, NumberOfRegions> m_last_active_state_ids;
};

Expand Down Expand Up @@ -125,11 +129,14 @@ class history_impl<front::shallow_history<Events...>, NumberOfRegions>
return !mp11::mp_contains<events_mp11,Event>::value;
}

private:
// Allow access to private members for serialization.
template<typename T, typename... Es, int N>
friend void serialize(T&, history_impl<front::shallow_history<Es...>, N>&);
template<typename T>
void serialize(T& archive)
{
archive & m_initial_state_ids;
archive & m_last_active_state_ids;
}

private:
std::array<int, NumberOfRegions> m_initial_state_ids;
std::array<int, NumberOfRegions> m_last_active_state_ids;
};
Expand Down
76 changes: 73 additions & 3 deletions include/boost/msm/backmp11/detail/state_machine_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,6 @@ class state_machine_base : public FrontEnd
friend class deferred_event;

// Allow access to private members for serialization.
// WARNING:
// No guarantee is given on the private member layout.
// Future changes may break existing serializer implementations.
template<typename T, typename A0, typename A1, typename A2>
friend void serialize(T&, state_machine_base<A0, A1, A2>&);

Expand Down Expand Up @@ -1144,6 +1141,79 @@ class state_machine_base : public FrontEnd
bool m_running{false};
};

template <typename T, typename Event, typename = void>
struct has_serialize_member : std::false_type {};
template <typename T, typename State>
struct has_serialize_member<T, State,
std::void_t<decltype(std::declval<State&>().serialize(std::declval<T&>()))>>
: std::true_type {};

template <typename T, typename Event, typename = void>
struct has_serialize_free : std::false_type {};
template <typename T, typename State>
struct has_serialize_free<T, State,
std::void_t<decltype(serialize(std::declval<T&>(), std::declval<State&>()))>>
: std::true_type {};

// TODOs:
// - Rewrite serialize methods with call operator
// - Try out adapter for Boost.Serialization
// - Try out nlohmann serialization

// Serialize API for state machine members.
// Can be overloaded to support key-value serialization.
template <typename T, typename U>
void serialize(T& archive, const char* /*key*/, U& member)
{
archive & member;
}

// Serialize POD data types.
// Supports serialization without instrumentation for simple states.
// Requires `#define BOOST_MSM_FRONT_ATTRIBUTES_CONTAINER void`
// to make msm::front::state<> trivially copyable.
template <typename T, typename U,
typename = std::enable_if_t<std::is_trivially_copyable_v<U>>>
void serialize(T& archive, U& value)
{
// STL type std::array as data carrier should be widely supported by
// serialization libraries.
auto& bytes = reinterpret_cast<std::array<std::byte, sizeof(U)>&>(value);
archive & bytes;
}

template <typename T, typename A0, typename A1, typename A2>
void serialize(T& archive, state_machine_base<A0, A1, A2>& sm)
{
using FrontEnd = typename state_machine_base<A0, A1, A2>::front_end_t;
if constexpr (has_serialize_member<T, FrontEnd>::value)
{
static_cast<FrontEnd&>(sm).serialize(archive);
}
else if constexpr (has_serialize_free<T, FrontEnd>::value)
{
serialize(archive, static_cast<FrontEnd&>(sm));
}
archive & sm.m_active_state_ids;
sm.m_history.serialize(archive);
serialize(archive, "event_processing", sm.m_event_processing);
// archive & sm.m_event_processing;
mp11::tuple_for_each(sm.m_states,
[&archive](auto& state)
{
using State = std::decay_t<decltype(state)>;
if constexpr (has_serialize_member<T, State>::value ||
has_state_machine_tag<State>::value)
{
archive & state;
}
else if constexpr (has_serialize_free<T, State>::value)
{
serialize(archive, state);
}
});
}

} // boost::msm::backmp11::detail

#endif // BOOST_MSM_BACKMP11_DETAIL_STATE_MACHINE_BASE_HPP
6 changes: 6 additions & 0 deletions include/boost/msm/backmp11/state_machine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ class state_machine<FrontEnd>
using Base::Base;
};

template<typename T, typename A0, typename A1, typename A2>
void serialize(T& archive, detail::state_machine_base<A0, A1, A2>& sm)
{
detail::serialize(archive, sm);
}

} // boost::msm::backmp11

#endif // BOOST_MSM_BACKMP11_STATE_MACHINE_HPP
1 change: 0 additions & 1 deletion include/boost/msm/front/common_states.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ namespace boost { namespace msm { namespace front
// default base: non-polymorphic, not visitable
struct default_base_state
{
~default_base_state(){}
};
// default polymorphic base state. Derive all states from it to get polymorphic behavior
struct polymorphic_state
Expand Down
12 changes: 10 additions & 2 deletions include/boost/msm/front/detail/common_states.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@

#include <boost/msm/front/detail/state_tags.hpp>

#ifndef BOOST_MSM_FRONT_ATTRIBUTES_CONTAINER
#define BOOST_MSM_FRONT_ATTRIBUTES_CONTAINER ::boost::fusion::map<>
#endif

namespace boost { namespace msm { namespace front {namespace detail
{

template <class Attributes= ::boost::fusion::map<> >
template <class Attributes = BOOST_MSM_FRONT_ATTRIBUTES_CONTAINER>
struct inherit_attributes
{
inherit_attributes():m_attributes(){}
Expand Down Expand Up @@ -57,8 +61,12 @@ struct inherit_attributes
Attributes m_attributes;
};

template <>
struct inherit_attributes<void> {};


// the interface for all states. Defines entry and exit functions. Overwrite to implement for any state needing it.
template<class USERBASE,class Attributes= ::boost::fusion::map<> >
template <class USERBASE, class Attributes = BOOST_MSM_FRONT_ATTRIBUTES_CONTAINER>
struct state_base : public inherit_attributes<Attributes>, USERBASE
{
typedef USERBASE user_state_base;
Expand Down
98 changes: 7 additions & 91 deletions test/Backmp11Adapter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
#include <boost/msm/back/queue_container_deque.hpp>
#define BOOST_PARAMETER_CAN_USE_MP11
#include <boost/parameter.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/array.hpp>
#include "Backmp11.hpp"

Expand Down Expand Up @@ -123,6 +122,13 @@ class state_machine_adapter
this->get_event_pool().events.clear();
}

template <class Archive>
void serialize(Archive& ar,
const unsigned int /*version*/)
{
backmp11::serialize(ar, *this);
}

// No adapter.
// Superseded by the visitor API.
// void visit_current_states(...) {...}
Expand All @@ -136,94 +142,4 @@ class state_machine_adapter
// auto get_state_by_id(int id) {...}
};

template <class Archive>
struct serialize_state
{
serialize_state(Archive& ar):ar_(ar){}

template<typename T>
typename ::boost::enable_if<
typename ::boost::mpl::or_<
typename has_do_serialize<T>::type,
typename detail::has_state_machine_tag<typename T::internal>::type
>::type
,void
>::type
operator()(T& t) const
{
ar_ & t;
}
template<typename T>
typename ::boost::disable_if<
typename ::boost::mpl::or_<
typename has_do_serialize<T>::type,
typename detail::has_state_machine_tag<typename T::internal>::type
>::type
,void
>::type
operator()(T&) const
{
// no state to serialize
}
Archive& ar_;
};

namespace detail
{

template <class Archive, class FrontEnd, class Config, class Derived>
void serialize(Archive& ar,
state_machine_base<FrontEnd, Config, Derived>& sm)
{
(serialize_state<Archive>(ar))(boost::serialization::base_object<FrontEnd>(sm));
ar & sm.m_active_state_ids;
ar & sm.m_history;
ar & sm.m_event_processing;
mp11::tuple_for_each(sm.m_states, serialize_state<Archive>(ar));
}

template<typename T, int N>
void serialize(T& ar, detail::history_impl<front::no_history, N>& history)
{
ar & history.m_initial_state_ids;
}

template<typename T, typename... Es, int N>
void serialize(T& ar, detail::history_impl<front::shallow_history<Es...>, N>& history)
{
ar & history.m_initial_state_ids;
ar & history.m_last_active_state_ids;
}

}

} // boost::msm::backmp11

namespace boost::serialization
{

template <class Archive, class A0, class A1, class A2, class A3, class A4>
void serialize(Archive& ar,
msm::backmp11::state_machine_adapter<A0, A1, A2, A3, A4>& sm,
const unsigned int /*version*/)
{
msm::backmp11::detail::serialize(ar, sm);
}

template<typename T, int N>
void serialize(T& ar,
msm::backmp11::detail::history_impl<msm::front::no_history, N>& history,
const unsigned int /*version*/)
{
msm::backmp11::detail::serialize(ar, history);
}

template<typename T, typename... Es, int N>
void serialize(T& ar,
msm::backmp11::detail::history_impl<msm::front::shallow_history<Es...>, N>& history,
const unsigned int /*version*/)
{
msm::backmp11::detail::serialize(ar, history);
}

} // boost::serialization
Loading
Loading