aboutsummaryrefslogtreecommitdiffstats
path: root/system/doc/design_principles
diff options
context:
space:
mode:
authorRaimo Niskanen <[email protected]>2016-09-22 17:40:47 +0200
committerRaimo Niskanen <[email protected]>2016-09-30 09:51:44 +0200
commit04d40c5cd18aca449606c19608e8044f593ee99e (patch)
tree6a27617e6abb5ac2887fba60d2578410d064945f /system/doc/design_principles
parent1778a9e0c677134a6b71975168812bcfdc70c7aa (diff)
downloadotp-04d40c5cd18aca449606c19608e8044f593ee99e.tar.gz
otp-04d40c5cd18aca449606c19608e8044f593ee99e.tar.bz2
otp-04d40c5cd18aca449606c19608e8044f593ee99e.zip
Change state entry events into state enter calls
Diffstat (limited to 'system/doc/design_principles')
-rw-r--r--system/doc/design_principles/statem.xml194
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(&lt;&lt;"001">>),
+ code_lock:put_chars(&lt;&lt;"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},