Question

I'm writing an event manager that will take a lot of different event handlers. This event manager will be notified with a lot of different events. Each handler only handle certain events, and ignore the rest. Each handler can also trigger certain other events based on situation.

For example, first handler to handle Event1

-module (first_handler).
-behavior (gen_event).

...

handle_event(Event1, State) -> {ok, State};
handle_event(_, State) -> {ok, State}.

Second handler to handle Event2

-module (second_handler).
-behavior (gen_event).

...

handle_event(Event2, State) -> 
  gen_event:notify(self(), Event1),
  {ok, State};
handle_event(_, State) -> {ok, State}.

The event triggering can be done by calling gen_event:notify(self(), NewEvent) within a handle_event of the handler, but I would rather abstract and export that out so that it can be called from the event manager.

Since pattern matching and ignoring events and triggering events are common to all the handlers, is there anyway I can extend gen_event behavior to provide those as built-ins?

I'll start with the default way to create a custom behavior:

-module (gen_new_event).
-behaviour (gen_event).

behaviour_info(Type) -> gen_event:behaviour_info(Type).

I'm not sure what to do next.

Was it helpful?

Solution

Your installed handlers are already running in the context of the event manager which you start and then install handlers into. So if their handle-event function throws out data, they already do what you want.

You don't need to extend the event behaviour. What you do is:

 handle_event(Event, State) ->
   generic:handle_event(Event, State).

and then let the generic module handle the generic parts. Note that you could supply generic a way to callback to this handler module for specialized handler behaviour should you need it. For example:

   generic:handle_event(fun ?MODULE:callback/2, Event, State)...

and so on.

OTHER TIPS

What are you trying to do exactly? I could not understand from the examples you provided. In second_handler's handle_event/2, Event1 is unbound. Also, does using self() work? Shouldn't that be the registered name of the manager. Not sure whether handle_event/2 gets executed by the manager or each handler process (but the latter makes more sense).

By implementing your gen_new_event module, you are implementing a handler (i.e. a callback module), and not an event manager. The fact that you have -behaviour(gen_event) means that you're asking the compiler to check that gen_new_event actually implements all the functions listed by gen_event:behaviour_info(callbacks), thereby making gen_new_event an eligible handler which you could add to an event manager via gen_event:add_handler(manager_registered_name, gen_new_event, []).

Now, if you take away -behaviour (gen_event), gen_new_event no longer has to implement the following functions:

35> gen_event:behaviour_info(callbacks).   
[{init,1},
 {handle_event,2},
 {handle_call,2},
 {handle_info,2},
 {terminate,2},
 {code_change,3}]

You could make gen_new_event a behaviour (i.e. an interface) by adding more functions which you will be requiring any module which uses -behaviour(gen_new_event) to implement:

-module (gen_new_event).
-export([behaviour_info/1]).

behaviour_info(callbacks) -> 
    [{some_fun, 2}, {some_other_fun, 3} | gen_event:behaviour_info(callbacks)].

Now, if in some module, for e.g. -module(example), you add the attribute -behaviour(gen_new_event), then the module example will have to implement all the gen_event callback functions + some_fun/2 and some_other_fun/3.

I doubt that's what you were looking for, but your last example seemed to suggest that you wanted to implement a behaviour. Note that, all you're doing by implementing a behaviour is requiring other modules to implement certain functions should they use -behaviour(your_behaviour).

(Also, if I understood you correctly, if you want to extend gen_event then you could always simply copy the code in gen_event.erl and extend it ... I guess, but is this really necessary for what you're trying to do?).

Edit

Objective: extract common code out of gen_event implementations. So for e.g. there's a handle_event/2 clause which you want in every one of your gen_events.

One way of going about it: You could use a parameterized module. This module would implement the gen_event behaviour, but, only the common behaviour which all your gen_event callback modules should have. Anything which is not "common" can be delegated to the module's parameter (which you'd bind to a module name containing the "custom" implementation of the gen_event callback.

E.g.

-module(abstract_gen_event, [SpecificGenEvent]).
-behaviour(gen_event).
-export(... all gen_event functions).

....

handle_event({info, Info}, State) ->
    %% Do something which you want all your gen_events to do.
handle_event(Event, State) ->
    %% Ok, now let the particular gen_event take over:
    SpecificGenEvent:handle_event(Event, State).

%% Same sort of thing for other callback functions
....

Then you'd implement one or more gen_event modules which you'll be plugging into abstract_gen_event. Lets say one of them is a_gen_event.

Then you should be able to do:

AGenEvent = abstract_gen_event:new(a_gen_event). %% Note: the function new/x is auto-generated and will have arity according to how many parameters a parameterized module has.

Then, I guess you could pass AGenEvent to gen_event:add_handler(some_ref, AGenEvent, []) and it should work but note that I have never tried this out.

Perhaps you could also get around this using macros or (but this is a bit overkill) do some playing around at compilation time using parse_transform/2. Just a thought though. See how this parameterized solution goes first.

2nd Edit

(Note: not sure whether I should delete everything prior to what is in this section. Please let me know or just delete it if you know what you're doing).

Ok, so I tried it out myself and yes, the return value of a parameterized module will crash when feeding it to gen_event:add_handler/3's second argument... too bad :(

I can't think of any other way of going about this then other than a) using macros b) using parse_transform/2.

a)

-module(ge).
-behaviour(gen_event).

-define(handle_event, 
handle_event({info, Info}, State) ->
    io:format("Info: ~p~n", [Info]),
    {ok, State}).

?handle_event;
handle_event(Event, State) ->
    io:format("got event: ~p~n", [Event]),
    {ok, State}.

So basically you would have all the callback function clauses for the common functionality defined in macro definitions in a header file which you include in every gen_event which uses this common functionality. Then you ?X before/after each callback function which uses the common functionality... I know it's not that clean and I'm generally weary of using macros myself but hey... if the problem is really nagging you that's one way to go about it.

b) Google around for some info on using parse_transform/2 in Erlang. You could implement a parse_transform which looks for the callback functions in you gen_event modules which have the specific cases for the callbacks but do not have the generic cases (i.e. clauses like the ({info, Info}, State) in the macro above). Then you would simply add the forms which make up the generic cases.

I would suggest doing something like this (add exports):

-module(tmp).
 parse_transform(Forms, Options) ->
     io:format("~p~n", [Forms]),
     Forms.

-module(generic).
gen(Event, State) ->
    io:format("Event is: ~p~n", [Event]),
    {ok, State}.

Now you can compile with:

c(tmp).
c(generic, {parse_transform, tmp}).
[{attribute,1,file,{"../src/generic.erl",1}},
 {attribute,4,module,generic},
 {attribute,14,compile,export_all},
 {function,19,gen,2,
       [{clause,19,
                [{var,19,'Event'},{var,19,'State'}],
                [],
                [{call,20,
                       {remote,20,{atom,20,io},{atom,20,format}},
                       [{string,20,"Event is: ~p~n"},
                        {cons,20,{var,20,'Event'},{nil,20}}]},
                 {tuple,21,[{atom,21,ok},{var,21,'State'}]}]}]},
 {eof,28}]
 {ok,generic}

That way you can copy-paste the forms you'll be injecting. You would copy them into a proper parse_transform/2 which, rather than just printing, would actually go through your source's code and inject the code you want where you want it.

As a side note, you could include the attribute -compile({parse_transform, tmp}) to every gen_event module of yours which needs to be parse_transformed in this way to add the generic functionality (i.e. and avoid having to pass this to the compiler yourself). Just make sure tmp or whichever module contains your parse_transform is loaded or compiled in a dir on the path.

b) seems like a lot of work I know...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top