aboutsummaryrefslogtreecommitdiffstats
path: root/system/doc
diff options
context:
space:
mode:
Diffstat (limited to 'system/doc')
-rw-r--r--system/doc/design_principles/statem.xml152
1 files changed, 113 insertions, 39 deletions
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 57e47431b8..8090016b54 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -109,7 +109,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</p>
<pre>
StateName(EventType, EventContent, Data) ->
- .. code for actions here ...
+ ... code for actions here ...
{next_state, NewStateName, NewData}.</pre>
</item>
<item>
@@ -120,7 +120,7 @@ StateName(EventType, EventContent, Data) ->
</p>
<pre>
handle_event(EventType, EventContent, State, Data) ->
- .. code for actions here ...
+ ... code for actions here ...
{next_state, NewState, NewData}</pre>
</item>
</list>
@@ -195,10 +195,39 @@ handle_event(EventType, EventContent, State, Data) ->
<!-- =================================================================== -->
<section>
+ <marker id="state_entry_events" />
+ <title>State Entry Events</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
+ near the rest of the state transition rules.
+ It typically looks like this:
+ </p>
+ <pre>
+StateName(enter, _OldState, Data) ->
+ ... code for state entry here ...
+ {keep_state, NewData};
+StateName(EventType, EventContent, Data) ->
+ ... code for actions here ...
+ {next_state, NewStateName, NewData}.</pre>
+ <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.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
<title>Example</title>
<p>
This example starts off as equivalent to the example in section
- <seealso marker="fsm"><c>gen_fsm</c> Behavior</seealso>.
+ <seealso marker="fsm"><c>gen_fsm</c>-Behavior</seealso>.
In later sections, additions and tweaks are made
using features in <c>gen_statem</c> that <c>gen_fsm</c> does not have.
The end of this chapter provides the example again
@@ -722,6 +751,12 @@ 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
@@ -981,7 +1016,7 @@ init(Code) ->
callback_mode() ->
state_functions.
-locked(internal, enter, _Data) ->
+locked(internal, enter, Data) ->
do_lock(),
{keep_state,Data#{remaining => Code}};
locked(
@@ -992,7 +1027,7 @@ locked(
enter(next_state, open, Data);
...
-open(internal, enter, _Data) ->
+open(internal, enter, Data) ->
Tref = erlang:start_timer(10000, self(), lock),
do_unlock(),
{keep_state,Data#{timer => Tref}};
@@ -1008,6 +1043,49 @@ enter(Tag, State, Data) ->
<!-- =================================================================== -->
<section>
+ <title>Using State Entry 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>.
+ You will have to handle the state entry events in every state.
+ If you want state entry code in just a few states the previous
+ example may be more suitable.
+ </p>
+ <code type="erl"><![CDATA[
+...
+init(Code) ->
+ process_flag(trap_exit, true),
+ Data = #{code => Code},
+ {ok, locked, Data}.
+
+callback_mode() ->
+ [state_functions,state_entry_events].
+
+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};
+...
+
+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};
+...
+ ]]></code>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
<title>Example Revisited</title>
<p>
This section includes the example after all mentioned modifications
@@ -1054,12 +1132,12 @@ code_length() ->
init(Code) ->
process_flag(trap_exit, true),
Data = #{code => Code},
- enter(ok, locked, Data).
+ {ok, locked, Data}.
callback_mode() ->
- state_functions.
+ [state_functions,state_entry_events].
-locked(internal, enter, #{code := Code} = Data) ->
+locked(enter, _OldState, #{code := Code} = Data) ->
do_lock(),
{keep_state,Data#{remaining => Code}};
locked(
@@ -1067,7 +1145,7 @@ locked(
#{code := Code, remaining := Remaining} = Data) ->
case Remaining of
[Digit] -> % Complete
- enter(next_state, open, Data);
+ {next_state, open, Data};
[Digit|Rest] -> % Incomplete
{keep_state,Data#{remaining := Rest}};
[_|_] -> % Wrong
@@ -1076,12 +1154,12 @@ locked(
locked(EventType, EventContent, Data) ->
handle_event(EventType, EventContent, 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};
open(cast, {button,_}, _) ->
{keep_state_and_data,[postpone]};
open(EventType, EventContent, Data) ->
@@ -1089,8 +1167,6 @@ open(EventType, EventContent, Data) ->
handle_event({call,From}, code_length, #{code := Code}) ->
{keep_state_and_data,[{reply,From,length(Code)}]}.
-enter(Tag, State, Data) ->
- {Tag,State,Data,[{next_event,internal,enter}]}.
do_lock() ->
io:format("Locked~n", []).
@@ -1111,8 +1187,9 @@ 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 generated
- entry actions, so this example first branches depending on state:
+ does not work that well here because of
+ the state entry events,
+ so this example first branches depending on state:
</p>
<code type="erl"><![CDATA[
...
@@ -1120,10 +1197,10 @@ code_change(_Vsn, State, Data, _Extra) ->
...
callback_mode() ->
- handle_event_function.
+ [handle_event_function,state_entry_events].
%% State: locked
-handle_event(internal, enter, locked, #{code := Code} = Data) ->
+handle_event(enter, _OldState, locked, #{code := Code} = Data) ->
do_lock(),
{keep_state,Data#{remaining => Code}};
handle_event(
@@ -1131,7 +1208,7 @@ handle_event(
#{code := Code, remaining := Remaining} = Data) ->
case Remaining of
[Digit] -> % Complete
- enter(next_state, open, Data);
+ {next_state, open, Data};
[Digit|Rest] -> % Incomplete
{keep_state,Data#{remaining := Rest}};
[_|_] -> % Wrong
@@ -1139,12 +1216,12 @@ handle_event(
end;
%%
%% State: open
-handle_event(internal, enter, open, Data) ->
+handle_event(enter, _OldState, open, Data) ->
Tref = erlang:start_timer(10000, self(), lock),
do_unlock(),
{keep_state,Data#{timer => Tref}};
handle_event(info, {timeout,Tref,lock}, open, #{timer := Tref} = Data) ->
- enter(next_state, locked, Data);
+ {next_state, locked, Data};
handle_event(cast, {button,_}, open, _) ->
{keep_state_and_data,[postpone]};
%%
@@ -1305,10 +1382,10 @@ set_lock_button(LockButton) ->
init({Code,LockButton}) ->
process_flag(trap_exit, true),
Data = #{code => Code, remaining => undefined, timer => undefined},
- enter(ok, {locked,LockButton}, Data, []).
+ {ok, {locked,LockButton}, Data}.
callback_mode() ->
- handle_event_function.
+ [handle_event_function,state_entry_events].
handle_event(
{call,From}, {set_lock_button,NewLockButton},
@@ -1320,55 +1397,52 @@ handle_event(
{_StateName,_LockButton}, #{code := Code}) ->
{keep_state_and_data,
[{reply,From,length(Code)}]};
+%%
+%% State: locked
handle_event(
EventType, EventContent,
{locked,LockButton}, #{code := Code, remaining := Remaining} = Data) ->
case {EventType,EventContent} of
- {internal,enter} ->
+ {enter,_OldState} ->
do_lock(),
{keep_state,Data#{remaining := Code}};
{{call,From},{button,Digit}} ->
case Remaining of
[Digit] -> % Complete
- next_state(
- {open,LockButton}, Data,
- [{reply,From,ok}]);
+ {next_state, {open,LockButton}, Data,
+ [{reply,From,ok}]};
[Digit|Rest] -> % Incomplete
- {keep_state,Data#{remaining := Rest},
+ {keep_state, Data#{remaining := Rest},
[{reply,From,ok}]};
[_|_] -> % Wrong
- {keep_state,Data#{remaining := Code},
+ {keep_state, Data#{remaining := Code},
[{reply,From,ok}]}
end
end;
+%%
+%% State: open
handle_event(
EventType, EventContent,
{open,LockButton}, #{timer := Timer} = Data) ->
case {EventType,EventContent} of
- {internal,enter} ->
+ {enter,_OldState} ->
Tref = erlang:start_timer(10000, self(), lock),
do_unlock(),
{keep_state,Data#{timer := Tref}};
{info,{timeout,Timer,lock}} ->
- next_state({locked,LockButton}, Data, []);
+ {next_state, {locked,LockButton}, Data};
{{call,From},{button,Digit}} ->
if
Digit =:= LockButton ->
erlang:cancel_timer(Timer),
- next_state(
- {locked,LockButton}, Data,
- [{reply,From,locked}]);
+ {next_state, {locked,LockButton}, Data,
+ [{reply,From,locked}]);
true ->
{keep_state_and_data,
[postpone]}
end
end.
-next_state(State, Data, Actions) ->
- enter(next_state, State, Data, Actions).
-enter(Tag, State, Data, Actions) ->
- {Tag,State,Data,[{next_event,internal,enter}|Actions]}.
-
do_lock() ->
io:format("Locked~n", []).
do_unlock() ->
@@ -1434,7 +1508,7 @@ handle_event(
EventType, EventContent,
{open,LockButton}, #{timer := Timer} = Data) ->
case {EventType,EventContent} of
- {internal,enter} ->
+ {enter,_OldState} ->
Tref = erlang:start_timer(10000, self(), lock),
do_unlock(),
{keep_state,Data#{timer := Tref},[hibernate]};