Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    2
  • comments
    0
  • views
    3518

Entries in this blog

 

Events/Delegates

One of the things that I've found wanting in wxWidgets is the call-back system. Whilst implementing the (so far) minimal GUI system I found myself pining for a .NET Event Handler/Borland VCL closure like concept. The boost library is an obvious alternative but in the past I've never really got along with boost, I have my reasons but I won't got into them now . I'm a big fan of this style of message passing, there's no interface binding from the producer to the consumer and a minimal binding the other way, making it easier to write modularised code.

I had a (very cursory) look around the internet at what others had done, there were a couple of very interesting, in-depth discussions on code-project.com (here and here). Whilst informative these articles focused mainly on the performance penalty of the various approaches. As I don't intend to use these events/delegates in performance sensitive code my main focus was ease of use/clean API rather than performance.

I wanted the usage to look similar to C# events/delegates and I am fairly happy with the result.



#include
#include

class Producer
{
public:

Event onPrint;

};

class Consumer
{
public:

void print(Producer &p, const std::string &str) {
std::cout " }
};

void print(Producer &p, const std::string &str) {
std::cout " }

void contrievedEventUsingMethod() {
Producer p;
Consumer c;
//add a function delegate to the producer's onPrint method (globals and statics supported)
p.onPrint += &print;
//add a method delegate to the producer's onPrint method
p.onPrint += c + &Consumer::print;

//now trigger the event
p.onPrint(p, "w00t");

//remove the method delegate as it's important the delegate doesn't outlive the instance of the class to which it belongs
p.onPrint -= c + &Consumer::print;
}



I'm not 100% satisfied with the implementation as it has a number of issues:
Heavy template use, bloats compiled code size. Dynamic memory allocation, this is all handled internally but it seems for something as simple as this it should be possible to avoid it. Virtual method call per delegate call, on-top of the indirect function/method call. It relies on casting function pointers to ints, I believe this isn't strictly standards compliant. Currently it doesn't support polymorphic argument types. It should be possible to over-come this with a couple more template methods. Only tested under MSVC++ 2010, I can't promise it'll compile anywhere else.
Here's the full source code for the implementation so if you fancy using it or passing a critical eye over it feel free.




#pragma once

//------------------------
//Standard Library Headers
//------------------------
#include
#include
#include

template class Event;

template class Delegate
{
public:

virtual void operator() (P &producer, A arg) = 0;

};

//Function Delegate
//-----------------

template class FunctionDelegate : public Delegate
{
public:

typedef void (*FunctionPtr) (P&, A);

FunctionDelegate(const FunctionPtr ptr);
FunctionDelegate(const FunctionDelegate &d);

FunctionDelegate & operator= (const FunctionDelegate &d);

void operator() (P &producer, A arg);

private:

FunctionPtr ptr;

friend class Event ;
};

template FunctionDelegate::FunctionDelegate(const FunctionPtr ptr)
: ptr(ptr) {}

template FunctionDelegate::FunctionDelegate(const FunctionDelegate &d)
: ptr(d.ptr) {}

template FunctionDelegate& FunctionDelegate ::operator= (const FunctionDelegate &d) {
ptr = d.ptr;
}

template void FunctionDelegate::operator() (P &producer, A arg) {
(*ptr)(producer, arg);
}

//Method Delegate
//---------------

template class MethodDelegate : public Delegate
{
public:

typedef void (C::*MethodPtr) (P&, A);

MethodDelegate(C *consumer, const MethodPtr ptr);
MethodDelegate(const MethodDelegate &d);

MethodDelegate & operator= (const MethodDelegate &d);

void operator() (P &producer, A arg);

private:

C *consumer;
MethodPtr ptr;

friend class Event ;
};


template MethodDelegate::MethodDelegate(C *consumer, const MethodPtr ptr)
: consumer(consumer), ptr(ptr) {}

template MethodDelegate::MethodDelegate(const MethodDelegate &d)
: consumer(d.consumer), ptr(d.ptr) {}

template MethodDelegate& MethodDelegate ::operator= (const MethodDelegate &d) {
consumer = d.consumer; ptr = d.ptr;
}

template void MethodDelegate::operator()(P &producer, A arg) {
(consumer->*ptr)(producer, arg);
}

//Method Lookup
//-------------

template class MethodLookup
{
public:

typedef void (C::*MethodPtr) (P&, A);

static uintptr_t getMethodID(MethodPtr ptr);

private:

static std::vector pointers;
};

template std::vector MethodLookup::pointers;

template uintptr_t MethodLookup::getMethodID(void (C::*ptr)(P&, A)) {
//determine whether we've already seen this pointer
for (std::vector::iterator it=pointers.begin(); it!=pointers.end(); ++it) {
if (*it==ptr) return it-pointers.begin();
}
//if not add it to the vector
pointers.push_back(ptr);
//return the new id
return pointers.size()-1;
}

//Event
//-----

template class Event
{
public:

typedef void (*FunctionPtr) (P&, A);

Event();
~Event();

Event& operator+= (const FunctionPtr ptr);
Event & operator-= (const FunctionPtr ptr);

template Event& operator+= (const MethodDelegate &d);
template Event& operator-= (const MethodDelegate &d);

void clear();

void operator() (P &producer, A arg);

private:

typedef std::map, Delegate*> DelegateMap;

DelegateMap delegates;
};

template Event::Event() {}

template Event::~Event() {
clear();
}

template Event& Event ::operator+= (const FunctionPtr ptr) {
//add a new delegate
delegates[std::make_pair(static_cast(0), reinterpret_cast(ptr))] = new FunctionDelegate(ptr);
//return a reference to this so we can chain it
return *this;
}

template Event& Event ::operator-= (const FunctionPtr ptr) {
//retrieve the existing delegate
DelegateMap::iterator it = delegates.find(std::make_pair(0, static_cast(ptr)));
//if it exists remove and delete it
if (it!=delegates.end()) {
delegates.erase(it);
delete it->second;
}
//return a reference to this so we can chain it
return *this;
}

template template Event& Event ::operator+= (const MethodDelegate &d) {
//determine the method's unique id
uintptr_t methodId = MethodLookup ::getMethodID(d.ptr);
//add a new delegate
delegates[std::make_pair(d.consumer, methodId)] = new MethodDelegate (d);
//return a reference to this so we can chain it
return *this;
}

template template Event& Event ::operator-= (const MethodDelegate &d) {
//determine the method's unique id
uintptr_t methodId = MethodLookup ::getMethodID(d.ptr);
//retrieve the existing delegate
DelegateMap::iterator it = delegates.find(std::make_pair(d.consumer, methodId));
//if it exists remove and delete it
if (it!=delegates.end()) {
//delete the delegate
delete it->second;
//remove the delegate from the map
delegates.erase(it);
}
//return a reference to this so we can chain it
return *this;
}

template void Event::clear() {
while (!delegates.empty()) {
DelegateMap::iterator it=delegates.begin();
//delete the delegate
delete it->second;
//remove the delegate from the map
delegates.erase(it);
}
}

template void Event::operator() (P &producer, A arg) {
//invoke all the delegates
for (DelegateMap::iterator it=delegates.begin(); it!=delegates.end(); ++it) (*(it->second))(producer, arg);
}

//Utilities
//---------

template MethodDelegate operator+ (C &consumer, void (C::*ptr)(P&, A)) {
return MethodDelegate (&consumer, ptr);
}


LowCalorieSoftDrink

LowCalorieSoftDrink

 

Introduction

I've been a gamedev.net member for a number of years, mostly reading and quietly appreciating rather than being an active contributor. However spurred on by the recent site overhaul and the availability of the fancy new features I though I might give this blogging thing a go.

I'm not sure what I actually want this to be, but I hope it to be a repository and portfolio of my hobbyist development efforts. To that end I'll introduce, Asylum. My intention is for it to be a general purpose, cross platform, graphics scene editor. Where it eventually leads is inevitably subject to my whimsy and chronic lack of focus but It should hopefully prove to be a solid framework for experimentation.

The pre-requisite technical details: it's a C++ application using OpenGL for graphics and wxWidgets for the GUI.

I look forward to sharing furture progress.

Cheers




LowCalorieSoftDrink

LowCalorieSoftDrink

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!