LiraNuna's Development Blog
Implementing flash.events.EventDispatcher in C++
Posted on Saturday 27 September 2008

Call me crazy, but I really like Flash’s EventDispatcher class – it’s simple, powerful and most of all relatively fast.

I felt the need to take EventDispatcher outside of my flash projects to my more advanced C++ ones. This turned out to be quite an easy task.

Instead of a boring ‘download code’ link, I will write the steps of implementing it using C++’s STLs, just because I feel like writing.

I would like to start by noting that I will only implement the EventDispatcher class, and not flash.events.Event. I will have a very basic and dull Event class. Another note would be the Object class, which isn’t reimplemented for performance (RTTI hacks are expensive).

Let’s start by examining our situation:

  • Events are identified as strings
  • An event can have several listeners
  • Event functions have priority from -2M to 2M (32bit signed)
  • Events have phases

In this article, I will tackle those one by one.

Let’s starts with the basics, we know EventDispatcher::addEventListener takes functions as callbacks, so let’s define an event listener type. Before we can do that though, we also need to define an Event class to be passed when the event callback is called.

	// Event class
class Event
{
	public:
		Event(const std::string &type, bool bubbles = false, bool cancelable = false):
			type(type), bubbles(bubbles), cancelable(cancelable)
		{
 
		}
 
		const std::string type;
 
		const bool bubbles;
		const bool cancelable;
	/*
		const void* target;
		const unsigned int eventPhase;
		const void* currentTarget;
	*/
};
 
	// Event function callback pointer type
typedef void (*eventFunctionPtr)(const Event &);

The Event class does nothing important but emulating flash’s Event class and holding a const std::string for the type of the event.

The callback type, called eventFunctionPtr basically points to a function that returns nothing (void) and takes a const refrence to an Event as an argument, so the following AS3 code:

public function eventListener(event:Event):void
{
	// Listener code ...
}

Would become this code in C++:

void eventListener(const Event &event)
{
	// Listener code ...
}

Now that we have our Event class type and the function callback type, let’s implement the basics – mapping string to function callbacks. This can easily be done using std::map which stores data (in this case, a function pointer) that can later be retrieved by using a key (in this case, a string). This is a bit like using a primary key to refer to a record in a database table.

The code at the moment is quite simple, having only addEventListener coded:

class EventDispatcher
{
	public:
		void addEventListener(const std::string &type, eventFunctionPtr listener, bool useCapture = false, int priority = 0, bool useWeakReference = false)
		{
				// Set the event listener to the key
			eventHandlerList[type] = listener;
		}
 
	private:
		std::map<const std::string, eventFunctionPtr > eventHandlerList;
};

Implementing the method hasEventListener is also effortless, since we are just checking to see if a key exists on the map:

bool hasEventListener(const std::string &type)
{
	return (eventHandlerList.find(type) != eventHandlerList.end());
}

Now for the heart of the EventDispatcher class – the dispatchEvent method, which will simply execute the function we get from the key:

void dispatchEvent(const Event &event)
{
	if(hasEventListener(event.type))
		eventHandlerList[event.type](event);
}

The reason we’re checking if the event listener exists for this event is because std::map will create a null function pointer, which will be slow, memory consuming and will report false positives when using hasEventListener when calling dispatchEvent with event type that is not registered with our EventDispatcher. This simple check makes sure we only execute the event listener if it has in fact, a callback.

When we try to implement the method removeEventListener, we come across a problem – we only have one callback for each event string. removeEventListener method takes a string and a function pointer which will not be used. If one callback per event is what you need, the following code will do the trick:

void removeEventListener(const std::string &type, eventFunctionPtr listener, bool useCapture = false)
{
	eventHandlerList.erase(type);
}

Since that’s not what we desire, we will move on to the next item on the list, which specifies that each event can have several listeners. We can easily do that by mapping a string to a list of function pointers using std::list.

Let’s start by redefining eventHandlerList to it’s new type:

std::map<const std::string, std::list<eventFunctionPtr > > eventHandlerList;

However that requires us to change addEventListener and removeEventListener, although hasEventListener will remain unchanged.

addEventListener has the easiest ‘fix’, now instead of assigning the listener to the map’s key, we add it to the list we receive. This changes addEventListener to the following code:

void addEventListener(const std::string &type, eventFunctionPtr listener, bool useCapture = false, int priority = 0, bool useWeakReference = false)
{
		// Simply add the event listener to the list of listeners
	eventHandlerList[type].push_back(listener);
}

In removeEventHandler’s case, we remove all occurrences of the listener from the list, first checking if the map has this key registered:

void removeEventListener(const std::string &type, eventFunctionPtr listener, bool useCapture = false)
{
	if(hasEventListener(type))
		eventHandlerList[type].remove(listener);
}

The dispatchEvent method, however, gets a complete make over, because this time we have to iterate over the list of callbacks and execute them all:

void dispatchEvent(const Event &event)
{
		// Leave if no event registered
	if(!hasEventListener(event.type))
		return;
 
		// A reference to keep code clean
	std::list<eventFunctionPtr > &allFunctions = eventHandlerList[event.type];
 
		// Iterate through all functions in the event and execute them
	for(std::list<eventFunctionPtr >::iterator i=allFunctions.begin(); i!=allFunctions.end(); ++i)
		(*i)(event);
}

The function hasEventListener does not need to change, and still works with our new structure, since the base data structure is still std::map.

Now that we are done with that, we are facing a new problem – priorities. Flash enables you to give priorities to the function, letting them be closer to the time of the event was dispatched. However, Flash is so flexible that it lets us set the priorities as an signed 32bit integer. Imagine this – an array of 4,294,967,296 (2 to the power of 32 – 4 billion) lists residing on memory for each event we have – this is huge!

A neat solution would be to use another map to map integers to list, this time for the sole purpose of saving memory, not speed.

So this time our eventHandlerList evolved into this scary looking type:

std::map<const std::string, std::map<int, std::list<eventFunctionPtr > > > eventHandlerList;;

In case you are lost, here is a quick description of what’s going on: this structure maps a string (event type) to another map, which maps a 32bit integer to a list of function pointers.

Again, we will start by modifying the simplest method, which is addEventListener. The change this time, will simply map the priority in addition to the type:

void addEventListener(const std::string &type, eventFunctionPtr listener, bool useCapture = false, int priority = 0, bool useWeakReference = false)
{
		// Simply add the event listener to the list of listeners for the selected priority
	eventHandlerList[type][priority].push_back(listener);
}

Dispatching an event now gets a little more interesting, since we have to iterate over two structures – first the map of priorities, then the list of callbacks. We will be iterating the map in reverse, since we want higher priority functions (higher keys) to be executed first as opposed to the way std::map iterates which is from lowest to highest (negative to positive).

void dispatchEvent(const Event &event)
{
		// Leave if no event registered
	if(!hasEventListener(event.type))
		return;
 
		// A reference to keep code clean
	std::map<int, std::list<eventFunctionPtr > > &allFunctions = eventHandlerList[event.type];
 
		// Iterate through all functions in the event, from high proproty to low
	for(std::map<int, std::list<eventFunctionPtr > >::reverse_iterator i=allFunctions.rbegin(); i!=allFunctions.rend(); ++i) {
		std::list<eventFunctionPtr > &funcList = i->second;
			// Execute callbacks
		for(std::list<eventFunctionPtr >::iterator f=funcList.begin(); f!=funcList.end(); ++f)
			(*f)(event);
	}
}

The real tricky method is removeEventListener – because we only receive the event type and the listener to remove, we have no information on what priority the callback has and where it’s located inside our structure, which means we will have to search for it.

Another problem is false positives – if we remove an event listener from the list, an empty list will stay in memory, meaning that hasEventListener will return true if the list is empty. A way to overcome this problem is to erase the list from the priority map whenever it’s empty AND remove the priority map from the event type map.

The following code will do both:

void removeEventListener(const std::string &type, eventFunctionPtr listener, bool useCapture = false)
{
		// Leave if no event registered
	if(!hasEventListener(type))
		return;
 
		// Reference to keep the code clean
	std::map<int, std::list<eventFunctionPtr > > &allFunctions = eventHandlerList[type];
 
		// Since we don't know the function's priority, we'll search for it
	for(std::map<int, std::list<eventFunctionPtr > >::iterator i=allFunctions.begin(); i!=allFunctions.end(); ++i) {
			// Saving a branch here: instead of checking if the callback exists let remove() do it for us
		i->second.remove(listener);
 
			// Remove object from the map if list gone empty to eliminate false positives
		if(i->second.empty())
			allFunctions.erase(i);
	}
 
		// Remove map to eliminate false positives
	if(allFunctions.empty())
		eventHandlerList.erase(type);
}

I could go on, but from here on, the class really starts integrating into the GUI code part (bubbling, phases…) which would require a GUI code.

I hope this would help someone, I just wanted to write something on my aging site.


21 Comments for 'Implementing flash.events.EventDispatcher in C++'

  1.  
    NinjaCoder
    March 12, 2009 | 4:27
     

    haha nice! I’m a professional flash coder at work, but developing nds stuff on my free time. Thinking of implementing a lite version of the “Event structure” in my ndslib… anyway nice artical

  2.  
    Guus Geurkink
    February 8, 2010 | 7:45
     

    Wow, this is fantastic work!
    I wrote a whole GUI class with Event listeners, and this helped me alot!

    Keep it up ~

  3.  
    Shannon
    February 9, 2010 | 5:44
     

    Thank you very much! This is awesome. As someone who programs in both Flex/AS3 and C++, and who likes the EventDispatcher way of doing things too, this saved me a lot of time. Keep up the good work!!!

  4.  
    Shannon
    February 14, 2010 | 2:43
     

    Perhaps you could have a crack at a C++ version of binding and a C++ version of MXML!

  5.  
    August 23, 2010 | 12:28
     

    Template et héritage…

    Afin de pouvoir faire de la programmation événementielle en C++ j’ai commencé par (ré)implémenter une classe EventDispatcher. Et comme je la voulais capable de propager n’importe quels types d’événement, je me suis heurté à quelques menus soucis……

  6.  
    Paul
    September 18, 2010 | 21:56
     

    You might consider using std::multimap to store the priorities and callbacks instead of std::map< int, std::list<…

    Cheers.

  7.  
    Shannon
    November 13, 2010 | 22:48
     

    The complete code

    Event

    // Event class
    #include
    class Event
    {
    public:
    Event(const std::string &type, bool bubbles = false, bool cancelable = false):
    type(type), bubbles(bubbles), cancelable(cancelable)
    {

    }

    const std::string type;

    const bool bubbles;
    const bool cancelable;
    /*
    const void* target;
    const unsigned int eventPhase;
    const void* currentTarget;
    */
    };

    // Event function callback pointer type
    typedef void (*eventFunctionPtr)(const Event &);

    EventDispatcher.hpp —————————————————————–

    #include
    #include
    #include "event.hpp"
    #include
    class EventDispatcher
    {

    private:
    std::map<const std::string, std::map<int, std::list > > eventHandlerList;;

    public:
    void addEventListener(const std::string &type, eventFunctionPtr listener, bool useCapture = false, int priority = 0, bool useWeakReference = false)
    {
    // Simply add the event listener to the list of listeners for the selected priority
    eventHandlerList[type][priority].push_back(listener);
    }

    bool hasEventListener(const std::string &type)
    {
    return (eventHandlerList.find(type) != eventHandlerList.end());
    }

    void dispatchEvent(const Event &event)
    {
    // Leave if no event registered
    if(!hasEventListener(event.type))
    return;

    // A reference to keep code clean
    std::map<int, std::list > &allFunctions = eventHandlerList[event.type];

    // Iterate through all functions in the event, from high proproty to low
    for(std::map<int, std::list >::reverse_iterator i=allFunctions.rbegin(); i!=allFunctions.rend(); ++i) {
    std::list &funcList = i->second;
    // Execute callbacks
    for(std::list::iterator f=funcList.begin(); f!=funcList.end(); ++f)
    (*f)(event);
    }
    }

    void removeEventListener(const std::string &type, eventFunctionPtr listener, bool useCapture = false)
    {
    // Leave if no event registered
    if(!hasEventListener(type))
    return;

    // Reference to keep the code clean
    std::map<int, std::list > &allFunctions = eventHandlerList[type];

    // Since we don't know the function's priority, we'll search for it
    for(std::map<int, std::list >::iterator i=allFunctions.begin(); i!=allFunctions.end(); ++i) {
    // Saving a branch here: instead of checking if the callback exists let remove() do it for us
    i->second.remove(listener);

    // Remove object from the map if list gone empty to eliminate false positives
    if(i->second.empty())
    allFunctions.erase(i);
    }

    // Remove map to eliminate false positives
    if(allFunctions.empty())
    eventHandlerList.erase(type);
    }

    };

    // Obviously put the implementation into a cpp file

  8.  
    Tyler
    March 16, 2011 | 21:22
     

    Very nice work!

    I have been trying to modify these classes so that I can create custom event listeners, i.e. derived classes from the base Event class like KeyboardEvent or MouseEvent. But so far I am unable to get the addEventListener function to work since it takes in Event as a parameter.

    Can anyone shed some light on how to do this?

  9.  
    Patrick
    September 9, 2011 | 20:44
     

    Tyler, you could use Event as a base class and create MouseEvent and KeyboardEvent classes that inherit from the base class.

  10.  
    Clark
    April 18, 2012 | 10:02
     

    But how do you know when to delete the Event? I love AS3 and I’m still new to C++ but don’t we rely on GarbageCollection to cleanup the Events? If we ‘fire and forget’ then won’t the Events fill up the heap?

  11.  
    Sidar
    June 17, 2012 | 6:34
     

    Just by looking at the implementation, it will remove all function references just by type alone and doesn’t take the listener into count ?

    Also I don’t see how you are looking for the priority in the removeListener? You are just iterating trough the whole list?

  12.  
    June 17, 2012 | 9:51
     

    Sidar,

    EventDispatcher::removeEventListener will remove only the function pointer as given, that’s what i->second.remove(listener); is for.

    The listeners are kept in a sorted std::map to accommodate the priorities, and all it takes is (reverse) traversal to get the correct order during the dispatch phase.

  13.  
    Sidar
    June 17, 2012 | 11:04
     

    BTW

    for(std::map<int, std::list >::iterator i=allFunctions.begin(); i!=allFunctions.end(); ++i) {
    // Saving a branch here: instead of checking if the callback exists let remove() do it for us
    i->second.remove(listener);

    // Remove object from the map if list gone empty to eliminate false positives
    if(i->second.empty())
    allFunctions.erase(i);
    }

    Doesn’t work due to the iterator being lost. Any suggestion?

  14.  
    June 17, 2012 | 11:05
     

    You are correct, that is a bug – it should be i = allFunctions.erase(i);. (erase returns a valid iterator to iterate on)

  15.  
    Sidar
    June 17, 2012 | 11:09
     

    I tried that.
    But it gives me

    error: passing ‘const std::list’ as ‘this’ argument of ‘void std::list::remove(const value_type&) [with _Tp = void (*)(const st::event::Event&), _Alloc = std::allocator, std::list::value_type = void (*)(const st::event::Event&)]’ discards qualifiers [-fpermissive]

    error: no matching function for call to ‘std::map<int, std::list >::erase(std::map<int, std::list >::const_iterator&)’

  16.  
    Sidar
    June 17, 2012 | 12:16
     

    Sorry for spamming your blog but this seem to do it for me:

    void removeEventListener(const std::string &type, function listener){
    if(!hasEventListener(type))
    return;

    std::map<int, std::list > &allFunctions = eventHandlerList[type];
    std::map<int, std::list >::iterator it;
    it=allFunctions.begin();

    while(it != allFunctions.end())
    {
    it->second.remove(listener);

    if (it->second.empty()) { allFunctions.erase(it++); }
    else { ++it; }
    }

    if(allFunctions.empty())
    eventHandlerList.erase(type);
    }

  17.  
    Renat
    February 5, 2013 | 21:15
     

    Nice work! It’s interesting to know what to do further.

    I’m trying to make a simple GUI system using the same approach. I define class UIControl derived from EventDispatcher and my purpose is to keep all the event handlers as member functions of corresponding classes, like I can do it in ActionScript3. Thus, I redefine eventFunctionPtr as typedef void (UIControl::*eventFunctionPtr)(const Event &) and this causes some problems:

    1)To execute the callback, now I need to know the UI control object itself, its member function and an event to pass, so I need to define new CallbackWrapper class to store object and member function.

    2)Instantiating CallbackWrapper fails if instead of UIControl instance I use its descendant, for example, Button. In particular, C++ compiler can’t cast void (UIControl::*eventFunctionPtr)(const Event &) to void (Button::*eventFunctionPtr)(const Event &).

    3)The problem can be particularly solved if we define CallbackWrapper as a template class, with the type of control as T and its callback type as void (T::*eventFunctionPtr)(const Event &). But in that case we can’t define a map of callbacks as we can use listeners of different type.

    Maybe I’m trying to make something strange and more simple solution exists?

  18.  
    February 5, 2013 | 21:19
     

    I did leave some of the stuff out – for example, dispatchEvent should be able to set event.target on the Event (mutable friend?)

  19.  
    February 27, 2013 | 5:25
     

    […] 『Implementing flash.events.EventDispatcher in C++』 […]

  20.  
    hehe
    March 2, 2013 | 4:23
     

    typedef void (*eventFunctionPtr)(const Event &); The concept of this code is nice, i’m / was an AS3 developer, but the realization is poor. At first the author uses C++ but the eventFunctionPtr is “C” style function pointer, that can’t be implemented in C++ (at least if your method’s aren’t static) because member function pointers for not static members, have different syntax and at the end aren’t so simple to implement like one could expect.

  21.  
    March 2, 2013 | 11:37
     

    The idea is to implement flash.events.EventDispatcher.
    You can easily replace the function pointer type with a functor or anything you’d like, the code stays pretty much the same.

Leave a comment

(required)

(required)


Information for comment users
Line and paragraph breaks are implemented automatically. Your e-mail address is never displayed. Please consider what you're posting.

Use the buttons below to customise your comment.


RSS feed for comments on this post | TrackBack URI