aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaimo Niskanen <[email protected]>2018-04-18 16:26:58 +0200
committerRaimo Niskanen <[email protected]>2018-04-18 16:26:58 +0200
commit728bc036aa72a83080e933722f8ad409ede69f70 (patch)
tree1f8a411eed66919a2ff366a59bb6e3d22006aed7
parentbf573371185de2c52e8b6ff46bff30f6d7d9f3c4 (diff)
downloadotp-728bc036aa72a83080e933722f8ad409ede69f70.tar.gz
otp-728bc036aa72a83080e933722f8ad409ede69f70.tar.bz2
otp-728bc036aa72a83080e933722f8ad409ede69f70.zip
Fix after feedback
-rw-r--r--system/doc/design_principles/statem.xml121
1 files changed, 76 insertions, 45 deletions
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index ed6338e306..80ee9c992f 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -62,7 +62,8 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<p>These relations are interpreted as follows:
if we are in state <c>S</c> and event <c>E</c> occurs, we
are to perform actions <c>A</c> and make a transition to
- state <c>S'</c>. Notice that <c>S'</c> can be equal to <c>S</c>.
+ state <c>S'</c>. Notice that <c>S'</c> can be equal to <c>S</c>
+ and that <c>A</c> can be empty.
</p>
<p>
As <c>A</c> and <c>S'</c> depend only on
@@ -108,6 +109,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<seealso marker="#Inserted Events">
Inserted Events
</seealso>
+ that is: events from the state machine to itself
(in particular purely internal events)
</item>
<item>
@@ -115,7 +117,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
State Enter Calls
</seealso>
(callback on state entry co-located with the rest
- of the state callback code)
+ of each state's callback code)
</item>
<item>
Easy-to-use timeouts
@@ -185,7 +187,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</tag>
<item>
<p>
- Events are handled by one callback functions per state.
+ Events are handled by one callback function per state.
</p>
</item>
<tag>
@@ -209,7 +211,8 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
that describes the event handling callback function(s).
</p>
<p>
- The callback mode is selected by implementing a callback function
+ The callback mode is selected by implementing
+ a mandatory callback function
<seealso marker="stdlib:gen_statem#Module:callback_mode/0">
<c>Module:callback_mode()</c>
</seealso>
@@ -233,16 +236,15 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
The short version: choose <c>state_functions</c> -
it is the one most like <c>gen_fsm</c>.
But if you do not want the restriction that the state
- must be an atom, or if having to write an event handler function
- per state is not as you like it; please read on...
+ must be an atom, or if you do not want to write
+ one event handler function per state; please read on...
</p>
<p>
The two
<seealso marker="#Callback Modes">Callback Modes</seealso>
- give different possibilities
- and restrictions, but one goal remains:
- you want to handle all possible combinations of
- events and states.
+ give different possibilities and restrictions,
+ with one common goal:
+ to handle all possible combinations of events and states.
</p>
<p>
This can be done, for example, by focusing on one state at the time
@@ -386,7 +388,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<item>
<p>
Same as the <c>next_state</c> values with
- <c>NextState =:= State</c>, that is no state change.
+ <c>NextState =:= State</c>, that is, no state change.
</p>
</item>
<tag>
@@ -396,7 +398,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<item>
<p>
Same as the <c>keep_state</c> values with
- <c>NextData =:= Data</c>, that is no change in server data.
+ <c>NextData =:= Data</c>, that is, no change in server data.
</p>
</item>
<tag>
@@ -412,7 +414,8 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<seealso marker="#State Enter Calls">
State Enter Calls
</seealso>
- are enabled, repeat that call.
+ are enabled, repeat the state enter call
+ as if this state was entered again.
</p>
</item>
<tag>
@@ -752,7 +755,7 @@ StateName(EventType, EventContent, Data) ->
an event is generated.
The pressed buttons are collected, up to the number of buttons
in the correct code.
- If correct, the door is unlocked for 10 seconds (10,000 milliseconds).
+ If correct, the door is unlocked for 10 seconds.
If not correct, we wait for a new button to be pressed.
</p>
<!-- The image is edited with dia in a .dia file,
@@ -803,7 +806,7 @@ locked(
NewButtons =:= Code -> % Correct
do_unlock(),
{next_state, open, Data#{buttons := []},
- [{state_timeout,10000,lock}]};
+ [{state_timeout,10000,lock}]}; % Time in milliseconds
true -> % Incomplete | Incorrect
{next_state, locked, Data#{buttons := NewButtons}}
end.
@@ -990,7 +993,7 @@ locked(
NewButtons =:= Code -> % Correct
do_unlock(),
{next_state, open, Data#{buttons := []},
- [{state_timeout,10000,lock}]};
+ [{state_timeout,10000,lock}]}; % Time in milliseconds
true -> % Incomplete | Incorrect
{next_state, locked, Data#{buttons := NewButtons}}
end.
@@ -1032,7 +1035,7 @@ open(cast, {button,_}, Data) ->
</p>
<code type="erl"><![CDATA[
{next_state, open, Data#{buttons := []},
- [{state_timeout,10000,lock}]};
+ [{state_timeout,10000,lock}]}; % Time in milliseconds
]]></code>
<p>
10,000 is a time-out value in milliseconds.
@@ -1068,8 +1071,7 @@ open(state_timeout, lock, Data) ->
</p>
<p>
Consider a <c>code_length/0</c> function that returns
- the length of the correct code
- (that should not be too sensitive to reveal).
+ the length of the correct code.
We dispatch all events that are not state-specific
to the common function <c>handle_common/3</c>:
</p>
@@ -1092,7 +1094,8 @@ open(EventType, EventContent, Data) ->
handle_common(EventType, EventContent, Data).
handle_common({call,From}, code_length, #{code := Code} = Data) ->
- {keep_state, Data, [{reply,From,length(Code)}]}.
+ {keep_state, Data,
+ [{reply,From,length(Code)}]}.
]]></code>
<p>
@@ -1111,7 +1114,8 @@ code_length() ->
?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
%%
handle_common({call,From}, code_length, #{code := Code} = Data) ->
- {keep_state, Data, [{reply,From,length(Code)}]}.
+ {keep_state, Data,
+ [{reply,From,length(Code)}]}.
...
locked(...) -> ... ;
@@ -1148,7 +1152,11 @@ open(...) -> ... ;
<marker id="One Event Handler" />
<title>One Event Handler</title>
<p>
- If mode <c>handle_event_function</c> is used,
+ If
+ <seealso marker="#Callback Modes">
+ Callback Mode
+ </seealso>
+ <c>handle_event_function</c> is used,
all events are handled in
<seealso marker="stdlib:gen_statem#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>
and we can (but do not have to) use an event-centered approach
@@ -1178,7 +1186,7 @@ handle_event(cast, {button,Digit}, State, #{code := Code} = Data) ->
NewButtons =:= Code -> % Correct
do_unlock(),
{next_state, open, Data#{buttons := []},
- [{state_timeout,10000,lock}]};
+ [{state_timeout,10000,lock}]}; % Time in milliseconds
true -> % Incomplete | Incorrect
{keep_state, Data#{buttons := NewButtons}}
end;
@@ -1187,7 +1195,11 @@ handle_event(cast, {button,Digit}, State, #{code := Code} = Data) ->
end;
handle_event(state_timeout, lock, open, Data) ->
do_lock(),
- {next_state, locked, Data}.
+ {next_state, locked, Data};
+handle_event(
+ {call,From}, code_length, _State, #{code := Code} = Data) ->
+ {keep_state, Data,
+ [{reply,From,length(Code)}]}.
...
]]></code>
@@ -1298,7 +1310,7 @@ locked(
...
true -> % Incomplete | Incorrect
{next_state, locked, Data#{buttons := NewButtons},
- 30000}
+ 30000} % Time in milliseconds
...
]]></code>
<p>
@@ -1359,7 +1371,7 @@ locked(
NewButtons =:= Code -> % Correct
do_unlock(),
{next_state, open, Data#{buttons := []},
- [{{timeout,open},10000,lock}]};
+ [{{timeout,open},10000,lock}]}; % Time in milliseconds
...
open({timeout,open}, lock, Data) ->
@@ -1415,7 +1427,9 @@ locked(
if
NewButtons =:= Code -> % Correct
do_unlock(),
- Tref = erlang:start_timer(10000, self(), lock),
+ Tref =
+ erlang:start_timer(
+ 10000, self(), lock), % Time in milliseconds
{next_state, open, Data#{buttons := [], timer => Tref}};
...
@@ -1570,7 +1584,7 @@ locked(Code, Length, Buttons) ->
<code type="erl"><![CDATA[
open(Code, Length) ->
receive
- after 10000 ->
+ after 10000 -> % Time in milliseconds
do_lock(),
locked(Code, Length, [])
end.
@@ -1658,7 +1672,8 @@ locked(
open(enter, _OldState, _Data) ->
do_unlock(),
- {keep_state_and_data, [{state_timeout,10000,lock}]};
+ {keep_state_and_data,
+ [{state_timeout,10000,lock}]}; % Time in milliseconds
open(state_timeout, lock, Data) ->
{next_state, locked, Data};
...
@@ -1715,18 +1730,29 @@ open(state_timeout, lock, Data) ->
to synchronize the state machines.
</p>
<p>
- The following example uses an input model where the buttons
- generate up/down events and the lock responds to an up
- event after the corresponding down event.
+ A variant of this is to use a
+ <seealso marker="#Complex State">
+ Complex State
+ </seealso>
+ with
+ <seealso marker="#One Event Handler">One Event Handler</seealso>.
+ The state is then modeled with for example a tuple
+ <c>{MainFSMState,SubFSMState}</c>.
+ </p>
+ <p>
+ To illustrate this we make up an example where the buttons
+ instead generate down and up (press and release) events,
+ and the lock responds to an up event only after
+ the corresponding down event.
</p>
<code type="erl"><![CDATA[
...
-export(down/1, up/1).
...
-down(button) ->
+down(Button) ->
gen_statem:cast(?NAME, {down,Button}).
-up(button) ->
+up(Button) ->
gen_statem:cast(?NAME, {up,Button}).
...
@@ -1832,12 +1858,13 @@ handle_common(cast, {up,Button}, Data) ->
case Data of
#{button := Button} ->
{keep_state, maps:remove(button, Data),
- [{next_event,internal,{button,Data}}]};
+ [{next_event,internal,{button,Button}}]};
#{} ->
keep_state_and_data
end;
handle_common({call,From}, code_length, #{code := Code}) ->
- {keep_state_and_data, [{reply,From,length(Code)}]}.
+ {keep_state_and_data,
+ [{reply,From,length(Code)}]}.
]]></code>
<code type="erl"><![CDATA[
locked(enter, _OldState, Data) ->
@@ -1861,14 +1888,15 @@ locked(
{next_state, open, Data};
true -> % Incomplete | Incorrect
{keep_state, Data#{buttons := NewButtons},
- [{state_timeout,30000,button}]}
+ [{state_timeout,30000,button}]} % Time in milliseconds
end;
?HANDLE_COMMON.
]]></code>
<code type="erl"><![CDATA[
open(enter, _OldState, _Data) ->
do_unlock(),
- {keep_state_and_data, [{state_timeout,10000,lock}]};
+ {keep_state_and_data,
+ [{state_timeout,10000,lock}]}; % Time in milliseconds
open(state_timeout, lock, Data) ->
{next_state, locked, Data};
open(internal, {button,_}, _) ->
@@ -1927,7 +1955,7 @@ handle_event(
{next_state, open, Data};
true -> % Incomplete | Incorrect
{keep_state, Data#{buttons := NewButtons},
- [{state_timeout,30000,button}]}
+ [{state_timeout,30000,button}]} % Time in milliseconds
end;
]]></code>
<code type="erl"><![CDATA[
@@ -1935,7 +1963,8 @@ handle_event(
%% State: open
handle_event(enter, _OldState, open, _Data) ->
do_unlock(),
- {keep_state_and_data, [{state_timeout,10000,lock}]};
+ {keep_state_and_data,
+ [{state_timeout,10000,lock}]}; % Time in milliseconds
handle_event(state_timeout, lock, open, Data) ->
{next_state, locked, Data};
handle_event(internal, {button,_}, open, _) ->
@@ -1949,12 +1978,14 @@ handle_event(cast, {up,Button}, _State, Data) ->
case Data of
#{button := Button} ->
{keep_state, maps:remove(button, Data),
- [{state_timeout,30000,button}]};
+ [{next_event,internal,{button,Button}},
+ {state_timeout,30000,button}]}; % Time in milliseconds
#{} ->
keep_state_and_data
end;
handle_event({call,From}, code_length, _State, #{length := Length}) ->
- {keep_state_and_data, [{reply,From,Length}]}.
+ {keep_state_and_data,
+ [{reply,From,Length}]}.
]]></code>
</section>
<p>
@@ -2136,7 +2167,7 @@ handle_event(
{next_state, {open,LockButton}, Data};
true -> % Incomplete | Incorrect
{keep_state, Data#{buttons := NewButtons},
- [{state_timeout,30000,button}]}
+ [{state_timeout,30000,button}]} % Time in milliseconds
end;
]]></code>
<code type="erl"><![CDATA[
@@ -2145,7 +2176,7 @@ handle_event(
handle_event(enter, _OldState, {open,_}, _Data) ->
do_unlock(),
{keep_state_and_data,
- [{state_timeout,10000,lock}]};
+ [{state_timeout,10000,lock}]}; % Time in milliseconds
handle_event(state_timeout, lock, {open,_}, Data) ->
{next_state, locked, Data};
handle_event(cast, {button,LockButton}, {open,LockButton}, Data) ->
@@ -2208,7 +2239,7 @@ terminate(_Reason, State, _Data) ->
handle_event(enter, _OldState, {open,_}, _Data) ->
do_unlock(),
{keep_state_and_data,
- [{state_timeout,10000,lock},
+ [{state_timeout,10000,lock}, % Time in milliseconds
hibernate]};
...
]]></code>