From 2591124991bf601321d44de37d6c32e49075e68f Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Like most
The
+ The two
+
+ You can for example do this by focusing on one state at the time + and for every state ensure that all events are handled, + or the other way around focus on one event at the time + and ensure that it is handled in every state, + or mix these strategies. +
+
+ With
+ This mode fits well when you have a regular state diagram + like the ones in this chapter that describes all events and actions + belonging to a state visually around that state, + and each state has its unique name. +
+
+ With
+ This mode works equally well when you want to focus on
+ one event at the time or when you want to focus on
+ one state at the time, but the
+ The mode enables the use of non-atom states for example
+ complex states or even hiearchical states.
+ If, for example, a state diagram is largely alike
+ for the client and for the server side of a protocol;
+ then you can have a state
+ The fact that a postponed event is only retried after a state change
+ translates into a requirement on the event and state space:
+ if you have a choice between storing a state data item
+ in the
+ What you want to avoid is that you maybe much later decide
+ to postpone an event in one state and by misfortune it is never retried
+ because the code only changes the
@@ -788,6 +871,7 @@ open(cast, {button,_}, Data) -> as in postpone it.
@@ -956,6 +1040,7 @@ enter(Tag, State, Data) ->
Nor does it show that the
@@ -1029,6 +1114,7 @@ code_change(_Vsn, State, Data, _Extra) -> {ok,State,Data}. ]]>
@@ -1059,7 +1145,7 @@ handle_event( #{code := Code, remaining := Remaining} = Data) -> case Remaining of [Digit] -> % Complete - enter(next_state, open, Data, []); + enter(next_state, open, Data); [Digit|Rest] -> % Incomplete {keep_state,Data#{remaining := Rest}}; [_|_] -> % Wrong @@ -1072,7 +1158,7 @@ handle_event(internal, enter, open, Data) -> do_unlock(), {keep_state,Data#{timer => Tref}}; handle_event(info, {timeout,Tref,lock}, open, #{timer := Tref} = Data) -> - enter(next_state, locked, Data, []); + enter(next_state, locked, Data); handle_event(cast, {button,_}, open, _) -> {keep_state_and_data,[postpone]}; %% @@ -1086,8 +1172,161 @@ handle_event({call,From}, code_length, _State, #{code := Code}) ->
Note that postponing buttons from the
+ The
+
+ One reason to use this is when you have
+ a state item that affects the event handling
+ in particular when combining that with postponing events.
+ Let us complicate the previous example
+ by introducing a configurable lock button
+ (this is the state item in question)
+ that in the
+ Suppose now that we call
+ So let us make the
+ If now one process calls
+ We define the state as
+ gen_statem:start_link(
+ {local,?NAME}, ?MODULE, {Code,LockButton}, []).
+stop() ->
+ gen_statem:stop(?NAME).
+
+button(Digit) ->
+ gen_statem:call(?NAME, {button,Digit}).
+code_length() ->
+ gen_statem:call(?NAME, code_length).
+set_lock_button(LockButton) ->
+ gen_statem:call(?NAME, {set_lock_button,LockButton}).
+
+init({Code,LockButton}) ->
+ process_flag(trap_exit, true),
+ Data = #{code => Code},
+ enter(handle_event_function, {locked,LockButton}, Data, []).
+
+%% State: locked
+handle_event(internal, enter, {locked,_}, #{code := Code} = Data) ->
+ do_lock(),
+ {keep_state,Data#{remaining => Code}};
+handle_event(
+ {call,From}, {button,Digit}, {locked,LockButton},
+ #{code := Code, remaining := Remaining} = Data) ->
+ case Remaining of
+ [Digit] -> % Complete
+ enter(next_state, {open,LockButton}, Data, [{reply,From,ok}]);
+ [Digit|Rest] -> % Incomplete
+ {keep_state,Data#{remaining := Rest},[{reply,From,ok}]};
+ [_|_] -> % Wrong
+ {keep_state,Data#{remaining := Code},[{reply,From,ok}]}
+ end;
+%%
+%% State: open
+handle_event(internal, enter, {open,_}, Data) ->
+ Tref = erlang:start_timer(10000, self(), lock),
+ do_unlock(),
+ {keep_state,Data#{timer => Tref}};
+handle_event(
+ info, {timeout,Tref,lock}, {open,LockButton},
+ #{timer := Tref} = Data) ->
+ enter(next_state, {locked,LockButton}, Data, []);
+handle_event(
+ {call,From}, {button,LockButton}, {open,LockButton},
+ #{timer := Tref} = Data) ->
+ erlang:cancel_timer(Tref),
+ enter(next_state, {locked,LockButton}, Data, [{reply,From,locked}]);
+handle_event({call,_}, {button,_}, {open,_}, _) ->
+ {keep_state_and_data,[postpone]};
+%%
+%% Any state
+handle_event(
+ {call,From}, {set_lock_button,NewLockButton}, {StateName,OldLockButton},
+ Data) ->
+ {next_state,{StateName,NewLockButton},Data,
+ [{reply,From,OldLockButton}]};
+handle_event({call,From}, code_length, _State, #{code := Code}) ->
+ {keep_state_and_data,[{reply,From,length(Code)}]}.
+
+enter(Tag, State, Data, Actions) ->
+ {Tag,State,Data,[{next_event,internal,enter}|Actions]}.
+
+do_lock() ->
+ io:format("Locked~n", []).
+do_unlock() ->
+ io:format("Open~n", []).
+
+terminate(_Reason, State, _Data) ->
+ State =/= locked andalso do_lock(),
+ ok.
+code_change(_Vsn, State, Data, _Extra) ->
+ {ok,State,Data}.
+ ]]>
+
+ It may be an ill-fitting model for a physical code lock
+ that the