@@ -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 };
0 commit comments