• Advertisement
Sign in to follow this  
  • entries
    2
  • comments
    0
  • views
    3456

Entries in this blog

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 smile.gif. 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 << "Consumer::print -> " << str << std::endl;
}
};

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

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);
    }

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



Sign in to follow this  
  • Advertisement