一、什么是gen_event
按照书上定义在OTP中,它由通用事件管理器进程组成,该进程具有动态添加和删除的任意数量的事件处理程序。事件可以是例如错误,警报或要记录的某些信息
简单来说,就是gen_event行为运行一个了一个事件管理进程,该进程接受消息(事件),并根据消息(事件)做对应的事件处理,而提供的对应事件处理其实就是添加的“回调函数”(事件处理器)。与gen_server不一样的是,他把回调的函数分类成多个module并称为事件处理器,需要注意的是事件处理器不是一个进程,看源码你会发现只是事件管理器进程里构造了一个记录列表,里面储存了对应的module和函数参数,必要时就调用。gen_event在游戏开发中除了在日记模块有用到,其他的地方几乎没有应用
二、如何使用
使用OTP事件管理器需要做四件事:
- 编写事件处理程序,
- 创建事件管理器,
- 向管理器添加处理程序,
- 通知事件管理器
以erlang趣学指南的冰球比赛为案例
事件管理器
-module(curling).
-export([start_link/2, set_teams/3, add_points/3, next_round/1]).
-export([join_feed/2, leave_feed/2]).
-export([game_info/1]).
start_link(TeamA, TeamB) ->
%% 创建事件管理进程
{ok, Pid} = gen_event:start_link(),
%% 添加事件处理器 gen_event:add_handler(事件管理进程pid, 回调模块, 参数),curling_scoreboard为计分板
gen_event:add_handler(Pid, curling_scoreboard, []),
%% accumulator为累加器
gen_event:add_handler(Pid, curling_accumulator, []),
set_teams(Pid, TeamA, TeamB),
{ok, Pid}.
%%设置队伍
set_teams(Pid, TeamA, TeamB) ->
%% 事件通知
gen_event:notify(Pid, {set_teams, TeamA, TeamB}).
%%添加分数
add_points(Pid, Team, N) ->
gen_event:notify(Pid, {add_points, Team, N}).
%%下一回合
next_round(Pid) ->
gen_event:notify(Pid, next_round).
%% Subscribes the pid ToPid to the event feed.
%% The specific event handler for the newsfeed is
%% returned in case someone wants to leave
%%join_feed(Pid, ToPid) ->
%% HandlerId = {curling_feed, make_ref()},
%% gen_event:add_sup_handler(Pid, HandlerId, [ToPid]),
%% HandlerId.
%%
%%leave_feed(Pid, HandlerId) ->
%% gen_event:delete_handler(Pid, HandlerId, leave_feed).
%% Returns the current game state.
game_info(Pid) ->
gen_event:call(Pid, curling_accumulator, game_data).
事件处理器
-module(curling_scoreboard).
-behaviour(gen_event).
-export([init/1, handle_event/2, handle_call/2, handle_info/2, code_change/3,
terminate/2]).
%%计分板
init([]) ->
io:format("curling_scoreboard init ~n"),
{ok, []}.
handle_event({set_teams, TeamA, TeamB}, State) ->
io:format("curling_scoreboard set_teams ~n"),
io:format("Scoreboard: Team ~s vs. Team ~s~n", [TeamA, TeamB]),
%% curling_scoreboard_hw:set_teams(TeamA, TeamB),
{ok, State};
handle_event({add_points, Team, N}, State) ->
io:format("curling_scoreboard add_points ~n"),
[io:format("Scoreboard: increased score of team ~s by 1~n", [Team]) || _ <- lists:seq(1,N)],
%% [curling_scoreboard_hw:add_point(Team) || _ <- lists:seq(1,N)],
{ok, State};
handle_event(next_round, State) ->
io:format("curling_scoreboard next_round ~n"),
io:format("Scoreboard: round over~n"),
%% curling_scoreboard_hw:next_round(),
{ok, State};
handle_event(_, State) ->
{ok, State}.
handle_call(_, State) ->
{ok, ok, State}.
handle_info(_, State) ->
{ok, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
-module(curling_accumulator).
-behaviour(gen_event).
-export([init/1, handle_event/2, handle_call/2, handle_info/2, code_change/3,
terminate/2]).
-record(state, {teams=orddict:new(), round=0}).
%% 累加器
init([]) ->
io:format("accumulator init ~n"),
{ok, #state{}}.
handle_event({set_teams, TeamA, TeamB}, S=#state{teams=T}) ->
io:format("accumulator set_teams ~n"),
Teams = orddict:store(TeamA, 0, orddict:store(TeamB, 0, T)),
{ok, S#state{teams=Teams}};
handle_event({add_points, Team, N}, S=#state{teams=T}) ->
io:format("accumulator add_points ~n"),
Teams = orddict:update_counter(Team, N, T),
{ok, S#state{teams=Teams}};
handle_event(next_round, S=#state{}) ->
io:format("accumulator next_round ~n"),
{ok, S#state{round = S#state.round+1}};
handle_event(_Event, Pid) ->
{ok, Pid}.
handle_call(game_data, S=#state{teams=T, round=R}) ->
{ok, {orddict:to_list(T), {round, R}}, S};
handle_call(_, State) ->
{ok, ok, State}.
handle_info(_, State) ->
{ok, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
三、具体过程
直接从源码上看
Add handler 的过程
-spec add_handler(emgr_ref(), handler(), term()) -> term().
add_handler(M, Handler, Args) -> rpc(M, {add_handler, Handler, Args}).
调用gen:call
rpc(M, Cmd) ->
{ok, Reply} = gen:call(M, self(), Cmd, infinity),
Reply.
事件管理器进程收到消息时调用 handle_msg(Msg, Parent, ServerName, MSL, Debug)
{From, Tag, {add_handler, Handler, Args}} ->
{Hib, Reply, MSL1} = server_add_handler(Handler, Args, MSL),
?reply(Reply),
loop(Parent, ServerName, MSL1, Debug, Hib);
新增一个handler记录里面记录了对应的mdule和ID,然后调用mdule:init(事件处理器初始化),返回后把新增的handler存进列表并作为进程主循环的函数参数
server_add_handler({Mod,Id}, Args, MSL) ->
Handler = #handler{module = Mod,
id = Id},
server_add_handler(Mod, Handler, Args, MSL);
server_add_handler(Mod, Args, MSL) ->
Handler = #handler{module = Mod},
server_add_handler(Mod, Handler, Args, MSL).
server_add_handler(Mod, Handler, Args, MSL) ->
case catch Mod:init(Args) of
{ok, State} ->
{false, ok, [Handler#handler{state = State}|MSL]};
{ok, State, hibernate} ->
{true, ok, [Handler#handler{state = State}|MSL]};
Other ->
{false, Other, MSL}
end.
Notify
notify(M, Event) -> send(M, {notify, Event}).
向事件管理器进程发送消息
send({global, Name}, Cmd) ->
catch global:send(Name, Cmd),
ok;
send({via, Mod, Name}, Cmd) ->
catch Mod:send(Name, Cmd),
ok;
send(M, Cmd) ->
M ! Cmd,
ok.
事件管理器进程收到消息时调用 handle_msg(Msg, Parent, ServerName, MSL, Debug)
{notify, Event} ->
{Hib,MSL1} = server_notify(Event, handle_event, MSL, ServerName),
loop(Parent, ServerName, MSL1, Debug, Hib);
遍历handle列表,调用handle_event (Event, State)
server_notify(Event, Func, [Handler|T], SName) ->
case server_update(Handler, Func, Event, SName) of
{ok, Handler1} ->
{Hib, NewHandlers} = server_notify(Event, Func, T, SName),
{Hib, [Handler1|NewHandlers]};
{hibernate, Handler1} ->
{_Hib, NewHandlers} = server_notify(Event, Func, T, SName),
{true, [Handler1|NewHandlers]};
no ->
server_notify(Event, Func, T, SName)
end;
server_notify(_, _, [], _) ->
{false, []}.
%% server_update(Handler, Func, Event, ServerName) -> Handler1 | no
server_update(Handler1, Func, Event, SName) ->
Mod1 = Handler1#handler.module,
State = Handler1#handler.state,
case catch Mod1:Func(Event, State) of
{ok, State1} ->
{ok, Handler1#handler{state = State1}};
{ok, State1, hibernate} ->
{hibernate, Handler1#handler{state = State1}};
{swap_handler, Args1, State1, Handler2, Args2} ->
do_swap(Mod1, Handler1, Args1, State1, Handler2, Args2, SName);
remove_handler ->
do_terminate(Mod1, Handler1, remove_handler, State,
remove, SName, normal),
no;
Other ->
do_terminate(Mod1, Handler1, {error, Other}, State,
Event, SName, crash),
no
end.