Current section
Files
Jump to
Current section
Files
lib/engine/process_manager/process_manager.ex
# Original work: Copyright (c) 2013. Saša Jurić
# Modified work: Copyright (c) 2016 Work Capital - Henry Hazan - henry@work.capital
defmodule Engine.Fsm do
defmacro __using__(opts) do
quote do
import Fsm
#import Kernel, except: [apply: 2]
defstruct uuid: nil, state: nil, version: 0, data: [], subscriptions: []
defmodule Data do
defstruct unquote(opts[:data])
end
@declaring_state nil
@declared_events HashSet.new
def new, do:
%__MODULE__{state: unquote(opts[:initial_state]), data: unquote(opts[:data])}
@doc "create a new data structure with an uuid"
def new(uuid) do
%__MODULE__{state: unquote(opts[:initial_state]),
data: unquote(opts[:data]),
subscriptions: unquote(opts[:subscriptions]),
uuid: uuid}
end
def state(%__MODULE__{state: state}), do: state
def data(%__MODULE__{data: data}), do: data
defp change_state(%__MODULE__{} = fsm, {:action_responses, responses}), do: parse_action_responses(fsm, responses)
defp change_state(%__MODULE__{} = fsm, _), do: fsm
defp parse_action_responses(fsm, responses) do
Enum.reduce(responses, fsm, fn(response, fsm) ->
handle_action_response(fsm, response)
end)
end
defp handle_action_response(fsm, {:next_state, next_state}), do: %__MODULE__{fsm | state: next_state}
defp handle_action_response(fsm, {:new_data, new_data}), do: %__MODULE__{fsm | data: new_data}
defp handle_action_response(fsm, {:respond, response}), do: {response, fsm}
# utility function to add commands to the command list
def dispatch(command_list, command), do: command_list ++ [command]
# clean command list from 'data'
def clean_commands(fsm), do: %{fsm | data: []}
# dispatch a command for the given process manager
# defp dispatch(%__MODULE__{commands: commands} = process_manager, command) do
# %__MODULE__{process_manager |
# commands: commands ++ [command]
# }
# end
# Receives a single event and is used to mutate the process manager's internal state
defp update(%__MODULE__{state: state} = process_manager, event) do
state = __MODULE__.apply(state, event)
%__MODULE__{process_manager |
state: state
}
end
end
end
def next_state(state), do: {:action_responses, [next_state: state]}
def next_state(state, data), do: {:action_responses, [next_state: state, new_data: data]}
def respond(response), do: {:action_responses, [respond: response]}
def respond(response, state), do: {:action_responses, [next_state: state, respond: response]}
def respond(response, state, data), do: {:action_responses, [next_state: state, new_data: data, respond: response]}
defmacro defstate(state, state_def) do
quote do
state_name = case unquote(Macro.escape(state, unquote: true)) do
name when is_atom(name) -> name
{name, _, _} -> name
end
@declaring_state state_name
unquote(state_def)
@declaring_state nil
end
end
# events
defmacro defevent(event) do
decl_event(event, false)
end
defmacro defeventp(event) do
decl_event(event, true)
end
# commands
defmacro defcommand(event) do
decl_event(event, false)
end
defmacro defcommandp(event) do
decl_event(event, true)
end
defp decl_event(event, private) do
quote do
{event_name, arity} = case unquote(Macro.escape(event, unquote: nil)) do
event_name when is_atom(event_name) -> {event_name, 0}
{:/, _, [{event_name, _, _}, arity]} -> {event_name, arity}
{event_name, _, _} -> {event_name, 0}
end
args = case arity do
0 -> []
n -> Enum.to_list(1..n)
end
private = unquote(private)
unquote(define_interface)
end
end
# events
defmacro defevent(event, opts) do
do_defevent(event, opts, opts[:do])
end
defmacro defevent(event, opts, do: event_def) do
do_defevent(event, opts, event_def)
end
defmacro defeventp(event, opts) do
do_defevent(event, [{:private, true} | opts], opts[:do])
end
defmacro defeventp(event, opts, do: event_def) do
do_defevent(event, [{:private, true} | opts], event_def)
end
# commands [behave like events...]
defmacro defcommand(event, opts) do
do_defevent(event, opts, opts[:do])
end
defmacro defcommand(event, opts, do: event_def) do
do_defevent(event, opts, event_def)
end
defmacro defcommandp(event, opts) do
do_defevent(event, [{:private, true} | opts], opts[:do])
end
defmacro defcommandp(event, opts, do: event_def) do
do_defevent(event, [{:private, true} | opts], event_def)
end
defp do_defevent(event_decl, opts, event_def) do
quote do
unquote(extract_args(event_decl, opts, event_def))
unquote(define_interface)
unquote(implement_transition)
end
end
defp extract_args(event_decl, opts, event_def) do
quote do
{event_name, args} = case unquote(Macro.escape(event_decl, unquote: true)) do
:_ -> {:_, []}
name when is_atom(name) -> {name, []}
{name, _, args} -> {name, args || []}
end
private = unquote(opts[:private])
state_arg = unquote(Macro.escape(opts[:state] || quote(do: _), unquote: true))
data_arg = unquote(Macro.escape(opts[:data] || quote(do: _), unquote: true))
event_arg = unquote(Macro.escape(opts[:event] || quote(do: _), unquote: true))
args_arg = unquote(Macro.escape(opts[:args] || quote(do: _), unquote: true))
event_def = unquote(Macro.escape(event_def, unquote: true))
guard = unquote(Macro.escape(opts[:when]))
end
end
defp define_interface do
quote bind_quoted: [] do
unless event_name == :_ or HashSet.member?(@declared_events, {event_name, length(args)}) do
interface_args = Enum.reduce(args, {0, []}, fn(_, {index, args}) ->
{
index + 1,
[{:"arg#{index}", [], nil} | args]
}
end)
|> elem(1)
|> Enum.reverse
body = quote do
transition(fsm, unquote(event_name), [unquote_splicing(interface_args)])
end
interface_args = [quote(do: fsm) | interface_args]
if private do
defp unquote(event_name)(unquote_splicing(interface_args)), do: unquote(body)
else
def unquote(event_name)(unquote_splicing(interface_args)), do: unquote(body)
end
@declared_events HashSet.put(@declared_events, {event_name, length(args)})
end
end
end
defp implement_transition do
quote bind_quoted: [] do
transition_args = [
if @declaring_state do
quote do
%__MODULE__{state: unquote(@declaring_state) = unquote(state_arg), data: unquote(data_arg)} = fsm
end
else
quote do
%__MODULE__{state: unquote(state_arg), data: unquote(data_arg)} = fsm
end
end,
quote do
unquote(if event_name == :_, do: quote(do: _), else: event_name) = unquote(event_arg)
end,
quote do
unquote(if event_name == :_, do: quote(do: _), else: args) = unquote(args_arg)
end
]
body = quote(do: change_state(fsm, (unquote(event_def))))
if guard do
def transition(unquote_splicing(transition_args)) when unquote(guard), do: unquote(body)
else
def transition(unquote_splicing(transition_args)), do: unquote(body)
end
end
end
end