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 MAKI_DETAIL_MAYBE_CATCH(start_now())
124 }
125 }
126
127 machine(const machine&) = delete;
128 machine(machine&&) = delete;
129 machine& operator=(const machine&) = delete;
130 machine& operator=(machine&&) = delete;
131 ~machine() = default;
132
137 {
138 return ctx_holder_.get();
139 }
140
144 const context_type& context() const
145 {
146 return ctx_holder_.get();
147 }
148
154 [[nodiscard]] bool running() const
155 {
156 return !impl_.completed();
157 }
158
170 template<class Event = events::start>
171 void start(const Event& event = {})
172 {
173 MAKI_DETAIL_MAYBE_CATCH(start_no_catch(event))
174 }
175
184 template<class Event = events::stop>
185 void stop(const Event& event = {})
186 {
187 MAKI_DETAIL_MAYBE_CATCH(stop_no_catch(event))
188 }
189
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& StateMold>
329 [[nodiscard]] const auto& state() const
330 {
331 return impl_.template state<StateMold>();
332 }
333
339 template<const auto& StateMold>
340 [[nodiscard]] bool is() const
341 {
342 return impl_.template is<StateMold>();
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 void start_now()
392 {
393 execute_operation_now<detail::machine_operation::start>(events::start{});
394 }
395
396 template<class Event>
397 void start_no_catch(const Event& event)
398 {
399 if(!running())
400 {
401 execute_operation<detail::machine_operation::start>(event);
402 }
403 }
404
405 template<class Event>
406 void stop_no_catch(const Event& event)
407 {
408 if(running())
409 {
410 execute_operation<detail::machine_operation::stop>(event);
411 }
412 }
413
414 template<class Event>
415 void process_event_now_no_catch(const Event& event)
416 {
417 static_assert
418 (
419 impl_of(conf).process_event_now_enabled,
420 "`maki::machine_conf::process_event_now_enabled()` hasn't been set to `true`"
421 );
422 execute_operation_now<detail::machine_operation::process_event>(event);
423 }
424
425 template<detail::machine_operation Operation, class Event>
426 void execute_operation(const Event& event)
427 {
428 if constexpr(impl_of(conf).run_to_completion)
429 {
430 if(!executing_operation_) //If call is not recursive
431 {
432 execute_operation_now<Operation>(event);
433 }
434 else
435 {
436 //Push event to RTC queue in case of recursive call
437 push_event_impl<Operation>(event);
438 }
439 }
440 else
441 {
442 execute_one_operation<Operation>(event);
443 }
444 }
445
446 template<detail::machine_operation Operation, class Event>
447 void execute_operation_now(const Event& event)
448 {
449 if constexpr(impl_of(conf).run_to_completion)
450 {
451 auto grd = executing_operation_guard{*this};
452
453 execute_one_operation<Operation>(event);
454
455 //Process enqueued events, if any
456 operation_queue_.invoke_and_pop_all(*this);
457 }
458 else
459 {
460 execute_one_operation<Operation>(event);
461 }
462 }
463
464 template<class Event>
465 MAKI_NOINLINE void push_event_no_catch(const Event& event)
466 {
467 static_assert(impl_of(conf).run_to_completion);
468 push_event_impl<detail::machine_operation::process_event>(event);
469 }
470
471 template<detail::machine_operation Operation, class Event>
472 void push_event_impl(const Event& event)
473 {
474 operation_queue_.template push<any_event_visitor<Operation>>(event);
475 }
476
484 void process_pending_events()
485 {
486 if(!executing_operation_)
487 {
488 auto grd = executing_operation_guard{*this};
489 operation_queue_.invoke_and_pop_all(*this);
490 }
491 }
492
493 template<detail::machine_operation Operation>
494 struct any_event_visitor
495 {
496 template<class Event>
497 static void call(const Event& event, machine& self)
498 {
499 self.execute_one_operation<Operation>(event);
500 }
501 };
502
503 template<detail::machine_operation Operation, class Event>
504 void execute_one_operation(const Event& event)
505 {
506 if constexpr(Operation == detail::machine_operation::start)
507 {
508 impl_.call_entry_action(*this, context(), event);
509 }
510 else if constexpr(Operation == detail::machine_operation::stop)
511 {
512 impl_.exit_to_finals(*this, context(), event);
513 }
514 else
515 {
516 constexpr auto has_matching_pre_processing_hook = detail::tlu::contains_if_v
517 <
518 pre_processing_hook_ptr_constant_list,
519 detail::event_action_traits::for_event<Event>::template has_containing_event_set
520 >;
521
522 constexpr auto has_matching_post_processing_hook = detail::tlu::contains_if_v
523 <
524 post_processing_hook_ptr_constant_list,
525 detail::event_action_traits::for_event<Event>::template has_containing_event_set
526 >;
527
528 //If running, execute pre-processing hook for `Event`, if any.
529 if constexpr(has_matching_pre_processing_hook)
530 {
531 if(running())
532 {
533 detail::call_matching_event_action<pre_processing_hook_ptr_constant_list>
534 (
535 *this,
536 context(),
537 event
538 );
539 }
540 }
541
542 /*
543 If running:
544 - process the event;
545 - execute the post-processing hook for `Event`, if any.
546 */
547 if constexpr(has_matching_post_processing_hook)
548 {
549 if(running())
550 {
551 auto processed = false;
552 impl_.template call_internal_action<false>(*this, context(), event, processed);
553 detail::call_matching_event_action<post_processing_hook_ptr_constant_list>
554 (
555 *this,
556 context(),
557 event,
558 processed
559 );
560 }
561 }
562 else
563 {
564 /*
565 Note: We don't need to check if we're running here, as
566 processing the event won't have any effect anyway if the machine
567 is stopped.
568 */
569
570 impl_.template call_internal_action<false>(*this, context(), event);
571 }
572 }
573 }
574
575 static constexpr auto pre_processing_hooks = impl_of(conf).pre_processing_hooks;
576 static constexpr auto post_processing_hooks = impl_of(conf).post_processing_hooks;
577 static constexpr auto path = detail::path{};
578
579 using pre_processing_hook_ptr_constant_list = detail::tuple_to_element_ptr_constant_list_t<pre_processing_hooks>;
580 using post_processing_hook_ptr_constant_list = detail::tuple_to_element_ptr_constant_list_t<post_processing_hooks>;
581
582 detail::context_holder<context_type, impl_of(conf).context_sig> ctx_holder_;
583 detail::state_impls::composite_no_context<&conf, path> impl_;
584 bool executing_operation_ = false;
585 operation_queue_type operation_queue_;
586};
587
588#undef MAKI_DETAIL_MAYBE_CATCH
589
590} //namespace
591
592#endif
The state machine implementation template.
Definition machine.hpp:78
bool is() const
Returns whether the state created by StateMold 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:185
const auto & state() const
Returns the maki::state object created by StateMold (of type maki::state_mold). Only valid if machine...
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:136
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:154
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:144
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:171
The Maki library.
Default event given to maki::machine::start().
Definition events.hpp:24
Definition machine.hpp:382