state_server v0.3.1 StateServer.State behaviour View Source
A behaviour that lets you organize code for your `StateServer` states.
Organization
When you define your `StateServer`, the `StateServer` module gives you the opportunity to define state modules. These are typically (but not necessarily) submodules scoped under the main `StateServer` module. In this way, your code for handling events can be neatly organized by state. In some (but not all) cases, this may be the most appropriate way to keep your state machine codebase sane.
Defining the state module.
the basic syntax for defining a state module is as follows:
defstate MyModule, for: :state do
# ... code goes here ...
def handle_call(:increment, _from, data) do
{:reply, :ok, update: data + 1}
end
end
note that the callback directives defined in this module are identical to those of `StateServer`, except that they are missing the `state` argument.
External state modules
You might want to use an external module to handle event processing for one your state machine. Reasons might include:
- to enable code reuse between state machines
- if your codebase is getting too long and you would like to put state modules in different files.
If you choose to do so, there is a short form `defstate` call, which is as follows:
defstate ExternalModule, for: :state
Precedence and Defer statements
Note that `handle_` functions written directly in the body of the `StateServer` take precedence over any functions written as a part of a state module. In the case where there are competing function calls, your handler functions written in the body* of the `StateServer` may emit `:defer` as a result, which will punt the processing of the event to the state modules.
# make sure query calls happen regardless of state
def handle_call(:query, _from, _state, data) do
{:reply, {state, data}}
end
# for all other call payloads, send to the state modules
def handle_call(_, _, _, _) do
:defer
end
defstate Start, for: :start do
def handle_call(...) do...
since this is a common pattern, we provide a `defer` macro which is equivalent to the above.
# make sure query calls happen regardless of state
def handle_call(:query, _from, _state, data) do
{:reply, {state, data}}
end
# for all other call payloads, send to the state modules
defer handle_call
You do not need this pattern for event handlers which are not implemented in the body of the function.
Example
The following code should produce a "light switch" state server that announces when it's been flipped.
defmodule SwitchWithStates do
@doc """
implements a light switch as a state server. In data, it keeps a count of
how many times the state of the light switch has changed.
On transition, it sends to standard error a comment that it has been flipped.
Note that the implementations are different between the two states.
"""
use StateServer, off: [flip: :on],
on: [flip: :off]
@type data :: non_neg_integer
def start_link, do: StateServer.start_link(__MODULE__, :ok)
@impl true
def init(:ok), do: {:ok, 0}
def flip(srv), do: StateServer.call(srv, :flip)
def query(srv), do: StateServer.call(srv, :query)
@impl true
def handle_call(:flip, _from, _state, _count) do
{:reply, :ok, transition: :flip}
end
defer handle_call
# we must defer the handle_call statement because there are both shared and
# individual implementation of handle_call features.
defstate Off, for: :off do
@impl true
def handle_transition(:flip, count) do
IO.puts(:stderr, "switch #{inspect self()} flipped on, #{count} times turned on")
{:noreply, update: count + 1}
end
@impl true
def handle_call(:query, _from, _count) do
{:reply, "state is off"}
end
end
defstate On, for: :on do
@impl true
def handle_transition(:flip, count) do
IO.puts(:stderr, "switch #{inspect self()} flipped off, #{count} times turned on")
:noreply
end
@impl true
def handle_call(:query, _from, _count) do
{:reply, "state is on"}
end
end
end
Link to this section Summary
Link to this section Callbacks
on_state_entry(atom, term)
View Source (optional)on_state_entry(atom(), term()) :: StateServer.on_state_entry_response()