aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaimo Niskanen <[email protected]>2016-09-13 14:00:04 +0200
committerRaimo Niskanen <[email protected]>2016-09-16 10:50:25 +0200
commit6ee0aefd8a0ea9c165211c42d5244182b5aa9210 (patch)
tree098e1dab48d14ce3c9af0dcdae8a71cb97025437
parentf986565050ac30075ef3c0a451bf6dad91c7c446 (diff)
downloadotp-6ee0aefd8a0ea9c165211c42d5244182b5aa9210.tar.gz
otp-6ee0aefd8a0ea9c165211c42d5244182b5aa9210.tar.bz2
otp-6ee0aefd8a0ea9c165211c42d5244182b5aa9210.zip
Implement state entry events
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml82
-rw-r--r--lib/stdlib/src/gen_statem.erl208
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl66
-rw-r--r--system/doc/design_principles/statem.xml152
4 files changed, 400 insertions, 108 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 17f1526a21..a4c5438a08 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -70,6 +70,7 @@
<item>The state can be any term.</item>
<item>Events can be postponed.</item>
<item>Events can be self-generated.</item>
+ <item>Automatic state entry events can be generated.</item>
<item>A reply can be sent from a later state.</item>
<item>There can be multiple <c>sys</c> traceable replies.</item>
</list>
@@ -193,6 +194,12 @@ erlang:'!' -----> Module:StateName/3
<seealso marker="gen_fsm"><c>gen_fsm</c></seealso>
to force processing an inserted event before others.
</p>
+ <p>
+ The <c>gen_statem</c> engine can automatically insert
+ a special event whenever a new state is entered; see
+ <seealso marker="#type-state_entry_mode"><c>state_entry_mode()</c></seealso>.
+ This makes it easy to handle code common to all state entries.
+ </p>
<note>
<p>If you in <c>gen_statem</c>, for example, postpone
an event in one state and then call another state function
@@ -515,7 +522,7 @@ handle_event(_, _, State, Data) ->
Type <c>info</c> originates from regular process messages sent
to the <c>gen_statem</c>. Also, the state machine
implementation can generate events of types
- <c>timeout</c> and <c>internal</c> to itself.
+ <c>timeout</c>, <c>enter</c> and <c>internal</c> to itself.
</p>
</desc>
</datatype>
@@ -551,6 +558,34 @@ handle_event(_, _, State, Data) ->
</desc>
</datatype>
<datatype>
+ <name name="state_entry_mode"/>
+ <desc>
+ <p>
+ The <em>state entry mode</em> is selected when starting the
+ <c>gen_statem</c> and after code change
+ using the return value from
+ <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>.
+ </p>
+ <p>
+ If
+ <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
+ returns a list containing <c>state_entry_events</c>,
+ the <c>gen_statem</c> engine will, at every state change,
+ insert an event of type
+ <seealso marker="#type-event_type">enter</seealso>
+ with content <c>OldState</c>. This event will be inserted
+ before all other events such as those generated by
+ <seealso marker="#type-action"><c>action()</c></seealso>
+ <c>next_event</c>.
+ </p>
+ <p>
+ If
+ <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
+ does not return such a list, no state entry events are inserted.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="transition_option"/>
<desc>
<p>
@@ -591,6 +626,16 @@ handle_event(_, _, State, Data) ->
</p>
</item>
<item>
+ <p>
+ If the state changes or is the initial state, and the
+ <seealso marker="#type-state_entry_mode"><em>state entry mode</em></seealso>
+ is <c>state_entry_events</c>, an event of type
+ <seealso marker="#type-event_type">enter</seealso>
+ with content <c>OldState</c> is inserted
+ to be processed before all other events including those above.
+ </p>
+ </item>
+ <item>
<p>
If an
<seealso marker="#type-event_timeout"><c>event_timeout()</c></seealso>
@@ -1288,7 +1333,9 @@ handle_event(_, _, State, Data) ->
<type>
<v>
CallbackMode =
- <seealso marker="#type-callback_mode">callback_mode()</seealso>
+ <seealso marker="#type-callback_mode">callback_mode()</seealso> |
+ [ <seealso marker="#type-callback_mode">callback_mode()</seealso>
+ | state_entry_events ]
</v>
</type>
<desc>
@@ -1313,12 +1360,35 @@ handle_event(_, _, State, Data) ->
<seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso>
returns.
</p>
+ <p>
+ The <c>CallbackMode</c> is either just
+ <seealso marker="#type-callback_mode">callback_mode()</seealso>
+ or a list containing
+ <seealso marker="#type-callback_mode">callback_mode()</seealso>
+ and possibly the atom
+ <seealso marker="#type-state_entry_mode"><c>state_entry_events</c></seealso>.
+ </p>
+ <p>
+ If the atom <c>state_entry_events</c> is present in the list,
+ the <c>gen_statem</c> engine will, at every state change,
+ insert an event of type
+ <seealso marker="#type-event_type">enter</seealso>
+ with content <c>OldState</c>. This event will be inserted
+ before all other events such as those generated by
+ <seealso marker="#type-action"><c>action()</c></seealso>
+ <c>next_event</c>.
+ </p>
+ <p>
+ No state entry event will be inserted after a
+ <seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso>
+ since transforming the state to a newer version is regarded
+ as staying in the same state even if the newer version state
+ should have a different name.
+ </p>
<note>
<p>
- If this function's body does not consist of solely one of two
- possible
- <seealso marker="#type-callback_mode">atoms</seealso>
- the callback module is doing something strange.
+ If this function's body does not return an inline constant
+ value the callback module is doing something strange.
</p>
</note>
</desc>
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 46c0e92a9b..7f437404ed 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -53,7 +53,7 @@
action/0]).
%% Fix problem for doc build
--export_type([transition_option/0]).
+-export_type([state_entry_mode/0,transition_option/0]).
%%%==========================================================================
%%% Interface functions.
@@ -72,9 +72,10 @@
-type event_type() ::
{'call',From :: from()} | 'cast' |
- 'info' | 'timeout' | 'internal'.
+ 'info' | 'timeout' | 'enter' | 'internal'.
-type callback_mode() :: 'state_functions' | 'handle_event_function'.
+-type state_entry_mode() :: 'state_entry_events'.
-type transition_option() ::
postpone() | hibernate() | event_timeout().
@@ -183,7 +184,9 @@
%%
%% It is called once after init/0 and code_change/4 but before
%% the first state callback StateName/3 or handle_event/4.
--callback callback_mode() -> callback_mode().
+-callback callback_mode() ->
+ callback_mode() |
+ [callback_mode() | state_entry_mode()].
%% Example state callback for StateName = 'state_name'
%% when callback_mode() =:= state_functions.
@@ -556,19 +559,27 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) ->
end,
S = #{
callback_mode => undefined,
+ state_entry_events => false,
module => Module,
name => Name,
- %% All fields below will be replaced according to the arguments to
+ %% The rest of the fields are set from to the arguments to
%% loop_event_actions/10 when it finally loops back to loop/3
- state => State,
- data => Data,
- postponed => P,
- hibernate => false,
- timer => undefined},
+ %% in loop_events_done/8
+ %%
+ %% Marker for initial state, cleared immediately when used
+ init_state => true
+ },
NewDebug = sys_debug(Debug, S, State, {enter,Event,State}),
- loop_event_actions(
- Parent, NewDebug, S, Events,
- State, Data, P, Event, State, NewActions).
+ case call_callback_mode(S) of
+ {ok,NewS} ->
+ loop_event_actions(
+ Parent, NewDebug, NewS, Events,
+ State, Data, P, Event, State, NewActions);
+ {Class,Reason,Stacktrace} ->
+ terminate(
+ Class, Reason, Stacktrace,
+ NewDebug, S, [Event|Events], State, Data, P)
+ end.
%%%==========================================================================
%%% gen callbacks
@@ -866,13 +877,93 @@ loop_events(
loop_events_done(Parent, Debug, S, Timer, State, Data, P, Hibernate) ->
NewS =
S#{
- state := State,
- data := Data,
- postponed := P,
- hibernate := Hibernate,
- timer := Timer},
+ state => State,
+ data => Data,
+ postponed => P,
+ hibernate => Hibernate,
+ timer => Timer},
loop(Parent, Debug, NewS).
+
+
+parse_callback_mode([], CBMode, SEntry) ->
+ {CBMode,SEntry};
+parse_callback_mode([H|T], CBMode, SEntry) ->
+ case callback_mode(H) of
+ true ->
+ parse_callback_mode(T, H, SEntry);
+ false ->
+ case H of
+ state_entry_events ->
+ parse_callback_mode(T, CBMode, true);
+ _ ->
+ {undefined,SEntry}
+ end
+ end;
+parse_callback_mode(_, _CBMode, SEntry) ->
+ {undefined,SEntry}.
+
+call_callback_mode(S, CallbackMode) ->
+ case
+ parse_callback_mode(
+ if
+ is_atom(CallbackMode) ->
+ [CallbackMode];
+ true ->
+ CallbackMode
+ end, undefined, false)
+ of
+ {undefined,_} ->
+ {error,
+ {bad_return_from_callback_mode,CallbackMode},
+ ?STACKTRACE()};
+ {CBMode,SEntry} ->
+ {ok,
+ S#{
+ callback_mode := CBMode,
+ state_entry_events := SEntry}}
+ end.
+
+call_callback_mode(#{module := Module} = S) ->
+ try Module:callback_mode() of
+ CallbackMode ->
+ call_callback_mode(S, CallbackMode)
+ catch
+ CallbackMode ->
+ call_callback_mode(S, CallbackMode);
+ error:undef ->
+ %% Process undef to check for the simple mistake
+ %% of calling a nonexistent state function
+ %% to make the undef more precise
+ case erlang:get_stacktrace() of
+ [{Module,callback_mode,[]=Args,_}
+ |Stacktrace] ->
+ {error,
+ {undef_callback,{Module,callback_mode,Args}},
+ Stacktrace};
+ Stacktrace ->
+ {error,undef,Stacktrace}
+ end;
+ Class:Reason ->
+ {Class,Reason,erlang:get_stacktrace()}
+ end.
+
+loop_event(
+ Parent, Debug,
+ #{callback_mode := undefined} = S,
+ Events,
+ State, Data, P, Event, Hibernate) ->
+ %% This happens after code_change/4
+ case call_callback_mode(S) of
+ {ok,NewS} ->
+ loop_event(
+ Parent, Debug, NewS, Events,
+ State, Data, P, Event, Hibernate);
+ {Class,Reason,Stacktrace} ->
+ terminate(
+ Class, Reason, Stacktrace,
+ Debug, S, [Event|Events], State, Data, P)
+ end;
loop_event(
Parent, Debug,
#{callback_mode := CallbackMode,
@@ -891,24 +982,16 @@ loop_event(
%%
try
case CallbackMode of
- undefined ->
- Module:callback_mode();
state_functions ->
erlang:apply(Module, State, [Type,Content,Data]);
handle_event_function ->
Module:handle_event(Type, Content, State, Data)
end
of
- Result when CallbackMode =:= undefined ->
- loop_event_callback_mode(
- Parent, Debug, S, Events, State, Data, P, Event, Result);
Result ->
loop_event_result(
Parent, Debug, S, Events, State, Data, P, Event, Result)
catch
- Result when CallbackMode =:= undefined ->
- loop_event_callback_mode(
- Parent, Debug, S, Events, State, Data, P, Event, Result);
Result ->
loop_event_result(
Parent, Debug, S, Events, State, Data, P, Event, Result);
@@ -936,14 +1019,6 @@ loop_event(
%% of calling a nonexistent state function
%% to make the undef more precise
case erlang:get_stacktrace() of
- [{Module,callback_mode,[]=Args,_}
- |Stacktrace]
- when CallbackMode =:= undefined ->
- terminate(
- error,
- {undef_callback,{Module,callback_mode,Args}},
- Stacktrace,
- Debug, S, [Event|Events], State, Data, P);
[{Module,State,[Type,Content,Data]=Args,_}
|Stacktrace]
when CallbackMode =:= state_functions ->
@@ -972,25 +1047,6 @@ loop_event(
Debug, S, [Event|Events], State, Data, P)
end.
-%% Interpret callback_mode() result
-loop_event_callback_mode(
- Parent, Debug, S, Events, State, Data, P, Event, CallbackMode) ->
- case callback_mode(CallbackMode) of
- true ->
- Hibernate = false, % We have already GC:ed recently
- loop_event(
- Parent, Debug,
- S#{callback_mode := CallbackMode},
- Events,
- State, Data, P, Event, Hibernate);
- false ->
- terminate(
- error,
- {bad_return_from_callback_mode,CallbackMode},
- ?STACKTRACE(),
- Debug, S, [Event|Events], State, Data, P)
- end.
-
%% Interpret all callback return variants
loop_event_result(
Parent, Debug, S, Events, State, Data, P, Event, Result) ->
@@ -1174,7 +1230,7 @@ loop_event_actions(
%%
%% End of actions list
loop_event_actions(
- Parent, Debug, S, Events,
+ Parent, Debug, #{state_entry_events := SEEvents} = S, Events,
State, NewData, P0, Event, NextState, [],
Postpone, Hibernate, Timeout, NextEvents) ->
%%
@@ -1196,7 +1252,25 @@ loop_event_actions(
{lists:reverse(P1, Events),[]}
end,
%% Place next events first in queue
- Q = lists:reverse(NextEvents, Q2),
+ Q3 = lists:reverse(NextEvents, Q2),
+ %% State entry events
+ Q =
+ case SEEvents of
+ true ->
+ %% Generate state entry events
+ case
+ (NextState =/= State)
+ orelse maps:is_key(init_state, S)
+ of
+ true ->
+ %% State change or initial state
+ [{enter,State}|Q3];
+ false ->
+ Q3
+ end;
+ false ->
+ Q3
+ end,
%%
NewDebug =
sys_debug(
@@ -1208,7 +1282,15 @@ loop_event_actions(
{consume,Event,NextState}
end),
loop_events(
- Parent, NewDebug, S, Q, NextState, NewData, P, Hibernate, Timeout).
+ Parent, NewDebug,
+ %% Avoid infinite loop in initial state with state entry events
+ case maps:is_key(init_state, S) of
+ true ->
+ maps:remove(init_state, S);
+ false ->
+ S
+ end,
+ Q, NextState, NewData, P, Hibernate, Timeout).
%%---------------------------------------------------------------------------
%% Server helpers
@@ -1285,7 +1367,9 @@ terminate(
error_info(
Class, Reason, Stacktrace,
- #{name := Name, callback_mode := CallbackMode},
+ #{name := Name,
+ callback_mode := CallbackMode,
+ state_entry_events := SEEvents},
Q, P, FmtData) ->
{FixedReason,FixedStacktrace} =
case Stacktrace of
@@ -1312,6 +1396,13 @@ error_info(
end;
_ -> {Reason,Stacktrace}
end,
+ CBMode =
+ case SEEvents of
+ true ->
+ [CallbackMode,state_entry_events];
+ false ->
+ CallbackMode
+ end,
error_logger:format(
"** State machine ~p terminating~n" ++
case Q of
@@ -1338,8 +1429,9 @@ error_info(
[] -> [];
[Event|_] -> [Event]
end] ++
- [FmtData,Class,FixedReason,
- CallbackMode] ++
+ [FmtData,
+ Class,FixedReason,
+ CBMode] ++
case Q of
[_|[_|_] = Events] -> [Events];
_ -> []
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index e092940174..eef8f265c4 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -37,7 +37,7 @@ all() ->
{group, stop_handle_event},
{group, abnormal},
{group, abnormal_handle_event},
- shutdown, stop_and_reply, event_order, code_change,
+ shutdown, stop_and_reply, enter_events, event_order, code_change,
{group, sys},
hibernate, enter_loop].
@@ -556,7 +556,8 @@ stop_and_reply(_Config) ->
{stop_and_reply,Reason,
[R1,{reply,From2,Reply2}]}
end},
- {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine}, []),
+ {ok,STM} =
+ gen_statem:start_link(?MODULE, {map_statem,Machine,[]}, []),
Self = self(),
Tag1 = make_ref(),
@@ -581,6 +582,61 @@ stop_and_reply(_Config) ->
+enter_events(_Config) ->
+ process_flag(trap_exit, true),
+ Self = self(),
+
+ Machine =
+ %% Abusing the internal format of From...
+ #{init =>
+ fun () ->
+ {ok,start,1}
+ end,
+ start =>
+ fun (enter, Prev, N) ->
+ Self ! {enter,start,Prev,N},
+ {keep_state,N + 1};
+ (internal, Prev, N) ->
+ Self ! {internal,start,Prev,N},
+ {keep_state,N + 1};
+ ({call,From}, echo, N) ->
+ {next_state,wait,N + 1,{reply,From,{echo,start,N}}};
+ ({call,From}, {stop,Reason}, N) ->
+ {stop_and_reply,Reason,[{reply,From,{stop,N}}],N + 1}
+ end,
+ wait =>
+ fun (enter, Prev, N) ->
+ Self ! {enter,wait,Prev,N},
+ {keep_state,N + 1};
+ ({call,From}, echo, N) ->
+ {next_state,start,N + 1,
+ [{next_event,internal,wait},
+ {reply,From,{echo,wait,N}}]}
+ end},
+ {ok,STM} =
+ gen_statem:start_link(
+ ?MODULE, {map_statem,Machine,[state_entry_events]}, []),
+
+ [{enter,start,start,1}] = flush(),
+ {echo,start,2} = gen_statem:call(STM, echo),
+ [{enter,wait,start,3}] = flush(),
+ {wait,[4|_]} = sys:get_state(STM),
+ {echo,wait,4} = gen_statem:call(STM, echo),
+ [{enter,start,wait,5},{internal,start,wait,6}] = flush(),
+ {stop,7} = gen_statem:call(STM, {stop,bye}),
+ [{'EXIT',STM,bye}] = flush(),
+
+ {noproc,_} =
+ ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason),
+ case flush() of
+ [] ->
+ ok;
+ Other2 ->
+ ct:fail({unexpected,Other2})
+ end.
+
+
+
event_order(_Config) ->
process_flag(trap_exit, true),
@@ -623,7 +679,7 @@ event_order(_Config) ->
Result
end},
- {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine}, []),
+ {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine,[]}, []),
Self = self(),
Tag1 = make_ref(),
gen_statem:cast(STM, {reply,{Self,Tag1},ok1}),
@@ -1315,9 +1371,9 @@ init({callback_mode,CallbackMode,Arg}) ->
ets:new(?MODULE, [named_table,private]),
ets:insert(?MODULE, {callback_mode,CallbackMode}),
init(Arg);
-init({map_statem,#{init := Init}=Machine}) ->
+init({map_statem,#{init := Init}=Machine,Modes}) ->
ets:new(?MODULE, [named_table,private]),
- ets:insert(?MODULE, {callback_mode,handle_event_function}),
+ ets:insert(?MODULE, {callback_mode,[handle_event_function|Modes]}),
case Init() of
{ok,State,Data,Ops} ->
{ok,State,[Data|Machine],Ops};
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]};