Maki
Loading...
Searching...
No Matches
machine.hpp
1//Copyright Florian Goujeon 2021 - 2025.
2//Distributed under the Boost Software License, Version 1.0.
3//(See accompanying file LICENSE or copy at
4//https://www.boost.org/LICENSE_1_0.txt)
5//Official repository: https://github.com/fgoujeon/maki
6
11
12#ifndef MAKI_MACHINE_HPP
13#define MAKI_MACHINE_HPP
14
15#include "machine_conf.hpp"
16#include "events.hpp"
17#include "null.hpp"
18#include "detail/path.hpp"
19#include "detail/state_impls/simple.hpp" //NOLINT misc-include-cleaner
20#include "detail/state_impls/composite.hpp" //NOLINT misc-include-cleaner
21#include "detail/state_impls/composite_no_context.hpp"
22#include "detail/context_holder.hpp"
23#include "detail/event_action.hpp"
24#include "detail/noinline.hpp"
25#include "detail/function_queue.hpp"
26#include "detail/tuple.hpp"
27#include "detail/tlu/contains_if.hpp"
28#include <type_traits>
29#include <exception>
30
31namespace maki
32{
33
34namespace detail
35{
36 enum class machine_operation: char
37 {
38 start,
39 stop,
40 process_event
41 };
42}
43
44#define MAKI_DETAIL_MAYBE_CATCH(statement) /*NOLINT(cppcoreguidelines-macro-usage)*/ \
45 if constexpr(detail::is_null_v<typename option_set_type::exception_handler_type>) \
46 { \
47 statement; \
48 } \
49 else \
50 { \
51 try \
52 { \
53 statement; \
54 } \
55 catch(...) \
56 { \
57 impl_of(conf).exception_handler(*this, std::current_exception()); \
58 } \
59 }
60
76template<const auto& Conf>
78{
79public:
83 static constexpr const auto& conf = Conf;
84
85#ifndef MAKI_DETAIL_DOXYGEN
86 using option_set_type = std::decay_t<decltype(impl_of(conf))>;
87#endif
88
89#ifdef MAKI_DETAIL_DOXYGEN
93 using context_type = IMPLEMENTATION_DETAIL;
94#else
95 using context_type = typename option_set_type::context_type;
96#endif
97
98 static_assert
99 (
100 detail::is_machine_conf_v<std::decay_t<decltype(conf)>>,
101 "Given `Conf` must be an instance of `maki::machine_conf`"
102 );
103
116 template<class... ContextArgs>
117 explicit machine(ContextArgs&&... ctx_args):
118 ctx_holder_(*this, std::forward<ContextArgs>(ctx_args)...),
119 impl_(*this, context())
120 {
121 if constexpr(impl_of(conf).auto_start)
122 {
123 //start
124 execute_operation_now<detail::machine_operation::start>(events::start{});
125 }
126 }
127
128 machine(const machine&) = delete;
129 machine(machine&&) = delete;
130 machine& operator=(const machine&) = delete;
131 machine& operator=(machine&&) = delete;
132 ~machine() = default;
133
138 {
139 return ctx_holder_.get();
140 }
141
145 const context_type& context() const
146 {
147 return ctx_holder_.get();
148 }
149
155 [[nodiscard]] bool running() const
156 {
157 return !impl_.completed();
158 }
159
171 template<class Event = events::start>
172 void start(const Event& event = {})
173 {
174 MAKI_DETAIL_MAYBE_CATCH(start_no_catch(event))
175 }
176
185 template<class Event = events::stop>
186 void stop(const Event& event = {})
187 {
188 MAKI_DETAIL_MAYBE_CATCH(stop_no_catch(event))
189 }
190
239 template<class Event>
240 void process_event(const Event& event)
241 {
242 MAKI_DETAIL_MAYBE_CATCH(process_event_no_catch(event))
243 }
244
249 template<class Event>
250 void process_event_no_catch(const Event& event)
251 {
252 execute_operation<detail::machine_operation::process_event>(event);
253 }
254
274 template<class Event>
275 void process_event_now(const Event& event)
276 {
277 MAKI_DETAIL_MAYBE_CATCH(process_event_now_no_catch(event))
278 }
279
291 template<class Event>
292 bool check_event(const Event& event) const
293 {
294 auto processed = false;
295 impl_.template call_internal_action<true>(*this, context(), event, processed);
296 return processed;
297 }
298
309 template<class Event>
310 MAKI_NOINLINE void push_event(const Event& event)
311 {
312 MAKI_DETAIL_MAYBE_CATCH(push_event_no_catch(event))
313 }
314
318 template<int Index>
319 [[nodiscard]] const auto& region() const
320 {
321 return impl_.template region<Index>();
322 }
323
328 template<const auto& StateBuilder>
329 [[nodiscard]] const auto& state() const
330 {
331 return impl_.template state<StateBuilder>();
332 }
333
339 template<const auto& StateBuilder>
340 [[nodiscard]] bool is() const
341 {
342 return impl_.template is<StateBuilder>();
343 }
344
345private:
346 class executing_operation_guard
347 {
348 public:
349 executing_operation_guard(machine& self):
350 self_(self)
351 {
352 self_.executing_operation_ = true;
353 }
354
355 executing_operation_guard(const executing_operation_guard&) = delete;
356 executing_operation_guard(executing_operation_guard&&) = delete;
357 executing_operation_guard& operator=(const executing_operation_guard&) = delete;
358 executing_operation_guard& operator=(executing_operation_guard&&) = delete;
359
360 ~executing_operation_guard()
361 {
362 self_.executing_operation_ = false;
363 }
364
365 private:
366 machine& self_; //NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members)
367 };
368
369 struct real_operation_queue_holder
370 {
371 template<bool = true> //Dummy template for lazy evaluation
372 using type = detail::function_queue
373 <
374 machine&,
375 impl_of(conf).small_event_max_size,
376 impl_of(conf).small_event_max_align
377 >;
378 };
379 struct empty_holder
380 {
381 template<bool = true> //Dummy template for lazy evaluation
382 struct type{};
383 };
384 using operation_queue_type = typename std::conditional_t
385 <
386 impl_of(conf).run_to_completion,
387 real_operation_queue_holder,
388 empty_holder
389 >::template type<>;
390
391 template<class Event>
392 void start_no_catch(const Event& event)
393 {
394 if(!running())
395 {
396 execute_operation<detail::machine_operation::start>(event);
397 }
398 }
399
400 template<class Event>
401 void stop_no_catch(const Event& event)
402 {
403 if(running())
404 {
405 execute_operation<detail::machine_operation::stop>(event);
406 }
407 }
408
409 template<class Event>
410 void process_event_now_no_catch(const Event& event)
411 {
412 static_assert
413 (
414 impl_of(conf).process_event_now_enabled,
415 "`maki::machine_conf::process_event_now_enabled()` hasn't been set to `true`"
416 );
417 execute_operation_now<detail::machine_operation::process_event>(event);
418 }
419
420 template<detail::machine_operation Operation, class Event>
421 void execute_operation(const Event& event)
422 {
423 if constexpr(impl_of(conf).run_to_completion)
424 {
425 if(!executing_operation_) //If call is not recursive
426 {
427 execute_operation_now<Operation>(event);
428 }
429 else
430 {
431 //Push event to RTC queue in case of recursive call
432 push_event_impl<Operation>(event);
433 }
434 }
435 else
436 {
437 execute_one_operation<Operation>(event);
438 }
439 }
440
441 template<detail::machine_operation Operation, class Event>
442 void execute_operation_now(const Event& event)
443 {
444 if constexpr(impl_of(conf).run_to_completion)
445 {
446 auto grd = executing_operation_guard{*this};
447
448 execute_one_operation<Operation>(event);
449
450 //Process enqueued events, if any
451 operation_queue_.invoke_and_pop_all(*this);
452 }
453 else
454 {
455 execute_one_operation<Operation>(event);
456 }
457 }
458
459 template<class Event>
460 MAKI_NOINLINE void push_event_no_catch(const Event& event)
461 {
462 static_assert(impl_of(conf).run_to_completion);
463 push_event_impl<detail::machine_operation::process_event>(event);
464 }
465
466 template<detail::machine_operation Operation, class Event>
467 void push_event_impl(const Event& event)
468 {
469 operation_queue_.template push<any_event_visitor<Operation>>(event);
470 }
471
479 void process_pending_events()
480 {
481 if(!executing_operation_)
482 {
483 auto grd = executing_operation_guard{*this};
484 operation_queue_.invoke_and_pop_all(*this);
485 }
486 }
487
488 template<detail::machine_operation Operation>
489 struct any_event_visitor
490 {
491 template<class Event>
492 static void call(const Event& event, machine& self)
493 {
494 self.execute_one_operation<Operation>(event);
495 }
496 };
497
498 template<detail::machine_operation Operation, class Event>
499 void execute_one_operation(const Event& event)
500 {
501 if constexpr(Operation == detail::machine_operation::start)
502 {
503 impl_.call_entry_action(*this, context(), event);
504 }
505 else if constexpr(Operation == detail::machine_operation::stop)
506 {
507 impl_.exit_to_finals(*this, context(), event);
508 }
509 else
510 {
511 constexpr auto has_matching_pre_processing_hook = detail::tlu::contains_if_v
512 <
513 pre_processing_hook_ptr_constant_list,
514 detail::event_action_traits::for_event<Event>::template has_containing_event_set
515 >;
516
517 constexpr auto has_matching_post_processing_hook = detail::tlu::contains_if_v
518 <
519 post_processing_hook_ptr_constant_list,
520 detail::event_action_traits::for_event<Event>::template has_containing_event_set
521 >;
522
523 //If running, execute pre-processing hook for `Event`, if any.
524 if constexpr(has_matching_pre_processing_hook)
525 {
526 if(running())
527 {
528 detail::call_matching_event_action<pre_processing_hook_ptr_constant_list>
529 (
530 *this,
531 context(),
532 event
533 );
534 }
535 }
536
537 /*
538 If running:
539 - process the event;
540 - execute the post-processing hook for `Event`, if any.
541 */
542 if constexpr(has_matching_post_processing_hook)
543 {
544 if(running())
545 {
546 auto processed = false;
547 impl_.template call_internal_action<false>(*this, context(), event, processed);
548 detail::call_matching_event_action<post_processing_hook_ptr_constant_list>
549 (
550 *this,
551 context(),
552 event,
553 processed
554 );
555 }
556 }
557 else
558 {
559 /*
560 Note: We don't need to check if we're running here, as
561 processing the event won't have any effect anyway if the machine
562 is stopped.
563 */
564
565 impl_.template call_internal_action<false>(*this, context(), event);
566 }
567 }
568 }
569
570 static constexpr auto pre_processing_hooks = impl_of(conf).pre_processing_hooks;
571 static constexpr auto post_processing_hooks = impl_of(conf).post_processing_hooks;
572 static constexpr auto path = detail::path{};
573
574 using pre_processing_hook_ptr_constant_list = detail::tuple_to_element_ptr_constant_list_t<pre_processing_hooks>;
575 using post_processing_hook_ptr_constant_list = detail::tuple_to_element_ptr_constant_list_t<post_processing_hooks>;
576
577 detail::context_holder<context_type, impl_of(conf).context_sig> ctx_holder_;
578 detail::state_impls::composite_no_context<&conf, path> impl_;
579 bool executing_operation_ = false;
580 operation_queue_type operation_queue_;
581};
582
583#undef MAKI_DETAIL_MAYBE_CATCH
584
585} //namespace
586
587#endif
The state machine implementation template.
Definition machine.hpp:78
bool is() const
Returns whether the state created by StateBuilder is active in the region of the state machine....
Definition machine.hpp:340
machine(ContextArgs &&... ctx_args)
The constructor.
Definition machine.hpp:117
void process_event(const Event &event)
Processes the given event.
Definition machine.hpp:240
void stop(const Event &event={})
Stops the state machine.
Definition machine.hpp:186
const auto & state() const
Returns the maki::state object created by StateBuilder (of type maki::state_builder)....
Definition machine.hpp:329
IMPLEMENTATION_DETAIL context_type
The context type given to maki::machine_conf::context_a() or its variants.
Definition machine.hpp:93
bool check_event(const Event &event) const
Checks whether calling process_event(event) would cause a state transition or a call to any action.
Definition machine.hpp:292
static constexpr const auto & conf
The state machine configuration.
Definition machine.hpp:83
context_type & context()
Returns the context instantiated at construction.
Definition machine.hpp:137
const auto & region() const
Returns the maki::region object at index Index.
Definition machine.hpp:319
bool running() const
Returns whether the region of the state machine is running. This function can only be called if the s...
Definition machine.hpp:155
void process_event_no_catch(const Event &event)
Like process_event(), but doesn't catch exceptions, even if maki::machine_conf::catch_mx() is set.
Definition machine.hpp:250
const context_type & context() const
Returns the context instantiated at construction.
Definition machine.hpp:145
MAKI_NOINLINE void push_event(const Event &event)
Enqueues event for later processing.
Definition machine.hpp:310
void process_event_now(const Event &event)
Like maki::machine::process_event(), but doesn't check if an event is being processed.
Definition machine.hpp:275
void start(const Event &event={})
Starts the state machine.
Definition machine.hpp:172
The Maki library.
Default event given to maki::machine::start().
Definition events.hpp:24
Definition machine.hpp:382