Maki
Loading...
Searching...
No Matches
machine.hpp
1//Copyright Florian Goujeon 2021 - 2026.
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_impl.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/context_storage.hpp"
24#include "detail/event_action.hpp"
25#include "detail/noinline.hpp"
26#include "detail/function_queue.hpp"
27#include "detail/mix.hpp"
28#include "detail/tlu/contains_if.hpp"
29#include <type_traits>
30#include <exception>
31
32namespace maki
33{
34
35namespace detail
36{
37 enum class machine_operation: char
38 {
39 start,
40 stop,
41 process_event
42 };
43}
44
45#define MAKI_DETAIL_MAYBE_CATCH(statement) /*NOLINT(cppcoreguidelines-macro-usage)*/ \
46 if constexpr(detail::is_null_v<typename option_set_type::exception_handler_type>) \
47 { \
48 statement; \
49 } \
50 else \
51 { \
52 try \
53 { \
54 statement; \
55 } \
56 catch(...) \
57 { \
58 impl_of(conf).exception_handler(*this, std::current_exception()); \
59 } \
60 }
61
77template<const auto& Conf>
79{
80public:
84 static constexpr const auto& conf = Conf;
85
86#ifndef MAKI_DETAIL_DOXYGEN
87 using option_set_type = std::decay_t<decltype(impl_of(conf))>;
88#endif
89
90#ifdef MAKI_DETAIL_DOXYGEN
94 using context_type = IMPLEMENTATION_DETAIL;
95#else
96 using context_type = typename option_set_type::context_type;
97#endif
98
99 static_assert
100 (
101 detail::is_machine_conf_v<std::decay_t<decltype(conf)>>,
102 "Given `Conf` must be an instance of `maki::machine_conf`"
103 );
104
117 template<class... ContextArgs>
118 explicit machine(ContextArgs&&... ctx_args):
119 ctx_holder_(*this, std::forward<ContextArgs>(ctx_args)...),
120 impl_(*this, context())
121 {
122 if constexpr(impl_of(conf).auto_start)
123 {
124 MAKI_DETAIL_MAYBE_CATCH(start_now())
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
240 template<class Event>
241 void process_event(const Event& event)
242 {
243 MAKI_DETAIL_MAYBE_CATCH(process_event_no_catch(event))
244 }
245
250 template<class Event>
251 void process_event_no_catch(const Event& event)
252 {
253 execute_operation<detail::machine_operation::process_event>(event);
254 }
255
275 template<class Event>
276 void process_event_now(const Event& event)
277 {
278 MAKI_DETAIL_MAYBE_CATCH(process_event_now_no_catch(event))
279 }
280
292 template<class Event>
293 bool check_event(const Event& event) const
294 {
295 return impl_.template call_internal_action<true>(*this, context(), event);
296 }
297
308 template<class Event>
309 MAKI_NOINLINE void push_event(const Event& event)
310 {
311 MAKI_DETAIL_MAYBE_CATCH(push_event_no_catch(event))
312 }
313
317 template<int Index>
318 [[nodiscard]] const auto& region() const
319 {
320 return impl_.template region<Index>();
321 }
322
327 template<const auto& StateMold>
328 [[nodiscard]] const auto& state() const
329 {
330 return impl_.template state<StateMold>();
331 }
332
338 template<const auto& StateMold>
339 [[nodiscard]] bool is() const
340 {
341 return impl_.template is<StateMold>();
342 }
343
344private:
345 class executing_operation_guard
346 {
347 public:
348 executing_operation_guard(machine& self):
349 self_(self)
350 {
351 self_.executing_operation_ = true;
352 }
353
354 executing_operation_guard(const executing_operation_guard&) = delete;
355 executing_operation_guard(executing_operation_guard&&) = delete;
356 executing_operation_guard& operator=(const executing_operation_guard&) = delete;
357 executing_operation_guard& operator=(executing_operation_guard&&) = delete;
358
359 ~executing_operation_guard()
360 {
361 self_.executing_operation_ = false;
362 }
363
364 private:
365 machine& self_; //NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members)
366 };
367
368 struct real_operation_queue_holder
369 {
370 template<bool = true> //Dummy template for lazy evaluation
371 using type = detail::function_queue
372 <
373 machine&,
374 impl_of(conf).small_event_max_size,
375 impl_of(conf).small_event_max_align
376 >;
377 };
378 struct empty_holder
379 {
380 template<bool = true> //Dummy template for lazy evaluation
381 struct type{};
382 };
383 using operation_queue_type = typename std::conditional_t
384 <
385 impl_of(conf).run_to_completion,
386 real_operation_queue_holder,
387 empty_holder
388 >::template type<>;
389
390 void start_now()
391 {
392 execute_operation_now<detail::machine_operation::start>(events::start{});
393 }
394
395 template<class Event>
396 void start_no_catch(const Event& event)
397 {
398 if(!running())
399 {
400 execute_operation<detail::machine_operation::start>(event);
401 }
402 }
403
404 template<class Event>
405 void stop_no_catch(const Event& event)
406 {
407 if(running())
408 {
409 execute_operation<detail::machine_operation::stop>(event);
410 }
411 }
412
413 template<class Event>
414 void process_event_now_no_catch(const Event& event)
415 {
416 static_assert
417 (
418 impl_of(conf).process_event_now_enabled,
419 "`maki::machine_conf::process_event_now_enabled()` hasn't been set to `true`"
420 );
421 execute_operation_now<detail::machine_operation::process_event>(event);
422 }
423
424 template<detail::machine_operation Operation, class Event>
425 void execute_operation(const Event& event)
426 {
427 if constexpr(impl_of(conf).run_to_completion)
428 {
429 if(!executing_operation_) //If call is not recursive
430 {
431 execute_operation_now<Operation>(event);
432 }
433 else
434 {
435 //Push event to RTC queue in case of recursive call
436 push_event_impl<Operation>(event);
437 }
438 }
439 else
440 {
441 execute_one_operation<Operation>(event);
442 }
443 }
444
445 template<detail::machine_operation Operation, class Event>
446 void execute_operation_now(const Event& event)
447 {
448 if constexpr(impl_of(conf).run_to_completion)
449 {
450 auto grd = executing_operation_guard{*this};
451
452 execute_one_operation<Operation>(event);
453
454 //Process enqueued events, if any
455 operation_queue_.invoke_and_pop_all(*this);
456 }
457 else
458 {
459 execute_one_operation<Operation>(event);
460 }
461 }
462
463 template<class Event>
464 MAKI_NOINLINE void push_event_no_catch(const Event& event)
465 {
466 static_assert(impl_of(conf).run_to_completion);
467 push_event_impl<detail::machine_operation::process_event>(event);
468 }
469
470 template<detail::machine_operation Operation, class Event>
471 void push_event_impl(const Event& event)
472 {
473 operation_queue_.template push<any_event_visitor<Operation>>(event);
474 }
475
483 void process_pending_events()
484 {
485 if(!executing_operation_)
486 {
487 auto grd = executing_operation_guard{*this};
488 operation_queue_.invoke_and_pop_all(*this);
489 }
490 }
491
492 template<detail::machine_operation Operation>
493 struct any_event_visitor
494 {
495 template<class Event>
496 static void call(const Event& event, machine& self)
497 {
498 self.execute_one_operation<Operation>(event);
499 }
500 };
501
502 template<detail::machine_operation Operation, class Event>
503 void execute_one_operation(const Event& event)
504 {
505 if constexpr(Operation == detail::machine_operation::start)
506 {
507 impl_.enter(*this, context(), event);
508 }
509 else if constexpr(Operation == detail::machine_operation::stop)
510 {
511 impl_.exit_to_finals(*this, context(), event);
512 }
513 else
514 {
515 constexpr auto has_matching_pre_processing_hook = detail::tlu::contains_if_v
516 <
517 pre_processing_hook_ptr_constant_list,
518 detail::event_action_traits::for_event<Event>::template has_containing_event_set
519 >;
520
521 constexpr auto has_matching_post_processing_hook = detail::tlu::contains_if_v
522 <
523 post_processing_hook_ptr_constant_list,
524 detail::event_action_traits::for_event<Event>::template has_containing_event_set
525 >;
526
527 //If running, execute pre-processing hook for `Event`, if any.
528 if constexpr(has_matching_pre_processing_hook)
529 {
530 if(running())
531 {
532 detail::call_matching_event_action<pre_processing_hook_ptr_constant_list>
533 (
534 *this,
535 context(),
536 event
537 );
538 }
539 }
540
541 /*
542 If running:
543 - process the event;
544 - execute the post-processing hook for `Event`, if any.
545 */
546 if constexpr(has_matching_post_processing_hook)
547 {
548 if(running())
549 {
550 const auto processed = impl_.template call_internal_action<false>(*this, context(), event);
551 detail::call_matching_event_action<post_processing_hook_ptr_constant_list>
552 (
553 *this,
554 context(),
555 event,
556 processed
557 );
558 }
559 }
560 else
561 {
562 /*
563 Note: We don't need to check if we're running here, as
564 processing the event won't have any effect anyway if the machine
565 is stopped.
566 */
567
568 impl_.template call_internal_action<false>(*this, context(), event);
569 }
570 }
571 }
572
573 static constexpr auto pre_processing_hooks = impl_of(conf).pre_processing_hooks;
574 static constexpr auto post_processing_hooks = impl_of(conf).post_processing_hooks;
575 static constexpr auto path = detail::path_impl{};
576
577 using pre_processing_hook_ptr_constant_list = detail::mix_constant_list_t<pre_processing_hooks>;
578 using post_processing_hook_ptr_constant_list = detail::mix_constant_list_t<post_processing_hooks>;
579
580 detail::context_holder
581 <
583 detail::context_storage::plain,
584 impl_of(conf).context_sig
585 > ctx_holder_;
586 detail::state_impls::composite_no_context
587 <
588 &conf,
589 path,
590 detail::context_storage::plain
591 > impl_;
592 bool executing_operation_ = false;
593 operation_queue_type operation_queue_;
594};
595
596#undef MAKI_DETAIL_MAYBE_CATCH
597
598} //namespace
599
600#endif
The state machine implementation template.
Definition machine.hpp:79
bool is() const
Returns whether the state created by StateMold is active in the region of the state machine....
Definition machine.hpp:339
machine(ContextArgs &&... ctx_args)
The constructor.
Definition machine.hpp:118
void process_event(const Event &event)
Processes the given event.
Definition machine.hpp:241
void stop(const Event &event={})
Stops the state machine.
Definition machine.hpp:186
const auto & state() const
Returns the maki::state object created by StateMold (of type maki::state_mold). Only valid if machine...
Definition machine.hpp:328
IMPLEMENTATION_DETAIL context_type
The context type given to maki::machine_conf::context_a() or its variants.
Definition machine.hpp:94
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:293
static constexpr const auto & conf
The state machine configuration.
Definition machine.hpp:84
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:318
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:251
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:309
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:276
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:381