Skip to content

Commit 62e7b88

Browse files
committed
Added support for transitions on state enter for HFSM
1 parent f9cf20d commit 62e7b88

File tree

4 files changed

+452
-37
lines changed

4 files changed

+452
-37
lines changed

include/etl/fsm.h

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -160,18 +160,6 @@ namespace etl
160160
}
161161
};
162162

163-
//***************************************************************************
164-
/// Exception for forbidden state changes.
165-
//***************************************************************************
166-
class fsm_state_composite_state_change_forbidden : public etl::fsm_exception
167-
{
168-
public:
169-
fsm_state_composite_state_change_forbidden(string_type file_name_, numeric_type line_number_)
170-
: etl::fsm_exception(ETL_ERROR_TEXT("fsm:change in composite state forbidden", ETL_FSM_FILE_ID"E"), file_name_, line_number_)
171-
{
172-
}
173-
};
174-
175163
//***************************************************************************
176164
/// Exception for message received but not started.
177165
//***************************************************************************
@@ -330,7 +318,6 @@ namespace etl
330318

331319
if (p_default_child == ETL_NULLPTR)
332320
{
333-
p_active_child = &state;
334321
p_default_child = &state;
335322
}
336323
}
@@ -481,7 +468,7 @@ namespace etl
481468
virtual void start(bool call_on_enter_state = true)
482469
{
483470
// Can only be started once.
484-
if (p_state == ETL_NULLPTR)
471+
if (!is_started())
485472
{
486473
p_state = state_list[0];
487474
ETL_ASSERT(p_state != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
@@ -589,7 +576,7 @@ namespace etl
589576
//*******************************************
590577
virtual void reset(bool call_on_exit_state = false)
591578
{
592-
if ((p_state != ETL_NULLPTR) && call_on_exit_state)
579+
if (is_started() && call_on_exit_state)
593580
{
594581
p_state->on_exit_state();
595582
}

include/etl/hfsm.h

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,32 @@ namespace etl
6060
void start(bool call_on_enter_state = true) ETL_OVERRIDE
6161
{
6262
// Can only be started once.
63-
if (p_state == ETL_NULLPTR)
63+
if (!is_started())
6464
{
65-
p_state = state_list[0];
66-
ETL_ASSERT(p_state != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
65+
etl::ifsm_state* p_first_state = state_list[0];
66+
ETL_ASSERT(p_first_state != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
67+
p_state = p_first_state;
6768

6869
if (call_on_enter_state)
6970
{
70-
etl::fsm_state_id_t next_state = do_enters(ETL_NULLPTR, p_state, true);
71+
do_enters_result result = do_enters(ETL_NULLPTR, p_first_state, true);
72+
73+
if (result.active_state_id != ifsm_state::No_State_Change)
74+
{
75+
// If the active_state_id is not No_State_Change, it means that an on_enter changed the target state.
76+
// Set the active state during that change as the present state before processing the state change.
77+
ETL_ASSERT(result.active_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception));
78+
p_state = state_list[result.active_state_id];
7179

72-
if (next_state != ifsm_state::No_State_Change)
80+
process_state_change(result.next_state_id);
81+
}
82+
else
7383
{
74-
p_state = state_list[next_state];
84+
if (have_changed_state(result.next_state_id))
85+
{
86+
ETL_ASSERT(result.next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception));
87+
p_state = state_list[result.next_state_id];
88+
}
7589
}
7690
}
7791
}
@@ -83,7 +97,7 @@ namespace etl
8397
//*******************************************
8498
virtual void reset(bool call_on_exit_state = false) ETL_OVERRIDE
8599
{
86-
if ((p_state != ETL_NULLPTR) && call_on_exit_state)
100+
if (is_started() && call_on_exit_state)
87101
{
88102
do_exits(ETL_NULLPTR, p_state);
89103
}
@@ -151,10 +165,21 @@ namespace etl
151165
return s;
152166
}
153167

168+
//*******************************************
169+
/// Result of a "do_enters()" call
170+
//*******************************************
171+
struct do_enters_result
172+
{
173+
// State which is presently being targeted as the next state
174+
etl::fsm_state_id_t next_state_id;
175+
// State which was active when the on_enter triggered a state change
176+
etl::fsm_state_id_t active_state_id;
177+
};
178+
154179
//*******************************************
155180
/// Entering the state.
156181
//*******************************************
157-
static etl::fsm_state_id_t do_enters(const etl::ifsm_state* p_root, etl::ifsm_state* p_target, bool activate_default_children)
182+
static do_enters_result do_enters(const etl::ifsm_state* p_root, etl::ifsm_state* p_target, bool activate_default_children)
158183
{
159184
ETL_ASSERT(p_target != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
160185

@@ -164,15 +189,26 @@ namespace etl
164189
if (p_target->p_parent != p_root)
165190
{
166191
// The parent we're calling shouldn't activate its defaults, or this state will be deactivated.
167-
do_enters(p_root, p_target->p_parent, false);
192+
do_enters_result result = do_enters(p_root, p_target->p_parent, false);
193+
194+
// Short circuit the do enters if the parent state decided that a different state should be entered.
195+
if (result.active_state_id != ifsm_state::No_State_Change)
196+
{
197+
return result;
198+
}
168199
}
169200

170201
// Set us as our parent's active child
171202
p_target->p_parent->p_active_child = p_target;
172203
}
173204

174205
etl::fsm_state_id_t next_state = p_target->on_enter_state();
175-
ETL_ASSERT(ifsm_state::No_State_Change == next_state, ETL_ERROR(etl::fsm_state_composite_state_change_forbidden));
206+
207+
// Short circuit the activation of any child states if the target state changed
208+
if (next_state != ifsm_state::No_State_Change)
209+
{
210+
return do_enters_result{next_state, p_target->get_state_id()};
211+
}
176212

177213
// Activate default child if we need to activate any initial states in an active composite state.
178214
if (activate_default_children)
@@ -182,13 +218,19 @@ namespace etl
182218
p_target = p_target->p_default_child;
183219
p_target->p_parent->p_active_child = p_target;
184220
next_state = p_target->on_enter_state();
185-
ETL_ASSERT(ifsm_state::No_State_Change == next_state, ETL_ERROR(etl::fsm_state_composite_state_change_forbidden));
221+
222+
// Short circuit the activation of any child states if the target state changed
223+
if (next_state != ifsm_state::No_State_Change)
224+
{
225+
return do_enters_result{next_state, p_target->get_state_id()};
226+
}
186227
}
187228

188229
next_state = p_target->get_state_id();
189230
}
190231

191-
return next_state;
232+
// Wrapping No_State_Change in a static_cast gets rid of the "undefined reference" error when compiling on C++11
233+
return do_enters_result{next_state, static_cast<fsm_state_id_t>(ifsm_state::No_State_Change)};
192234
}
193235

194236
//*******************************************
@@ -217,25 +259,48 @@ namespace etl
217259
//*******************************************
218260
etl::fsm_state_id_t process_state_change(etl::fsm_state_id_t next_state_id) ETL_OVERRIDE
219261
{
220-
if (have_changed_state(next_state_id))
262+
if (is_self_transition(next_state_id))
263+
{
264+
p_state->on_exit_state();
265+
next_state_id = p_state->on_enter_state();
266+
}
267+
268+
while (have_changed_state(next_state_id))
221269
{
222270
ETL_ASSERT_OR_RETURN_VALUE(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception), p_state->get_state_id());
223271

224272
etl::ifsm_state* p_next_state = state_list[next_state_id];
225273
etl::ifsm_state* p_root = common_ancestor(p_state, p_next_state);
226274

227275
do_exits(p_root, p_state);
228-
next_state_id = do_enters(p_root, p_next_state, true);
229276

230-
ETL_ASSERT_OR_RETURN_VALUE(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception), p_state->get_state_id());
231-
p_state = state_list[next_state_id];
232-
}
233-
else if (is_self_transition(next_state_id))
234-
{
235-
p_state->on_exit_state();
236-
p_state->on_enter_state();
237-
}
277+
do_enters_result result = do_enters(p_root, p_next_state, true);
278+
next_state_id = result.next_state_id;
238279

280+
if (result.active_state_id != ifsm_state::No_State_Change)
281+
{
282+
// If the active_state_id is not No_State_Change, it means that an on_enter changed the target state.
283+
// Set the state pointer as the active state to use it as the new origin for the transition to the
284+
// updated target state
285+
ETL_ASSERT(result.active_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception));
286+
p_state = state_list[result.active_state_id];
287+
ETL_ASSERT(result.next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception));
288+
p_next_state = state_list[result.next_state_id];
289+
}
290+
else if (result.next_state_id != ifsm_state::No_State_Change)
291+
{
292+
// If the next state is different, means that default children were activated.
293+
// Assign both p_state and p_next_state to get out of the loop.
294+
ETL_ASSERT(result.next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception));
295+
p_state = state_list[result.next_state_id];
296+
p_next_state = state_list[result.next_state_id];
297+
}
298+
else
299+
{
300+
p_state = p_next_state;
301+
}
302+
}
303+
239304
return p_state->get_state_id();
240305
}
241306
};

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ add_executable(etl_tests
194194
test_hash.cpp
195195
test_hfsm.cpp
196196
test_hfsm_recurse_to_inner_state_on_start.cpp
197+
test_hfsm_transition_on_enter.cpp
197198
test_histogram.cpp
198199
test_index_of_type.cpp
199200
test_indirect_vector.cpp

0 commit comments

Comments
 (0)