-module(ebb_par_fsm). -behaviour(gen_fsm). -include("../include/ebb_prim.hrl"). %% API -export([start_link/3]). %% gen_fsm callbacks -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). -export([forwarding/2]). -define(DICT, orddict). -record(state, {par, in_map, input}). %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link() -> {ok,Pid} | ignore | {error,Error} %% Description:Creates a gen_fsm process which calls Module:init/1 to %% initialize. To ensure a synchronized start-up procedure, this function %% does not return until Module:init/1 has returned. %%-------------------------------------------------------------------- start_link(Par = #par{}, Run, Receiver) when is_pid(Receiver) -> gen_fsm:start_link(?MODULE, {Par, Run, Receiver}, []). %%==================================================================== %% gen_fsm callbacks %%==================================================================== %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, StateName, State} | %% {ok, StateName, State, Timeout} | %% ignore | %% {stop, StopReason} %% Description:Whenever a gen_fsm is started using gen_fsm:start/[3,4] or %% gen_fsm:start_link/3,4, this function is called by the new process to %% initialize. %%-------------------------------------------------------------------- init({Par, Run, Receiver}) -> Fsms = run_ops(Par, Run, Receiver), State = #state{par=Par, in_map=build_in_map(Par, Fsms), input=?DICT:new()}, {ok, forwarding, State}. %%-------------------------------------------------------------------- %% Function: %% state_name(Event, State) -> {next_state, NextStateName, NextState}| %% {next_state, NextStateName, %% NextState, Timeout} | %% {stop, Reason, NewState} %% Description:There should be one instance of this function for each possible %% state name. Whenever a gen_fsm receives an event sent using %% gen_fsm:send_event/2, the instance of this function with the same name as %% the current state name StateName is called to handle the event. It is also %% called if a timeout occurs. %%-------------------------------------------------------------------- forwarding({in, N, Val}, State = #state{par=#par{in=In}, input=Input, in_map=Map}) when N > 0, N =< In -> State2 = State#state{input=?DICT:store(N, Val, Input)}, {N2, Receiver} = ?DICT:fetch(N, Map), ebb_event:in(Receiver, N2, Val), case is_done(State2) of true -> {stop, normal, State2}; false -> {next_state, forwarding, State2} end. %%-------------------------------------------------------------------- %% Function: %% state_name(Event, From, State) -> {next_state, NextStateName, NextState} | %% {next_state, NextStateName, %% NextState, Timeout} | %% {reply, Reply, NextStateName, NextState}| %% {reply, Reply, NextStateName, %% NextState, Timeout} | %% {stop, Reason, NewState}| %% {stop, Reason, Reply, NewState} %% Description: There should be one instance of this function for each %% possible state name. Whenever a gen_fsm receives an event sent using %% gen_fsm:sync_send_event/2,3, the instance of this function with the same %% name as the current state name StateName is called to handle the event. %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- %% Function: %% handle_event(Event, StateName, State) -> {next_state, NextStateName, %% NextState} | %% {next_state, NextStateName, %% NextState, Timeout} | %% {stop, Reason, NewState} %% Description: Whenever a gen_fsm receives an event sent using %% gen_fsm:send_all_state_event/2, this function is called to handle %% the event. %%-------------------------------------------------------------------- handle_event(_Event, StateName, State) -> {next_state, StateName, State}. %%-------------------------------------------------------------------- %% Function: %% handle_sync_event(Event, From, StateName, %% State) -> {next_state, NextStateName, NextState} | %% {next_state, NextStateName, NextState, %% Timeout} | %% {reply, Reply, NextStateName, NextState}| %% {reply, Reply, NextStateName, NextState, %% Timeout} | %% {stop, Reason, NewState} | %% {stop, Reason, Reply, NewState} %% Description: Whenever a gen_fsm receives an event sent using %% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle %% the event. %%-------------------------------------------------------------------- handle_sync_event(_Event, _From, StateName, State) -> Reply = ok, {reply, Reply, StateName, State}. %%-------------------------------------------------------------------- %% Function: %% handle_info(Info,StateName,State)-> {next_state, NextStateName, NextState}| %% {next_state, NextStateName, NextState, %% Timeout} | %% {stop, Reason, NewState} %% Description: This function is called by a gen_fsm when it receives any %% other message than a synchronous or asynchronous event %% (or a system message). %%-------------------------------------------------------------------- handle_info(_Info, StateName, State) -> {next_state, StateName, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, StateName, State) -> void() %% Description:This function is called by a gen_fsm when it is about %% to terminate. It should be the opposite of Module:init/1 and do any %% necessary cleaning up. When it returns, the gen_fsm terminates with %% Reason. The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, _StateName, _State) -> ok. %%-------------------------------------------------------------------- %% Function: %% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- run_ops(#par{ops=Ops}, Run, Receiver) -> {_, Fsms} = lists:foldl( fun(Op, {Offset, Fsms}) -> Out = ebb_prim:out_arity(Op), {ok, Target} = case Offset of 0 -> {ok, Receiver}; _ -> ebb_offset_fsm:start_link(Out, Offset, Receiver) end, case Run(Op, Target) of {ok, Fsm} -> {Offset+Out, [Fsm | Fsms]}; ignore -> {Offset+Out, [ignore | Fsms]} end end, {0, []}, Ops), lists:reverse(Fsms). build_in_map(#par{ops=Ops}, Fsms) -> {_, Map} = lists:foldl( fun({Op, Fsm}, {Offset, Map}) -> In = ebb_prim:in_arity(Op), L = [ {N+Offset, {N, Fsm}} || N <- lists:seq(1, In) ], {Offset+In, L ++ Map} end, {0, []}, lists:zip(Ops, Fsms)), ?DICT:from_list(Map). is_done(#state{par=#par{in=In}, input=Inputs}) -> (?DICT:size(Inputs) == In).