defmodule Crossing do
@behaviour :gen_statem
def start_link, do: :gen_statem.start_link(__MODULE__, [], [])
def cross(crossing), do: :gen_statem.call(crossing, :cross)
def light(crossing), do: :gen_statem.call(crossing, :light)
# `:state_functions` means one function per state:
# `green/3`, `yellow/3`, `red/3`. Each gets the event
# type, the event and the data. `:state_enter` turns
# on enter calls.
@impl true
def callback_mode, do: [:state_functions, :state_enter]
# `init/1` returns the first state and the data. The
# data here counts the people who crossed.
@impl true
def init([]), do: {:ok, :green, 0}
# An enter call gets `:enter` and the previous state.
# It may set timeouts, but it can't change state.
def green(:enter, _previous, _crossed) do
IO.puts("cars: green")
:keep_state_and_data
end
# Someone wants to cross: switch to yellow and
# `:postpone` the call. Postponed events come back
# after every state change, so the same call is
# handled again in `yellow/3`.
def green({:call, _from}, :cross, crossed) do
{:next_state, :yellow, crossed, [:postpone]}
end
def green(type, event, crossed), do: common(type, event, :green, crossed)
# A state timeout: 30 ms after entering yellow, the
# event `:turn_red` arrives. Leaving yellow any other
# way would cancel it.
def yellow(:enter, _previous, _crossed) do
IO.puts("cars: yellow")
{:keep_state_and_data, [{:state_timeout, 30, :turn_red}]}
end
def yellow(:state_timeout, :turn_red, crossed), do: {:next_state, :red, crossed}
# Still not safe. Postpone again; the caller keeps
# waiting inside `cross/1`.
def yellow({:call, _from}, :cross, _crossed), do: {:keep_state_and_data, [:postpone]}
def yellow(type, event, crossed), do: common(type, event, :yellow, crossed)
# Red for cars. Every postponed `cross` is retried
# here, in arrival order, and finally gets its reply.
def red(:enter, _previous, _crossed) do
IO.puts("cars: red")
{:keep_state_and_data, [{:state_timeout, 50, :turn_green}]}
end
def red({:call, from}, :cross, crossed) do
{:keep_state, crossed + 1, [{:reply, from, :walk}]}
end
def red(:state_timeout, :turn_green, crossed), do: {:next_state, :green, crossed}
def red(type, event, crossed), do: common(type, event, :red, crossed)
# Events every state handles the same way. With
# `:state_functions` each state has to pass them on.
defp common({:call, from}, :light, state, crossed) do
{:keep_state_and_data, [{:reply, from, {state, crossed}}]}
end
end