Sign in to follow this  
TheComet

Generic events - Enforcing the correct function signatures.

Recommended Posts

TheComet    3900

I began development on a game written in C89 about a month ago. The design of this game is entirely centred around a plugin system which basically consists of a module loader, a service directory, and an events system.

The events system is horribly prone to human error and this is what I want to get some input on.

Consider the host application and two plugins, pluginA and pluginB. pluginA registers an event "foo" to the host application, and pluginB listens to that event. The flow of information, thus, looks like this:

         host app
      event dispatcher
        ^          \
       /            \
      /              v
   pluginA         pluginB
FIRE_EVENT(foo)  EVENT_LISTENER(on_foo)

The event system is implemented as follows:

/* generic function, any amount of arguments can be passed to a call of this function type (only works in C, not C++) */
typedef void (*event_listener_callback)();

struct event_t
{
    char* name;
    struct vector_t listeners; /* a vector of event_listener_t objects */
};

struct event_listener_t
{
    char* listener_name;
    event_listener_callback exec;
};

#define EVENT_H(event_name) extern struct event_t* event_name;
#define EVENT_C(event_name) struct event_t* event_name = NULL;

#define EVENT_FIRE0(event_name) \
    VECTOR_FOR_EACH((event_name)->listeners, struct event_listener_t, listener) \
    { \
        listener->exec(event_name); \
    }

#define EVENT_LISTENER0(name) \
    void name(const struct event_t* evt)

Note that there are macros for EVENT_FIRE1, EVENT_FIRE2, etc. depending on how many arguments the event wishes to send.

 

From pluginA's perspective, this is how it would register an event:

EVENT_C(evt_foo)

void pluginA_init(void)
{
    evt_foo = event_create("foo"); /* this returns a new event_t object */
}

From pluginB's perspective, this is how it would listen to the event "foo":

void register_events(void)
{
    event_register_listener("foo", on_foo); /* this adds the function "on_foo" to the corresponding event's listeners vector */
}

EVENT_LISTENER0(on_foo)
{
    puts("event foo received!");
}

The Problem

 

The issue with this system is that the receiving function "on_foo" could have any function signature, and it would compile anyway. Similarly, EVENT_FIRE could callback any number of arguments and yet it would compile anyway.

 

Furthermore, from pluginB's perspective, it's very hard to figure out what each event's callback function signature should be. The only place where it's defined is EVENT_FIRE0().

 

Is there any better way to implement this? Is there any way to enforce the programmer to match all function signatures correctly? The fact that the callback is delegated using a generic function probably makes this impossible, so I'm wondering if there's a better way to implement such a system?

 

[EDIT] How do I add the "C" prefix to my thread?

Edited by TheComet

Share this post


Link to post
Share on other sites
TheComet    3900

I don't believe C is expressive enough to capture what you are trying to do. The C APIs I have used appear to favour the idiom of a single callback function signature with a void pointer for the variable data. The callee is expected to cast the pointer to a structure of the appropriate type (often indicated by a integral "type" parameter).

Would you recommend adopting the favoured technique in place of what I'm currently doing? I'm guessing it has a reason, but I can't really see what that reason might be.
 
What's also bothering me is I can't find any information on these "generic" function signatures. I'm not sure what to call them:

typedef void (*func1)();     /* this function allows any number of arguments with any type */
typedef void (*func2)(void); /* this function does not - it only accepts void */

Is this a compiler bug?

Share this post


Link to post
Share on other sites
rip-off    10979

I believe it is a legacy "feature". In the earliest versions of C, there was no checking of function parameters. Functions were written like so:

someFunction(count, string)
int count;
char *string;
{
    // ...
}

Some light Googling suggests that support for this is dropped in C99. Note even the return type was omitted, in such cases "int" was assumed.

 

I believe "void" was added to the language to be explicit about functions that took no parameters and / or did not return anything.

 

Another reason to prefer the idiomatic solution!

Edited by rip-off

Share this post


Link to post
Share on other sites
TheComet    3900

Thanks for the input! That's very interesting. I can confirm that it compiles under C99 mode on both GCC and clang, but it fails to compile using a C++ compiler.
 
I played around with my idea a little more to see how far I can really go, I found that it actually is possible to do what I seek with the GCC compiler extension Typeof. Changing the macros to the following will somewhat enforce coherent function signatures:

#define EVENT_DUMMY_FUNCTION_SIGNATURE_CHECK1(event, arg1) do { \
            void (*func)(const struct event_t*, TYPEOF(arg1)); \
            (void)(func == event_internal_func_##event); } while(0);

#define EVENT_FIRE1(event_name, arg1) \
    EVENT_DUMMY_FUNCTION_SIGNATURE_CHECK1(event, arg1) \
    VECTOR_FOR_EACH((event_name)->listeners, struct event_listener_t, listener) \
    { \
        listener->exec(event_name); \
    }


#define EVENT_H1(event, arg1_t) \
    void event_internal_func_##event(const struct event_t* evt, arg1_t arg1); \
    extern struct event_t* event


#define EVENT_C1(event, arg1_t) \
    void event_internal_func_##event(const struct event_t* evt, arg1_t arg1) {} \
    struct event_t* event = (struct event_t*)0

Where TYPEOF is:

#define TYPEOF(x) __typeof__(x)

This forces the arguments passed to EVENT_H(), EVENT_C() and EVENT_FIRE() to match, or a compiler warning is generated.

 

I can't say I'm particularly proud of this macro wizardry I've created and it probably belongs in the coding horrors, but it works. I'm pretty sure it would also work with an MSVC compiler with decltype, although I think that's only for C++ code.

 

For the complete implementation of my event system, see here:

https://github.com/TheComet93/lightship/blob/13580471e99243a385acd9e3174db8f18d3a8a09/util/include/util/event_api.h

Edited by TheComet

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this