From 6ee0aefd8a0ea9c165211c42d5244182b5aa9210 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
StateName(EventType, EventContent, Data) -> - .. code for actions here ... + ... code for actions here ... {next_state, NewStateName, NewData}.
handle_event(EventType, EventContent, State, Data) -> - .. code for actions here ... + ... code for actions here ... {next_state, NewState, NewData}
+ The
+StateName(enter, _OldState, Data) -> + ... code for state entry here ... + {keep_state, NewData}; +StateName(EventType, EventContent, Data) -> + ... code for actions here ... + {next_state, NewStateName, NewData}.+
+ 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. +
+
This example starts off as equivalent to the example in section
-
+ Here is the same example as the previous but instead using
+ the built in
+
+ 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};
+...
+ ]]>
+
...
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]};
--
cgit v1.2.3
From 4ebdabdca2c964887115f21405993f3916843d10 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Fri, 16 Sep 2016 10:15:22 +0200
Subject: Improve docs
---
system/doc/design_principles/statem.xml | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
(limited to 'system/doc')
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 8090016b54..43359829b2 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -989,7 +989,17 @@ do_unlock() ->
from your state machine to itself.
- One example of using self-generated events can be when you have
+ One example for this is to pre-process incoming data, for example
+ decrypting chunks or collecting characters up to a line break.
+ This could be modelled with a separate state machine that sends
+ the pre-processed events to the main state machine, or to decrease
+ overhead the small pre-processing state machine can be implemented
+ in the common state event handling of the main state machine
+ using a few state data variables and then send the pre-processed
+ events as internal events to the main state machine.
+
+
+ Another example of using self-generated events can be when you have
a state machine specification that uses state entry actions.
You can code that using a dedicated function
to do the state transition. But if you want that code to be
@@ -1050,7 +1060,15 @@ enter(Tag, State, Data) ->
state entry events .
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.
+ example may be more suitable, especially to only send internal
+ events when entering just those few states.
+
+
+ You can also in the previous example choose to generate
+ events looking just like the events you get from using
+ state entry events .
+ This may be confusing, or practical,
+ depending on your point of view.
Date: Tue, 20 Sep 2016 17:03:24 +0200
Subject: Improve docs
---
system/doc/design_principles/statem.xml | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
(limited to 'system/doc')
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 43359829b2..565b0e5274 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -1010,6 +1010,8 @@ do_unlock() ->
the state, you must explicitly
insert the internal event
or use a state transition function.
+ This is something that can be forgotten, and if you find that
+ annoying please look at the next chapter.
The following is an implementation of entry actions
@@ -1058,10 +1060,18 @@ enter(Tag, State, Data) ->
Here is the same example as the previous but instead using
the built in
state entry events .
- You will have to handle the state entry events in every state.
+
+
+ Since the state entry events are unconditionally inserted by
+ the gen_statem engine you can not forget to insert them
+ yourself and 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, especially to only send internal
events when entering just those few states.
+ Note: additional discipline will be required.
You can also in the previous example choose to generate
--
cgit v1.2.3
From 04d40c5cd18aca449606c19608e8044f593ee99e Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Thu, 22 Sep 2016 17:40:47 +0200
Subject: Change state entry events into state enter calls
---
system/doc/design_principles/statem.xml | 194 ++++++++++++++------------------
1 file changed, 86 insertions(+), 108 deletions(-)
(limited to 'system/doc')
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 565b0e5274..d2a9b23570 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -195,21 +195,19 @@ handle_event(EventType, EventContent, State, Data) ->
-
- State Entry Events
+
+ State Enter Calls
The gen_statem behavior can regardless of callback mode
- automatically generate an
-
- event whenever the state changes
-
- so you can write state entry code
+ automatically call the state function
+ with special arguments whenever the state changes
+ so you can write state entry actions
near the rest of the state transition rules.
It typically looks like this:
StateName(enter, _OldState, Data) ->
- ... code for state entry here ...
+ ... code for state entry actions here ...
{keep_state, NewData};
StateName(EventType, EventContent, Data) ->
... code for actions here ...
@@ -217,7 +215,7 @@ StateName(EventType, EventContent, Data) ->
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.
+ you will have to handle the state enter call in all states.
@@ -751,12 +749,6 @@ stop() ->
Generated by any regular process message sent to
the gen_statem process.
- enter
- -
- Generated by a state transition with
-
OldState =/= NewState when running with
- state entry events .
-
timeout
-
Generated by state transition action
@@ -972,63 +964,35 @@ do_unlock() ->
- Self-Generated Events
-
- It can sometimes be beneficial to be able to generate events
- to your own state machine.
- This can be done with the state transition
- action
- {next_event,EventType,EventContent} .
-
+ State Entry Actions
- You can generate events of any existing
- type ,
- but the internal type can only be generated through action
- next_event . Hence, it cannot come from an external source,
- so you can be certain that an internal event is an event
- from your state machine to itself.
+ Say you have a state machine specification
+ that uses state entry actions.
+ Allthough you can code this using self-generated events
+ (described in the next section), especially if just
+ one or a few states has got state entry actions,
+ this is a perfect use case for the built in
+ state enter calls .
- One example for this is to pre-process incoming data, for example
- decrypting chunks or collecting characters up to a line break.
- This could be modelled with a separate state machine that sends
- the pre-processed events to the main state machine, or to decrease
- overhead the small pre-processing state machine can be implemented
- in the common state event handling of the main state machine
- using a few state data variables and then send the pre-processed
- events as internal events to the main state machine.
-
-
- Another example of using self-generated events can be when you have
- a state machine specification that uses state entry actions.
- You can code that using a dedicated function
- to do the state transition. But if you want that code to be
- visible besides the other state logic, you can insert
- an internal event that does the entry actions.
- This has the same unfortunate consequence as using
- state transition functions: everywhere you go to
- the state, you must explicitly
- insert the internal event
- or use a state transition function.
- This is something that can be forgotten, and if you find that
- annoying please look at the next chapter.
-
-
- The following is an implementation of entry actions
- using internal events with content enter
- using a helper function enter/3 for state entry:
+ You return a list containing state_enter from your
+ callback_mode/0
+ function and the gen_statem engine will call your
+ state function once with the arguments
+ (enter, OldState, ...) whenever the state changes.
+ Then you just need to handle these event-like calls in all states.
process_flag(trap_exit, true),
Data = #{code => Code},
- enter(ok, locked, Data).
+ {ok, locked, Data}.
callback_mode() ->
- state_functions.
+ [state_functions,state_enter].
-locked(internal, enter, Data) ->
+locked(enter, _OldState, Data) ->
do_lock(),
{keep_state,Data#{remaining => Code}};
locked(
@@ -1036,79 +1000,94 @@ locked(
#{code := Code, remaining := Remaining} = Data) ->
case Remaining of
[Digit] ->
- enter(next_state, open, Data);
+ {next_state, open, 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};
...
-
-enter(Tag, State, Data) ->
- {Tag,State,Data,[{next_event,internal,enter}]}.
]]>
- Using State Entry Events
+ Self-Generated Events
- Here is the same example as the previous but instead using
- the built in
- state entry events .
+ It can sometimes be beneficial to be able to generate events
+ to your own state machine.
+ This can be done with the state transition
+ action
+ {next_event,EventType,EventContent} .
- Since the state entry events are unconditionally inserted by
- the gen_statem engine you can not forget to insert them
- yourself and you will have to handle the state entry events
- in every state.
+ You can generate events of any existing
+ type ,
+ but the internal type can only be generated through action
+ next_event . Hence, it cannot come from an external source,
+ so you can be certain that an internal event is an event
+ from your state machine to itself.
- If you want state entry code in just a few states the previous
- example may be more suitable, especially to only send internal
- events when entering just those few states.
- Note: additional discipline will be required.
+ One example for this is to pre-process incoming data, for example
+ decrypting chunks or collecting characters up to a line break.
+ Purists may argue that this should be modelled with a separate
+ state machine that sends pre-processed events
+ to the main state machine.
+ But to decrease overhead the small pre-processing state machine
+ can be implemented in the common state event handling
+ of the main state machine using a few state data variables
+ that then sends the pre-processed events as internal events
+ to the main state machine.
- You can also in the previous example choose to generate
- events looking just like the events you get from using
- state entry events .
- This may be confusing, or practical,
- depending on your point of view.
+ The following example use an input model where you give the lock
+ characters with put_chars(Chars) and then call
+ enter() to finish the input.
- process_flag(trap_exit, true),
- Data = #{code => Code},
- {ok, locked, Data}.
+-export(put_chars/1, enter/0).
+...
+put_chars(Chars) when is_binary(Chars) ->
+ gen_statem:call(?NAME, {chars,Chars}).
-callback_mode() ->
- [state_functions,state_entry_events].
+enter() ->
+ gen_statem:call(?NAME, enter).
+
+...
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};
+ {keep_state,Data#{remaining => Code, buf => []}};
...
-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};
+handle_event({call,From}, {chars,Chars}, #{buf := Buf} = Data) ->
+ {keep_state, Data#{buf := [Chars|Buf],
+ [{reply,From,ok}]};
+handle_event({call,From}, enter, #{buf := Buf} = Data) ->
+ Chars = unicode:characters_to_binary(lists:reverse(Buf)),
+ try binary_to_integer(Chars) of
+ Digit ->
+ {keep_state, Data#{buf := []},
+ [{reply,From,ok},
+ {next_event,internal,{button,Chars}}]}
+ catch
+ error:badarg ->
+ {keep_state, Data#{buf := []},
+ [{reply,From,{error,not_an_integer}}]}
+ end;
...
]]>
+
+ If you start this program with code_lock:start([17])
+ you can unlock with code_lock:put_chars(<<"001">>),
+ code_lock:put_chars(<<"7">>), code_lock:enter() .
+
@@ -1117,7 +1096,7 @@ open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
Example Revisited
This section includes the example after all mentioned modifications
- and some more using the entry actions,
+ and some more using state enter calls,
which deserves a new state diagram:
@@ -1163,7 +1142,7 @@ init(Code) ->
{ok, locked, Data}.
callback_mode() ->
- [state_functions,state_entry_events].
+ [state_functions,state_enter].
locked(enter, _OldState, #{code := Code} = Data) ->
do_lock(),
@@ -1215,8 +1194,7 @@ code_change(_Vsn, State, Data, _Extra) ->
This section describes what to change in the example
to use one handle_event/4 function.
The previously used approach to first branch depending on event
- does not work that well here because of
- the state entry events,
+ does not work that well here because of the state enter calls,
so this example first branches depending on state:
...
callback_mode() ->
- [handle_event_function,state_entry_events].
+ [handle_event_function,state_enter].
%% State: locked
handle_event(enter, _OldState, locked, #{code := Code} = Data) ->
@@ -1413,7 +1391,7 @@ init({Code,LockButton}) ->
{ok, {locked,LockButton}, Data}.
callback_mode() ->
- [handle_event_function,state_entry_events].
+ [handle_event_function,state_enter].
handle_event(
{call,From}, {set_lock_button,NewLockButton},
--
cgit v1.2.3
From 800265f49f912dcf66846b13aa8032bf2f380caf Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Fri, 30 Sep 2016 11:17:22 +0200
Subject: Improve docs and types
---
system/doc/design_principles/statem.xml | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
(limited to 'system/doc')
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index d2a9b23570..69d1e8e9fa 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -29,7 +29,7 @@
statem.xml
-
+
This section is to be read with the
gen_statem(3)
@@ -199,7 +199,10 @@ handle_event(EventType, EventContent, State, Data) ->
State Enter Calls
The gen_statem behavior can regardless of callback mode
- automatically call the state function
+ automatically
+
+ call the state function
+
with special arguments whenever the state changes
so you can write state entry actions
near the rest of the state transition rules.
@@ -214,8 +217,13 @@ StateName(EventType, EventContent, Data) ->
{next_state, NewStateName, NewData}.
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 enter call in all states.
+ this can be a very useful feature,
+ but it forces you to handle the state enter calls in all states.
+ See also the
+
+ State Entry Actions
+
+ chapter.
Say you have a state machine specification
--
cgit v1.2.3
From 77e175589b0ee3c1a4c94aef3cdcdf54cd84c53c Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
This section is to be read with the
Established Automata Theory does not deal much with
@@ -94,7 +95,7 @@ State(S) x Event(E) -> Actions(A), State(S')
The
+ This form is used in most examples here for example in section
+
@@ -121,7 +127,13 @@ StateName(EventType, EventContent, Data) ->
+ Se section
+
@@ -134,10 +146,11 @@ handle_event(EventType, EventContent, State, Data) ->
The two
-
The
+ In the first section
+
+ There are more specific state-transition actions
+ that a callback function can order the
+ For details, see the
+
+ Events are categorized in different
+
+ The following is a complete list of event types and where
+ they come from:
+
This example starts off as equivalent to the example in section
- The code is explained in the next sections.
In the example in the previous section,
If name registration succeeds, the new The function notifying the code lock about a button event is
implemented using
@@ -451,11 +615,13 @@ button(Digit) ->
The event is made into a message and sent to the
If the door is locked and a button is pressed, the pressed
@@ -490,38 +656,55 @@ open(cast, {button,_}, Data) ->
restarts from the start of the code sequence.
- In state
+ In state
When a correct code has been given, the door is unlocked and
the following tuple is returned from
10,000 is a time-out value in milliseconds.
After this time (10 seconds), a time-out occurs.
- Then,
+ The timer for a state time-out is automatically cancelled
+ when the state machine changes states. You can restart
+ a state time-out by setting it to a new time, which cancels
+ the running timer and starts a new. This implies that
+ you can cancel a state time-out by restarting it with
+ time
Sometimes events can arrive in any state of the
This example uses
If mode
If the
If the
- In the first sections actions were mentioned as a part of
- the general state machine model. These general actions
- are implemented with the code that callback module
-
- There are more specific state-transition actions
- that a callback function can order the
- In the example earlier was mentioned the event time-out
- and replying to a caller.
- An example of event postponing is included later in this chapter.
- For details, see the
-
- The previous sections mentioned a few
-
- The following is a complete list of event types and where
- they come from:
+ An event timeout is cancelled by any other event so you either
+ get some other event or the timeout event. It is therefore
+ not possible nor needed to cancel or restart an event timeout.
+ Whatever event you act on has already cancelled
+ the event timeout...
- The time-out event generated by state transition action
-
- Often you want a timer not to be cancelled by any event
- or you want to start a timer in one state and respond
- to the time-out in another. This can be accomplished
- with a regular Erlang timer:
-
- For the example so far in this chapter: using the
-
- Suppose that we do not want a button to lock the door,
- instead we want to ignore button events in the
+ Removing the
If you need to cancel a timer because of some other event, you can use
- Another way to cancel a timer is not to cancel it,
+ Another way to handle a late time-out can be to not cancel it,
but to ignore it if it arrives in a state
where it is known to be late.
If you want to ignore a particular event in the current state
@@ -877,6 +1029,7 @@ open(cast, {button,_}, Data) ->
It is not uncommon that a state diagram does not specify
@@ -893,6 +1046,7 @@ open(cast, {button,_}, Data) ->
Erlang's selective receive statement is often used to
@@ -972,7 +1126,7 @@ do_unlock() ->
Say you have a state machine specification
@@ -981,7 +1135,7 @@ do_unlock() ->
(described in the next section), especially if just
one or a few states has got state entry actions,
this is a perfect use case for the built in
-
You return a list containing
It can sometimes be beneficial to be able to generate events
@@ -1054,7 +1208,7 @@ open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
to the main state machine.
- The following example use an input model where you give the lock
+ The following example uses an input model where you give the lock
characters with
- This section includes the example after all mentioned modifications
- and some more using state enter calls,
+ This section includes the example after most of the mentioned
+ modifications and some more using state enter calls,
which deserves a new state diagram:
Using state functions:
@@ -1155,7 +1311,11 @@ callback_mode() ->
locked(enter, _OldState, #{code := Code} = Data) ->
do_lock(),
- {keep_state,Data#{remaining => Code}};
+ {keep_state, Data#{remaining => Code}};
+locked(
+ timeout, _,
+ #{code := Code, remaining := Remaining} = Data) ->
+ {keep_state, Data#{remaining := Code}};
locked(
cast, {button,Digit},
#{code := Code, remaining := Remaining} = Data) ->
@@ -1163,26 +1323,25 @@ locked(
[Digit] -> % Complete
{next_state, open, Data};
[Digit|Rest] -> % Incomplete
- {keep_state,Data#{remaining := Rest}};
+ {keep_state, Data#{remaining := Rest}, 30000};
[_|_] -> % Wrong
- {keep_state,Data#{remaining := Code}}
+ {keep_state, Data#{remaining := Code}}
end;
locked(EventType, EventContent, Data) ->
handle_event(EventType, EventContent, Data).
-open(enter, _OldState, Data) ->
- Tref = erlang:start_timer(10000, self(), lock),
+open(enter, _OldState, _Data) ->
do_unlock(),
- {keep_state,Data#{timer => Tref}};
-open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
+ {keep_state_and_data, [{state_timeout,10000,lock}]};
+open(state_timeout, lock, Data) ->
{next_state, locked, Data};
open(cast, {button,_}, _) ->
- {keep_state_and_data,[postpone]};
+ {keep_state_and_data, [postpone]};
open(EventType, EventContent, Data) ->
handle_event(EventType, EventContent, Data).
handle_event({call,From}, code_length, #{code := Code}) ->
- {keep_state_and_data,[{reply,From,length(Code)}]}.
+ {keep_state_and_data, [{reply,From,length(Code)}]}.
do_lock() ->
io:format("Locked~n", []).
@@ -1198,6 +1357,7 @@ code_change(_Vsn, State, Data, _Extra) ->
This section describes what to change in the example
@@ -1215,9 +1375,15 @@ callback_mode() ->
[handle_event_function,state_enter].
%% State: locked
-handle_event(enter, _OldState, locked, #{code := Code} = Data) ->
+handle_event(
+ enter, _OldState, locked,
+ #{code := Code} = Data) ->
do_lock(),
- {keep_state,Data#{remaining => Code}};
+ {keep_state, Data#{remaining => Code}};
+handle_event(
+ timeout, _, locked,
+ #{code := Code, remaining := Remaining} = Data) ->
+ {keep_state, Data#{remaining := Code}};
handle_event(
cast, {button,Digit}, locked,
#{code := Code, remaining := Remaining} = Data) ->
@@ -1225,31 +1391,30 @@ handle_event(
[Digit] -> % Complete
{next_state, open, Data};
[Digit|Rest] -> % Incomplete
- {keep_state,Data#{remaining := Rest}};
+ {keep_state, Data#{remaining := Rest}, 30000};
[_|_] -> % Wrong
- {keep_state,Data#{remaining := Code}}
+ {keep_state, Data#{remaining := Code}}
end;
%%
%% State: open
-handle_event(enter, _OldState, open, Data) ->
- Tref = erlang:start_timer(10000, self(), lock),
+handle_event(enter, _OldState, open, _Data) ->
do_unlock(),
- {keep_state,Data#{timer => Tref}};
-handle_event(info, {timeout,Tref,lock}, open, #{timer := Tref} = Data) ->
+ {keep_state_and_data, [{state_timeout,10000,lock}]};
+handle_event(state_timeout, lock, open, Data) ->
{next_state, locked, Data};
handle_event(cast, {button,_}, open, _) ->
{keep_state_and_data,[postpone]};
%%
%% Any state
handle_event({call,From}, code_length, _State, #{code := Code}) ->
- {keep_state_and_data,[{reply,From,length(Code)}]}.
+ {keep_state_and_data, [{reply,From,length(Code)}]}.
...
]]>
Notice that postponing buttons from the
The example servers so far in this chapter
@@ -1317,12 +1483,13 @@ format_status(Opt, [_PDict,State,Data]) ->
The callback mode
@@ -1396,7 +1563,7 @@ set_lock_button(LockButton) ->
init({Code,LockButton}) ->
process_flag(trap_exit, true),
- Data = #{code => Code, remaining => undefined, timer => undefined},
+ Data = #{code => Code, remaining => undefined},
{ok, {locked,LockButton}, Data}.
callback_mode() ->
@@ -1405,29 +1572,31 @@ callback_mode() ->
handle_event(
{call,From}, {set_lock_button,NewLockButton},
{StateName,OldLockButton}, Data) ->
- {next_state,{StateName,NewLockButton},Data,
+ {next_state, {StateName,NewLockButton}, Data,
[{reply,From,OldLockButton}]};
handle_event(
{call,From}, code_length,
{_StateName,_LockButton}, #{code := Code}) ->
{keep_state_and_data,
- [{reply,From,length(Code)}]};
+ [{reply,From,length(Code)}]};
%%
%% State: locked
handle_event(
EventType, EventContent,
{locked,LockButton}, #{code := Code, remaining := Remaining} = Data) ->
- case {EventType,EventContent} of
- {enter,_OldState} ->
+ case {EventType, EventContent} of
+ {enter, _OldState} ->
do_lock(),
- {keep_state,Data#{remaining := Code}};
- {{call,From},{button,Digit}} ->
+ {keep_state, Data#{remaining := Code}};
+ {timeout, _} ->
+ {keep_state, Data#{remaining := Code}};
+ {{call,From}, {button,Digit}} ->
case Remaining of
[Digit] -> % Complete
{next_state, {open,LockButton}, Data,
[{reply,From,ok}]};
[Digit|Rest] -> % Incomplete
- {keep_state, Data#{remaining := Rest},
+ {keep_state, Data#{remaining := Rest, 30000},
[{reply,From,ok}]};
[_|_] -> % Wrong
{keep_state, Data#{remaining := Code},
@@ -1438,18 +1607,16 @@ handle_event(
%% State: open
handle_event(
EventType, EventContent,
- {open,LockButton}, #{timer := Timer} = Data) ->
- case {EventType,EventContent} of
- {enter,_OldState} ->
- Tref = erlang:start_timer(10000, self(), lock),
+ {open,LockButton}, Data) ->
+ case {EventType, EventContent} of
+ {enter, _OldState} ->
do_unlock(),
- {keep_state,Data#{timer := Tref}};
- {info,{timeout,Timer,lock}} ->
+ {keep_state_and_data, [{state_timeout,10000,lock}]};
+ {state_timeout, lock} ->
{next_state, {locked,LockButton}, Data};
- {{call,From},{button,Digit}} ->
+ {{call,From}, {button,Digit}} ->
if
Digit =:= LockButton ->
- erlang:cancel_timer(Timer),
{next_state, {locked,LockButton}, Data,
[{reply,From,locked}]);
true ->
@@ -1494,6 +1661,7 @@ format_status(Opt, [_PDict,State,Data]) ->
If you have many servers in one node
@@ -1519,20 +1687,21 @@ format_status(Opt, [_PDict,State,Data]) ->
- The
-
+ Another not uncommon scenario is to use the event time-out
+ to triger hibernation after a certain time of inactivity.
+
This server probably does not use
heap memory worth hibernating for.
--
cgit v1.2.3
From f4de3f5887be010db178a178e1f20027f3e5d22b Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
StateName(EventType, EventContent, Data) ->
... code for actions here ...
- {next_state, NewStateName, NewData}.
+ {next_state, NewStateName, NewData}.
+
+
handle_event(EventType, EventContent, State, Data) ->
... code for actions here ...
- {next_state, NewState, NewData}
+ {next_state, NewState, NewData}
+
+
+
+
init(Code) ->
do_lock(),
Data = #{code => Code, remaining => Code},
- {ok,locked,Data}.
+ {ok, locked, Data}.
callback_mode() ->
state_functions.
@@ -287,19 +449,19 @@ locked(
case Remaining of
[Digit] ->
do_unlock(),
- {next_state,open,Data#{remaining := Code},10000};
+ {next_state, open, Data#{remaining := Code},
+ [{state_timeout,10000,lock}];
[Digit|Rest] -> % Incomplete
- {next_state,locked,Data#{remaining := Rest}};
+ {next_state, locked, Data#{remaining := Rest}};
_Wrong ->
- {next_state,locked,Data#{remaining := Code}}
+ {next_state, locked, Data#{remaining := Code}}
end.
-open(timeout, _, Data) ->
+open(state_timeout, lock, Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state, locked, Data};
open(cast, {button,_}, Data) ->
- do_lock(),
- {next_state,locked,Data}.
+ {next_state, open, Data}.
do_lock() ->
io:format("Lock~n", []).
@@ -310,7 +472,7 @@ terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
- {ok,State,Data}.
+ {ok, State, Data}.
]]>
% Complete
do_unlock(),
- {next_state,open,Data#{remaining := Code},10000};
+ {next_state, open, Data#{remaining := Code},
+ [{state_timeout,10000,lock}]};
[Digit|Rest] -> % Incomplete
- {next_state,locked,Data#{remaining := Rest}};
+ {next_state, locked, Data#{remaining := Rest}};
[_|_] -> % Wrong
- {next_state,locked,Data#{remaining := Code}}
+ {next_state, locked, Data#{remaining := Code}}
end.
-open(timeout, _, Data) ->
+open(state_timeout, lock, Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state, locked, Data};
open(cast, {button,_}, Data) ->
- do_lock(),
- {next_state,locked,Data}.
+ {next_state, open, Data}.
]]>
+open(state_timeout, lock, Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state, locked, Data};
]]>
+
-
-
do_unlock(),
Tref = erlang:start_timer(10000, self(), lock),
- {next_state,open,Data#{remaining := Code, timer := Tref}};
+ {next_state, open, Data#{remaining := Code, timer => Tref}};
...
open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state,locked,maps:remove(timer, Data)};
open(cast, {button,_}, Data) ->
{keep_state,Data};
...
]]>
+
- case {EventType,EventContent} of
- {enter,_OldState} ->
- Tref = erlang:start_timer(10000, self(), lock),
+ {open,LockButton}, Data) ->
+ case {EventType, EventContent} of
+ {enter, _OldState} ->
do_unlock(),
- {keep_state,Data#{timer := Tref},[hibernate]};
+ {keep_state_and_data,
+ [{state_timeout,10000,lock},hibernate]};
...
]]>