Gaffer Docs

Event

Events in Gaffer provide a mechanism of communication between subsystems, ProcessMgr instances running in different threads, and between game clients and severs in a networked setting.

Event Class

Event serves as a base class for Gaffer and client defined events.

typedef boost::uint32_t EventType_t;

// Event base class.
//
// Child classes must register their unqiue event type with
// EventMgr::registerEvent().  The registration includes a function
// pointer that returns an Event * of their type.  See std_event.h for
// several examples of how these should be defined.
class Event
{
public:
    // Child classes must designate a unique event id.
    // If it's not unique, an exception will be thrown when registering.
    virtual EventType_t type() const = 0;

    // Return string representation of type.  This needs to be a
    // unique name across all events.  It will be used for scripting
    // and for defining what events get called during GUI interaaction
    // in .gagui files.
    virtual const char * name() const = 0;

    // Return or'd values of event flags.
    virtual EventFlags_t flags() const = 0;

    // Many events won't need to serialize any data.  TYPE and EVENT
    // are serialized automatically.  The value of type() will be
    // serialized by EventMgr, and both TYPE and EVENT will be
    // deserialized when EventFactory calls the create function.
    virtual void serialize(std::ostream &os) const {}
    virtual void deserialize(std::istream &is) {}

    virtual ~Event() {}
};

Event Type

Each event has a unique type, which is a 32bit unsigned int. The high order byte represents the Client ID. Gaffer uses Client ID = 0, but Clients may designate other unique values for themselves. The remaining 24 bits are reserved for unique event identifiers, however currently the event identifier must be less than 1024. If it's discovered that clients need more than 1024 unique event identifiers, it is a simple change to some constants in Gaffer to increase this number, but 1024 seems adequate for all but the most unusual cases. Internally, Gaffer does table lookups based on these identifiers, so it is important to keep it reasonably small so that vectors can be allocated to cover every possible identifier.

Event Flags

It's useful to classify events into several categories used by Gaffer to determine how to handle the events in different situations. For example, certain events, like low level keypress events, might not be appropriate to send over the network to the game server. Thus we can set a NO_NETWORK flag in the event flags for those event types.

Serialization/Deserialization

The serialize and deserialize methods are used when events are sent over the network. Simple events containing no data don't need to override these methods as EventMgr and EventFactory take care of serialization concerns for the event type.

StandardEvent Class

In order to make event declaration more concise, StandardEvent is a template class that takes type and flags as parameters to the template. We also want to incorporate events into our EventFactory, and thus we events need a createEvent method.

// We need Event to be a simple base class so that we can use
// runtime polymorphism to pass Event pointers around.  However,
// every Event should implement a TYPE and FLAGS static const
// public members, and this template class simplifies those
// definitions.
// This StandardEvent also includes a class type to return
// from the createEvent method.  This makes it compatible with
// the STANDARD_REGISTRATION macro in EventFactory.h.
template <class T, int typeVal, EventFlags_t flagsVal>
class StandardEvent : public Event
{
public:
    static const EventType_t TYPE = typeVal;
    virtual EventType_t type() const { return TYPE; }

    static const EventFlags_t FLAGS = flagsVal;
    virtual EventFlags_t flags() const { return FLAGS; }

    virtual ~StandardEvent() {}

    static Event * createEvent() { return new T; }

    virtual const char * name() const
    {
        throw GA_EXCEPTION_1("StandardEvent::name should always should be a subclass call.  EventType_t = %d", TYPE);
    }

};

STANDARD_EVENT Macro

Finally, to make event delcaration even more consise, there's a macro called STANDARD_EVENT.

#define STANDARD_EVENT(T, TYPEVAL, FLAGSVAL) \
    class T : public StandardEvent<T, TYPEVAL, FLAGSVAL> \
    { \
    public: \
        virtual const char * name() const { return #T; } \
    }

As an example, suppose we want an event to clear the scene. When clients fire this event, all actors will be removed from the scene, cameras and lights reset, etc.

STANDARD_EVENT(ClearScene, 10, EVENT_FLAG_LOW_LEVEL);
That's all there is to it. Clients should register all data-less events in this manner. Events that have data are similarly declared, and that is dicussed in the next section.

Declaration isn't the only thing required, as the event still needs to be registered. I discuss that in detail in the EventFactory section.

Data Events

Most events have data associated with them. For example, a mouse move event will have X and Y move distances. To support data events, we just need to extend StandardEvent a bit further. There are DataEventX template classes, each containing an increasing number of data members. If you need more than five data members, you'll need to wrap some of your data members in some other class. I recommend the Boost Tuple Library if you find this necessary.

DataEvent1 Class

// Data event with 1 value
template <int typeVal, EventFlags_t flagsVal, typename T1>
class DataEvent1 : public StandardEvent<DataEvent1<typeVal, flagsVal, T1>,
                                        typeVal, flagsVal>
{
public:
    DataEvent1() {}
    
    DataEvent1(T1 data1)
      : data1_(data1) {}

    T1 data1() { return data1_; }
    void setData1(T1 val) { data1_ = val; }

    virtual ~DataEvent1() {}

    virtual void serialize(std::ostream &os) const
    {
        os.write(reinterpret_cast<const char*>(&data1_), sizeof(T1));
    }
    
    virtual void deserialize(std::istream &is)
    {
        is.read(reinterpret_cast<char*>(&data1_), sizeof(T1));
    }

private:
    T1 data1_;
};

The data can be accessed by the data1 and setData1 methods. The class also takes care of serialize/deserialize for you, so you don't need to implement those either.

DATA_EVENT_1 Macro

To simplify declaring data events, there's a similar macro to STANDARD_EVENT provided.

#define DATA_EVENT_1(T, TYPEVAL, FLAGSVAL, T1) \
    class T : public DataEvent1<TYPEVAL, FLAGSVAL, T1> \
    { \
    public: \
        T() {} \
        T(T1 data1) : DataEvent1<TYPEVAL, FLAGSVAL, T1>::DataEvent1(data1) {} \
        virtual const char * name() const { return #T; } \
    }

Higher Order Data Events

In addition to DataEvent1 and DATA_EVENT_1, there are also corresponding classes and macros for 2 through 5. As an example, to declare a MouseMove event, you can simply write:

DATA_EVENT_2(MouseMove, 13, EVENT_FLAG_LOW_LEVEL, MoveDistance_t, MoveDistance_t);

Summary

This section has shown a lot of code examples, and the inner design of events in Gaffer. As a client, you only need to concern yourself with the following macros to declare events:

And you must remember to register your events, which is dicussed in the EventFactory section.