diff options
Diffstat (limited to 'system/doc')
-rw-r--r-- | system/doc/design_principles/statem.xml | 152 |
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]}; |