1 /*
2 SObjectizer 5.
3 */
4
5 /*!
6 \file
7 \brief A base class for agents.
8 */
9
10 #pragma once
11
12 #include <so_5/compiler_features.hpp>
13 #include <so_5/declspec.hpp>
14 #include <so_5/types.hpp>
15 #include <so_5/current_thread_id.hpp>
16 #include <so_5/atomic_refcounted.hpp>
17 #include <so_5/spinlocks.hpp>
18 #include <so_5/outliving.hpp>
19
20 #include <so_5/exception.hpp>
21 #include <so_5/error_logger.hpp>
22
23 #include <so_5/details/rollback_on_exception.hpp>
24 #include <so_5/details/abort_on_fatal_error.hpp>
25 #include <so_5/details/at_scope_exit.hpp>
26
27 #include <so_5/fwd.hpp>
28
29 #include <so_5/agent_ref_fwd.hpp>
30 #include <so_5/agent_context.hpp>
31 #include <so_5/mbox.hpp>
32 #include <so_5/agent_state_listener.hpp>
33 #include <so_5/event_queue.hpp>
34 #include <so_5/subscription_storage_fwd.hpp>
35 #include <so_5/handler_makers.hpp>
36 #include <so_5/message_handler_format_detector.hpp>
37 #include <so_5/coop_handle.hpp>
38
39 #include <atomic>
40 #include <map>
41 #include <memory>
42 #include <vector>
43 #include <utility>
44 #include <type_traits>
45
46 #if defined( SO_5_MSVC )
47 #pragma warning(push)
48 #pragma warning(disable: 4251)
49 #endif
50
51 namespace so_5
52 {
53
54 //
55 // exception_reaction_t
56 //
57 /*!
58 * \since
59 * v.5.2.3
60 *
61 * \brief A reaction of SObjectizer to an exception from agent event.
62 */
63 enum exception_reaction_t
64 {
65 //! Execution of application must be aborted immediatelly.
66 abort_on_exception = 1,
67 //! Agent must be switched to special state and SObjectizer
68 //! Environment will be stopped.
69 shutdown_sobjectizer_on_exception = 2,
70 //! Agent must be switched to special state and agent's cooperation
71 //! must be deregistered.
72 deregister_coop_on_exception = 3,
73 //! Exception should be ignored and agent should continue its work.
74 ignore_exception = 4,
75 /*!
76 * \since
77 * v.5.3.0
78 *
79 * \brief Exception reaction should be inherited from SO Environment.
80 */
81 inherit_exception_reaction = 5
82 };
83
84 //
85 // subscription_bind_t
86 //
87
88 /*!
89 * \brief A class for creating a subscription to messages from the mbox.
90 *
91 * This type provides one of the ways to subscribe an agent's event handlers.
92 * There are two way to do that. The first one uses so_5::state_t::event()
93 * methods:
94 * \code
95 * class subscribe_demo : public so_5::agent_t
96 * {
97 * // Some states for the agent.
98 * state_t st_first{this}, st_second{this}, st_third{this};
99 * ...
100 * virtual void so_define_agent() override {
101 * // Subscribe just one event handler for st_first.
102 * st_first.event(some_mbox, &subscribe_demo::event_handler_1);
103 *
104 * // Subscribe two event handlers for st_second.
105 * st_second
106 * .event(some_mbox, &subscribe_demo::event_handler_1)
107 * .event(some_mbox, &subscribe_demo::event_handler_2);
108 *
109 * // Subscribe two event handlers for st_third.
110 * st_third
111 * .event(some_mbox, &subscribe_demo::event_handler_1)
112 * .event(some_mbox, &subscribe_demo::event_handler_3)
113 * }
114 * };
115 * \endcode
116 * But this way do not allow to subscribe the same event handler for
117 * several states in the compact way.
118 *
119 * This can be done via agent_t::so_subscribe(), agent_t::so_subscribe_self()
120 * and subscription_bind_t object:
121 * \code
122 * class subscribe_demo : public so_5::agent_t
123 * {
124 * // Some states for the agent.
125 * state_t st_first{this}, st_second{this}, st_third{this};
126 * ...
127 * virtual void so_define_agent() override {
128 * // Subscribe event_handler_1 for all three states
129 * so_subscribe(some_mbox)
130 * .in(st_first)
131 * .in(st_second)
132 * .in(st_third)
133 * .event(&subscribe_demo::event_handler_1);
134 *
135 * // Subscribe just one event handler for st_second and st_third.
136 * so_subscribe(some_mbox)
137 * .in(st_second)
138 * .event(&subscribe_demo::event_handler_2);
139 *
140 * // Subscribe two event handlers for st_third.
141 * so_subscribe(some_mbox)
142 * .in(st_third)
143 * .event(&subscribe_demo::event_handler_3)
144 * }
145 * };
146 * \endcode
147 *
148 * \par Some words about binder logic...
149 * An object of type subscription_bind_t collects list of states
150 * enumerated by calls to subscription_bind_t::in() method.
151 * Every call to in() method add a state to that list. It means:
152 * \code
153 * so_subscribe(some_mbox) // list is: {}
154 * .in(st_first) // list is: {st_first}
155 * .in(st_second) // list is: {st_first, st_second}
156 * .in(st_third) // list is: {st_first, st_second, st_third}
157 * ...
158 * \endcode
159 * A call to event() or suppress() or just_switch_to() applies subscription
160 * to all states which are currently in the list. But these calls do not
161 * remove the content of that list. It means:
162 * \code
163 * so_subscribe(some_mbox) // list is: {}
164 * .in(st_first) // list is: {st_first}
165 * .event(handler_1) // subscribe for state st_first only.
166 * .in(st_second) // list is: {st_first, st_second}
167 * .event(handler_2) // subscribe for state st_first and for st_second.
168 * .in(st_third) // list is: {st_first, st_second, st_third}
169 * .event(handler_3) // subscribe for state st_first, st_second and st_third.
170 * ...
171 * \endcode
172 */
173 class subscription_bind_t
174 {
175 public:
176 inline
177 subscription_bind_t(
178 //! Agent to subscribe.
179 agent_t & agent,
180 //! Mbox for messages to be subscribed.
181 const mbox_t & mbox_ref );
182
183 //! Set up a state in which events are allowed be processed.
184 inline subscription_bind_t &
185 in(
186 //! State in which events are allowed.
187 const state_t & state );
188
189 //! Make subscription to the message.
190 /*!
191 * \since
192 * v.5.5.14
193 *
194 *
195 * \note Can be used for message and signal handlers.
196 *
197 * \par Usage example
198 * \code
199 struct engine_control : public so_5::message_t { ... };
200 struct check_status : public so_5::signal_t {};
201 class engine_controller : public so_5::agent_t
202 {
203 public :
204 virtual void so_define_agent() override
205 {
206 so_subscribe_self()
207 .event( &engine_controller::control )
208 .event( &engine_controller::check_status );
209 .event( &engine_controller::accelerate );
210 ...
211 }
212 ...
213 private :
214 void control( so_5::mhood_t< engine_control > & cmd ) { ... }
215 void check_status( so_5::mhood_t< check_status > & cmd ) { ... }
216 void accelerate( so_5::mhood_t< int > & cmd ) { ... }
217 };
218 * \endcode
219 */
220 template< typename Method_Pointer >
221 typename std::enable_if<
222 details::is_agent_method_pointer<
223 details::method_arity::unary,
224 Method_Pointer>::value,
225 subscription_bind_t & >::type
226 event(
227 //! Event handling method.
228 Method_Pointer pfn,
229 //! Thread safety of the event handler.
230 thread_safety_t thread_safety = not_thread_safe );
231
232 /*!
233 * \since
234 * v.5.3.0
235 *
236 * \brief Make subscription to the message by lambda-function.
237 *
238 * \attention Only lambda-function in the forms:
239 * \code
240 Result (const Message &)
241 Result (Message)
242 Result (so_5::mhood_t<Message>)
243 Result (const so_5::mhood_t<Message> &)
244 * \endcode
245 * are supported.
246 *
247 * \par Usage example.
248 * \code
249 enum class engine_control { turn_on, turn_off, slow_down };
250 struct setup_params : public so_5::message_t { ... };
251 struct update_settings { ... };
252
253 class engine_controller : public so_5::agent_t
254 {
255 public :
256 virtual void so_define_agent() override
257 {
258 so_subscribe_self()
259 .event( [this]( engine_control evt ) {...} )
260 .event( [this]( const setup_params & evt ) {...} )
261 .event( [this]( const update_settings & evt ) {...} )
262 ...
263 }
264 ...
265 };
266 * \endcode
267 */
268 template< class Lambda >
269 typename std::enable_if<
270 details::lambda_traits::is_lambda<Lambda>::value,
271 subscription_bind_t & >::type
272 event(
273 //! Event handler code.
274 Lambda && lambda,
275 //! Thread safety of the event handler.
276 thread_safety_t thread_safety = not_thread_safe );
277
278 /*!
279 * \since
280 * v.5.5.15
281 *
282 * \brief An instruction for switching agent to the specified
283 * state and transfering event proceessing to new state.
284 *
285 * \par Usage example:
286 * \code
287 class device : public so_5::agent_t {
288 state_t off{ this, "off" };
289 state_t on{ this, "on" };
290 public :
291 virtual void so_define_agent() override {
292 so_subscribe_self().in( off )
293 .transfer_to_state< key_on >( on )
294 .transfer_to_state< key_info >( on );
295 }
296 ...
297 };
298 * \endcode
299 *
300 * \note Event will not be postponed or returned to event queue.
301 * A search for a handler for this event will be performed immediately
302 * after switching to the new state.
303 *
304 * \note New state can use transfer_to_state for that event too:
305 * \code
306 class device : public so_5::agent_t {
307 state_t off{ this, "off" };
308 state_t on{ this, "on" };
309 state_t status_dialog{ this, "status" };
310 public :
311 virtual void so_define_agent() override {
312 so_subscribe_self().in( off )
313 .transfer_to_state< key_on >( on )
314 .transfer_to_state< key_info >( on );
315
316 so_subscribe_self().in( on )
317 .transfer_to_state< key_info >( status_dialog )
318 ...;
319 }
320 ...
321 };
322 * \endcode
323 *
324 * \note Since v.5.5.22.1 actual execution of transfer_to_state operation
325 * can raise so_5::exception_t with so_5::rc_transfer_to_state_loop
326 * error code if a loop in transfer_to_state is detected.
327 */
328 template< typename Msg >
329 subscription_bind_t &
330 transfer_to_state(
331 const state_t & target_state );
332
333 /*!
334 * \since
335 * v.5.5.15
336 *
337 * \brief Suppress processing of event in this state.
338 *
339 * \note This method is useful because the event is not passed to
340 * event handlers from parent states. For example:
341 * \code
342 class demo : public so_5::agent_t
343 {
344 state_t S1{ this, "1" };
345 state_t S2{ initial_substate_of{ S1 }, "2" };
346 state_t S3{ initial_substate_of{ S2 }, "3" };
347 public :
348 virtual void so_define_agent() override
349 {
350 so_subscribe_self().in( S1 )
351 // Default event handler which will be inherited by states S2 and S3.
352 .event< msg1 >(...)
353 .event< msg2 >(...)
354 .event< msg3 >(...);
355
356 so_subscribe_self().in( S2 )
357 // A special handler for msg1.
358 // For msg2 and msg3 event handlers from state S1 will be used.
359 .event< msg1 >(...);
360
361 so_subscribe_self().in( S3 )
362 // Message msg1 will be suppressed. It will be simply ignored.
363 // No events from states S1 and S2 will be called.
364 .suppress< msg1 >()
365 // The same for msg2.
366 .suppress< msg2 >()
367 // A special handler for msg3. Overrides handler from state S1.
368 .event< msg3 >(...);
369 }
370 };
371 * \endcode
372 */
373 template< typename Msg >
374 subscription_bind_t &
375 suppress();
376
377 /*!
378 * \since
379 * v.5.5.15
380 *
381 * \brief Define handler which only switches agent to the specified
382 * state.
383 *
384 * \note This method differes from transfer_to_state() method:
385 * just_switch_to() changes state of the agent, but there will not be a
386 * look for event handler for message/signal in the new state. It means
387 * that just_switch_to() is just a shorthand for:
388 * \code
389 virtual void demo::so_define_agent() override
390 {
391 so_subscribe_self().in( S1 )
392 .event< some_signal >( [this]{ this >>= S2; } );
393 }
394 * \endcode
395 * With just_switch_to() this code can looks like:
396 * \code
397 virtual void demo::so_define_agent() override
398 {
399 so_subscribe_self().in( S1 )
400 .just_switch_to< some_signal >( S2 );
401 }
402 * \endcode
403 */
404 template< typename Msg >
405 subscription_bind_t &
406 just_switch_to(
407 const state_t & target_state );
408
409 private:
410 //! Agent to which we are subscribing.
411 agent_t * m_agent;
412 //! Mbox for messages to subscribe.
413 mbox_t m_mbox_ref;
414
415 /*!
416 * \since
417 * v.5.3.0
418 *
419 * \brief Type of vector of states.
420 */
421 typedef std::vector< const state_t * > state_vector_t;
422
423 /*!
424 * \since
425 * v.5.3.0
426 *
427 * \brief States of agents the event to be subscribed in.
428 */
429 state_vector_t m_states;
430
431 /*!
432 * \since
433 * v.5.3.0
434 *
435 * \brief Create subscription of event for all states.
436 */
437 void
438 create_subscription_for_states(
439 const std::type_index & msg_type,
440 const event_handler_method_t & method,
441 thread_safety_t thread_safety,
442 event_handler_kind_t handler_kind ) const;
443
444 /*!
445 * \brief Additional check for subscription to a mutable message
446 * from MPMC mbox.
447 *
448 * Such attempt must be disabled because delivery of mutable
449 * messages via MPMC mboxes is prohibited.
450 *
451 * \throw so_5::exception_t if m_mbox_ref is a MPMC mbox and
452 * \a handler is for mutable message.
453 *
454 * \since
455 * v.5.5.19
456 */
457 void
458 ensure_handler_can_be_used_with_mbox(
459 const so_5::details::msg_type_and_handler_pair_t & handler ) const;
460 };
461
462 //
463 // agent_t
464 //
465
466 //! A base class for agents.
467 /*!
468 An agent in SObjctizer must be derived from the agent_t.
469
470 The base class provides various methods whose can be splitted into
471 the following groups:
472 \li methods for the interaction with SObjectizer;
473 \li predefined hook-methods which are called during: cooperation
474 registration, starting and stopping of an agent;
475 \li methods for the message subscription and unsubscription;
476 \li methods for working with an agent state;
477
478 <b>Methods for the interaction with SObjectizer</b>
479
480 Method so_5::agent_t::so_environment() serves for the access to the
481 SObjectizer Environment (and, therefore, to all methods of the
482 SObjectizer Environment).
483 This method could be called immediatelly after the agent creation.
484 This is because agent is bound to the SObjectizer Environment during
485 the creation process.
486
487 <b>Hook methods</b>
488
489 The base class defines several hook-methods. Its default implementation
490 do nothing.
491
492 The method agent_t::so_define_agent() is called just before agent will
493 started by SObjectizer as a part of the agent registration process.
494 It should be reimplemented for the initial subscription of the agent
495 to messages.
496
497 There are two hook-methods related to important agent's lifetime events:
498 agent_t::so_evt_start() and agent_t::so_evt_finish(). They are called
499 by SObjectizer in next circumstances:
500 - method so_evt_start() is called when the agent is starting its work
501 inside of SObjectizer. At that moment all agents are defined (all
502 their methods agent_t::so_define_agent() have executed);
503 - method so_evt_finish() is called during the agent's cooperation
504 deregistration just after agent processed the last pending event.
505
506 Methods so_evt_start() and so_evt_finish() are called by SObjectizer and
507 user can just reimplement them to implement the agent-specific logic.
508
509 <b>Message subscription and unsubscription methods</b>
510
511 Any method with one of the following prototypes can be used as an event
512 handler:
513 \code
514 return_type evt_handler( mhood_t< Message > msg );
515 return_type evt_handler( const mhood_t< Message > & msg );
516 return_type evt_handler( const Message & msg );
517 return_type evt_handler( Message msg );
518 // Since v.5.5.20:
519 return_type evt_handler( mhood_t< Message > msg ) const;
520 return_type evt_handler( const mhood_t< Message > & msg ) const;
521 return_type evt_handler( const Message & msg ) const;
522 return_type evt_handler( Message msg ) const;
523 \endcode
524 Where \c evt_handler is a name of the event handler, \c Message is a
525 message type.
526
527 The class mhood_t is a wrapper on pointer to an instance
528 of the \c Message. It is very similar to <tt>std::unique_ptr</tt>.
529 The pointer to \c Message can be a nullptr. It happens in case when
530 the message has no actual data and servers just a signal about something.
531
532 Please note that handlers with the following prototypes can be used
533 only for messages, not signals:
534 \code
535 return_type evt_handler( const Message & msg );
536 return_type evt_handler( Message msg );
537 // Since v.5.5.20:
538 return_type evt_handler( const Message & msg ) const;
539 return_type evt_handler( Message msg ) const;
540 \endcode
541
542 A subscription to the message is performed by the methods so_subscribe()
543 and so_subscribe_self().
544 This method returns an instance of the so_5::subscription_bind_t which
545 does all actual actions of the subscription process. This instance already
546 knows agents and message mbox and uses the default agent state for
547 the event subscription (binding to different state is also possible).
548
549 The presence of a subscription can be checked by so_has_subscription()
550 method.
551
552 A subscription can be dropped (removed) by so_drop_subscription() and
553 so_drop_subscription_for_all_states() methods.
554
555 <b>Deadletter handlers subscription and unsubscription</b>
556
557 Since v.5.5.21 SObjectizer supports deadletter handlers. Such handlers
558 are called if there is no any ordinary event handler for a specific
559 messages from a specific mbox.
560
561 Deadletter handler can be implemented by an agent method or by lambda
562 function. Deadletter handler can have one of the following formats:
563 \code
564 void evt_handler( mhood_t< Message > msg );
565 void return_type evt_handler( mhood_t< Message > msg ) const;
566 void return_type evt_handler( const mhood_t< Message > & msg );
567 void return_type evt_handler( const mhood_t< Message > & msg ) const;
568 void return_type evt_handler( const Message & msg );
569 void return_type evt_handler( const Message & msg ) const;
570 void return_type evt_handler( Message msg );
571 void return_type evt_handler( Message msg ) const;
572 \endcode
573
574 Subscription for a deadletter handler can be created by
575 so_subscribe_deadletter_handler() method.
576
577 The presence of a deadletter handler can be checked by
578 so_has_deadletter_handler() method.
579
580 A deadletter can be dropped (removed) by so_drop_deadletter_handler()
581 and so_drop_subscription_for_all_states() methods.
582
583 <b>Methods for working with an agent state</b>
584
585 The agent can change its state by his so_change_state() method.
586
587 An attempt to switch an agent to the state which belongs to the another
588 agent is an error. If state is belong to the same agent there are
589 no possibility to any run-time errors. In this case changing agent
590 state is a very safe operation.
591
592 In some cases it is necessary to detect agent state switching.
593 For example for application monitoring purposes. This can be done
594 by "state listeners".
595
596 Any count of state listeners can be set for an agent. There are
597 two methods for that:
598 - so_add_nondestroyable_listener() is for listeners whose lifetime
599 are controlled by a programmer, not by SObjectizer;
600 - so_add_destroyable_listener() is for listeners whose lifetime
601 must be controlled by agent itself.
602
603 <b>Work thread identification</b>
604
605 Since v.5.4.0 some operations for agent are enabled only on agent's
606 work thread. They are:
607 - subscription management operations (creation or dropping);
608 - changing agent's state.
609
610 Work thread for an agent is defined as follows:
611 - before invocation of so_define_agent() the work thread is a
612 thread on which agent is created (id of that thread is detected in
613 agent's constructor);
614 - during cooperation registration the working thread is a thread on
615 which so_environment::register_coop() is working;
616 - after successful agent registration the work thread for it is
617 specified by the dispatcher.
618
619 \note Some dispatchers could provide several work threads for
620 an agent. In such case there would not be work thread id. And
621 operations like changing agent state or creation of subscription
622 would be prohibited after agent registration.
623 */
624 class SO_5_TYPE agent_t
625 : private atomic_refcounted_t
626 , public message_limit::message_limit_methods_mixin_t
627 {
628 friend class subscription_bind_t;
629 friend class state_t;
630
631 friend class so_5::impl::mpsc_mbox_t;
632 friend class so_5::impl::state_switch_guard_t;
633 friend class so_5::impl::internal_agent_iface_t;
634
635 friend class so_5::enveloped_msg::impl::agent_demand_handler_invoker_t;
636
637 template< typename T >
638 friend class intrusive_ptr_t;
639
640 public:
641 /*!
642 * \since
643 * v.5.5.4
644 *
645 * \brief Short alias for agent_context.
646 */
647 using context_t = so_5::agent_context_t;
648 /*!
649 * \since
650 * v.5.5.13
651 *
652 * \brief Short alias for %so_5::state_t.
653 */
654 using state_t = so_5::state_t;
655 /*!
656 * \since
657 * v.5.5.14
658 *
659 * \brief Short alias for %so_5::mhood_t.
660 */
661 template< typename T >
662 using mhood_t = so_5::mhood_t< T >;
663 /*!
664 * \since
665 * v.5.5.19
666 *
667 * \brief Short alias for %so_5::mutable_mhood_t.
668 */
669 template< typename T >
670 using mutable_mhood_t = so_5::mutable_mhood_t< T >;
671 /*!
672 * \since
673 * v.5.5.15
674 *
675 * \brief Short alias for %so_5::initial_substate_of.
676 */
677 using initial_substate_of = so_5::initial_substate_of;
678 /*!
679 * \since
680 * v.5.5.15
681 *
682 * \brief Short alias for %so_5::substate_of.
683 */
684 using substate_of = so_5::substate_of;
685 /*!
686 * \since
687 * v.5.5.15
688 *
689 * \brief Short alias for %so_5::state_t::history_t::shallow.
690 */
691 static constexpr const state_t::history_t shallow_history =
692 state_t::history_t::shallow;
693 /*!
694 * \since
695 * v.5.5.15
696 *
697 * \brief Short alias for %so_5::state_t::history_t::deep.
698 */
699 static constexpr const state_t::history_t deep_history =
700 state_t::history_t::deep;
701
702 //! Constructor.
703 /*!
704 Agent must be bound to the SObjectizer Environment during
705 its creation. And that binding cannot be changed anymore.
706 */
707 explicit agent_t(
708 //! The Environment for this agent must exist.
709 environment_t & env );
710
711 /*!
712 * \since
713 * v.5.5.3
714 *
715 * \brief Constructor which allows specification of
716 * agent's tuning options.
717 *
718 * \par Usage sample:
719 \code
720 using namespace so_5;
721 class my_agent : public agent_t
722 {
723 public :
724 my_agent( environment_t & env )
725 : agent_t( env, agent_t::tuning_options()
726 .subscription_storage_factory(
727 vector_based_subscription_storage_factory() ) )
728 {...}
729 }
730 \endcode
731 */
732 agent_t(
733 environment_t & env,
734 agent_tuning_options_t tuning_options );
735
736 /*!
737 * \since
738 * v.5.5.4
739 *
740 * \brief Constructor which simplifies agent construction with
741 * or without agent's tuning options.
742 *
743 * \par Usage sample:
744 * \code
745 class my_agent : public so_5::agent_t
746 {
747 public :
748 my_agent( context_t ctx )
749 : so_5::agent( ctx + limit_then_drop< get_status >(1) )
750 {}
751 ...
752 };
753 class my_more_specific_agent : public my_agent
754 {
755 public :
756 my_more_specific_agent( context_t ctx )
757 : my_agent( ctx + limit_then_drop< reconfigure >(1) )
758 {}
759 };
760
761 // Then somewhere in the code:
762 auto coop = env.make_coop();
763 auto a = coop->make_agent< my_agent >();
764 auto b = coop->make_agent< my_more_specific_agent >();
765 * \endcode
766 */
767 agent_t( context_t ctx );
768
769 virtual ~agent_t();
770
771 //! Get the raw pointer of itself.
772 /*!
773 This method is intended for use in the member initialization
774 list instead 'this' to suppres compiler warnings.
775 For example for an agent state initialization:
776 \code
777 class a_sample_t : public so_5::agent_t
778 {
779 typedef so_5::agent_t base_type_t;
780
781 // Agent state.
782 const so_5::state_t m_sample_state;
783 public:
784 a_sample_t( so_5::environment_t & env )
785 :
786 base_type_t( env ),
787 m_sample_state( self_ptr() )
788 {
789 // ...
790 }
791
792 // ...
793
794 };
795 \endcode
796 */
797 inline const agent_t *
self_ptr() const798 self_ptr() const
799 {
800 return this;
801 }
802
803 inline agent_t *
self_ptr()804 self_ptr()
805 {
806 return this;
807 }
808
809 //! Hook on agent start inside SObjectizer.
810 /*!
811 It is guaranteed that this method will be called first
812 just after end of the cooperation registration process.
813
814 During cooperation registration agent is bound to some
815 working thread. And the first method which is called for
816 the agent on that working thread context is this method.
817
818 \code
819 class a_sample_t : public so_5::agent_t
820 {
821 // ...
822 virtual void
823 so_evt_start();
824 // ...
825 };
826
827 a_sample_t::so_evt_start()
828 {
829 std::cout << "first agent action on bound dispatcher" << std::endl;
830 ... // Some application logic actions.
831 }
832 \endcode
833 */
834 virtual void
835 so_evt_start();
836
837 //! Hook of agent finish in SObjectizer.
838 /*!
839 It is guaranteed that this method will be called last
840 just before deattaching agent from it's working thread.
841
842 This method should be used to perform some cleanup
843 actions on it's working thread.
844 \code
845 class a_sample_t : public so_5::agent_t
846 {
847 // ...
848 virtual void
849 so_evt_finish();
850 // ...
851 };
852
853 a_sample_t::so_evt_finish()
854 {
855 std::cout << "last agent activity";
856
857 if( so_current_state() == m_db_error_happened )
858 {
859 // Delete the DB connection on the same thread where
860 // connection was established and where some
861 // error happened.
862 m_db.release();
863 }
864 }
865 \endcode
866 */
867 virtual void
868 so_evt_finish();
869
870 //! Access to the current agent state.
871 /*!
872 * \note
873 * There is a change in behaviour of this methon in v.5.5.22.
874 * If some on_enter/on_exit handler calls this method during
875 * the state change procedure this method will return the state
876 * for which this on_enter/on_exit handler is called. For example:
877 * \code
878 * class demo final : public so_5::agent_t {
879 * state_t st_1{ this };
880 * state_t st_1_1{ initial_substate_of{st_1} };
881 * state_t st_1_2{ substate_of{st_1}};
882 * ...
883 * virtual void so_define_agent() override {
884 * st_1.on_enter([this]{
885 * assert(st_1 == so_current_state());
886 * ...
887 * });
888 * st_1_1.on_enter([this]{
889 * assert(st_1_1 == so_current_state());
890 * ...
891 * });
892 * ...
893 * }
894 * };
895 * \endcode
896 */
897 inline const state_t &
so_current_state() const898 so_current_state() const
899 {
900 return *m_current_state_ptr;
901 }
902
903 /*!
904 * \since
905 * v.5.5.15
906 *
907 * \brief Is a state activated?
908 *
909 * \note Since v.5.5.15 a state can have substates. For example
910 * state A can have substates B and C. If B is the current state
911 * then so_current_state() will return a reference to B. But
912 * state A is active too because it is a superstate for B.
913 * Method so_is_active_state(A) will return \a true in that case:
914 * \code
915 class demo : public so_5::agent_t
916 {
917 state_t A{ this, "A" };
918 state_t B{ initial_substate_of{ A }, "B" };
919 state_t C{ substate_of{ A }, "C" };
920 ...
921 void some_event()
922 {
923 this >>= C;
924
925 assert( C == so_current_state() );
926 assert( !( A == so_current_state() ) );
927 assert( so_is_active_state(A) );
928 ...
929 }
930 };
931 * \endcode
932 *
933 * \attention This method is not thread safe. Be careful calling
934 * this method from outside of agent's working thread.
935 *
936 * \return \a true if state \a state_to_check is the current state
937 * or if the current state is a substate of \a state_to_check.
938 *
939 */
940 bool
941 so_is_active_state( const state_t & state_to_check ) const noexcept;
942
943 //! Add a state listener to the agent.
944 /*!
945 * A programmer should guarantee that the lifetime of
946 * \a state_listener is exceeds lifetime of the agent.
947 */
948 void
949 so_add_nondestroyable_listener(
950 agent_state_listener_t & state_listener );
951
952 //! Add a state listener to the agent.
953 /*!
954 * Agent takes care of the \a state_listener destruction.
955 */
956 void
957 so_add_destroyable_listener(
958 agent_state_listener_unique_ptr_t state_listener );
959
960 /*!
961 * \since
962 * v.5.2.3
963 *
964 * \brief A reaction from SObjectizer to an exception from
965 * agent's event.
966 *
967 * If an exception is going out from agent's event it will be
968 * caught by SObjectizer. Then SObjectizer will call this method
969 * and perform some actions in dependence of return value.
970 *
971 * \note Since v.5.3.0 default implementation calls
972 * coop_t::exception_reaction() for agent's cooperation
973 * object.
974 */
975 virtual exception_reaction_t
976 so_exception_reaction() const;
977
978 /*!
979 * \since 5.2.3
980 * \brief Switching agent to special state in case of unhandled
981 * exception.
982 */
983 void
984 so_switch_to_awaiting_deregistration_state();
985
986 //! Push an event to the agent's event queue.
987 /*!
988 This method is used by SObjectizer for the
989 agent's event scheduling.
990 */
991 static inline void
call_push_event(agent_t & agent,const message_limit::control_block_t * limit,mbox_id_t mbox_id,std::type_index msg_type,const message_ref_t & message)992 call_push_event(
993 agent_t & agent,
994 const message_limit::control_block_t * limit,
995 mbox_id_t mbox_id,
996 std::type_index msg_type,
997 const message_ref_t & message )
998 {
999 agent.push_event( limit, mbox_id, msg_type, message );
1000 }
1001
1002 /*!
1003 * \since
1004 * v.5.4.0
1005 *
1006 * \brief Get the agent's direct mbox.
1007 */
1008 const mbox_t &
1009 so_direct_mbox() const;
1010
1011 /*!
1012 * \brief Create a new direct mbox for that agent.
1013 *
1014 * This method creates a new MPSC mbox which is connected
1015 * with that agent. Only agent for that so_make_new_direct_mbox()
1016 * has been called can make subscriptions for a new mbox.
1017 *
1018 * Note. The new mbox doesn't replaces the standard direct mbox
1019 * for the agent. Old direct mbox is still here and can still be used
1020 * for sending messages to the agent. But new mbox is not related
1021 * to the old direct mbox: they are different mboxes and can be used
1022 * for different subscriptions.
1023 * For example:
1024 * \code
1025 * class my_agent final : public so_5::agent_t {
1026 * ...
1027 * void so_evt_start() override {
1028 * so_subscribe_self().event( [](mhood_t<hello>) {
1029 * std::cout << "hello from the direct mbox" << std::endl;
1030 * } );
1031 *
1032 * const new_mbox = so_make_new_direct_mbox();
1033 * so_subscribe( new_mbox ).event( [](mhood_t<hello) {
1034 * std::cout << "hello from a new mbox" << std::endl;
1035 * }
1036 *
1037 * so_5::send<hello>(*this);
1038 * so_5::send<hello>(new_mbox);
1039 * }
1040 * };
1041 * \endcode
1042 * The output will be:
1043 \verbatim
1044 hello from the direct mbox
1045 hello from a new mbox
1046 \endverbatim
1047 *
1048 * \since
1049 * v.5.6.0
1050 */
1051 mbox_t
1052 so_make_new_direct_mbox();
1053
1054 /*!
1055 * \since
1056 * v.5.5.3
1057 *
1058 * \brief Create tuning options object with default values.
1059 */
1060 inline static agent_tuning_options_t
tuning_options()1061 tuning_options()
1062 {
1063 return agent_tuning_options_t();
1064 }
1065
1066 protected:
1067 /*!
1068 * \name Methods for working with the agent state.
1069 * \{
1070 */
1071
1072 //! Access to the agent's default state.
1073 const state_t &
1074 so_default_state() const;
1075
1076 public : /* Note: since v.5.5.1 method so_change_state() is public */
1077
1078 //! Method changes state.
1079 /*!
1080 Usage sample:
1081 \code
1082 void a_sample_t::evt_smth( mhood_t< message_one_t > msg )
1083 {
1084 // If something wrong with the message then we should
1085 // switch to the error_state.
1086 if( error_in_data( *msg ) )
1087 so_change_state( m_error_state );
1088 }
1089 \endcode
1090 */
1091 void
1092 so_change_state(
1093 //! New agent state.
1094 const state_t & new_state );
1095 /*!
1096 * \}
1097 */
1098
1099 public : /* Note: since v.5.2.3.2 subscription-related method are
1100 made public. */
1101
1102 /*!
1103 * \name Subscription methods.
1104 * \{
1105 */
1106
1107 //! Initiate subscription.
1108 /*!
1109 This method starts a subscription procedure by returning
1110 an instance of subscription_bind_t. The subscription details and
1111 the completion of a subscription is controlled by this
1112 subscription_bind_t object.
1113
1114 Usage sample:
1115 \code
1116 void a_sample_t::so_define_agent()
1117 {
1118 // Subscription for state `state_one`
1119 so_subscribe( mbox_target )
1120 .in( state_one )
1121 .event( &a_sample_t::evt_sample_handler );
1122
1123 // Subscription for the default state.
1124 so_subscribe( another_mbox )
1125 .event( &a_sample_t::evt_another_handler );
1126
1127 // Subscription for several event handlers in the default state.
1128 so_subscribe( yet_another_mbox )
1129 .event( &a_sample_t::evt_yet_another_handler )
1130 // Lambda-function can be used as event handler too.
1131 .event( [this](mhood_t<some_message> cmd) {...} );
1132
1133 // Subscription for several event handlers.
1134 // All of them will be subscribed for states first_state and second_state.
1135 so_subscribe( some_mbox )
1136 .in( first_state )
1137 .in( second_state )
1138 .event( &a_sample_t::evt_some_handler_1 )
1139 .event( &a_sample_t::evt_some_handler_2 )
1140 .event( &a_sample_t::evt_some_handler_3 );
1141 }
1142 \endcode
1143 */
1144 inline subscription_bind_t
so_subscribe(const mbox_t & mbox_ref)1145 so_subscribe(
1146 //! Mbox for messages to subscribe.
1147 const mbox_t & mbox_ref )
1148 {
1149 return subscription_bind_t( *this, mbox_ref );
1150 }
1151
1152 /*!
1153 * \since
1154 * v.5.5.1
1155 *
1156 * \brief Initiate subscription to agent's direct mbox.
1157 *
1158 * Note that is just a short form of:
1159 * \code
1160 * void a_sample_t::so_define_agent()
1161 * {
1162 * so_subscribe( so_direct_mbox() )
1163 * .in( some_state )
1164 * .in( another_state )
1165 * .event( some_event_handler )
1166 * .event( some_another_handler );
1167 * }
1168 * \endcode
1169 * Instead of writting `so_subscribe(so_direct_mbox())` it is possible
1170 * to write just `so_subscribe_self()`.
1171 *
1172 * \par Usage sample:
1173 \code
1174 void a_sample_t::so_define_agent()
1175 {
1176 // Subscription for state `state_one`
1177 so_subscribe_self()
1178 .in( state_one )
1179 .event( &a_sample_t::evt_sample_handler );
1180
1181 // Subscription for the default state.
1182 so_subscribe_self()
1183 .event( &a_sample_t::evt_another_handler );
1184
1185 // Subscription for several event handlers in the default state.
1186 so_subscribe_self()
1187 .event( &a_sample_t::evt_yet_another_handler )
1188 // Lambda-function can be used as event handler too.
1189 .event( [this](mhood_t<some_message> cmd) {...} );
1190
1191 // Subscription for several event handlers.
1192 // All of them will be subscribed for states first_state and second_state.
1193 so_subscribe_self()
1194 .in( first_state )
1195 .in( second_state )
1196 .event( &a_sample_t::evt_some_handler_1 )
1197 .event( &a_sample_t::evt_some_handler_2 )
1198 .event( &a_sample_t::evt_some_handler_3 );
1199 }
1200 \endcode
1201 */
1202 inline subscription_bind_t
so_subscribe_self()1203 so_subscribe_self()
1204 {
1205 return so_subscribe( so_direct_mbox() );
1206 }
1207
1208 /*!
1209 * \brief Create a subscription for an event.
1210 *
1211 * \note
1212 * Before v.5.5.21 it was a private method. Since v.5.5.21
1213 * it is a public method with a standard so_-prefix.
1214 * It was made public to allow creation of subscriptions
1215 * to agent from outside of agent.
1216 *
1217 * \note
1218 * Parameter \a handler_kind was introduced in v.5.7.0.
1219 */
1220 void
1221 so_create_event_subscription(
1222 //! Message's mbox.
1223 const mbox_t & mbox_ref,
1224 //! Message type.
1225 std::type_index type_index,
1226 //! State for event.
1227 const state_t & target_state,
1228 //! Event handler caller.
1229 const event_handler_method_t & method,
1230 //! Thread safety of the event handler.
1231 thread_safety_t thread_safety,
1232 //! Kind of that event handler.
1233 event_handler_kind_t handler_kind );
1234
1235 /*!
1236 * \brief Destroy event subscription.
1237 *
1238 * \note
1239 * This method was introduced in v.5.5.21 to allow manipulation
1240 * of agent's subscriptions from outside of an agent.
1241 *
1242 * \note
1243 * It is safe to try to destroy nonexistent subscription.
1244 *
1245 * \since
1246 * v.5.5.21
1247 */
1248 void
so_destroy_event_subscription(const mbox_t & mbox,const std::type_index & subscription_type,const state_t & target_state)1249 so_destroy_event_subscription(
1250 //! Message's mbox.
1251 const mbox_t & mbox,
1252 //! Message's type.
1253 const std::type_index & subscription_type,
1254 //! Target state of a subscription.
1255 const state_t & target_state )
1256 {
1257 do_drop_subscription(
1258 mbox,
1259 subscription_type,
1260 target_state );
1261 }
1262
1263 /*!
1264 * \since
1265 * v.5.2.3
1266 *
1267 * \brief Drop subscription for the state specified.
1268 *
1269 * This overload is indended to be used when there is an event-handler in
1270 * the form of agent's method. And there is a need to unsubscribe this
1271 * event handler.
1272 * For example:
1273 * \code
1274 * class demo : public so_5::agent_t {
1275 * void on_some_event(mhood_t<some_msg> cmd) {
1276 * if(cmd->some_condition)
1277 * // New subscription must be created.
1278 * so_subscribe(some_mbox).in(some_state)
1279 * .event(&demo::one_shot_message_handler);
1280 * ...
1281 * }
1282 *
1283 * void one_shot_message_handler(mhood_t<another_msg> cmd) {
1284 * ... // Some actions.
1285 * // Subscription is no more needed.
1286 * so_drop_subscription(some_mbox, some_state,
1287 * &demo::one_shot_message_handler);
1288 * }
1289 * };
1290 * \endcode
1291 *
1292 * \note Doesn't throw if there is no such subscription.
1293 *
1294 * \note Subscription is removed even if agent was subscribed
1295 * for this message type with different method pointer.
1296 * The pointer to event routine is necessary only to
1297 * detect MSG type.
1298 */
1299 template< typename Method_Pointer >
1300 typename std::enable_if<
1301 details::is_agent_method_pointer<
1302 details::method_arity::unary,
1303 Method_Pointer>::value,
1304 void >::type
so_drop_subscription(const mbox_t & mbox,const state_t & target_state,Method_Pointer)1305 so_drop_subscription(
1306 const mbox_t & mbox,
1307 const state_t & target_state,
1308 Method_Pointer /*pfn*/ )
1309 {
1310 using pfn_traits = details::is_agent_method_pointer<
1311 details::method_arity::unary, Method_Pointer>;
1312
1313 using message_type =
1314 typename details::message_handler_format_detector<
1315 typename pfn_traits::argument_type >::type;
1316
1317 do_drop_subscription( mbox,
1318 message_payload_type< message_type >::subscription_type_index(),
1319 target_state );
1320 }
1321
1322 /*!
1323 * \since
1324 * v.5.5.3
1325 *
1326 * \brief Drop subscription for the state specified.
1327 *
1328 * Usage example:
1329 * \code
1330 * class demo : public so_5::agent_t {
1331 * void on_turn_listening_on(mhood_t<turn_on> cmd) {
1332 * // New subscription must be created.
1333 * so_subscribe(cmd->listeting_mbox()).in(some_state)
1334 * .event([this](mhood_t<state_change_notify> cmd) {...});
1335 * ...
1336 * }
1337 *
1338 * void on_turn_listening_off(mhood_t<turn_off> cmd) {
1339 * // Subscription is no more needed.
1340 * so_drop_subscription<state_change_notify>(cmd->listening_mbox(), some_state);
1341 * ...
1342 * }
1343 * };
1344 * \endcode
1345 *
1346 * \note Doesn't throw if there is no such subscription.
1347 */
1348 template< class Message >
1349 inline void
so_drop_subscription(const mbox_t & mbox,const state_t & target_state)1350 so_drop_subscription(
1351 const mbox_t & mbox,
1352 const state_t & target_state )
1353 {
1354 do_drop_subscription(
1355 mbox,
1356 message_payload_type< Message >::subscription_type_index(),
1357 target_state );
1358 }
1359
1360 /*!
1361 * \since
1362 * v.5.2.3
1363 *
1364 * \brief Drop subscription for the default agent state.
1365 *
1366 * This overload is indended to be used when there is an event-handler in
1367 * the form of agent's method. And there is a need to unsubscribe this
1368 * event handler.
1369 * For example:
1370 * \code
1371 * class demo : public so_5::agent_t {
1372 * void on_some_event(mhood_t<some_msg> cmd) {
1373 * if(cmd->some_condition)
1374 * // New subscription must be created.
1375 * so_subscribe(some_mbox)
1376 * .event(&demo::one_shot_message_handler);
1377 * ...
1378 * }
1379 *
1380 * void one_shot_message_handler(mhood_t<another_msg> cmd) {
1381 * ... // Some actions.
1382 * // Subscription is no more needed.
1383 * so_drop_subscription(some_mbox,
1384 * &demo::one_shot_message_handler);
1385 * }
1386 * };
1387 * \endcode
1388 *
1389 * \note Doesn't throw if there is no such subscription.
1390 *
1391 * \note Subscription is removed even if agent was subscribed
1392 * for this message type with different method pointer.
1393 * The pointer to event routine is necessary only to
1394 * detect Msg type.
1395 */
1396 template< typename Method_Pointer >
1397 typename std::enable_if<
1398 details::is_agent_method_pointer<
1399 details::method_arity::unary,
1400 Method_Pointer>::value,
1401 void >::type
so_drop_subscription(const mbox_t & mbox,Method_Pointer)1402 so_drop_subscription(
1403 const mbox_t & mbox,
1404 Method_Pointer /*pfn*/ )
1405 {
1406 using pfn_traits = details::is_agent_method_pointer<
1407 details::method_arity::unary, Method_Pointer>;
1408
1409 using message_type =
1410 typename details::message_handler_format_detector<
1411 typename pfn_traits::argument_type >::type;
1412
1413 do_drop_subscription(
1414 mbox,
1415 message_payload_type< message_type >::subscription_type_index(),
1416 so_default_state() );
1417 }
1418
1419 /*!
1420 * \since
1421 * v.5.5.3
1422 *
1423 * \brief Drop subscription for the default agent state.
1424 *
1425 * Usage example:
1426 * \code
1427 * class demo : public so_5::agent_t {
1428 * void on_turn_listening_on(mhood_t<turn_on> cmd) {
1429 * // New subscription must be created.
1430 * so_subscribe(cmd->listening_mbox())
1431 * .event([this](mhood_t<state_change_notify> cmd) {...});
1432 * ...
1433 * }
1434 *
1435 * void on_turn_listening_off(mhood_t<turn_off> cmd) {
1436 * // Subscription is no more needed.
1437 * so_drop_subscription<state_change_notify>(cmd->listening_mbox());
1438 * ...
1439 * }
1440 * };
1441 * \endcode
1442 *
1443 * \note Doesn't throw if there is no such subscription.
1444 */
1445 template< class Message >
1446 inline void
so_drop_subscription(const mbox_t & mbox)1447 so_drop_subscription(
1448 const mbox_t & mbox )
1449 {
1450 do_drop_subscription(
1451 mbox,
1452 message_payload_type< Message >::subscription_type_index(),
1453 so_default_state() );
1454 }
1455
1456 /*!
1457 * \since
1458 * v.5.2.3
1459 *
1460 * \brief Drop subscription for all states.
1461 *
1462 * Usage example:
1463 * \code
1464 * class demo : public so_5::agent_t {
1465 * state_t st_working{this}, st_waiting{this}, st_stopping{this};
1466 * ...
1467 * void on_turn_listening_on(mhood_t<turn_on> cmd) {
1468 * // Make subscriptions for message of type state_change_notify.
1469 * st_working.event(cmd->listening_mbox(),
1470 * &demo::on_state_notify_when_working);
1471 * st_waiting.event(cmd->listening_mbox(),
1472 * &demo::on_state_notify_when_waiting);
1473 * st_waiting.event(cmd->listening_mbox(),
1474 * &demo::on_state_notify_when_stopping);
1475 * ...
1476 * }
1477 * void on_turn_listening_off(mhood_t<turn_off> cmd) {
1478 * // Subscriptions are no more needed.
1479 * // All three event handlers for state_change_notify
1480 * // will be unsubscribed.
1481 * so_drop_subscription_for_all_states(cmd->listening_mbox(),
1482 * &demo::on_state_notify_when_working);
1483 * }
1484 * ...
1485 * void on_state_notify_when_working(mhood_t<state_change_notify> cmd) {...}
1486 * void on_state_notify_when_waiting(mhood_t<state_change_notify> cmd) {...}
1487 * void on_state_notify_when_stopping(mhood_t<state_change_notify> cmd) {...}
1488 * };
1489 * \endcode
1490 *
1491 * \note Doesn't throw if there is no any subscription for
1492 * that mbox and message type.
1493 *
1494 * \note Subscription is removed even if agent was subscribed
1495 * for this message type with different method pointer.
1496 * The pointer to event routine is necessary only to
1497 * detect Msg type.
1498 *
1499 * \note
1500 * Since v.5.5.21 this method also drops the subscription
1501 * for a deadletter handler for that type of message/signal.
1502 */
1503 template< typename Method_Pointer >
1504 typename std::enable_if<
1505 details::is_agent_method_pointer<
1506 details::method_arity::unary,
1507 Method_Pointer>::value,
1508 void >::type
so_drop_subscription_for_all_states(const mbox_t & mbox,Method_Pointer)1509 so_drop_subscription_for_all_states(
1510 const mbox_t & mbox,
1511 Method_Pointer /*pfn*/ )
1512 {
1513 using pfn_traits = details::is_agent_method_pointer<
1514 details::method_arity::unary, Method_Pointer>;
1515
1516 using message_type =
1517 typename details::message_handler_format_detector<
1518 typename pfn_traits::argument_type >::type;
1519
1520 do_drop_subscription_for_all_states(
1521 mbox,
1522 message_payload_type< message_type >::subscription_type_index() );
1523 }
1524
1525 /*!
1526 * \since
1527 * v.5.5.3
1528 *
1529 * \brief Drop subscription for all states.
1530 *
1531 * Usage example:
1532 * \code
1533 * class demo : public so_5::agent_t {
1534 * state_t st_working{this}, st_waiting{this}, st_stopping{this};
1535 * ...
1536 * void on_turn_listening_on(mhood_t<turn_on> cmd) {
1537 * // Make subscriptions for message of type state_change_notify.
1538 * st_working.event(cmd->listening_mbox(),
1539 * [this](mhood_t<state_change_notify> cmd) {...});
1540 * st_waiting.event(cmd->listening_mbox(),
1541 * [this](mhood_t<state_change_notify> cmd) {...});
1542 * st_waiting.event(cmd->listening_mbox(),
1543 * [this](mhood_t<state_change_notify> cmd) {...});
1544 * ...
1545 * }
1546 * void on_turn_listening_off(mhood_t<turn_off> cmd) {
1547 * // Subscriptions are no more needed.
1548 * // All three event handlers for state_change_notify
1549 * // will be unsubscribed.
1550 * so_drop_subscription_for_all_states<state_change_notify>(cmd->listening_mbox());
1551 * }
1552 * ...
1553 * };
1554 * \endcode
1555 * \note Doesn't throw if there is no any subscription for
1556 * that mbox and message type.
1557 *
1558 * \note
1559 * Since v.5.5.21 this method also drops the subscription
1560 * for a deadletter handler for that type of message/signal.
1561 */
1562 template< class Message >
1563 inline void
so_drop_subscription_for_all_states(const mbox_t & mbox)1564 so_drop_subscription_for_all_states(
1565 const mbox_t & mbox )
1566 {
1567 do_drop_subscription_for_all_states(
1568 mbox,
1569 message_payload_type< Message >::subscription_type_index() );
1570 }
1571
1572 /*!
1573 * \brief Check the presence of a subscription.
1574 *
1575 * This method can be used to avoid an exception from so_subscribe()
1576 * in the case if the subscription is already present. For example:
1577 * \code
1578 * void my_agent::evt_create_new_subscription(mhood_t<data_source> cmd)
1579 * {
1580 * // cmd can contain mbox we have already subscribed to.
1581 * // If we just call so_subscribe() then an exception can be thrown.
1582 * // Because of that check the presence of subscription first.
1583 * if(!so_has_subscription<message>(cmd->mbox(), so_default_state()))
1584 * {
1585 * // There is no subscription yet. New subscription can be
1586 * // created.
1587 * so_subscribe(cmd->mbox()).event(...);
1588 * }
1589 * }
1590 * \endcode
1591 *
1592 * \note
1593 * Please do not call this method from outside of working context
1594 * of the agent.
1595 *
1596 * \return true if subscription is present for \a target_state.
1597 *
1598 * \tparam Message a type of message/signal subscription to which
1599 * must be checked.
1600 *
1601 * \since
1602 * v.5.5.19.5
1603 */
1604 template< class Message >
1605 bool
so_has_subscription(const mbox_t & mbox,const state_t & target_state) const1606 so_has_subscription(
1607 //! A mbox from which message/signal of type \a Message is expected.
1608 const mbox_t & mbox,
1609 //! A target state for the subscription.
1610 const state_t & target_state ) const noexcept
1611 {
1612 return do_check_subscription_presence(
1613 mbox,
1614 message_payload_type< Message >::subscription_type_index(),
1615 target_state );
1616 }
1617
1618 /*!
1619 * \brief Check the presence of a subscription in the default_state.
1620 *
1621 * This method can be used to avoid an exception from so_subscribe()
1622 * in the case if the subscription is already present. For example:
1623 * \code
1624 * void my_agent::evt_create_new_subscription(mhood_t<data_source> cmd)
1625 * {
1626 * // cmd can contain mbox we have already subscribed to.
1627 * // If we just call so_subscribe() then an exception can be thrown.
1628 * // Because of that check the presence of subscription first.
1629 * if(!so_has_subscription<message>(cmd->mbox()))
1630 * {
1631 * // There is no subscription yet. New subscription can be
1632 * // created.
1633 * so_subscribe(cmd->mbox()).event(...);
1634 * }
1635 * }
1636 * \endcode
1637 *
1638 * \note
1639 * Please do not call this method from outside of working context
1640 * of the agent.
1641 *
1642 * \return true if subscription is present for the default_state.
1643 *
1644 * \tparam Message a type of message/signal subscription to which
1645 * must be checked.
1646 *
1647 * \since
1648 * v.5.5.19.5
1649 */
1650 template< class Message >
1651 bool
so_has_subscription(const mbox_t & mbox) const1652 so_has_subscription(
1653 //! A mbox from which message/signal of type \a Message is expected.
1654 const mbox_t & mbox ) const noexcept
1655 {
1656 return do_check_subscription_presence(
1657 mbox,
1658 message_payload_type< Message >::subscription_type_index(),
1659 so_default_state() );
1660 }
1661
1662 /*!
1663 * \brief Check the presence of a subscription.
1664 *
1665 * Type of message is deducted from event-handler signature.
1666 *
1667 * Usage example:
1668 * \code
1669 * void my_agent::evt_create_new_subscription(mhood_t<data_source> cmd)
1670 * {
1671 * // cmd can contain mbox we have already subscribed to.
1672 * // If we just call so_subscribe() then an exception can be thrown.
1673 * // Because of that check the presence of subscription first.
1674 * if(!so_has_subscription(cmd->mbox(), my_state, &my_agent::my_event))
1675 * {
1676 * // There is no subscription yet. New subscription can be
1677 * // created.
1678 * so_subscribe(cmd->mbox()).event(...);
1679 * }
1680 * }
1681 * \endcode
1682 *
1683 * \note
1684 * Please do not call this method from outside of working context
1685 * of the agent.
1686 *
1687 * \return true if subscription is present for \a target_state.
1688 *
1689 * \since
1690 * v.5.5.19.5
1691 */
1692 template< typename Method_Pointer >
1693 typename std::enable_if<
1694 details::is_agent_method_pointer<
1695 details::method_arity::unary,
1696 Method_Pointer>::value,
1697 bool >::type
so_has_subscription(const mbox_t & mbox,const state_t & target_state,Method_Pointer) const1698 so_has_subscription(
1699 //! A mbox from which message/signal is expected.
1700 const mbox_t & mbox,
1701 //! A target state for the subscription.
1702 const state_t & target_state,
1703 Method_Pointer /*pfn*/ ) const noexcept
1704 {
1705 using pfn_traits = details::is_agent_method_pointer<
1706 details::method_arity::unary, Method_Pointer>;
1707
1708 using message_type =
1709 typename details::message_handler_format_detector<
1710 typename pfn_traits::argument_type>::type;
1711
1712 return this->so_has_subscription<message_type>(
1713 mbox, target_state );
1714 }
1715
1716 /*!
1717 * \brief Check the presence of a subscription.
1718 *
1719 * Subscription is checked for the default agent state.
1720 *
1721 * Type of message is deducted from event-handler signature.
1722 *
1723 * Usage example:
1724 * \code
1725 * void my_agent::evt_create_new_subscription(mhood_t<data_source> cmd)
1726 * {
1727 * // cmd can contain mbox we have already subscribed to.
1728 * // If we just call so_subscribe() then an exception can be thrown.
1729 * // Because of that check the presence of subscription first.
1730 * if(!so_has_subscription(cmd->mbox(), &my_agent::my_event))
1731 * {
1732 * // There is no subscription yet. New subscription can be
1733 * // created.
1734 * so_subscribe(cmd->mbox()).event(...);
1735 * }
1736 * }
1737 * \endcode
1738 *
1739 * \note
1740 * Please do not call this method from outside of working context
1741 * of the agent.
1742 *
1743 * \return true if subscription is present for the default state.
1744 *
1745 * \since
1746 * v.5.5.19.5
1747 */
1748 template< typename Method_Pointer >
1749 typename std::enable_if<
1750 details::is_agent_method_pointer<
1751 details::method_arity::unary,
1752 Method_Pointer>::value,
1753 bool >::type
so_has_subscription(const mbox_t & mbox,Method_Pointer) const1754 so_has_subscription(
1755 //! A mbox from which message/signal is expected.
1756 const mbox_t & mbox,
1757 Method_Pointer /*pfn*/ ) const noexcept
1758 {
1759 using pfn_traits = details::is_agent_method_pointer<
1760 details::method_arity::unary, Method_Pointer>;
1761
1762 using message_type =
1763 typename details::message_handler_format_detector<
1764 typename pfn_traits::argument_type>::type;
1765
1766 return this->so_has_subscription<message_type>(
1767 mbox, so_default_state() );
1768 }
1769 /*!
1770 * \}
1771 */
1772
1773 /*!
1774 * \name Methods for dealing with deadletter subscriptions.
1775 * \{
1776 */
1777 /*!
1778 * \brief Create a subscription for a deadletter handler.
1779 *
1780 * \note
1781 * This is low-level method intended to be used by libraries writters.
1782 * Do not call it directly if you don't understand its purpose and
1783 * what its arguments mean. Use so_subscribe_deadletter_handler()
1784 * instead.
1785 *
1786 * This method actually creates a subscription to deadletter handler
1787 * for messages/signal of type \a msg_type from mbox \a mbox.
1788 *
1789 * \throw so_5::exception_t in the case when the subscription
1790 * of a deadletter handler for type \a msg_type from \a mbox is
1791 * already exists.
1792 *
1793 * \since
1794 * v.5.5.21
1795 */
1796 void
1797 so_create_deadletter_subscription(
1798 //! Message's mbox.
1799 const mbox_t & mbox,
1800 //! Message type.
1801 const std::type_index & msg_type,
1802 //! Event handler caller.
1803 const event_handler_method_t & method,
1804 //! Thread safety of the event handler.
1805 thread_safety_t thread_safety );
1806
1807 /*!
1808 * \brief Destroy a subscription for a deadletter handler.
1809 *
1810 * \note
1811 * This is low-level method intended to be used by libraries writters.
1812 * Do not call it directly if you don't understand its purpose and
1813 * what its arguments mean. Use so_drop_deadletter_handler() instead.
1814 *
1815 * This method actually destroys a subscription to deadletter handler
1816 * for messages/signal of type \a msg_type from mbox \a mbox.
1817 *
1818 * \note
1819 * It is safe to call this method if there is no such
1820 * deadletter handler. It will do nothing in such case.
1821 *
1822 * \since
1823 * v.5.5.21
1824 */
1825 void
1826 so_destroy_deadletter_subscription(
1827 //! Message's mbox.
1828 const mbox_t & mbox,
1829 //! Message type.
1830 const std::type_index & msg_type );
1831
1832 /*!
1833 * \brief Create a subscription for deadletter handler for
1834 * a specific message from a specific mbox.
1835 *
1836 * Type of a message for deadletter handler will be detected
1837 * automatically from the signature of the \a handler.
1838 *
1839 * A deadletter handler can be a pointer to method of agent or
1840 * lambda-function. The handler should have one of the following
1841 * format:
1842 * \code
1843 * void deadletter_handler(message_type);
1844 * void deadletter_handler(const message_type &);
1845 * void deadletter_handler(mhood_t<message_type>);
1846 * \endcode
1847 *
1848 * Usage example:
1849 * \code
1850 * class demo : public so_5::agent_t {
1851 * void on_some_message(mhood_t<some_message> cmd) {...}
1852 * ...
1853 * virtual void so_define_agent() override {
1854 * // Create deadletter handler via pointer to method.
1855 * // Event handler will be not-thread-safe.
1856 * so_subscribe_deadletter_handler(
1857 * so_direct_mbox(),
1858 * &demo::on_some_message );
1859 *
1860 * // Create deadletter handler via lambda-function.
1861 * so_subscribe_deadletter_handler(
1862 * status_mbox(), // Any mbox can be used, not only agent's direct mbox.
1863 * [](mhood_t<status_request> cmd) {
1864 * so_5::send<current_status>(cmd->reply_mbox, "workind");
1865 * },
1866 * // This handler will be thread-safe one.
1867 * so_5::thread_safe );
1868 * }
1869 * };
1870 * \endcode
1871 *
1872 * \throw so_5::exception_t in the case when the subscription
1873 * of a deadletter handler for type \a msg_type from \a mbox is
1874 * already exists.
1875 *
1876 * \since
1877 * v.5.5.21
1878 */
1879 template< typename Event_Handler >
1880 void
so_subscribe_deadletter_handler(const so_5::mbox_t & mbox,Event_Handler && handler,thread_safety_t thread_safety=thread_safety_t::unsafe)1881 so_subscribe_deadletter_handler(
1882 const so_5::mbox_t & mbox,
1883 Event_Handler && handler,
1884 thread_safety_t thread_safety = thread_safety_t::unsafe )
1885 {
1886 using namespace details::event_subscription_helpers;
1887
1888 const auto ev = preprocess_agent_event_handler(
1889 mbox,
1890 *this,
1891 std::forward<Event_Handler>(handler) );
1892
1893 so_create_deadletter_subscription(
1894 mbox,
1895 ev.m_msg_type,
1896 ev.m_handler,
1897 thread_safety );
1898 }
1899
1900 /*!
1901 * \brief Drops the subscription for deadletter handler.
1902 *
1903 * A message type must be specified explicitely via template
1904 * parameter.
1905 *
1906 * Usage example:
1907 * \code
1908 * class demo : public so_5::agent_t {
1909 * void some_deadletter_handler(mhood_t<some_message> cmd) {
1910 * ... // Do some stuff.
1911 * // There is no need for deadletter handler.
1912 * so_drop_deadletter_handler<some_message>(some_mbox);
1913 * }
1914 * ...
1915 * };
1916 * \endcode
1917 *
1918 * \note
1919 * Is is safe to call this method if there is no a deadletter
1920 * handler for message of type \a Message from message box
1921 * \a mbox.
1922 *
1923 * \tparam Message Type of a message or signal for deadletter
1924 * handler.
1925 *
1926 * \since
1927 * v.5.5.21
1928 */
1929 template< typename Message >
1930 void
so_drop_deadletter_handler(const so_5::mbox_t & mbox)1931 so_drop_deadletter_handler(
1932 //! A mbox from which the message is expected.
1933 const so_5::mbox_t & mbox )
1934 {
1935 so_destroy_deadletter_subscription(
1936 mbox,
1937 message_payload_type< Message >::subscription_type_index() );
1938 }
1939
1940 /*!
1941 * \brief Checks the presence of deadletter handler for a message of
1942 * a specific type from a specific mbox.
1943 *
1944 * Message type must be specified explicitely via template
1945 * parameter \a Message.
1946 *
1947 * \return true if a deadletter for a message/signal of type
1948 * \a Message from message mbox \a mbox exists.
1949 *
1950 * Usage example:
1951 * \code
1952 * class demo : public so_5::agent_t {
1953 * void on_some_request(mhood_t<request_data> cmd) {
1954 * if(!so_has_deadletter_handler<some_message>(some_mbox))
1955 * // There is no deadletter handler yet.
1956 * // It should be created now.
1957 * so_subscribe_deadletter_handler(
1958 * some_mbox,
1959 * [this](mhood_t<some_message> cmd) {...});
1960 * ...
1961 * }
1962 * };
1963 * \endcode
1964 *
1965 * \tparam Message Type of a message or signal for deadletter
1966 * handler.
1967 *
1968 * \since
1969 * v.5.5.21
1970 */
1971 template< typename Message >
1972 bool
so_has_deadletter_handler(const so_5::mbox_t & mbox) const1973 so_has_deadletter_handler(
1974 //! A mbox from which the message is expected.
1975 const so_5::mbox_t & mbox ) const noexcept
1976 {
1977 return do_check_deadletter_presence(
1978 mbox,
1979 message_payload_type< Message >::subscription_type_index() );
1980 }
1981 /*!
1982 * \}
1983 */
1984
1985 protected :
1986
1987 /*!
1988 * \name Agent initialization methods.
1989 * \{
1990 */
1991 /*!
1992 * \since
1993 * v.5.4.0
1994 *
1995 * \brief A correct initiation of so_define_agent method call.
1996 *
1997 * Before the actual so_define_agent() method it is necessary
1998 * to temporary set working thread id. And then drop this id
1999 * to non-actual value after so_define_agent() return.
2000 *
2001 * Because of that this method must be called during cooperation
2002 * registration procedure instead of direct call of so_define_agent().
2003 */
2004 void
2005 so_initiate_agent_definition();
2006
2007 //! Hook on define agent for SObjectizer.
2008 /*!
2009 This method is called by SObjectizer during the cooperation
2010 registration process before agent will be bound to its
2011 working thread.
2012
2013 Should be used by the agent to make necessary message subscriptions.
2014
2015 Usage sample;
2016 \code
2017 class a_sample_t : public so_5::agent_t
2018 {
2019 // ...
2020 virtual void
2021 so_define_agent();
2022
2023 void evt_handler_1( mhood_t< message1 > msg );
2024 // ...
2025 void evt_handler_N( mhood_t< messageN > & msg );
2026
2027 };
2028
2029 void
2030 a_sample_t::so_define_agent()
2031 {
2032 // Make subscriptions...
2033 so_subscribe( m_mbox1 )
2034 .in( m_state_1 )
2035 .event( &a_sample_t::evt_handler_1 );
2036 // ...
2037 so_subscribe( m_mboxN )
2038 .in( m_state_N )
2039 .event( &a_sample_t::evt_handler_N );
2040 }
2041 \endcode
2042 */
2043 virtual void
2044 so_define_agent();
2045
2046 //! Is method define_agent already called?
2047 /*!
2048 Usage sample:
2049 \code
2050 class a_sample_t : public so_5::agent_t
2051 {
2052 // ...
2053
2054 public:
2055 void
2056 set_target_mbox( const so_5::mbox_t & mbox )
2057 {
2058 // mbox cannot be changed after agent registration.
2059 if( !so_was_defined() )
2060 {
2061 m_target_mbox = mbox;
2062 }
2063 }
2064
2065 private:
2066 so_5::mbox_t m_target_mbox;
2067 };
2068 \endcode
2069 */
2070 bool
2071 so_was_defined() const;
2072 /*!
2073 * \}
2074 */
2075
2076 public:
2077 //! Access to the SObjectizer Environment which this agent is belong.
2078 /*!
2079 Usage sample for other cooperation registration:
2080 \code
2081 void a_sample_t::evt_on_smth( mhood_t< some_message_t > msg )
2082 {
2083 so_5::coop_unique_holder_t coop = so_environment().make_coop();
2084
2085 // Filling the cooperation...
2086 coop->make_agent< a_another_t >( ... );
2087 // ...
2088
2089 // Registering cooperation.
2090 so_environment().register_coop( std::move(coop) );
2091 }
2092 \endcode
2093
2094 Usage sample for the SObjectizer shutting down:
2095 \code
2096 void a_sample_t::evt_last_event( mhood_t< message_one_t > msg )
2097 {
2098 ...
2099 so_environment().stop();
2100 }
2101 \endcode
2102 */
2103 environment_t &
2104 so_environment() const;
2105
2106 /*!
2107 * \brief Get a handle of agent's coop.
2108 *
2109 * \note
2110 * This method is a replacement for so_coop_name() method
2111 * from previous versions of SObjectizer-5.
2112 *
2113 * \attention
2114 * If this method is called when agent is not registered (e.g.
2115 * there is no coop for agent) then this method will throw.
2116 *
2117 * Usage example:
2118 * \code
2119 * class parent final : public so_5::agent_t {
2120 * ...
2121 * void so_evt_start() override {
2122 * // Create a child coop.
2123 * auto coop = so_environment().make_coop(
2124 * // We as a parent coop.
2125 * so_coop() );
2126 * ...; // Fill the coop.
2127 * so_environment().register_coop( std::move(coop) );
2128 * }
2129 * };
2130 * \endcode
2131 *
2132 * \since
2133 * v.5.6.0
2134 */
2135 [[nodiscard]]
2136 coop_handle_t
2137 so_coop() const;
2138
2139 /*!
2140 * \since
2141 * v.5.4.0
2142 *
2143 * \brief Binding agent to the dispatcher.
2144 *
2145 * This is an actual start of agent's work in SObjectizer.
2146 *
2147 * \note
2148 * This method was a de-facto noexcept in previous versions of
2149 * SObjectizer. But didn't marked as noexcept because of need of
2150 * support old C++ compilers. Since v.5.6.0 it is officially noexcept.
2151 */
2152 void
2153 so_bind_to_dispatcher(
2154 //! Actual event queue for an agent.
2155 event_queue_t & queue ) noexcept;
2156
2157 /*!
2158 * \since
2159 * v.5.4.0
2160 *
2161 * \brief Create execution hint for the specified demand.
2162 *
2163 * The hint returned is intendent for the immediately usage.
2164 * It must not be stored for the long time and used sometime in
2165 * the future. It is because internal state of the agent
2166 * can be changed and some references from hint object to
2167 * agent's internals become invalid.
2168 */
2169 static execution_hint_t
2170 so_create_execution_hint(
2171 //! Demand for execution of event handler.
2172 execution_demand_t & demand );
2173
2174 /*!
2175 * \since
2176 * v.5.4.0
2177 *
2178 * \brief A helper method for deregistering agent's coop.
2179 */
2180 void
2181 so_deregister_agent_coop( int dereg_reason );
2182
2183 /*!
2184 * \since
2185 * v.5.4.0
2186 *
2187 * \brief A helper method for deregistering agent's coop
2188 * in case of normal deregistration.
2189 *
2190 * \note It is just a shorthand for:
2191 \code
2192 so_deregister_agent_coop( so_5::dereg_reason::normal );
2193 \endcode
2194 */
2195 void
2196 so_deregister_agent_coop_normally();
2197
2198 /*!
2199 * \name Methods for dealing with message delivery filters.
2200 * \{
2201 */
2202 /*!
2203 * \since
2204 * v.5.5.5
2205 *
2206 * \brief Set a delivery filter.
2207 *
2208 * \tparam Message type of message to be filtered.
2209 */
2210 template< typename Message >
2211 void
so_set_delivery_filter(const mbox_t & mbox,delivery_filter_unique_ptr_t filter)2212 so_set_delivery_filter(
2213 //! Message box from which message is expected.
2214 //! This must be MPMC-mbox.
2215 const mbox_t & mbox,
2216 //! Delivery filter instance.
2217 delivery_filter_unique_ptr_t filter )
2218 {
2219 ensure_not_signal< Message >();
2220
2221 do_set_delivery_filter(
2222 mbox,
2223 message_payload_type< Message >::subscription_type_index(),
2224 std::move(filter) );
2225 }
2226
2227 /*!
2228 * \since
2229 * v.5.5.5
2230 *
2231 * \brief Set a delivery filter.
2232 *
2233 * \tparam Lambda type of lambda-function or functional object which
2234 * must be used as message filter.
2235 *
2236 * \par Usage sample:
2237 \code
2238 void my_agent::so_define_agent() {
2239 so_set_delivery_filter( temp_sensor,
2240 []( const current_temperature & msg ) {
2241 return !is_normal_temperature( msg );
2242 } );
2243 ...
2244 }
2245 \endcode
2246 */
2247 template< typename Lambda >
2248 void
2249 so_set_delivery_filter(
2250 //! Message box from which message is expected.
2251 //! This must be MPMC-mbox.
2252 const mbox_t & mbox,
2253 //! Delivery filter as lambda-function or functional object.
2254 Lambda && lambda );
2255
2256 /*!
2257 * \since
2258 * v.5.5.5
2259 *
2260 * \brief Drop a delivery filter.
2261 *
2262 * \tparam Message type of message filtered.
2263 */
2264 template< typename Message >
2265 void
so_drop_delivery_filter(const mbox_t & mbox)2266 so_drop_delivery_filter(
2267 //! Message box to which delivery filter was set.
2268 //! This must be MPMC-mbox.
2269 const mbox_t & mbox ) noexcept
2270 {
2271 do_drop_delivery_filter(
2272 mbox,
2273 message_payload_type< Message >::subscription_type_index() );
2274 }
2275 /*!
2276 * \}
2277 */
2278
2279 /*!
2280 * \name Dealing with priority.
2281 * \{
2282 */
2283 /*!
2284 * \since
2285 * v.5.5.8
2286 *
2287 * \brief Get the priority of the agent.
2288 */
2289 priority_t
so_priority() const2290 so_priority() const
2291 {
2292 return m_priority;
2293 }
2294 /*!
2295 * \}
2296 */
2297
2298 private:
2299 const state_t st_default{ self_ptr(), "<DEFAULT>" };
2300
2301 //! Current agent state.
2302 const state_t * m_current_state_ptr;
2303
2304 /*!
2305 * \brief Enumeration of possible agent statuses.
2306 *
2307 * \since
2308 * v.5.5.18
2309 */
2310 enum class agent_status_t : char
2311 {
2312 //! Agent is not defined yet.
2313 //! This is an initial agent status.
2314 not_defined_yet,
2315 //! Agent is defined.
2316 defined,
2317 //! State switch operation is in progress.
2318 state_switch_in_progress
2319 };
2320
2321 /*!
2322 * \brief Current agent status.
2323 *
2324 * \since
2325 * v.5.5.18
2326 */
2327 agent_status_t m_current_status;
2328
2329 //! State listeners controller.
2330 impl::state_listener_controller_t m_state_listener_controller;
2331
2332 /*!
2333 * \since
2334 * v.5.5.9
2335 *
2336 * \brief Type of function for searching event handler.
2337 */
2338 using handler_finder_t =
2339 const impl::event_handler_data_t *(*)(
2340 execution_demand_t & /* demand */,
2341 const char * /* context_marker */ );
2342
2343 /*!
2344 * \since
2345 * v.5.5.9
2346 *
2347 * \brief Function for searching event handler.
2348 *
2349 * \note The value is set only once in the constructor and
2350 * doesn't changed anymore.
2351 */
2352 handler_finder_t m_handler_finder;
2353
2354 /*!
2355 * \since
2356 * v.5.4.0
2357 *
2358 * \brief All agent's subscriptions.
2359 */
2360 impl::subscription_storage_unique_ptr_t m_subscriptions;
2361
2362 /*!
2363 * \since
2364 * v.5.5.4
2365 *
2366 * \brief Run-time information for message limits.
2367 *
2368 * Created only of message limits are described in agent's
2369 * tuning options.
2370 *
2371 * \attention This attribute must be initialized before the
2372 * \a m_direct_mbox attribute. It is because the value of
2373 * \a m_message_limits is used in \a m_direct_mbox creation.
2374 * Because of that \a m_message_limits is declared before
2375 * \a m_direct_mbox.
2376 */
2377 std::unique_ptr< message_limit::impl::info_storage_t > m_message_limits;
2378
2379 //! SObjectizer Environment for which the agent is belong.
2380 environment_t & m_env;
2381
2382 /*!
2383 * \since
2384 * v.5.5.8
2385 *
2386 * \brief Event queue operation protector.
2387 *
2388 * Initially m_event_queue is NULL. It is changed to actual value
2389 * in so_bind_to_dispatcher() method. And reset to nullptr again
2390 * in shutdown_agent().
2391 *
2392 * nullptr in m_event_queue means that methods push_event() and
2393 * push_service_request() will throw away any new demand.
2394 *
2395 * It is necessary to provide guarantee that m_event_queue will
2396 * be reset to nullptr in shutdown_agent() only if there is no
2397 * working push_event()/push_service_request() methods. To do than
2398 * default_rw_spinlock_t is used. Methods push_event() and
2399 * push_service_request() acquire it in read-mode and shutdown_agent()
2400 * acquires it in write-mode. It means that shutdown_agent() cannot
2401 * get access to m_event_queue until there is working
2402 * push_event()/push_service_request().
2403 */
2404 default_rw_spinlock_t m_event_queue_lock;
2405
2406 /*!
2407 * \since
2408 * v.5.5.8
2409 *
2410 * \brief A pointer to event_queue.
2411 *
2412 * After binding to the dispatcher is it pointed to the actual
2413 * event queue.
2414 *
2415 * After shutdown it is set to nullptr.
2416 *
2417 * \attention Access to m_event_queue value must be done only
2418 * under acquired m_event_queue_lock.
2419 */
2420 event_queue_t * m_event_queue;
2421
2422 /*!
2423 * \since
2424 * v.5.4.0
2425 *
2426 * \brief A direct mbox for the agent.
2427 */
2428 const mbox_t m_direct_mbox;
2429
2430 /*!
2431 * \since
2432 * v.5.4.0
2433 *
2434 * \brief Working thread id.
2435 *
2436 * Some actions like managing subscriptions and changing states
2437 * are enabled only on working thread id.
2438 */
2439 so_5::current_thread_id_t m_working_thread_id;
2440
2441 //! Agent is belong to this cooperation.
2442 coop_t * m_agent_coop;
2443
2444 /*!
2445 * \since
2446 * v.5.5.5
2447 *
2448 * \brief Delivery filters for that agents.
2449 *
2450 * \note Storage is created only when necessary.
2451 */
2452 std::unique_ptr< impl::delivery_filter_storage_t > m_delivery_filters;
2453
2454 /*!
2455 * \since
2456 * v.5.5.8
2457 *
2458 * \brief Priority of the agent.
2459 */
2460 const priority_t m_priority;
2461
2462 //! Make an agent reference.
2463 /*!
2464 * This is an internal SObjectizer method. It is called when
2465 * it is guaranteed that the agent is still necessary and something
2466 * has reference to it.
2467 */
2468 agent_ref_t
2469 create_ref();
2470
2471 /*!
2472 * \name Embedding agent into the SObjectizer Run-time.
2473 * \{
2474 */
2475
2476 //! Bind agent to the cooperation.
2477 /*!
2478 * Initializes an internal cooperation pointer.
2479 */
2480 void
2481 bind_to_coop(
2482 //! Cooperation for that agent.
2483 coop_t & coop );
2484
2485 //! Agent shutdown deriver.
2486 /*!
2487 * \since
2488 * v.5.2.3
2489 *
2490 *
2491 * Method destroys all agent subscriptions.
2492 */
2493 void
2494 shutdown_agent() noexcept;
2495 /*!
2496 * \}
2497 */
2498
2499 /*!
2500 * \name Subscription/unsubscription implementation details.
2501 * \{
2502 */
2503
2504 /*!
2505 * \since
2506 * v.5.5.4
2507 *
2508 * \brief Detect limit for that message type.
2509 *
2510 * \note
2511 * Since v.5.7.1 it isn't a const method.
2512 *
2513 * \return nullptr if message limits are not used.
2514 *
2515 * \throw exception_t if message limits are used but the limit
2516 * for that message type is not found.
2517 */
2518 const message_limit::control_block_t *
2519 detect_limit_for_message_type(
2520 const std::type_index & msg_type );
2521
2522 /*!
2523 * \since
2524 * v.5.2.3
2525 *
2526 * \brief Remove subscription for the state specified.
2527 */
2528 void
2529 do_drop_subscription(
2530 //! Message's mbox.
2531 const mbox_t & mbox,
2532 //! Message type.
2533 const std::type_index & msg_type,
2534 //! State for event.
2535 const state_t & target_state );
2536
2537 /*!
2538 * \since
2539 * v.5.2.3
2540 *
2541 * \brief Remove subscription for all states.
2542 */
2543 void
2544 do_drop_subscription_for_all_states(
2545 //! Message's mbox.
2546 const mbox_t & mbox,
2547 //! Message type.
2548 const std::type_index & msg_type );
2549
2550 /*!
2551 * \brief Check the presence of a subscription.
2552 *
2553 * \since
2554 * v.5.5.19.5
2555 */
2556 bool
2557 do_check_subscription_presence(
2558 //! Message's mbox.
2559 const mbox_t & mbox,
2560 //! Message type.
2561 const std::type_index & msg_type,
2562 //! State for the subscription.
2563 const state_t & target_state ) const noexcept;
2564
2565 /*!
2566 * \brief Check the presence of a deadletter handler.
2567 *
2568 * \since
2569 * v.5.5.21
2570 */
2571 bool
2572 do_check_deadletter_presence(
2573 //! Message's mbox.
2574 const mbox_t & mbox,
2575 //! Message type.
2576 const std::type_index & msg_type ) const noexcept;
2577 /*!
2578 * \}
2579 */
2580
2581
2582 /*!
2583 * \name Event handling implementation details.
2584 * \{
2585 */
2586
2587 //! Push event into the event queue.
2588 void
2589 push_event(
2590 //! Optional message limit.
2591 const message_limit::control_block_t * limit,
2592 //! ID of mbox for this event.
2593 mbox_id_t mbox_id,
2594 //! Message type for event.
2595 std::type_index msg_type,
2596 //! Event message.
2597 const message_ref_t & message );
2598 /*!
2599 * \}
2600 */
2601
2602 // NOTE: demand handlers declared as public to allow
2603 // access this handlers from unit-tests.
2604 public :
2605 /*!
2606 * \name Demand handlers.
2607 * \{
2608 */
2609 /*!
2610 * \since
2611 * v.5.2.0
2612 *
2613 * \brief Calls so_evt_start method for agent.
2614 */
2615 static void
2616 demand_handler_on_start(
2617 current_thread_id_t working_thread_id,
2618 execution_demand_t & d );
2619
2620 /*!
2621 * \since
2622 * v.5.5.8
2623 *
2624 * \brief Ensures that all agents from cooperation are
2625 * bound to dispatchers.
2626 */
2627 void
2628 ensure_binding_finished();
2629
2630 /*!
2631 * \since
2632 * v.5.4.0
2633 *
2634 * \note This method is necessary for GCC on Cygwin.
2635 */
2636 static demand_handler_pfn_t
2637 get_demand_handler_on_start_ptr() noexcept;
2638
2639 /*!
2640 * \since
2641 * v.5.2.0
2642 *
2643 * \brief Calls so_evt_finish method for agent.
2644 */
2645 static void
2646 demand_handler_on_finish(
2647 current_thread_id_t working_thread_id,
2648 execution_demand_t & d );
2649
2650 /*!
2651 * \since
2652 * v.5.4.0
2653 *
2654 * \note This method is necessary for GCC on Cygwin.
2655 */
2656 static demand_handler_pfn_t
2657 get_demand_handler_on_finish_ptr() noexcept;
2658
2659 /*!
2660 * \since
2661 * v.5.2.0
2662 *
2663 * \brief Calls event handler for message.
2664 */
2665 static void
2666 demand_handler_on_message(
2667 current_thread_id_t working_thread_id,
2668 execution_demand_t & d );
2669
2670 /*!
2671 * \since
2672 * v.5.4.0
2673 *
2674 * \note This method is necessary for GCC on Cygwin.
2675 */
2676 static demand_handler_pfn_t
2677 get_demand_handler_on_message_ptr() noexcept;
2678
2679 /*!
2680 * \since
2681 * v.5.5.23
2682 *
2683 * \brief Handles the enveloped message.
2684 */
2685 static void
2686 demand_handler_on_enveloped_msg(
2687 current_thread_id_t working_thread_id,
2688 execution_demand_t & d );
2689
2690 /*!
2691 * \since
2692 * v.5.5.24
2693 */
2694 static demand_handler_pfn_t
2695 get_demand_handler_on_enveloped_msg_ptr() noexcept;
2696 /*!
2697 * \}
2698 */
2699
2700 private :
2701 /*!
2702 * \since
2703 * v.5.4.0
2704 *
2705 * \brief Actual implementation of message handling.
2706 *
2707 * \note Since v.5.5.17.1 argument \a method is passed as copy.
2708 * It prevents deallocation of event_handler_method in the following
2709 * case:
2710 * \code
2711 auto mbox = so_environment().create_mbox();
2712 so_subscribe( mbox ).event< some_signal >( [this, mbox] {
2713 so_drop_subscription< some_signal >( mbox );
2714 ... // Some other actions.
2715 } );
2716 * \endcode
2717 */
2718 static void
2719 process_message(
2720 current_thread_id_t working_thread_id,
2721 execution_demand_t & d,
2722 event_handler_method_t method );
2723
2724 /*!
2725 * \brief Actual implementation of enveloped message handling.
2726 *
2727 * \note
2728 * handler_data can be nullptr. It means that an event handler
2729 * for that message type if not found and special hook will
2730 * be called for the envelope.
2731 *
2732 * \since
2733 * v.5.5.23
2734 */
2735 static void
2736 process_enveloped_msg(
2737 current_thread_id_t working_thread_id,
2738 execution_demand_t & d,
2739 const impl::event_handler_data_t * handler_data );
2740
2741 /*!
2742 * \since
2743 * v.5.4.0
2744 *
2745 * \brief Enables operation only if it is performed on agent's
2746 * working thread.
2747 */
2748 void
2749 ensure_operation_is_on_working_thread(
2750 const char * operation_name ) const;
2751
2752 /*!
2753 * \since
2754 * v.5.5.0
2755 *
2756 * \brief Drops all delivery filters.
2757 */
2758 void
2759 drop_all_delivery_filters() noexcept;
2760
2761 /*!
2762 * \since
2763 * v.5.5.5
2764 *
2765 * \brief Set a delivery filter.
2766 */
2767 void
2768 do_set_delivery_filter(
2769 const mbox_t & mbox,
2770 const std::type_index & msg_type,
2771 delivery_filter_unique_ptr_t filter );
2772
2773 /*!
2774 * \since
2775 * v.5.5.5
2776 *
2777 * \brief Drop a delivery filter.
2778 */
2779 void
2780 do_drop_delivery_filter(
2781 const mbox_t & mbox,
2782 const std::type_index & msg_type ) noexcept;
2783
2784 /*!
2785 * \since
2786 * v.5.5.9
2787 *
2788 * \brief Handler finder for the case when message delivery
2789 * tracing is disabled.
2790 */
2791 static const impl::event_handler_data_t *
2792 handler_finder_msg_tracing_disabled(
2793 execution_demand_t & demand,
2794 const char * context_marker );
2795
2796 /*!
2797 * \since
2798 * v.5.5.9
2799 *
2800 * \brief Handler finder for the case when message delivery
2801 * tracing is enabled.
2802 */
2803 static const impl::event_handler_data_t *
2804 handler_finder_msg_tracing_enabled(
2805 execution_demand_t & demand,
2806 const char * context_marker );
2807
2808 /*!
2809 * \since
2810 * v.5.5.15
2811 *
2812 * \brief Actual search for event handler with respect
2813 * to parent-child relationship between agent states.
2814 */
2815 static const impl::event_handler_data_t *
2816 find_event_handler_for_current_state(
2817 execution_demand_t & demand );
2818
2819 /*!
2820 * \brief Search for event handler between deadletter handlers.
2821 *
2822 * \return nullptr if event handler is not found.
2823 *
2824 * \since
2825 * v.5.5.21
2826 */
2827 static const impl::event_handler_data_t *
2828 find_deadletter_handler(
2829 execution_demand_t & demand );
2830
2831 /*!
2832 * \since
2833 * v.5.5.15
2834 *
2835 * \brief Actual action for switching agent state.
2836 */
2837 void
2838 do_state_switch(
2839 //! New state to be set as the current state.
2840 const state_t & state_to_be_set ) noexcept;
2841
2842 /*!
2843 * \since
2844 * v.5.5.15
2845 *
2846 * \brief Return agent to the default state.
2847 *
2848 * \note This method is called just before invocation of
2849 * so_evt_finish() to return agent to the default state.
2850 * This return will initiate invocation of on_exit handlers
2851 * for all active states of the agent.
2852 *
2853 * \attention State switch is not performed is agent is already
2854 * in default state or if it waits deregistration after unhandled
2855 * exception.
2856 */
2857 void
2858 return_to_default_state_if_possible() noexcept;
2859 };
2860
2861 /*!
2862 * \brief Helper function template for the creation of smart pointer
2863 * to an agent.
2864 *
2865 * This function can be useful if a pointer to an agent should be passed
2866 * somewhere with the guarantee that this pointer will remain valid even
2867 * if the agent will be deregistered.
2868 *
2869 * This could be necessary, for example, if a pointer to an agent is
2870 * passed to some callback (like it is done in Asio):
2871 * \code
2872 * void my_agent::on_some_event(mhood_t<some_msg> cmd) {
2873 * connection_.async_read_some(input_buffer_,
2874 * [self = so_5::make_agent_ref(this)](
2875 * const asio::error_code & ec,
2876 * std::size_t bytes_transferred )
2877 * {
2878 * if(!ec)
2879 * self->handle_incoming_data(bytes_transferred);
2880 * }
2881 * );
2882 * }
2883 * \endcode
2884 *
2885 * \since
2886 * v.5.7.1
2887 */
2888 template< typename Derived >
2889 [[nodiscard]]
2890 intrusive_ptr_t< Derived >
make_agent_ref(Derived * agent)2891 make_agent_ref( Derived * agent )
2892 {
2893 static_assert( std::is_base_of_v< agent_t, Derived >,
2894 "type should be derived from so_5::agent_t" );
2895
2896 return { agent };
2897 }
2898
2899 /*!
2900 * \since
2901 * v.5.5.5
2902 *
2903 * \brief Template-based implementations of delivery filters.
2904 */
2905 namespace delivery_filter_templates
2906 {
2907
2908 /*!
2909 * \since
2910 * v.5.5.5
2911 *
2912 * \brief An implementation of delivery filter represented by lambda-function
2913 * like object.
2914 *
2915 * \tparam Lambda type of lambda-function or functional object.
2916 */
2917 template< typename Lambda, typename Message >
2918 class lambda_as_filter_t : public delivery_filter_t
2919 {
2920 Lambda m_filter;
2921
2922 public :
lambda_as_filter_t(Lambda && filter)2923 lambda_as_filter_t( Lambda && filter )
2924 : m_filter( std::forward< Lambda >( filter ) )
2925 {}
2926
2927 bool
check(const agent_t &,message_t & msg) const2928 check(
2929 const agent_t & /*receiver*/,
2930 message_t & msg ) const noexcept override
2931 {
2932 return m_filter(message_payload_type< Message >::payload_reference( msg ));
2933 }
2934 };
2935
2936 } /* namespace delivery_filter_templates */
2937
2938 template< typename Lambda >
2939 void
so_set_delivery_filter(const mbox_t & mbox,Lambda && lambda)2940 agent_t::so_set_delivery_filter(
2941 const mbox_t & mbox,
2942 Lambda && lambda )
2943 {
2944 using namespace so_5::details::lambda_traits;
2945 using namespace delivery_filter_templates;
2946
2947 using argument_type = typename argument_type_if_lambda< Lambda >::type;
2948
2949 ensure_not_signal< argument_type >();
2950
2951 do_set_delivery_filter(
2952 mbox,
2953 message_payload_type< argument_type >::subscription_type_index(),
2954 delivery_filter_unique_ptr_t{
2955 new lambda_as_filter_t< Lambda, argument_type >(
2956 std::move( lambda ) )
2957 } );
2958 }
2959
2960 //
2961 // subscription_bind_t implementation
2962 //
2963 inline
subscription_bind_t(agent_t & agent,const mbox_t & mbox_ref)2964 subscription_bind_t::subscription_bind_t(
2965 agent_t & agent,
2966 const mbox_t & mbox_ref )
2967 : m_agent( &agent )
2968 , m_mbox_ref( mbox_ref )
2969 {
2970 }
2971
2972 inline subscription_bind_t &
in(const state_t & state)2973 subscription_bind_t::in(
2974 const state_t & state )
2975 {
2976 if( !state.is_target( m_agent ) )
2977 {
2978 SO_5_THROW_EXCEPTION(
2979 rc_agent_is_not_the_state_owner,
2980 "agent doesn't own the state" );
2981 }
2982
2983 m_states.push_back( &state );
2984
2985 return *this;
2986 }
2987
2988 template< typename Method_Pointer >
2989 typename std::enable_if<
2990 details::is_agent_method_pointer<
2991 details::method_arity::unary,
2992 Method_Pointer>::value,
2993 subscription_bind_t & >::type
event(Method_Pointer pfn,thread_safety_t thread_safety)2994 subscription_bind_t::event(
2995 Method_Pointer pfn,
2996 thread_safety_t thread_safety )
2997 {
2998 using namespace details::event_subscription_helpers;
2999
3000 const auto ev = preprocess_agent_event_handler( m_mbox_ref, *m_agent, pfn );
3001 create_subscription_for_states(
3002 ev.m_msg_type,
3003 ev.m_handler,
3004 thread_safety,
3005 event_handler_kind_t::final_handler );
3006
3007 return *this;
3008 }
3009
3010 template<typename Lambda>
3011 typename std::enable_if<
3012 details::lambda_traits::is_lambda<Lambda>::value,
3013 subscription_bind_t & >::type
event(Lambda && lambda,thread_safety_t thread_safety)3014 subscription_bind_t::event(
3015 Lambda && lambda,
3016 thread_safety_t thread_safety )
3017 {
3018 using namespace details::event_subscription_helpers;
3019
3020 const auto ev = preprocess_agent_event_handler(
3021 m_mbox_ref,
3022 *m_agent,
3023 std::forward<Lambda>(lambda) );
3024
3025 create_subscription_for_states(
3026 ev.m_msg_type,
3027 ev.m_handler,
3028 thread_safety,
3029 event_handler_kind_t::final_handler );
3030
3031 return *this;
3032 }
3033
3034 template< typename Msg >
3035 subscription_bind_t &
transfer_to_state(const state_t & target_state)3036 subscription_bind_t::transfer_to_state(
3037 const state_t & target_state )
3038 {
3039 /*
3040 * Note. Since v.5.5.22.1 there is a new implementation of transfer_to_state.
3041 * New implementation protects from loops in transfer_to_state calls.
3042 * For example in the following cases:
3043 *
3044 * \code
3045 class a_simple_case_t final : public so_5::agent_t
3046 {
3047 state_t st_base{ this, "base" };
3048 state_t st_disconnected{ initial_substate_of{st_base}, "disconnected" };
3049 state_t st_connected{ substate_of{st_base}, "connected" };
3050
3051 struct message {};
3052
3053 public :
3054 a_simple_case_t(context_t ctx) : so_5::agent_t{ctx} {
3055 this >>= st_base;
3056
3057 st_base.transfer_to_state<message>(st_disconnected);
3058 }
3059
3060 virtual void so_evt_start() override {
3061 so_5::send<message>(*this);
3062 }
3063 };
3064
3065 class a_two_state_loop_t final : public so_5::agent_t
3066 {
3067 state_t st_one{ this, "one" };
3068 state_t st_two{ this, "two" };
3069
3070 struct message {};
3071
3072 public :
3073 a_two_state_loop_t(context_t ctx) : so_5::agent_t{ctx} {
3074 this >>= st_one;
3075
3076 st_one.transfer_to_state<message>(st_two);
3077 st_two.transfer_to_state<message>(st_one);
3078 }
3079
3080 virtual void so_evt_start() override {
3081 so_5::send<message>(*this);
3082 }
3083 };
3084 * \endcode
3085 *
3086 * For such protection an additional objects with the current state
3087 * of transfer_to_state operation is necessary. There will be a boolean
3088 * flag in that state. When transfer_to_state will be started this
3089 * flag will should be 'false'. But if it is already 'true' then there is
3090 * a loop in transfer_to_state calls.
3091 */
3092
3093 // This is the state of transfer_to_state operation.
3094 struct transfer_op_state_t
3095 {
3096 agent_t * m_agent;
3097 mbox_id_t m_mbox_id;
3098 const state_t & m_target_state;
3099 bool m_in_progress;
3100
3101 transfer_op_state_t(
3102 agent_t * agent,
3103 mbox_id_t mbox_id,
3104 outliving_reference_t<const state_t> tgt_state)
3105 : m_agent( agent )
3106 , m_mbox_id( mbox_id )
3107 , m_target_state( tgt_state.get() )
3108 , m_in_progress( false )
3109 {}
3110 };
3111
3112 //NOTE: shared_ptr is used because capture of unique_ptr
3113 //makes std::function non-copyable, but we need to copy
3114 //resulting 'method' object.
3115 //
3116 auto op_state = std::make_shared< transfer_op_state_t >(
3117 m_agent, m_mbox_ref->id(), outliving_const(target_state) );
3118
3119 auto method = [op_state]( message_ref_t & msg )
3120 {
3121 // The current transfer_to_state operation should be inactive.
3122 if( op_state->m_in_progress )
3123 SO_5_THROW_EXCEPTION( rc_transfer_to_state_loop,
3124 "transfer_to_state loop detected. target_state: " +
3125 op_state->m_target_state.query_name() +
3126 ", current_state: " +
3127 op_state->m_agent->so_current_state().query_name() );
3128
3129 // Activate transfer_to_state operation and make sure that it
3130 // will be deactivated on return automatically.
3131 op_state->m_in_progress = true;
3132 auto in_progress_reset = details::at_scope_exit( [&op_state] {
3133 op_state->m_in_progress = false;
3134 } );
3135
3136 //
3137 // The main logic of transfer_to_state operation.
3138 //
3139 op_state->m_agent->so_change_state( op_state->m_target_state );
3140
3141 execution_demand_t demand{
3142 op_state->m_agent,
3143 nullptr, // Message limit is not actual here.
3144 op_state->m_mbox_id,
3145 typeid( Msg ),
3146 msg,
3147 // We have very simple choice here: message is an enveloped
3148 // message or just classical message/signal.
3149 // So we should select an appropriate demand handler.
3150 message_kind_t::enveloped_msg == message_kind( msg ) ?
3151 agent_t::get_demand_handler_on_enveloped_msg_ptr() :
3152 agent_t::get_demand_handler_on_message_ptr()
3153 };
3154
3155 demand.call_handler( query_current_thread_id() );
3156 };
3157
3158 create_subscription_for_states(
3159 typeid( Msg ),
3160 method,
3161 thread_safety_t::unsafe,
3162 event_handler_kind_t::intermediate_handler );
3163
3164 return *this;
3165 }
3166
3167 template< typename Msg >
3168 subscription_bind_t &
suppress()3169 subscription_bind_t::suppress()
3170 {
3171 // A method with nothing inside.
3172 auto method = []( message_ref_t & ) {};
3173
3174 create_subscription_for_states(
3175 typeid( Msg ),
3176 method,
3177 thread_safety_t::safe,
3178 // Suppression of a message is a kind of ignoring of the message.
3179 // In the case of enveloped message intermediate_handler receives
3180 // the whole envelope (not the payload) and the whole envelope
3181 // should be ignored. We can't specify final_handler here because
3182 // in that case the payload of an enveloped message will be
3183 // extracted from the envelope and envelope will be informed about
3184 // the handling of message. But message won't be handled, it will
3185 // be ignored.
3186 event_handler_kind_t::intermediate_handler );
3187
3188 return *this;
3189 }
3190
3191 template< typename Msg >
3192 subscription_bind_t &
just_switch_to(const state_t & target_state)3193 subscription_bind_t::just_switch_to(
3194 const state_t & target_state )
3195 {
3196 agent_t * agent_ptr = m_agent;
3197
3198 auto method = [agent_ptr, &target_state]( message_ref_t & )
3199 {
3200 agent_ptr->so_change_state( target_state );
3201 };
3202
3203 create_subscription_for_states(
3204 typeid( Msg ),
3205 method,
3206 thread_safety_t::unsafe,
3207 // Switching to some state is a kind of message processing.
3208 // So if there is an enveloped message then the envelope will be
3209 // informed about the processing of the payload.
3210 event_handler_kind_t::final_handler );
3211
3212 return *this;
3213 }
3214
3215 inline void
create_subscription_for_states(const std::type_index & msg_type,const event_handler_method_t & method,thread_safety_t thread_safety,event_handler_kind_t handler_kind) const3216 subscription_bind_t::create_subscription_for_states(
3217 const std::type_index & msg_type,
3218 const event_handler_method_t & method,
3219 thread_safety_t thread_safety,
3220 event_handler_kind_t handler_kind ) const
3221 {
3222 if( m_states.empty() )
3223 // Agent should be subscribed only in default state.
3224 m_agent->so_create_event_subscription(
3225 m_mbox_ref,
3226 msg_type,
3227 m_agent->so_default_state(),
3228 method,
3229 thread_safety,
3230 handler_kind );
3231 else
3232 for( auto s : m_states )
3233 m_agent->so_create_event_subscription(
3234 m_mbox_ref,
3235 msg_type,
3236 *s,
3237 method,
3238 thread_safety,
3239 handler_kind );
3240 }
3241
3242 inline void
ensure_handler_can_be_used_with_mbox(const so_5::details::msg_type_and_handler_pair_t & handler) const3243 subscription_bind_t::ensure_handler_can_be_used_with_mbox(
3244 const so_5::details::msg_type_and_handler_pair_t & handler ) const
3245 {
3246 ::so_5::details::event_subscription_helpers::ensure_handler_can_be_used_with_mbox(
3247 handler,
3248 m_mbox_ref );
3249 }
3250
3251 /*
3252 * Implementation of template methods of state_t class.
3253 */
3254 inline bool
is_active() const3255 state_t::is_active() const noexcept
3256 {
3257 return m_target_agent->so_is_active_state( *this );
3258 }
3259
3260 template< typename... Args >
3261 const state_t &
event(Args &&...args) const3262 state_t::event( Args&&... args ) const
3263 {
3264 return this->subscribe_message_handler(
3265 m_target_agent->so_direct_mbox(),
3266 std::forward< Args >(args)... );
3267 }
3268
3269 template< typename... Args >
3270 const state_t &
event(mbox_t from,Args &&...args) const3271 state_t::event( mbox_t from, Args&&... args ) const
3272 {
3273 return this->subscribe_message_handler( from,
3274 std::forward< Args >(args)... );
3275 }
3276
3277 template< typename Msg >
3278 bool
has_subscription(const mbox_t & from) const3279 state_t::has_subscription( const mbox_t & from ) const
3280 {
3281 return m_target_agent->so_has_subscription< Msg >( from, *this );
3282 }
3283
3284 template< typename Method_Pointer >
3285 bool
has_subscription(const mbox_t & from,Method_Pointer && pfn) const3286 state_t::has_subscription(
3287 const mbox_t & from,
3288 Method_Pointer && pfn ) const
3289 {
3290 return m_target_agent->so_has_subscription(
3291 from,
3292 *this,
3293 std::forward<Method_Pointer>(pfn) );
3294 }
3295
3296 template< typename Msg >
3297 void
drop_subscription(const mbox_t & from) const3298 state_t::drop_subscription( const mbox_t & from ) const
3299 {
3300 m_target_agent->so_drop_subscription< Msg >( from, *this );
3301 }
3302
3303 template< typename Method_Pointer >
3304 void
drop_subscription(const mbox_t & from,Method_Pointer && pfn) const3305 state_t::drop_subscription(
3306 const mbox_t & from,
3307 Method_Pointer && pfn ) const
3308 {
3309 m_target_agent->so_drop_subscription(
3310 from,
3311 *this,
3312 std::forward<Method_Pointer>(pfn) );
3313 }
3314
3315 template< typename Msg >
3316 const state_t &
transfer_to_state(mbox_t from,const state_t & target_state) const3317 state_t::transfer_to_state( mbox_t from, const state_t & target_state ) const
3318 {
3319 m_target_agent->so_subscribe( from )
3320 .in( *this )
3321 .transfer_to_state< Msg >( target_state );
3322
3323 return *this;
3324 }
3325
3326 template< typename Msg >
3327 const state_t &
transfer_to_state(const state_t & target_state) const3328 state_t::transfer_to_state( const state_t & target_state ) const
3329 {
3330 return this->transfer_to_state< Msg >(
3331 m_target_agent->so_direct_mbox(),
3332 target_state );
3333 }
3334
3335 template< typename Msg >
3336 const state_t &
just_switch_to(mbox_t from,const state_t & target_state) const3337 state_t::just_switch_to( mbox_t from, const state_t & target_state ) const
3338 {
3339 m_target_agent->so_subscribe( from )
3340 .in( *this )
3341 .just_switch_to< Msg >( target_state );
3342
3343 return *this;
3344 }
3345
3346 template< typename Msg >
3347 const state_t &
just_switch_to(const state_t & target_state) const3348 state_t::just_switch_to( const state_t & target_state ) const
3349 {
3350 return this->just_switch_to< Msg >(
3351 m_target_agent->so_direct_mbox(),
3352 target_state );
3353 }
3354
3355 template< typename Msg >
3356 const state_t &
suppress() const3357 state_t::suppress() const
3358 {
3359 return this->suppress< Msg >( m_target_agent->so_direct_mbox() );
3360 }
3361
3362 template< typename Msg >
3363 const state_t &
suppress(mbox_t from) const3364 state_t::suppress( mbox_t from ) const
3365 {
3366 m_target_agent->so_subscribe( from )
3367 .in( *this )
3368 .suppress< Msg >();
3369
3370 return *this;
3371 }
3372
3373 template< typename Method_Pointer >
3374 typename std::enable_if<
3375 details::is_agent_method_pointer<
3376 details::method_arity::nullary,
3377 Method_Pointer>::value,
3378 state_t & >::type
on_enter(Method_Pointer pfn)3379 state_t::on_enter( Method_Pointer pfn )
3380 {
3381 using namespace details::event_subscription_helpers;
3382
3383 using pfn_traits = details::is_agent_method_pointer<
3384 details::method_arity::nullary, Method_Pointer>;
3385
3386 // Agent must have right type.
3387 auto cast_result =
3388 get_actual_agent_pointer<
3389 typename pfn_traits::agent_type >(
3390 *m_target_agent );
3391
3392 return this->on_enter( [cast_result, pfn]() { (cast_result->*pfn)(); } );
3393 }
3394
3395 template< typename Method_Pointer >
3396 typename std::enable_if<
3397 details::is_agent_method_pointer<
3398 details::method_arity::nullary,
3399 Method_Pointer>::value,
3400 state_t & >::type
on_exit(Method_Pointer pfn)3401 state_t::on_exit( Method_Pointer pfn )
3402 {
3403 using namespace details::event_subscription_helpers;
3404
3405 using pfn_traits = details::is_agent_method_pointer<
3406 details::method_arity::nullary, Method_Pointer>;
3407
3408 // Agent must have right type.
3409 auto cast_result =
3410 get_actual_agent_pointer<
3411 typename pfn_traits::agent_type >(
3412 *m_target_agent );
3413
3414 return this->on_exit( [cast_result, pfn]() { (cast_result->*pfn)(); } );
3415 }
3416
3417 template< typename... Args >
3418 const state_t &
subscribe_message_handler(const mbox_t & from,Args &&...args) const3419 state_t::subscribe_message_handler(
3420 const mbox_t & from,
3421 Args&&... args ) const
3422 {
3423 m_target_agent->so_subscribe( from ).in( *this )
3424 .event( std::forward< Args >(args)... );
3425
3426 return *this;
3427 }
3428
3429 /*!
3430 * \since
3431 * v.5.5.1
3432 *
3433 * \brief A shortcat for switching the agent state.
3434 *
3435 * \par Usage example.
3436 \code
3437 class my_agent : public so_5::agent_t
3438 {
3439 const so_5::state_t st_normal = so_make_state();
3440 const so_5::state_t st_error = so_make_state();
3441 ...
3442 public :
3443 virtual void so_define_agent() override
3444 {
3445 this >>= st_normal;
3446
3447 st_normal.handle( [=]( const msg_failure & evt ) {
3448 this >>= st_error;
3449 ...
3450 });
3451 ...
3452 };
3453 ...
3454 };
3455 \endcode
3456 */
3457 inline void
operator >>=(agent_t * agent,const state_t & new_state)3458 operator>>=( agent_t * agent, const state_t & new_state )
3459 {
3460 agent->so_change_state( new_state );
3461 }
3462
3463 } /* namespace so_5 */
3464
3465 #if defined( SO_5_MSVC )
3466 #pragma warning(pop)
3467 #endif
3468
3469