diff options
-rw-r--r-- | lib/stdlib/doc/src/gen_statem.xml | 82 | ||||
-rw-r--r-- | lib/stdlib/src/gen_statem.erl | 208 | ||||
-rw-r--r-- | lib/stdlib/test/gen_statem_SUITE.erl | 66 | ||||
-rw-r--r-- | system/doc/design_principles/statem.xml | 152 |
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]}; |