diff options
Diffstat (limited to 'system/doc/design_principles')
-rw-r--r-- | system/doc/design_principles/statem.xml | 194 |
1 files changed, 86 insertions, 108 deletions
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 565b0e5274..d2a9b23570 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -195,21 +195,19 @@ handle_event(EventType, EventContent, State, Data) -> <!-- =================================================================== --> <section> - <marker id="state_entry_events" /> - <title>State Entry Events</title> + <marker id="state_enter" /> + <title>State Enter Calls</title> <p> The <c>gen_statem</c> behavior can regardless of callback mode - automatically generate an - <seealso marker="stdlib:gen_statem#type-state_entry_mode"> - event whenever the state changes - </seealso> - so you can write state entry code + automatically call the state function + with special arguments whenever the state changes + so you can write state entry actions near the rest of the state transition rules. It typically looks like this: </p> <pre> StateName(enter, _OldState, Data) -> - ... code for state entry here ... + ... code for state entry actions here ... {keep_state, NewData}; StateName(EventType, EventContent, Data) -> ... code for actions here ... @@ -217,7 +215,7 @@ StateName(EventType, EventContent, Data) -> <p> Depending on how your state machine is specified, this can be a very useful feature, but if you use it - you will have to handle the state entry events in all states. + you will have to handle the state enter call in all states. </p> </section> @@ -751,12 +749,6 @@ stop() -> Generated by any regular process message sent to the <c>gen_statem</c> process. </item> - <tag><c>enter</c></tag> - <item> - Generated by a state transition with - <c>OldState =/= NewState</c> when running with - <seealso marker="#state_entry_events">state entry events</seealso>. - </item> <tag><c>timeout</c></tag> <item> Generated by state transition action @@ -972,63 +964,35 @@ do_unlock() -> <!-- =================================================================== --> <section> - <title>Self-Generated Events</title> - <p> - It can sometimes be beneficial to be able to generate events - to your own state machine. - This can be done with the state transition - <seealso marker="stdlib:gen_statem#type-action">action</seealso> - <c>{next_event,EventType,EventContent}</c>. - </p> + <title>State Entry Actions</title> <p> - You can generate events of any existing - <seealso marker="stdlib:gen_statem#type-action">type</seealso>, - but the <c>internal</c> type can only be generated through action - <c>next_event</c>. Hence, it cannot come from an external source, - so you can be certain that an <c>internal</c> event is an event - from your state machine to itself. + Say you have a state machine specification + that uses state entry actions. + Allthough you can code this using self-generated events + (described in the next section), especially if just + one or a few states has got state entry actions, + this is a perfect use case for the built in + <seealso marker="#state_enter">state enter calls</seealso>. </p> <p> - One example for this is to pre-process incoming data, for example - decrypting chunks or collecting characters up to a line break. - This could be modelled with a separate state machine that sends - the pre-processed events to the main state machine, or to decrease - overhead the small pre-processing state machine can be implemented - in the common state event handling of the main state machine - using a few state data variables and then send the pre-processed - events as internal events to the main state machine. - </p> - <p> - Another example of using self-generated events can be when you have - a state machine specification that uses state entry actions. - You can code that using a dedicated function - to do the state transition. But if you want that code to be - visible besides the other state logic, you can insert - an <c>internal</c> event that does the entry actions. - This has the same unfortunate consequence as using - state transition functions: everywhere you go to - the state, you must explicitly - insert the <c>internal</c> event - or use a state transition function. - This is something that can be forgotten, and if you find that - annoying please look at the next chapter. - </p> - <p> - The following is an implementation of entry actions - using <c>internal</c> events with content <c>enter</c> - using a helper function <c>enter/3</c> for state entry: + You return a list containing <c>state_enter</c> from your + <seealso marker="stdlib:gen_statem#Module:callback_mode/0"><c>callback_mode/0</c></seealso> + function and the <c>gen_statem</c> engine will call your + state function once with the arguments + <c>(enter, OldState, ...)</c> whenever the state changes. + Then you just need to handle these event-like calls in all states. </p> <code type="erl"><![CDATA[ ... init(Code) -> process_flag(trap_exit, true), Data = #{code => Code}, - enter(ok, locked, Data). + {ok, locked, Data}. callback_mode() -> - state_functions. + [state_functions,state_enter]. -locked(internal, enter, Data) -> +locked(enter, _OldState, Data) -> do_lock(), {keep_state,Data#{remaining => Code}}; locked( @@ -1036,79 +1000,94 @@ locked( #{code := Code, remaining := Remaining} = Data) -> case Remaining of [Digit] -> - enter(next_state, open, Data); + {next_state, open, Data}; ... -open(internal, enter, Data) -> +open(enter, _OldState, Data) -> Tref = erlang:start_timer(10000, self(), lock), do_unlock(), {keep_state,Data#{timer => Tref}}; open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) -> - enter(next_state, locked, Data); + {next_state, locked, Data}; ... - -enter(Tag, State, Data) -> - {Tag,State,Data,[{next_event,internal,enter}]}. ]]></code> </section> <!-- =================================================================== --> <section> - <title>Using State Entry Events</title> + <title>Self-Generated Events</title> <p> - Here is the same example as the previous but instead using - the built in - <seealso marker="#state_entry_events">state entry events</seealso>. + It can sometimes be beneficial to be able to generate events + to your own state machine. + This can be done with the state transition + <seealso marker="stdlib:gen_statem#type-action">action</seealso> + <c>{next_event,EventType,EventContent}</c>. </p> <p> - Since the state entry events are unconditionally inserted by - the <c>gen_statem</c> engine you can not forget to insert them - yourself and you will have to handle the state entry events - in every state. + You can generate events of any existing + <seealso marker="stdlib:gen_statem#type-action">type</seealso>, + but the <c>internal</c> type can only be generated through action + <c>next_event</c>. Hence, it cannot come from an external source, + so you can be certain that an <c>internal</c> event is an event + from your state machine to itself. </p> <p> - If you want state entry code in just a few states the previous - example may be more suitable, especially to only send internal - events when entering just those few states. - Note: additional discipline will be required. + One example for this is to pre-process incoming data, for example + decrypting chunks or collecting characters up to a line break. + Purists may argue that this should be modelled with a separate + state machine that sends pre-processed events + to the main state machine. + But to decrease overhead the small pre-processing state machine + can be implemented in the common state event handling + of the main state machine using a few state data variables + that then sends the pre-processed events as internal events + to the main state machine. </p> <p> - You can also in the previous example choose to generate - events looking just like the events you get from using - <seealso marker="#state_entry_events">state entry events</seealso>. - This may be confusing, or practical, - depending on your point of view. + The following example use an input model where you give the lock + characters with <c>put_chars(Chars)</c> and then call + <c>enter()</c> to finish the input. </p> <code type="erl"><![CDATA[ ... -init(Code) -> - process_flag(trap_exit, true), - Data = #{code => Code}, - {ok, locked, Data}. +-export(put_chars/1, enter/0). +... +put_chars(Chars) when is_binary(Chars) -> + gen_statem:call(?NAME, {chars,Chars}). -callback_mode() -> - [state_functions,state_entry_events]. +enter() -> + gen_statem:call(?NAME, enter). + +... locked(enter, _OldState, Data) -> do_lock(), - {keep_state,Data#{remaining => Code}}; -locked( - cast, {button,Digit}, - #{code := Code, remaining := Remaining} = Data) -> - case Remaining of - [Digit] -> - {next_state, open, Data}; + {keep_state,Data#{remaining => Code, buf => []}}; ... -open(enter, _OldState, Data) -> - Tref = erlang:start_timer(10000, self(), lock), - do_unlock(), - {keep_state,Data#{timer => Tref}}; -open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) -> - {next_state, locked, Data}; +handle_event({call,From}, {chars,Chars}, #{buf := Buf} = Data) -> + {keep_state, Data#{buf := [Chars|Buf], + [{reply,From,ok}]}; +handle_event({call,From}, enter, #{buf := Buf} = Data) -> + Chars = unicode:characters_to_binary(lists:reverse(Buf)), + try binary_to_integer(Chars) of + Digit -> + {keep_state, Data#{buf := []}, + [{reply,From,ok}, + {next_event,internal,{button,Chars}}]} + catch + error:badarg -> + {keep_state, Data#{buf := []}, + [{reply,From,{error,not_an_integer}}]} + end; ... ]]></code> + <p> + If you start this program with <c>code_lock:start([17])</c> + you can unlock with <c>code_lock:put_chars(<<"001">>), + code_lock:put_chars(<<"7">>), code_lock:enter()</c>. + </p> </section> <!-- =================================================================== --> @@ -1117,7 +1096,7 @@ open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) -> <title>Example Revisited</title> <p> This section includes the example after all mentioned modifications - and some more using the entry actions, + and some more using state enter calls, which deserves a new state diagram: </p> <image file="../design_principles/code_lock_2.png"> @@ -1163,7 +1142,7 @@ init(Code) -> {ok, locked, Data}. callback_mode() -> - [state_functions,state_entry_events]. + [state_functions,state_enter]. locked(enter, _OldState, #{code := Code} = Data) -> do_lock(), @@ -1215,8 +1194,7 @@ code_change(_Vsn, State, Data, _Extra) -> This section describes what to change in the example to use one <c>handle_event/4</c> function. The previously used approach to first branch depending on event - does not work that well here because of - the state entry events, + does not work that well here because of the state enter calls, so this example first branches depending on state: </p> <code type="erl"><![CDATA[ @@ -1225,7 +1203,7 @@ code_change(_Vsn, State, Data, _Extra) -> ... callback_mode() -> - [handle_event_function,state_entry_events]. + [handle_event_function,state_enter]. %% State: locked handle_event(enter, _OldState, locked, #{code := Code} = Data) -> @@ -1413,7 +1391,7 @@ init({Code,LockButton}) -> {ok, {locked,LockButton}, Data}. callback_mode() -> - [handle_event_function,state_entry_events]. + [handle_event_function,state_enter]. handle_event( {call,From}, {set_lock_button,NewLockButton}, |