View Source Protean.Builder (Protean v0.1.0-alpha.2)
API for defining Protean machines.
This module is imported by default when use Protean
is invoked.
defining-a-machine
Defining a machine
At the outermost level, machines are specified as a keyword list, usually associated with the
@machine
module attribute of the defining module.
@machine [
states: [
# ...
]
]
For the most part, a machine definition is similar to a compound or parallel state definition,
except that it allows for an addition :assigns
option to specify the default assigns for the
machine context.
@machine [
assigns: %{
# ...
},
# ...
]
The top-level machine can be parallel by specifying type: :parallel
:
@machine [
type: :parallel,
states: [
# ...
]
]
See compound/2
and parallel/2
for corresponding options.
Link to this section Summary
Types
Additional state stored in the machine context.
Functions
Builds an atomic state.
Builds a compound state.
Builds a delayed transition.
Builds a final state.
Builds an invoked process.
Builds a pattern-matching event transition.
Builds a parallel state.
Builds a transition.
Link to this section Types
@type action() :: Protean.Action.t() | term()
@type assigns() :: Enumerable.t()
Additional state stored in the machine context.
The specified assigns will be converted to a map/0
.
@type atomic_state_option() :: {:invoke, invokes()} | {:entry, actions()} | {:exit, actions()} | {:always, transitions()} | {:after, delayed_transitions()} | {:on, event_transitions()}
@type atomic_state_options() :: [atomic_state_option()]
@type compound_machine_option() :: {:assigns, assigns()} | compound_state_option()
@type compound_state_option() :: atomic_state_option() | {:initial, state_name()} | {:states, states()} | {:done, transitions()}
@type compound_state_options() :: [compound_state_option()]
@type delayed_transition() :: [delayed_transition_option()]
@type delayed_transition_option() :: transition_option() | {:delay, milliseconds :: non_neg_integer()}
@type delayed_transitions() :: [delayed_transition()]
@type event_transition() :: {matcher :: function() | term(), transition()}
@type event_transitions() :: [event_transition()]
@type final_state_options() :: [final_state_option()]
@type invoke_option() :: {:id, String.t()} | {:done, transitions()} | {:error, transitions()} | {:autoforward, boolean()}
@type invoke_options() :: [invoke_option()]
@type machine_options() :: [compound_machine_option()] | [parallel_machine_option()]
@type parallel_machine_option() :: {:type, :parallel} | {:assigns, assigns()} | parallel_machine_option()
@type parallel_state_option() :: atomic_state_option() | {:states, states()} | {:done, transitions()}
@type parallel_state_options() :: [parallel_state_option()]
@type state() :: {state_name(), keyword()}
@type states() :: [state()]
@type transition() :: [transition_option()]
@type transition_option() :: {:target, state_name()} | {:actions, actions()} | {:guard, Protean.Guard.t()}
@type transition_options() :: [transition_option()]
@type transitions() :: [transition()]
Link to this section Functions
@spec atomic(state_name(), atomic_state_options()) :: state()
Builds an atomic state.
Atomic states are simple states that cannot define children, but represent some intermediary state of the machine.
states: [
atomic(:loading,
# ...
)
]
options
Options
:invoke
- list of processes to invoke, seeinvoked/3
;:entry
- actions to execute when entering this state;:exit
- actions to execute when exiting this state;:always
- transitions to immediately take when their guard is true, seetransition/1
;:after
- transitions to take after a given delay, seedelay/2
;:on
- transitions to take in response to an event, seematch/2
.
@spec compound(state_name(), compound_state_options()) :: state()
Builds a compound state.
Compound states have children defined by a :states
list, of which only one will be active at
a given time. They additional define an :initial
attribute specifying which child should
become active if we transition directly to the compound state.
states: [
compound(:parent,
initial: :child_a,
states: [
atomic(:child_a)
]
)
]
Compound states can define a :done
transition that will be taken if one of its final
children become active. In the example below, the :parent
state will transition to its
sibling if the final :child_b
state is entered.
states: [
compound(:parent,
initial: :child_a,
done: transition(target: :sibling),
states: [
atomic(:child_a,
# ...
),
final(:child_b)
]
)
]
options
Options
:initial
(required) - child state to enter when entering the compound state;:states
(required) - one or more child states;:done
- transition to take if afinal
child state is entered;- all options available to
atomic/2
.
@spec delay(milliseconds :: non_neg_integer() | term(), transition_options()) :: keyword()
Builds a delayed transition.
Delayed transitions run automatically after the given delay so long as the machine is still in the state that defined it and any given guard allows it.
Accepts the same options as transition/1
.
@spec final(state_name(), final_state_options()) :: state()
Builds a final state.
Final states are a variation of atomic states that represent some form of completion. Final
states cannot define transitions of their own, but entering a final state can trigger a
transition in a compound or parallel parent. See compound/2
and parallel/2
.
states: [
final(:completed)
]
options
Options
:entry
- actions to execute when entering this state;:exit
- actions to execute when exiting this state (as a result of a parent transition).
@spec invoked(Protean.invoke_type(), term(), invoke_options()) :: keyword()
Builds an invoked process.
Invoked processes are subprocesses supervised by Protean that are started when the machine enters the state that defines them and exited when the machine exits that state.
type
(optional, defaults to:delegate
)spec
- iftype
is other than:delegate
this must be a value that can be invoked directly. Otherwise,Protean.invoke/3
will be called to get the type and spec.
invoke-types
Invoke types
invoke_type
determines how the process will be started and the kind of interaction it will
have with the machine. It can be one of:
:delegate
- delegate toProtean.invoke/3
.:task
- an asynchronous task that is expected to return a single value, after which any specified:done
transition for the invoke will run. The return value will be available as the transition event. The:error
transition will run if the task crashes.:proc
- an arbitrary process (including other machines). The:done
transition is run if the process exits normally. The:error
transition is run if the process crashes or exits abnormally (with a reason other than:normal
,:shutdown
, or{:shutdown, term()}
).:stream
- an event stream that will be consumed with each element of the stream being sent to the machine as an event. The:done
transition is run after the stream is fully consumed. The:error
transition is run if the stream crashes.
options
Options
:id
:done
:error
:autoforward
example
Example
Start a task specified by a callback at runtime.
invoke: [
invoked(:some_long_running_task,
done: transition(target: :completed, actions: :save_result),
error: transition(target: :failed, actions: :log_error)
)
]
# ...
@impl true
def invoke(:some_long_running_task, _, _) do
{:task, fn -> ... end}
end
Start a task specified directly.
invoke: [
invoked(:task, fn -> ... end,
done: transition(target: :completed, actions: :save_result),
error: transition(target: :failed, actions: :log_error)
)
]
Builds a pattern-matching event transition.
Accepts the same options as transition/1
.
example
Example
on: [
match({:event_with_payload, _payload}, action: :save_payload),
match(%Events.OtherEvent{}, target: :other)
]
@spec parallel(state_name(), parallel_state_options()) :: state()
Builds a parallel state.
Parallel states have child states defined by a :states
list, all of which will be considered
active concurrently when the parallel state is active.
states: [
parallel(:parent,
states: [
atomic(:child_a,
entry: :child_a_action
),
atomic(:child_b,
entry: :child_b_action
)
]
)
]
In the example above, transitioning to :parent
would enter both child states and cause both
of their entry actions to execute.
Parallel states can define a :done
transition that will be taken when all of its children
are in a final state. Usually, this means the parallel state's children are compound states
with active final children.
states: [
parallel(:parent,
done: transition(target: :sibling),
states: [
compound(:compound_a,
states: [
atomic(:a_child1),
final(:a_child2)
]
),
compound(:compound_b,
states: [
atomic(:b_child1),
final(:b_child2)
]
)
]
)
]
In the example above, the parent parallel state will transition to its sibling once both compound states have active final children.
options
Options
:states
(required) - one or more child states, all of which will be concurrently entered when the parallel state becomes active;:done
- transition to take when all children are in a final state.- all options available to
atomic/2
.
@spec transition(transition_options()) :: keyword()
Builds a transition.
options
Options
:target
- the target state of the transition;:actions
- one or more actions that should be executed when the transition occurs;:guard
- condition that must be true in order for the transition to occur.
guards
Guards
See Protean.Guard
TODO