From 6ee0aefd8a0ea9c165211c42d5244182b5aa9210 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen <raimo@erlang.org>
Date: Tue, 13 Sep 2016 14:00:04 +0200
Subject: Implement state entry events

---
 system/doc/design_principles/statem.xml | 152 ++++++++++++++++++++++++--------
 1 file changed, 113 insertions(+), 39 deletions(-)

(limited to 'system/doc')

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>
@@ -192,13 +192,42 @@ handle_event(EventType, EventContent, State, Data) ->
     </section>
   </section>
 
+<!-- =================================================================== -->
+
+  <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}};
@@ -1005,6 +1040,49 @@ enter(Tag, State, Data) ->
     ]]></code>
   </section>
 
+<!-- =================================================================== -->
+
+  <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>
@@ -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]};
-- 
cgit v1.2.3