-
Notifications
You must be signed in to change notification settings - Fork 0
/
cfsm.hpp
1048 lines (909 loc) · 32.8 KB
/
cfsm.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
MIT License
Copyright (c) 2024 notweerdmonk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef __SMBUILDER_HPP__
#define __SMBUILDER_HPP__
/**
* @file cfsm.hpp
* @brief Complie-time finite state machine library.
* @author notweerdmonk
*/
/**
* @mainpage Compile-time finite state machine library
*
* Browse @link cfsm.hpp @endlink
*
* @section header Static disposition
* The states and transitions between them must be known at compile-time. The
* library uses templates to implement state transitions.
*
* @section states States
* All user defined states which are represented by classes should inherit
* from the base "state" class provided in "cfsm.hpp". The base class
* provides on_enter and on_exit member functions which are called on entry
* to state and exit from the state respectively. These pure virtual member
* functions shall be overriden in the user defined classes.
*
* The "state_machine" class provides a "state" member function template which
* will return a pointer to the current state object which is dynamic_cast
* into the state class provided as the template parameter. When current state
* is not of the requested type, nullptr is returned. This can be used as a
* test for the current state of a state machine.
*
* @section transitions State transitions
* Transitions between user defined states are represented by a struct
* template named "transition". The template parameters are the types of
* source state class and target state class respectively.
*
* User defined template specializations should provide two members. First,
* a static constexpr const bool member variable named "exists" which should be
* set as "true"; an helper macro "TRANSITION_EXISTS" is provided for this
* purpose. Second, an implementation of the "operator()" function, the call
* operator overload, which returns void and takes a void pointer as its
* argument. This function shall be called on a valid transition (source and
* target states are valid classes and the template specialization of
* "transition" struct for source and target state exists), triggered by
* calling the "transition" member function of the "state_machine" class.
* The function signature shall be:
*
* void operator()(void *dataptr);
*
*
* @section state_machine_types State machine types
* State machines are classified based on their state object allocation scheme.
* These are lazily allocated, externally preallocated and internally
* preallocated. The enum class "alloc_type" passed as a template parameter,
* specifies the allocation scheme for a state machine.
*
* @section lazy Lazily allocated states
* If the "state_pool" template parameter is set to nullptr (default) the
* memory for state objects is dynamically allocated with new operator when
* state transition occurs (by calling "transition" member function).
*
* @section external_prealloc Externally managed preallocated states storage
* An external array of state objects is required. These state objects will be
* used by the state machine. The "state_pool" template parameter shall be an
* array of pointers of the base "state" class, having size equal to the number
* of states. The array is indexed into by the type identifiers of respective
* state classes enabling constant time access. Base class pointers should point
* to objects of derived state classes. Memory for these derived state class
* objects shall be managed by user.
*
* @section internal_prealloc Internally managed preallocated state storage
* The array of preallocated state objects can be managed by the state machine
* internally. Type identifiers of the derivedstate classes will be used to
* index into this array. The state objects' lifetimes span the lifetime of the
* state machine object. This scheme offers conveniece at the cost of reduced
* flexibility.
*
* @section type_id Type identifier for derived state classes
* The type identifier of a derived state class is an unsigned integer of type
* as std::size_t. Provide a static member function named "type_id" to each
* derived state class which returns the type identifier.
* The function signature shall be:
*
* static std::size_t type_id();
*
*
* See example programs for type identifier schemes. A static helper function
* named "gen_type_id" which returns a std::size_t value based on an
* incremented static variable is provided for the "state_machine" class.
*
* @see cfsm.hpp
*/
#include <type_traits>
#include <sstream>
#include <cassert>
namespace cfsm {
#if __cplusplus < 201703L
#if __cplusplus >= 201402L
/* C++11 alternatives for std::disjunction and std::conjunction */
template <typename...>
struct disjunction : std::false_type {};
template <typename B1>
struct disjunction<B1> : B1 {};
template <typename B1, typename... Bn>
struct disjunction<B1, Bn...>
: std::conditional<B1::value, B1, disjunction<Bn...>>::type {};
template <typename... B>
constexpr bool disjunction_v = disjunction<B...>::value;
template <typename...>
struct conjunction : std::true_type {};
template <typename B1>
struct conjunction<B1> : B1 {};
template <typename B1, typename... Bn>
struct conjunction<B1, Bn...>
: std::conditional<B1::value, conjunction<Bn...>, B1>::type {};
template <typename... B>
constexpr bool conjunction_v = conjunction<B...>::value;
#endif /* __cplusplus >= 201402L */
#else /* __cplusplus < 201703L */
/* For C++17 and above */
using std::disjunction_v;
using std::conjunction_v;
#endif /* __cplusplus < 201703L */
/**
* @brief Abstract base class representing a state in the state machine.
*
* Any state in the state machine should inherit from this class and implement
* the `on_enter` and `on_exit` member functions to define behavior when the
* state machine enters or exits that state respectively.
*/
class state {
public:
/**
* @brief Called when the state machine enters the state.
*
* This member function should be overridden in derived state classes to
* define specific behavior that should happen when the state machine enters
* the state.
*
* @param dataptr Opaque pointer to user data.
*/
virtual void on_enter(void *dataptr) const = 0;
/**
* @brief Called when the state machine exits the state.
*
* This member function should be overridden in derived state classes to
* define specific behavior that should happen when the state machine exits
* the state.
*
* @param dataptr Opaque pointer to user data.
*/
virtual void on_exit(void *dataptr) const = 0;
/// Virtual destructor for safe polymorphic deletion.
virtual ~state() = default;
};
/* Transition functor (generic fallback) */
/**
* @brief Functor template to handle state transitions.
*
* This is a generic fallback for transitions that are not explicitly
* specialized. It asserts a false condition indicating that the transition
* is invalid.
*
* @tparam from_state The state type representing the current state.
* @tparam to_state The state type representing the target state.
*/
template <typename from_state, typename to_state>
struct transition {
/**
* @brief Static constant bool member variable which indicates a valid
* transition between states, if its value is `true`.
*/
static constexpr const bool exists = false;
/**
* @brief Helper macro to define "exists" static member variable as true.
*/
#define TRANSITION_EXISTS static constexpr const bool exists = true
/**
* @brief Functor call operator for the transition.
*
* This operator is invoked during a state transition. The default behavior
* is to print an invalid transition message, unless specialized for
* specific state transitions.
*
* @param dataptr Opaque pointer to user data.
*/
void operator()(void *dataptr) {
assert((0, "Generic transition between states"));
}
};
/**
* @brief Helper macro to begin definition of transition between `from` and
* `to` states.
*
* User needs to implement the `operator()` member function.
*
* TRANSITION_BEGIN(from, to)
* void operator()() {
*
* }
* TRANSITION_END
*
* @see TRANSITION_END
*/
#define TRANSITION_BEGIN(from, to) \
template <> \
struct cfsm::transition<from, to> { \
TRANSITION_EXISTS;
/**
* @brief Helper macro to end definition of transition between `from` and
* `to` states.
*
* @see TRANSITION_BEGIN
*/
#define TRANSITION_END \
};
/**
* @brief Enum which specifies state objects allocation scheme for a state
* machine.
*/
enum class alloc_type {
LAZY, ///< Lazily allocated state objects
PREALLOCED, ///< User managed preallocated state objects array
INTERNAL, ///< Self managed preallocated state objects array
STATIC ///< Self managed static preallocated state objects array
};
/* Finite state machine class */
#if __cplusplus >= 201402L
/**
* @brief Template class representing a finite state machine.
*
* This state machine class manages transitions between states and calls
* appropriate `on_enter` and `on_exit` member functions for the states.
* States must derive from a base state class.
*
* @tparam base_state The base class from which all state types must derive.
* @tparam type Enum specifying the state object allocation scheme.
* @tparam state_pool Array of pointers to base state class objects.
* @tparam states List of state classes present in the state machine.
*/
template <
typename base_state,
enum alloc_type type = alloc_type::LAZY,
base_state* state_pool[] = nullptr,
typename... states
>
#else /* __cplusplus >= 201402L */
/**
* @brief Template class representing a finite state machine.
*
* This state machine class manages transitions between states and calls
* appropriate * `on_enter` and `on_exit` member functions for the states.
* States must derive from a base state class.
*
* @tparam base_state The base class from which all state types must derive.
* @tparam type Enum specifying the state object allocation scheme.
* @tparam state_pool Array of pointers to base state class objects.
* @tparam state_count Number of states present in the state machine.
*/
template <
typename base_state,
enum alloc_type type = alloc_type::LAZY,
base_state* state_pool[] = nullptr,
std::size_t state_count = 0
>
#endif /* __cplusplus >= 201402L */
class state_machine {
base_state* p_current_state = nullptr; ///< Pointer to the current state object.
/* Ensure the given state is valid */
#if __cplusplus >= 201402L
/**
* @brief Checks if a type is derived from a base class.
*
* This struct evaluates whether a given type `derived` is derived from the
* specified base class `base`.
*
* @tparam base The base class to check against.
* @tparam derived The type to check if it's derived from `base`.
*/
template <typename base, typename derived>
struct is_base_of_v {
/**
* @brief The boolean result indicating whether `derived` is derived from
* `base`.
*/
static constexpr bool value = std::is_base_of<base, derived>::value;
};
/**
* @brief Checks if all provided types are derived from the base class.
*
* This structure checks if a variadic list of `derived` types are all
* derived from the specified base class `base` using conjunction.
*
* @tparam base The base class to check against.
* @tparam derived The variadic template list of types to check.
*/
template <typename base, typename... derived>
struct are_valid_states {
/**
* @brief The boolean result indicating whether all `derived` types are
* derived from `base`.
*/
static constexpr bool value =
conjunction_v<is_base_of_v<base, derived>...>;
};
/**
* @brief Checks if a given type matches any type in the list of valid
* states.
*
* This static function evaluates whether the given type `T` matches
* any of the valid states in a predefined list of types.
*
* @tparam T The type to be checked against valid states.
* @return constexpr bool `true` if `T` matches any of the valid states,
* `false` otherwise.
*/
template <typename T>
static
constexpr bool is_valid_state() {
return disjunction_v<std::is_same<T, states>...>;
}
#else /* __cplusplus >= 201402L */
/**
* @brief Checks if a given type is derived from a base state.
*
* This static function evaluates whether the given type `T` is derived
* from the predefined `base_state` type.
*
* @tparam T The type to be checked.
* @return constexpr bool `true` if `T` is derived from `base_state`,
* `false` otherwise.
*/
template <typename T>
static
constexpr bool is_valid_state() {
return std::is_base_of<base_state, T>::value;
}
#endif /* __cplusplus >= 201402L */
/**
* @brief Provides a pointer to an object of requested state class.
*
* This struct contains a static method named `state` which returns a
* pointer of `alloc_base_state` class.
*
* @tparam alloc_base_state The base state class.
* @tparam size The size of the state object pointer array.
*/
template <typename alloc_base_state, std::size_t size = 0>
struct state_allocator {
private:
#if __cplusplus >= 201402L
/**
* @brief Manages a pool of state objects.
*
* The `internal_state_pool` struct is used to manage a collection (pool)
* of state objects, offering a pointer to this pool and ensuring proper
* cleanup of dynamically allocated objects.
*/
struct internal_state_pool {
alloc_base_state *pool[sizeof...(states)];
/**
* @brief Constructs an internal state pool and allocates state objects.
*
* Initializes the pool with dynamically allocated objects of
* each state type provided in the `states` parameter pack.
*/
internal_state_pool() {
alloc_base_state *pool_[sizeof...(states)] = { new states... };
for (std::size_t i = 0; i < sizeof...(states); ++i) {
pool[i] = pool_[i];
}
}
/**
* @brief Destroys the internal state pool and releases memory.
*
* Frees each dynamically allocated state object.
*/
~internal_state_pool() {
for (std::size_t i = 0; i < sizeof...(states); ++i) {
delete pool[i];
}
}
};
#endif /* __cplusplus >= 201402L */
public:
/**
* @brief Provides a pointer to an object of requested state class from
* given array of pointers.
*
* Takes an array of base state class pointers as argument. The pointers
* in this array shall point to state objects of the classes enlisted with
* the state machine. This array is indexed into using `type_id` argument
* to get the address of an object of the requested state class.
*
* @param pool The state object pointers array.
* @param type_id The type identifier of the derived state class which
* the address of an object of, is returned in the base state class
* pointer.
* @return A pointer to derived state class object of the base state
* class.
*/
static
alloc_base_state* state(alloc_base_state *pool[size],
const std::size_t type_id) {
return type_id < size ? pool[type_id] : nullptr;
}
#if __cplusplus >= 201402L
private:
static
internal_state_pool*& get_state_pool() {
static internal_state_pool *p_statepool_ = nullptr;
return !p_statepool_ ? (p_statepool_ = new internal_state_pool)
: p_statepool_;
}
public:
/**
* @brief Provides a pointer to an object of requested state class from
* internally managed array of pointers.
*
* Allocates objects of enlisted state classes and stores their addresses
* in an array of base state class pointers. This array is indexed into
* using `type_id` argument to get the address of an object of the
* requested state class.
*
* @param type_id The type identifier of the derived state class which
* the address of an object of, is returned in the base state class
* pointer.
* @return A pointer to derived state class object of the base state
* class.
*/
template <
enum alloc_type type_ = type,
typename std::enable_if<type_ == alloc_type::INTERNAL, int>::type = 0
>
static
alloc_base_state* state(const std::size_t type_id) {
internal_state_pool *p_statepool = get_state_pool();
return p_statepool && type_id < size ? p_statepool->pool[type_id]
: nullptr;
}
template <
enum alloc_type type_ = type,
typename std::enable_if<type_ == alloc_type::STATIC, int>::type = 0
>
static
alloc_base_state* state(const std::size_t type_id) {
static internal_state_pool p_statepool_;
return type_id < size ? p_statepool_.pool[type_id] : nullptr;
}
template <
enum alloc_type type_ = type,
typename std::enable_if<type_ == alloc_type::INTERNAL, int>::type = 0
>
static
void delete_state_pool() {
internal_state_pool *&statepool = get_state_pool();
statepool ? delete statepool : (void)0;
statepool = nullptr;
}
template <
enum alloc_type type_ = type,
typename std::enable_if<type_ != alloc_type::INTERNAL, int>::type = 0
>
static
void delete_state_pool() {
}
#endif /* __cplusplus >= 201402L */
};
/**
* @brief Functon template to fetch a pointer to an object of requested
* state class, from an array of base state class pointers.
*
* This function is a wrapper over the actual implementation.
* @see state_allocator::state(allocate_state**, const std::size_t)
*
* @tparam new_state The requested state class.
* @tparam state_pool_ The state object pointer array.
* @return A pointer to derived state class object of the base state
* class.
*/
template <
typename new_state,
enum alloc_type type_ = type,
typename std::enable_if<type_ == alloc_type::PREALLOCED, int>::type = 0
>
base_state* allocate_state() {
#if __cplusplus >= 201402L
return state_allocator<cfsm::state, sizeof...(states)>::state(state_pool,
new_state::type_id());
#else
return state_allocator<cfsm::state, state_count>
::state(state_pool, new_state::type_id());
#endif
}
/* Internal allocator is unavailable below C++14 */
#if __cplusplus >= 201402L
/**
* @brief Functon template to fetch a pointer to an object of requested
* state class, from an internally managed array of base state class
* pointers.
*
* This function is a wrapper over the actual implementation.
* @see state_allocator::state(const std::size_t)
*
* @tparam new_state The requested state class.
* @tparam state_pool_ The state object pointer array.
* @return A pointer to derived state class object of the base state
* class.
*/
template <
typename new_state,
enum alloc_type type_ = type,
typename std::enable_if <
type_ == alloc_type::INTERNAL || type_ == alloc_type::STATIC,
int
>::type = 0
>
base_state* allocate_state() {
return state_allocator<cfsm::state, sizeof...(states)>
::state(new_state::type_id());
}
#endif /* __cplusplus >= 201402L */
/**
* @brief Functon template to allocate a new state object and return its
* address as a pointer of base state class.
*
* Uses new operator on the requested state class.
*
* @tparam new_state The requested state class.
* @tparam state_pool_ The state object pointer array.
* @return A pointer to derived state class object of the base state
* class.
*/
template <
typename new_state,
enum alloc_type type_ = type,
typename std::enable_if<type_ == alloc_type::LAZY, int>::type = 0
>
base_state* allocate_state() {
return new new_state();
}
/**
* @brief Free the current state object.
*
* Sets the current state pointer as nullptr;
*/
void delete_current_state() {
if (type == alloc_type::LAZY) {
delete p_current_state;
}
p_current_state = nullptr;
}
public:
/**
* @brief Returns a non-negative integer of `std::size_t` type.
*
* Returns incremented value of a static variable every time it gets called
* starting with zero. This function is static member of the "state_machine"
* class, hence can be used to assign type identifiers to derived state
* classes.
*
* @return A non-negative integer.
*/
static
std::size_t gen_type_id() {
static std::size_t type_id_gen = 0;
return type_id_gen++;
}
#if __cplusplus >= 201402L
/**
* @brief Constructor for the state machine.
*
* Checks if the states provided as template parameters are derived from
* the base "state" class.
*/
state_machine() {
static_assert(are_valid_states<base_state, states...>::value,
"Invalid states in ctor");
}
#else /* __cplusplus >= 201402L */
/**
* @brief Constructor for the state machine.
*/
state_machine() = default;
#endif /* __cplusplus >= 201402L */
/**
* @brief Starts the state machine in an initial state.
*
* The state machine transitions into the initial state and calls the
* state's `on_enter` member function.
*
* @tparam initial_state The type of the initial state, which must inherit
* from `base_state`.
* @param dataptr Opaque pointer to user data.
*/
template <typename initial_state>
void start(void *dataptr) {
static_assert(is_valid_state<initial_state>(), "Invalid initial state");
p_current_state = state_machine::allocate_state<initial_state>();
if (!p_current_state) {
throw std::runtime_error("State pointer is null");
}
p_current_state->on_enter(dataptr);
}
/**
* @brief Transitions the state machine to a new state.
*
* The state machine transitions from the current state to the new state
* and calls the appropriate `on_exit` member function for the current
* state and `on_enter` member function for the new state. It also invokes
* the transition functor for the specific transition between the two
* states.
*
* @tparam new_state The type of the target state, which must inherit from
* `base_state`.
* @param dataptr Opaque pointer to user data.
* @return true on successfull state transition, false on error.
*/
template <
typename from_state, typename to_state,
typename std::enable_if<transition<from_state, to_state>::exists,
bool>::type = false
>
bool transition(void *dataptr) {
static_assert(is_valid_state<from_state>(), "Invalid source state");
static_assert(is_valid_state<to_state>(), "Invalid target state");
if (dynamic_cast<from_state*>(p_current_state) == nullptr) {
return false;
}
if (!p_current_state) {
throw std::runtime_error("State pointer is null");
}
base_state* p_new_state = state_machine::allocate_state<to_state>();
if (!p_new_state) {
std::ostringstream oss;
oss << "Failed to allocate new state, state_pool: " << state_pool;
throw std::runtime_error(oss.str());
}
p_current_state->on_exit(dataptr);
if (type == alloc_type::LAZY) {
delete p_current_state;
}
/* Call transition functor for "from_state" to "to_state" transition */
cfsm::transition<from_state, to_state>()(dataptr);
p_current_state = p_new_state;
p_current_state->on_enter(dataptr);
return true;
}
/**
* @brief Stops the state machine.
*
* This function calls the `on_exit` member function and frees the current
* state object. The `start` member function is required to be called for
* the state machine to start operating.
*
* @param dataptr Opaque pointer to user data.
*/
void stop(void *dataptr) {
if (!p_current_state) {
return;
}
p_current_state->on_exit(dataptr);
delete_current_state();
#if __cplusplus >= 201402L
/*
* Calling stop() or dtor after saving the state machine will never call
* delete_state_pool(). The internal state pool will remain valid in
* memory till an operable state machine instance calls stop() or dtor.
*/
state_allocator<cfsm::state, sizeof...(states)>::delete_state_pool();
#endif
}
/**
* @brief Returns pointer to constant current state object.
*
* This function template returns a pointer of the state type provided as
* template argument which points to constant object of the current state
* class. If the current state object does not belong the the state type
* requested, nullptr is returned.
*
* @tparam state_type The expected state class of the current state.
* @return Pointer of the state class provided as template parameter.
*/
template<typename state_type = base_state>
const state_type* state() const {
return dynamic_cast<state_type*>(p_current_state);
}
/**
* @brief Save the state machine's state to memory.
*
* Writes the address of the current state object to the provided char
* array. The array whose size is also provided should be large enough
* to store an address. If the array is smaller the function does nothing.
*
* @param pdata Pointer to char array.
* @param datalen Size of the char array.
*/
std::size_t save(char *pdata, std::size_t datalen) {
if (!pdata) {
return 0;
}
if (datalen < sizeof(p_current_state)) {
return 0;
}
*reinterpret_cast<base_state**>(pdata) = p_current_state;
/* To avoid incorrect free in dtor set p_current_state as nullptr */
p_current_state = nullptr;
return sizeof(p_current_state);
}
/**
* @brief Load the state machine's state from memory.
*
* Reads the address of the current state object from the provided char
* array. If the size of the array is smaller than size of an address the
* function does nothing.
*
* @param pdata Pointer to char array.
* @param datalen Size of the char array.
*/
std::size_t load(const char *pdata, std::size_t datalen) {
if (!pdata) {
return 0;
}
if (datalen < sizeof(p_current_state)) {
return 0;
}
p_current_state = *reinterpret_cast<base_state *const *>(pdata);
return sizeof(p_current_state);
}
/**
* @brief Destructor for the state machine.
*
* Frees the current state object when the state machine is destroyed.
* @see delete_current_state()
*/
~state_machine() {
if (!p_current_state) {
return;
}
delete_current_state();
#if __cplusplus >= 201402L
/*
* Calling stop() or dtor after saving the state machine will never call
* delete_state_pool(). The internal state pool will remain valid in
* memory till an operable state machine instance calls stop() or dtor.
*/
state_allocator<cfsm::state, sizeof...(states)>::delete_state_pool();
#endif
}
};
/* State machine type aliases */
#if __cplusplus >= 201402L
/**
* @brief Type alias representing a finite state machine with lazy state
* object allocation scheme.
*
* @tparam base_state The base class from which all state types must derive.
* @tparam state_pool Array of pointers to base state class objects.
* @tparam states List of state classes present in the state machine.
*/
template <
typename base_state,
base_state* state_pool[] = nullptr,
typename... states
>
using state_machine_lazy = state_machine<
base_state,
alloc_type::LAZY,
state_pool,
states...
>;
/**
* @brief Type alias representing a finite state machine with externally
* managed preallocated state objects scheme.
*
* @tparam base_state The base class from which all state types must derive.
* @tparam state_pool Array of pointers to base state class objects.
* @tparam states List of state classes present in the state machine.
*/
template <
typename base_state,
base_state* state_pool[] = nullptr,
typename... states
>
using state_machine_ext = state_machine<
base_state,
alloc_type::PREALLOCED,
state_pool,
states...
>;
/**
* @brief Type alias representing a finite state machine with internally
* managed preallocated state objects scheme.
*
* @tparam base_state The base class from which all state types must derive.
* @tparam state_pool Array of pointers to base state class objects.
* @tparam states List of state classes present in the state machine.
*/
template <
typename base_state,
base_state* state_pool[] = nullptr,
typename... states
>
using state_machine_int = state_machine<
base_state,
alloc_type::INTERNAL,
state_pool,
states...
>;
template <
typename base_state,
base_state* state_pool[] = nullptr,
typename... states
>
using state_machine_static = state_machine<
base_state,
alloc_type::STATIC,
state_pool,
states...
>;
#else /* __cplusplus >= 201402L */
/**
* @brief Type alias representing a finite state machine with lazy state
* object allocation scheme.
*
* @tparam base_state The base class from which all state types must derive.
* @tparam state_pool Array of pointers to base state class objects.
* @tparam state_count Number of states present in the state machine.
*/
template <
typename base_state,
base_state* state_pool[] = nullptr,
std::size_t state_count = 0
>
using state_machine_lazy = state_machine<
base_state,
alloc_type::LAZY,
state_pool,
state_count
>;
/**
* @brief Type alias representing a finite state machine with internally
* managed preallocated state objects scheme.
*
* @tparam base_state The base class from which all state types must derive.
* @tparam state_pool Array of pointers to base state class objects.
* @tparam state_count Number of states present in the state machine.
*/
template <