1 /*
2 SObjectizer 5.
3 */
4
5 /*!
6 \file
7 \brief Definition of the template class message_holder.
8
9 \since
10 v.5.6.0
11 */
12
13 #pragma once
14
15 #include <so_5/message.hpp>
16
17 #include <so_5/compiler_features.hpp>
18
19 #include <type_traits>
20
21 namespace so_5
22 {
23
24 /*!
25 * \brief Type of ownership of a message instance inside message_holder.
26 *
27 * This type is intended to be used as parameter for message_holder_t
28 * template.
29 *
30 * \since
31 * v.5.6.0
32 */
33 enum class message_ownership_t
34 {
35 //! Type of ownership will be automatically detected in dependency
36 //! of message mutability.
37 autodetected,
38 //! An instance of message_holder should be the unique holder of
39 //! the message instance.
40 //! In that case message_holder will be similar to unique_ptr.
41 unique,
42 //! Several instances of message_holder can own the message instance.
43 //! In that case message_holder will be similar to shared_ptr.
44 shared
45 };
46
47 namespace details
48 {
49
50 namespace message_holder_details
51 {
52
53 //! A helper function to get a const raw pointer from smart pointer.
54 template< typename M >
55 M *
get_ptr(const intrusive_ptr_t<M> & msg)56 get_ptr( const intrusive_ptr_t<M> & msg ) noexcept
57 {
58 return msg.get();
59 }
60
61 //! A helper function to get a const raw pointer from smart pointer.
62 /*!
63 * This overload is for case when M is a user type message.
64 */
65 template< typename M >
66 M *
get_ptr(const intrusive_ptr_t<user_type_message_t<M>> & msg)67 get_ptr( const intrusive_ptr_t< user_type_message_t<M> > & msg ) noexcept
68 {
69 return std::addressof(msg->m_payload);
70 }
71
72 /*!
73 * \brief Basic part of message_holder implementations.
74 *
75 * Contains method which should be present in all implementations.
76 *
77 * \since
78 * v.5.6.0
79 */
80 template< typename Payload, typename Envelope >
81 class basic_message_holder_impl_t
82 {
83 protected :
84 //! Message instance.
85 /*!
86 * Can be empty if the message_holder doens't hold anything
87 * (in that case empty message_holder is an analogue of nullptr).
88 */
89 intrusive_ptr_t< Envelope > m_msg;
90
91 public :
92 using payload_type = Payload;
93 using envelope_type = Envelope;
94
95 basic_message_holder_impl_t() noexcept = default;
96
basic_message_holder_impl_t(intrusive_ptr_t<Envelope> msg)97 basic_message_holder_impl_t(
98 intrusive_ptr_t< Envelope > msg ) noexcept
99 : m_msg{ std::move(msg) }
100 {}
101
102 //! Drops to pointer to the message instance.
103 /*!
104 * The message_holder becomes empty as the result.
105 */
106 void
reset()107 reset() noexcept
108 {
109 m_msg.reset();
110 }
111
112 //! Check for the emptiness of message_holder.
113 [[nodiscard]]
114 bool
empty() const115 empty() const noexcept
116 {
117 return !static_cast<bool>( m_msg );
118 }
119
120 //! Check for the non-emptiness of message_holder.
121 [[nodiscard]]
operator bool() const122 operator bool() const noexcept
123 {
124 return !this->empty();
125 }
126
127 //! Check for the emptiness of message_holder.
128 [[nodiscard]]
operator !() const129 bool operator!() const noexcept
130 {
131 return this->empty();
132 }
133 };
134
135 /*!
136 * \brief A part of implementation of message_holder to be used for
137 * shared ownership of message instances.
138 *
139 * This implementation allows copy and move.
140 *
141 * This implementation provides const make_reference() method that
142 * returns a copy of underlying smart pointer.
143 *
144 * \since
145 * v.5.6.0
146 */
147 template< typename Payload, typename Envelope >
148 class shared_message_holder_impl_t
149 : public basic_message_holder_impl_t<Payload, Envelope>
150 {
151 using direct_base_type = basic_message_holder_impl_t<Payload, Envelope>;
152
153 public :
154 using direct_base_type::direct_base_type;
155
156 //! Make an another reference to the message.
157 /*!
158 * Returns empty smart pointer if message_holder is empty.
159 */
160 [[nodiscard]]
161 intrusive_ptr_t< Envelope >
make_reference() const162 make_reference() const noexcept
163 {
164 return this->m_msg;
165 }
166 };
167
168 /*!
169 * \brief A part of implementation of message_holder to be used for
170 * unique ownership of message instances.
171 *
172 * This implementation allows only move operations and disables
173 * copy constructor/operator.
174 *
175 * This implementation provides non-const make_reference() method that
176 * extracts underlying smart pointer and leaves message_holder empty.
177 *
178 * \since
179 * v.5.6.0
180 */
181 template< typename Payload, typename Envelope >
182 class unique_message_holder_impl_t
183 : public basic_message_holder_impl_t<Payload, Envelope>
184 {
185 using direct_base_type = basic_message_holder_impl_t<Payload, Envelope>;
186
187 public :
188 using direct_base_type::direct_base_type;
189
190 unique_message_holder_impl_t(
191 const unique_message_holder_impl_t & ) = delete;
192
193 unique_message_holder_impl_t(
194 unique_message_holder_impl_t && ) = default;
195
196 unique_message_holder_impl_t &
197 operator=(
198 const unique_message_holder_impl_t & ) = delete;
199
200 unique_message_holder_impl_t &
201 operator=(
202 unique_message_holder_impl_t && ) = default;
203
204 //! Extracts the smart pointer to the message.
205 /*!
206 * Returns empty smart pointer if message_holder is empty.
207 *
208 * Leaves the message_holder instance empty.
209 */
210 [[nodiscard]]
211 intrusive_ptr_t< Envelope >
make_reference()212 make_reference() noexcept
213 {
214 return { std::move(this->m_msg) };
215 }
216 };
217
218 /*!
219 * \brief A meta-function for selection a base of message_holder implementation
220 * in compile-time.
221 *
222 * If Ownership is message_ownership_t::autodetect then message's mutability
223 * is examined. If message immutable then shared_message_holder_impl_t
224 * will be selected as the base class. For mutable messages
225 * unique_message_holder_impl_t will be used.
226 *
227 * \since
228 * v.5.6.0
229 */
230 template<
231 typename Msg,
232 message_ownership_t Ownership >
233 struct impl_selector
234 {
235 static_assert( !is_signal<Msg>::value,
236 "Signals can't be used with message_holder" );
237
238 using P = typename message_payload_type< Msg >::payload_type;
239 using E = typename message_payload_type< Msg >::envelope_type;
240
241 using type = std::conditional_t<
242 message_ownership_t::autodetected == Ownership,
243 std::conditional_t<
244 message_mutability_t::immutable_message ==
245 message_mutability_traits<Msg>::mutability,
246 shared_message_holder_impl_t<P, E>,
247 unique_message_holder_impl_t<P, E> >,
248 std::conditional_t<
249 message_ownership_t::shared == Ownership,
250 shared_message_holder_impl_t<P, E>,
251 unique_message_holder_impl_t<P, E> >
252 >;
253 };
254
255 /*!
256 * \brief Just a shortcut for impl_selector meta-function.
257 *
258 * \since
259 * v.5.6.0
260 */
261 template<
262 typename Msg,
263 message_ownership_t Ownership >
264 using impl_selector_t = typename impl_selector<Msg, Ownership>::type;
265
266 /*!
267 * \brief An of mixin with getters for message_holder.
268 *
269 * It is assumed that shared_message_holder_impl_t or
270 * unique_message_holder_impl_t will be used as Base template parameter.
271 *
272 * \since
273 * v.5.6.0
274 */
275 template< typename Base, typename Return_Type >
276 class msg_accessors_t : public Base
277 {
278 public :
279 using Base::Base;
280
281 //! Get a pointer to the message inside message_holder.
282 /*!
283 * \attention
284 * Returns nullptr is message_holder is empty.
285 */
286 [[nodiscard]]
287 Return_Type *
get() const288 get() const noexcept
289 {
290 return get_ptr( this->m_msg );
291 }
292
293 //! Get a reference to the message inside message_holder.
294 /*!
295 * \attention
296 * An attempt to use this method on empty message_holder is UB.
297 */
298 [[nodiscard]]
299 Return_Type &
operator *() const300 operator * () const noexcept { return *get(); }
301
302 //! Get a pointer to the message inside message_holder.
303 /*!
304 * \attention
305 * An attempt to use this method on empty message_holder is UB.
306 */
307 [[nodiscard]]
308 Return_Type *
operator ->() const309 operator->() const noexcept { return get(); }
310 };
311
312 /*!
313 * \brief A meta-function for selection of type of accessors mixin.
314 *
315 * \since
316 * v.5.6.0
317 */
318 template<
319 message_mutability_t Mutability,
320 typename Base >
321 struct accessor_selector
322 {
323 using type = std::conditional_t<
324 message_mutability_t::immutable_message == Mutability,
325 msg_accessors_t<Base, typename Base::payload_type const>,
326 msg_accessors_t<Base, typename Base::payload_type> >;
327 };
328
329 /*!
330 * \brief Just a shortcut for accessor_selector meta-function.
331 *
332 * \since
333 * v.5.6.0
334 */
335 template<
336 message_mutability_t Mutability,
337 typename Base >
338 using accessor_selector_t =
339 typename accessor_selector<Mutability, Base>::type;
340
341 } /* namespace message_holder_details */
342
343 } /* namespace details */
344
345 /*!
346 * \brief A class for holding an instance of a message.
347 *
348 * \attention
349 * This class should be used with messages only. Signals are not supported
350 * by that class.
351 *
352 * This class is intended for simplification of holding message instances
353 * for some time and resending them later. For example:
354 * \code
355 * class my_actor final : public so_5::agent_t {
356 * // A stored message.
357 * so_5::message_holder_t<my_message> stored_;
358 * ...
359 * void on_message(mhood_t<my_message> cmd) {
360 * // Store message inside the agent.
361 * stored_ = cmd.make_holder();
362 * ...
363 * // Initiate a delayed message to resend the stored message later.
364 * so_5::send_delayed<resend_message>(*this, 10s);
365 * }
366 * ...
367 * void on_resend_message(mhood_t<resend_message>) {
368 * // Resend the stored message.
369 * so_5::send(some_target, stored_);
370 * // The stored message is no more needed.
371 * stored_.reset();
372 *
373 * // Or we can write:
374 * // so_5::send(some_target, std::move(stored_));
375 * }
376 * };
377 * \endcode
378 * This class is also intended to be used with preallocated messages:
379 * \code
380 * class prealloc_msg_demo final : public so_5::agent_t {
381 * so_5::message_holder_t<request> request_;
382 * ...
383 * prealloc_msg_demo(
384 * context_t ctx,
385 * ... // Some other params.
386 * ) : request_{std::piecewise_construct, ...} // Preallocation of message.
387 * {}
388 *
389 * void on_some_event(...) {
390 * ...
391 * // It is time to send preallocated message.
392 * so_5::send(some_target, request_);
393 * ...
394 * }
395 * };
396 * \endcode
397 *
398 * The main benefit of that class is the ability to correctly handle
399 * messages of arbitrary user types (e.g. messages not derived from
400 * so_5::message_t class) and mutability flags. For example, the following
401 * cases are correctly handled by message_holder_t:
402 * \code
403 * struct status_data { // This is message that is not derived from so_5::message_t.
404 * ... // Some fields.
405 * };
406 *
407 * so_5::message_holder_t<status_data> msg1;
408 * so_5::message_holder_t<so_5::immutable_msg<status_data>> msg2;
409 * so_5::message_holder_t<so_5::mutable_msg<status_data>> msg3;
410 * \endcode
411 *
412 * \par Methods of message_holder_t class
413 *
414 * Class message_holder_t provides the following methods:
415 * \code
416 * // Default constructor. Creates an empty holder.
417 * message_holder_t();
418 *
419 * // Constructs holder for holding the specified message instance.
420 * message_holder_t(so_5::intrusive_ptr<envelope_type> msg);
421 *
422 * // Creates a new instance of message from 'args' and constructs holder for it.
423 * template<typename... Args>
424 * message_holder_t(std::piecewise_construct_t, Args && ...args);
425 *
426 * // Creates a new instance of message from 'args' and constructs holder for it.
427 * template<typename... Args>
428 * static message_holder_t make(Args && ...args);
429 *
430 * // Returns true if message_holder is empty.
431 * bool empty() const noexcept;
432 * bool operator!() const noexcept;
433 *
434 * // Returns true if message_holder is not empty.
435 * operator bool() const noexcept;
436 *
437 * // Drops the content of message_holder.
438 * void reset() noexcept;
439 * \endcode
440 *
441 * There are also some more methods which are depend on mutability of
442 * message and the type of ownership. They are described below.
443 *
444 * \par Getters are depend on mutability of message
445 *
446 * If a message_holder holds an immutable message then there are the following
447 * getter methods:
448 * \code
449 * const payload_type * get() const noexcept;
450 * const payload_type & operator*() const noexcept;
451 * const payload_type * operator->() const noexcept;
452 * \endcode
453 * But if message_holder holds a mutable message those getters are still here
454 * but they have non-const return type:
455 * \code
456 * payload_type * get() const noexcept;
457 * payload_type & operator*() const noexcept;
458 * payload_type * operator->() const noexcept;
459 * \endcode
460 *
461 * \par Shared and unique ownership
462 *
463 * A message_holder works like a smart pointer. But what kind of smart pointer?
464 *
465 * It depends on Ownership template parameters.
466 *
467 * But default Ownership is so_5::message_ownership_t::autodetected.
468 * In this case the behavior of a message_holder depends of the mutability
469 * of message. If message is immutable then message_holders is like
470 * std::shared_ptr: several message_holders can hold pointers to the
471 * same message instances.
472 *
473 * If message is mutable then message_holder is like
474 * std::unique_ptr: only one message_holder can hold a pointer to a message
475 * instance.
476 *
477 * For example:
478 * \code
479 * // Immutable message.
480 * so_5::message_holder_t<my_msg> msg1{std::piecewise_construct, ...};
481 * so_5::message_holder_t<my_msg> msg2{ msg1 };
482 * assert(msg1.get() == msg2.get()); // Now msg1 and msg2 refer to the same msg.
483 *
484 * // Mutable message.
485 * so_5::message_holder_t<so_5::mutable_msg<my_msg>> msg3{...};
486 * so_5::message_holder_t<so_5::mutable_msg<my_msg>> msg4{ msg3 }; // WON'T COMPILE!
487 * so_5::message_holder_t<so_5::mutable_msg<my_msg>> msg5{ std::move(msg3) };
488 * assert(msg3.empty()); // Now msg3 is empty.
489 * assert(!msg5.empty()); // And only msg5 holds the message.
490 * \endcode
491 *
492 * The value of Ownership parameter can be specified manually.
493 * In that case we can have an unique-holder for an immutable message:
494 * \code
495 * so_5::message_holder_t<my_msg, so_5::message_ownership_t::unique> msg1{...};
496 * // WON'T COMPILE!
497 * so_5::message_holder_t<my_msg, so_5::message_ownership_t::unique> msg2{ msg1 };
498 * // Will compile but ownership will be moved:
499 * so_5::message_holder_t<my_msg, so_5::message_ownership_t::unique> msg3{ std::move(msg) };
500 * \endcode
501 * There can also be a shared-holder for a mutable message:
502 * \code
503 * so_5::message_holder_t<my_msg, so_5::message_ownership_t::shared> msg1{...};
504 * // No problems.
505 * so_5::message_holder_t<my_msg, so_5::message_ownership_t::shared> msg2{ msg1 };
506 * \endcode
507 * But this approach should be taken with an additional care because it allows
508 * to make several sends of the same mutable message instances at the same time.
509 *
510 * If a message_holder works as std::shared_ptr then there is the following
511 * methods:
512 * \code
513 * // Copy constructor and operator.
514 * message_holder_t(const message_holder_t &) noexcept;
515 * message_holder_t & operator=(const message_holder_t &) noexcept;
516 * // Move constructor and operator.
517 * message_holder_t(message_holder_t &&) noexcept;
518 * message_holder_t & operator=(message_holder_t &&) noexcept;
519 *
520 * // Getter for the underlying smart pointer.
521 * intrusive_ptr_t<envelope_type> make_reference() const noexcept;
522 * \endcode
523 *
524 * If a message_holder works as std::unique_ptr then copy operator/constructors
525 * are disabled and make_reference() leaves the message_holder object empty:
526 * \code
527 * // Move constructor and operator.
528 * message_holder_t(message_holder_t &&) noexcept;
529 * message_holder_t & operator=(message_holder_t &&) noexcept;
530 *
531 * // Extracts the underlying smart pointer.
532 * // Leaves the message_holder object empty.
533 * intrusive_ptr_t<envelope_type> make_reference() noexcept;
534 * \endcode
535 *
536 * \par Creation of an instance of message to be stored inside a message_holder
537 *
538 * There are several ways of creation of a message to be stored inside
539 * a message_holder object.
540 *
541 * The recommended way is to use the constructor of message_holder with
542 * std::piecewise_construct_t argument. This constructor automatically
543 * creates an underlying message instance:
544 * \code
545 * struct my_msg {
546 * int a_;
547 * std::string b_;
548 * };
549 * so_5::message_holder_t<my_msg> msg{std::piecewise_construct,
550 * 0, // value for my_msg's a_ field.
551 * "hello" // value for my_msg's b_ field.
552 * };
553 * \endcode
554 * Sometimes a static method make() can be used for similar purpose:
555 * \code
556 * auto make_message() {
557 * return so_5::message_holder_t<my_msg>::make(0, "hello");
558 * }
559 * \endcode
560 *
561 * But sometimes an instance of message is present as raw pointer,
562 * std::unique_ptr or so_5::intrusive_ptr_t objects. In that case the
563 * constructor that accepts intrusive_ptr_t can be used:
564 * \code
565 * // Somewhere in 3rd-party library.
566 * std::unique_ptr<some_message> make_message() {
567 * return std::make_unique<some_message>(...);
568 * }
569 *
570 * // Somewhere in your code.
571 * so_5::message_holder_t<some_message> msg{make_message()};
572 * \endcode
573 *
574 * \since
575 * v.5.6.0
576 */
577 template<
578 typename Msg,
579 message_ownership_t Ownership = message_ownership_t::autodetected >
580 class message_holder_t
581 : public details::message_holder_details::accessor_selector_t<
582 details::message_mutability_traits<Msg>::mutability,
583 details::message_holder_details::impl_selector_t<Msg, Ownership> >
584 {
585 using base_type = details::message_holder_details::accessor_selector_t<
586 details::message_mutability_traits<Msg>::mutability,
587 details::message_holder_details::impl_selector_t<Msg, Ownership> >;
588
589 public :
590 using payload_type = typename base_type::payload_type;
591 using envelope_type = typename base_type::envelope_type;
592
593 using base_type::base_type;
594
595 //! Special constructor for constructing message_holder with
596 //! a new message instance inside.
597 /*!
598 * Usage example:
599 * \code
600 * struct my_message {
601 * int a_;
602 * std::string b_;
603 * std::chrono::millisecons c_;
604 * };
605 *
606 * so_5::message_holder_t<my_message> msg{ std::piecewise_construct,
607 * 0, // value for my_message's a_ field.
608 * "hello", // value for my_message's b_ field.
609 * 15s // value for my_message's c_ field.
610 * };
611 * \endcode
612 */
613 template< typename... Args >
message_holder_t(std::piecewise_construct_t,Args &&...args)614 message_holder_t(
615 std::piecewise_construct_t,
616 Args && ...args )
617 : base_type{ make_msg_instance( std::forward<Args>(args)... ) }
618 {}
619
620 friend void
swap(message_holder_t & a,message_holder_t & b)621 swap( message_holder_t & a, message_holder_t & b ) noexcept
622 {
623 using std::swap;
624 swap( a.m_msg, b.m_msg );
625 }
626
627 //! Create a new instance of message_holder with a new message inside.
628 /*!
629 * Usage example:
630 * \code
631 * struct my_message {
632 * int a_;
633 * std::string b_;
634 * std::chrono::millisecons c_;
635 * };
636 *
637 * auto make_message() {
638 * return so_5::message_holder_t<my_message>(
639 * 0, // value for my_message's a_ field.
640 * "hello", // value for my_message's b_ field.
641 * 15s ); // value for my_message's c_ field.
642 * }
643 * \endcode
644 */
645 template< typename... Args >
646 [[nodiscard]]
647 static message_holder_t
make(Args &&...args)648 make( Args && ...args )
649 {
650 return { make_msg_instance( std::forward<Args>(args)... ) };
651 }
652
653 private :
654 //! Create a new instance of message.
655 template< typename... Args >
656 [[nodiscard]]
657 static intrusive_ptr_t< envelope_type >
make_msg_instance(Args &&...args)658 make_msg_instance( Args && ...args )
659 {
660 using namespace details;
661
662 intrusive_ptr_t< envelope_type > msg{
663 make_message_instance< Msg >( std::forward<Args>(args)... )
664 };
665 mark_as_mutable_if_necessary< Msg >( *msg );
666
667 return msg;
668 }
669 };
670
671 } /* namespace so_5 */
672
673