Jump to content
  • Advertisement
Sign in to follow this  
crancran

Event Framework

This topic is 2565 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I quickly threw together a crude event system which I have included below. It allows for a quick invocation of callback handlers in a list using the notify() member function and a queuing option where the events will be delivered during the next event loop using the queue() and processQueue() methods. I am not pleased with this implementation because it was thrown together with little thought beyond the notion that it needed to support being able to invoke member function callbacks of various classes and that the signature would always be the same for all callbacks.

I was curious whether some others could offer feedback on ways I could improve this class, possibly even illustrating with code samples of more efficient alternatives. Secondly, this event system expects that all events sent through the system derive from a base class Event which is then typedef'd to a std::shared_ptr class EventPtr. What I don't like is that the senders of events are using new to allocate event objects. This is naturally bad practice when I could be allocating many event objects in short periods of time and a pool of event objects that are popped off, used, and then added back to a freelist would be far more advantageous. Unfortunately I am unclear on how best to implement such functionality since I derive various event classes from Event itself for specific event cases.

Anyone have any suggestions and thoughts?


typedef boost::function<void EventPtr>)> TEventCallback;
class EventSystem
{
typedef std::vector<TEventCallback> TCallbackList;
typedef TCallbackList::iterator TCallbackListIt;
typedef std::map<EventType, TCallbackList> TCallbackMap;
typedef TCallbackMap::iterator TCallbackMapIt;
typedef std::deque<EventPtr> TEventQueue;
public:
EventSystem() { }
~EventSystem() { }

template<typename T>
void registerHandler(EventType type, T* handlerObject, void(T::* handlerCallback)(EventPtr)) {
TEventCallback cb = boost::bind(handlerCallback, handlerObject, _1);
TCallbackMapIt i = mHandlers.find(type);
if(i != mHandlers.end()) {
i->second.push_back(cb);
return;
}
mHandlers[type] = std::vector<TEventCallback>(1, cb);
}

template<typename T>
void unregisterHandler(T* handlerObject) {
TCallbackMapIt itstart = mHandlers.begin();
TCallbackMapIt itend = mHandlers.end();
for(; itstart != itend; ++itstart) {
TCallbackListIt liststart = itstart->second.begin();
TCallbackListIt listend = itstart->second.end();
while(liststart != listend) {
if(*liststart == handlerObject) {
itstart->second.erase(liststart);
break;
}
++liststart;
}
}
}

void notify(EventPtr evt) {
TCallbackMapIt i = mHandlers.find(evt->getType());
assert(i != mHandlers.end());
TCallbackListIt list = i->second.begin();
for(; list != i->second.end(); ++list)
(*list)(evt);
}

void queue(EventPtr evt) {
mQueue.push_back(evt);
}

void processQueue() {
while(!mQueue.empty()) {
EventPtr evt = mQueue.front();
mQueue.pop_front();
notify(evt);
}
}

protected:
TCallbackMap mHandlers;
TEventQueue mQueue;
};

Share this post


Link to post
Share on other sites
Advertisement
Regarding [font="'Courier New"]new[/font] being inefficient, the new/delete operators can be overridden on a per-class basis, so you can make your events derive from a [font="'Courier New"]PoolAllocated[/font] class, etc, which implements pool allocation/deallocation in it's new/delete operator.

Out of curiosity, what is the actual use-case for this event dispatching system?

Share this post


Link to post
Share on other sites
If events only stay around for one frame, you could just do a simple buffer for all events in one frame. This is even faster if they are POD types since cleanup is just one assignment.

Some pseudo-code:
class EventBuffer
{
void *m_buffer = malloc(1024*100); // Something large enough to fit more events than will ever happen in a single frame
void *m_bufferPos = m_buffer;
typedef void (*EventDestroyer)(Event*);

template <class EventType>
Event *allocate(..., EventDestroyer dealloc_func) {
// Store size of this event so we know how many bytes to skip to next event in buffer
*(size_t*)m_bufferPos = sizeof(EventType);
m_bufferPos += sizeof(unsigned int);
// Store destructor
*m_bufferPos = (void*)dealloc_func;
m_bufferPos += sizeof(EventDestroyer);

// Allocate the event
Event *e = new[m_bufferPos](...);
m_bufferPos += sizeof(EventType);

// Check so we don't run out of space
ASSERT(m_bufferPos < m_buffer + 1024*100);

return e;
}

void clear()
{
// Loop through buffer and call destructors
// If your events are all POD, you can just do m_bufferPos = m_buffer
void *v = m_buffer;

while (v < m_bufferPos)
{
// Fetch size
size_t event_size = *(size_t*)v;
v += sizeof(size_t);
// Fetch destructor
EventDestroyer dealloc_func = (EventDestroyer)v;
v += sizeof(EventDestroyer);
// Fetch event
Event* e = (Event*)v;
v += event_size;

// call destructor, no need to free
dealloc_func(e);
}

m_bufferPos = m_buffer;
}
}





Call EventBuffer.allocate<EventType>(...) to create an event, and at the end of your game loop call EventBuffer.clear();




If you need long-term events, you can even have two (or more) event-pools, use one for short-term events, one for long-term, and clear them at different intervals (copy over events to new pool if necessary).

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!