-
Notifications
You must be signed in to change notification settings - Fork 10
fsm behavior
Each agent is explicitly defined in the form of a finite state machine or a pushdown automation and is driven by events generated externally by interface processors or internally by event handles or timers. Each agent is fault-isolated. Incorrect agent implementation should not affect the behavior of an agent which doesn't have any errors.
State transition table defines finite state machine or pushdown automation in the form of list of transitions trans(), returned by Module:trans/0. Each transition state_trans() is a tuple of a state and a list of transitions to another state. The transitions trans_arrow() are double tuple or quadruple. If you have only double tuples as trans_arrow() in a transition table, this table defines finite state machine, otherwise it is a pushdown automation.
The requirement to define a state transition table explicitly and the support of pushdown automation option are key differences from the standard Erlang behaviors gen_fsm and gen_statem.
Below is an example of a state transition table, defined as macro ?TRANS returned by callback trans() in fsm behavior implementation src/fsm_triv_alh.erl:
-define(TRANS, [
{idle,
[{internal, idle},
{answer_timeout, idle},
{send_data, transmit},
{backoff, transmit},
{sendend, idle},
{recvend, idle},
{sendstart, sense},
{recvstart, sense}
]},
{transmit,
[{data_sent, idle}
]},
{sense,
[{answer_timeout, sense},
{backoff, sense},
{send_data, sense},
{sendstart, sense},
{recvstart, sense},
{sendend, idle},
{recvend, idle}
]},
{alarm,[]}
]).
trans() -> ?TRANS.
The defined above finite state machine has states idle, transmit, sense and alarm and events internal, answer_timeout, send_data, backoff, sendend, recvend, sendstart, recvstart, data_sent. Per default transition occurs to the state alarm, if a reaction on some specific event is not defined in the state transition table in any given state.
States and events in the state transition tables can be any atom. The fsm behavior implementation must export event handlers for each event. Handle function name is build as handle_ plus the state name. For the defined above transition tables the following handlers are implemented and exported in the src/fsm_triv_alh.erl:
-export([handle_idle/3, handle_alarm/3, handle_sense/3, handle_transmit/3]).
The handler are not called explicitly in the fsm implementation. The corresponding to the current state and event handler call occurs from fsm:run_event()/3. The fsm:run_event()/3 must be used in the Module:handle_event/3 callback implementation, when incoming messages are preprocessed and converted to the event.
Agents are connected to each other and to external processes via interface processors, converting raw data coming from external interfaces to well-defined messages and messages received from the corresponding agents back to raw data. Each agent specify particular interface processors required to be connected to it.
Interface processors implement role_worker behavior.
When new messages arrive from an interface processor, Module:handle_event/3 is called, the message is passed as a third parameter Term. The Term reception may trigger event in the Module:handle_event/3 and cause the fsm:run_event/3 call.
In the example below, if double tuple is received from any interface processor and the first tuple element is an atom send_data, event send_data is triggered by modifying event field of the internal state SM and fsm:run_event/3 is called to handle this event according to the state transition table.
handle_event(MM, #sm{env = #{got_sync := Got_sync}} = SM, Term) ->
case Term of
...
{send_data, _} when Got_sync ->
fsm:run_event(MM, SM#sm{event=send_data}, Term);
...
end.
Interface processors must be bound to the fsm implementation in the fsm_worker implementation module based on the configuration file.
tbd
The central callback for event processing is a Module:handle_event/3 that preprocesses the incoming messages. Here these messages received from the interface processors or from timers are transformed into events of the particular state machine. For each state a corresponding handler must be created, called on each event occurrence.
tbd
time() = {s, integer() > 0} | {ms, integer() > 0} | {us, integer() > 0}
trans() = [state_trans()]
state_trans() = {State :: atom(), [trans_arrow()]}
trans_arrow() =
{Event :: atom(), State :: atom()} |
{Event :: atom(), Pop :: atom(), Push :: atom(), State :: atom()}
at() anything that can be accepted by role_at:from_term
Types
SM = sm()
Event = atom()
Result = sm()
Types
SM = sm()
Result = sm()
Types
SM = sm()
EventList = [atom()]
Result = sm()
Types
SM = sm()
Time = time()
Event = atom()
Result = sm()
Types
SM = sm()
Event = atom()
Result = true | false
Types
SM = sm()
Event = atom()
Result = sm()
Types
MiddleMan = mm()
SM = sm()
Term = any()
Result = sm()
Types
SM = sm()
AT = at()
Result = sm()
Types
SM = sm()
Target = atom()
Term = any()
Result = sm()
Types
SM = sm()
Target = atom()
Term = any()
Result = sm()
Types
SM = sm()
MiddleMan = mm()
EOpts = any()
Term = any()
Condition = fun((MiddleMan, Role :: atom(), EOpts) -> true | false)
Result = sm()
Types
SM = sm()
Target = atom()
Result = true | false
The following functions must be exported from a fsm callback module.
Types
SM = sm()
Result = {ok,pid()} | ignore | {error,any()}
Types
SM = sm()
Result = sm()
Types
SM = sm()
Types
SM = sm()
MM = mm()
Term = any()
Result = sm()
Types
Result = trans()
Types
Result = [atom()]
Types
Result = atom()