Don't like ads? PRO users don't see any ads ;-)
Guest

Untitled

By: a guest on Sep 21st, 2012  |  syntax: None  |  size: 4.39 KB  |  hits: 6  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. # FSM Strawman
  2.  
  3. I've been thinking about some of the shortcomings of `gen_fsm`, and some
  4. common FSM actions I've either implemented myself, or seen my FSMs suffer
  5. from not having. I've been playing around with a couple ideas for alternative
  6. ways to describe an FSM that would still be driven by `gen_fsm`. This is the
  7. the first concrete idea I've come up with, but it certainly still is far from
  8. completely fleshed out. That said, I think there's enough to start getting
  9. feedback.
  10.  
  11. ### Motivation
  12.  
  13. I've often noticed certain patterns in mine and others' FSMs. Things like,
  14. update the `StateData` in this way whenever this event is received, regardless
  15. of whate state-name I'm currently in. Or count how many times I leave
  16. the any state and go into the "overflow" state, even though there may
  17. be several places in code this happens.
  18.  
  19. ### Current Idea
  20.  
  21. What I've come up with so far is a set of callbacks that you (optionally)
  22. implement. I say optionally because several of them have sane defaults.
  23. The options are then passed in as a `proplist` as args to the "FSM translator",
  24. a module that implements `gen_fsm`, but goes through state changes just like
  25. your spec says. The functions you can implement are:
  26.  
  27. 1. `init/1` Just like normal `gen_fsm` init
  28. 1. `next_state_sync/3` A pure fun that is caled with `[Event, PrevStateName, StateData]`
  29. and returns just a `StateName`, or `{stop, ...}`
  30. 1. `next_state_async/3` A pure fun that is caled with `[Event, PrevStateName, StateData]`
  31. 1. `after_state/2` (optional) A `StateData` modifying fun called with `[LeavingStateName, StateData]`
  32. 1. `before_state/2` (optional) A `StateData` modifying fun called with `[EnteringStateName, StateData]`
  33. 1. `on_event/2` (optional) A `StateData` modifying fun called with `[EventName, StateData]`
  34. 1. `state_change_sync/4` The most like normal FSM state callback, `[Event, PrevStateName, NextState, StateData]`
  35. 1. `state_change_async/5` ... ^, plus the `From` argument
  36. 1. `handle_info/3` (optional)
  37.  
  38. The order of callbacks is defined to be:
  39.  
  40.  1. `next_state_[sync, async]` call this first just to get the name of the next state,
  41.  as it doesn't modify state data at all
  42.  1. `after_state`
  43.  1. `on_event`
  44.  1. `before_state`
  45.  1. `state_change_[sync, async]`
  46.  
  47. ### What it looks like
  48.  
  49. ```erlang
  50. -module(simple_fsm).
  51.  
  52. -export([start_link/1]).
  53.  
  54. -record(state, {count :: integer(),
  55.                 max_count :: integer()}).
  56.  
  57. start_link(Args) ->
  58.     %% fsm2 is the module that takes your spec
  59.     %% and runs an FSM according to it
  60.     fsm2:start_link([spec(), Args]).
  61.  
  62. spec() ->
  63.     [{init, fun init/1},
  64.      {on_event, fun on_event/2},
  65.      {next_state_sync, fun next_state_sync/3},
  66.      {next_state_async, fun next_state_async/3},
  67.      {before_state, fun before_state/2},
  68.      {after_state, fun after_state/2},
  69.      {state_change_sync, fun state_change_sync/4},
  70.      {state_change_async, fun state_change_async/5}].
  71.  
  72.  
  73. init([StartingCount, MaxCount]) ->
  74.     #state{count=StartingCount,
  75.            max_count=MaxCount}.
  76.  
  77. on_event({incr, _Amount}, State=#state{count=C,
  78.                                        max_count=C}) ->
  79.     State;
  80. on_event({incr, Amount}, State=#state{count=C,
  81.                                       max_count=M}) when (C + Amount) =< M ->
  82.     State#state{count=C + Amount};
  83. on_event({incr, _Amount}, State=#state{max_count=M}) ->
  84.     State#state{count=M};
  85. on_event({decr, Amount}, State=#state{count=C}) ->
  86.     Count = erlang:max(0, (C - Amount)),
  87.     State#state{count=Count};
  88. %% catchall, w/ identity fun
  89. on_event(_Event, State) ->
  90.     State.
  91.  
  92. after_state(full, State) ->
  93.     lager:debug("leaving full state"),
  94.     State;
  95. after_state(_StateName, State) ->
  96.     State.
  97.  
  98. before_state(full, State) ->
  99.     lager:debug("entering full state"),
  100.     State;
  101. before_state(_StateName, State) ->
  102.     State.
  103.  
  104. next_state_sync(_Event, _PrevStateName, State=#state{count=C, max_count=C}) ->
  105.     {next_state, max, State};
  106. next_state_sync(_Event, _PrevStateName, State) ->
  107.     {next_state, free, State}.
  108.  
  109. next_state_async(_Event, _PrevStateName, State) ->
  110.     {stop, unexpected, State}.
  111.  
  112. state_change_sync(status, _PrevState, full, State=#state{count=C}) ->
  113.     Reply = io_lib:format("in full state with count ~p", [C]),
  114.     {reply, Reply, State};
  115. state_change_sync(status, _PrevState, free, State=#state{count=C}) ->
  116.     Reply = io_lib:format("in free state with count ~p", [C]),
  117.     {reply, Reply, State}.
  118.  
  119. state_change_async(_Event, _PrevState, _CurrentState, StateData, _From) ->
  120.     {stop, unexpected, StateData}.
  121. ```