Jump to content
  • Advertisement
Sign in to follow this  
T1Oracle

Unity C# Delegate in C++?

This topic is 4119 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'm sure that this doesn't fully match the C# delegate spec as I did not look to closer at the spec. However, this method does allow for me to make an event handler interface that lets me connect event listeners and automatically disconnect them when they go out of scope in a thread safe manner. I was inspired to do this a GDNet thread, the realization that Boost signal is not thread safe, and the feeling that my previous solution could be improved upon. This is the prototype that I came up with. Do note that to ensure thread safety, the user must make all event listeners thread safe. Listeners can be called at any time.
template <typename ListenerT,typename TriggerFuncT>
class Delegate
{
public:
    typedef TriggerFuncT tiggerFunc_t; // trigger function type
    typedef boost::shared_ptr<ListenerT> listenerP_t; // function pointer type
    typedef boost::weak_ptr<ListenerT> listenerWkP_t; // weak function pointer type
    // connects a function pointer to the delegate
    inline void Connect(listenerP_t func) { boost::mutex::scoped_lock lock(mutex);functions.push_back(listenerWkP_t(func)); };

    // triggers all objects in functions vector
    void Trigger(TriggerFuncT &trigger)
    {
        boost::mutex::scoped_lock lock(mutex); // lock access mutex
        typedef typename std::vector<listenerWkP_t>::iterator it_t;
        listenerP_t p;
        for (it_t i=functions.begin();i!=functions.end();i++)
        {
            // copy to shared_ptr
            p=i->lock();
            // if not expired then trigger
            if (!i->expired())
                trigger(p);
            else // elsewise remove from functions list
                functions.erase(i);
        }
    }
private:
    boost::mutex mutex; // access mutex
    std::vector<listenerWkP_t> functions; // list of functions
};

// USAGE EXAMPLE

// EXAMPLE LISTENER BASE CLASS
class ResChangeListener
{
    public:
    virtual void operator()(const math::vector2i &resolution) = 0;
};

// EXAMPLE LISTENER TRIGGER
class ResChangeTrigger
{
public:
    // delegate type
    typedef Delegate<ResChangeListener,ResChangeTrigger> delegate_t;
    // function pointer type
    typedef delegate_t::listenerP_t listenerP_t;

    ResChangeTrigger(const math::vector2i &resolution_) : resolution(resolution_) {};
    void operator () (listenerP_t func)
    { func->operator()(resolution); };
private:
    math::vector2i resolution; // new resolution
};

// EXAMPLE USE OF A DELEGATE
class Window
{
public:
    // constructs window
    Window(const std::string &appTitle,math::vector2i &resolution,bool isFullscreen);
    // sets the window dimensions
    void SetResolution(const math::vector2i &resolution);
    // swaps the display buffers
    void SwapBuffers() const { glfwSwapBuffers(); };
    // issues request to close window
    void Quit() {};
    // set an on reschange listener
    void OnResChange(ResChangeTrigger::listenerP_t func) { resChangeEvent.Connect(func); };
private:
    ResChangeTrigger::delegate_t resChangeEvent;
    boost::mutex mutex; // thread mutex for window
    GLFWInstance glfwIns; // glfw Instance
};

// EXAMPLE RESCHANGE LISTENER OBJECT
class ResChanger : public renwin::ResChangeListener
{
    public:
    ResChanger(math::vector2i &resolution_) : resolution(resolution_),isChanged(false) {};
    void operator()(const math::vector2i &resolution_)
    {
        boost::mutex::scoped_lock lock(mutex);
        isChanged=true;
        resolution=resolution_;
    };
    bool IsChanged() { return isChanged; };
    math::vector2i Resolution() { boost::mutex::scoped_lock lock(mutex);return resolution; };
    private:
    volatile bool isChanged; // is changed flag
    math::vector2i resolution; // screen resolution
    boost::mutex mutex; // access mutex
};

// EXAMPLE IMPLEMENTATION CODE IN MAIN
math::vector2i screen(1024,768); // screen dimensions

renwin::ResChangeTrigger::listenerP_t func(new ResChanger(screen));
window.OnResChange(func);
window.SetResolution(math::vector2i(800,600));

log.Mode(lg::MESSAGE)<<screen; // logs the screen dimensions to file
if (((ResChanger*)func.get())->IsChanged())
    screen=((ResChanger*)func.get())->Resolution();
log.Mode(lg::MESSAGE)<<screen; // logs the screen dimensions to file

The log output shows success
Quote:
Log <Entry caller="main()" level="Message">[ 1024 768 ]</Entry> <Entry caller="main()" level="Message">[ 800 600 ]</Entry>
I am still experimenting with this code but I'm thinking I may use it in my GUI. I do plan on limiting my use of this method as I feel event driven design requires too much synchronization. All feedback is appreciated, thank you.

Share this post


Link to post
Share on other sites
Advertisement
If you're using this for GUI, then you won't benefit from multi-threading.

A lot of attempts have been made at thread-safe window managers, but ultimately, there's a "except..." case which breaks usefulness.

For this very reason, GUI frameworks use internal event pipeline that executes all events from a single thread. Even then, various problems arise. Or simply put: there are many hidden pitfalls when trying to do multi-threaded UIs.

When using implicit locking on every notification, the overhead is through the roof as soon as you have a moderate amount of resources.

But perhaps the greatest problem with this is that it's a dead-lock waiting to happen.

Button1--trigger-->B--trigger-->C // thread 1
Button2--trigger-->C--trigger-->B // thread 2



So you're left with two options: Using per object lock, resulting in above, or using global lock, which negates any benefit from multi-threading.

The only way to avoid this would be, well, what many window managers use - separate thread for event invocation.

And you're back to square one.

Long story short: Warning, there be dragons here. Boost folks aren't lazy. The reason it's not thread safe is due to design issues.

Share this post


Link to post
Share on other sites
Interesting, right now I am not sure if my GUI will need to be multi-threaded i just don't want to limit it unnecessarily. It seems that Qt has multi-threaded GUI handled well, although I don't want another library attached to my project, not for something with as limited use to my project as multi-threaded event handling.

My Window wrapper however, will need multi-threaded event handling.

If that is the only place that needs it then I should probably make a more closed system that only provides one "already defined for you" event listener type per event. That way I would limit the places where listener types could cause multi-threading issues.

Share this post


Link to post
Share on other sites
Your Delegate class depends on the listener class as a template parameter, which keeps it from being particularly useful. The ability to register a listener without inheriting from an explicit XyzListener class is the main advantage of delegates.

Share this post


Link to post
Share on other sites
^
Quote:
Original post by T1Oracle
I'm sure that this doesn't fully match the C# delegate spec...
However, this method does allow for me to make an event handler interface that lets me connect event listeners and automatically disconnect them when they go out of scope in a thread safe manner.

Regardless, I am about to restrict the "usefulness" even further in the name of thread safety by forcing users to use my XYZListener class instead being able to derive their own. For detecting things like changes in window dimensions, loss of window focus, and the closing of the application there isn't much need for offering that level of flexibility. Changes of window dimensions can be handled by doing an if (condition==true) and then explicitly copying the new dimensions by calling another class method if needed.

I see no such situation where more must be demanded of the event interface, to implement a useful functionality.

Share this post


Link to post
Share on other sites
Delegates area about organization, not functionality. If you don't want the convenience of a heterogeneous collection of listeners, why not just use a container of weak_ptrs to your listener interface?

Share this post


Link to post
Share on other sites
Quote:
Original post by T1OracleInteresting, right now I am not sure if my GUI will need to be multi-threaded i just don't want to limit it unnecessarily. It seems that Qt has multi-threaded GUI handled well, although I don't want another library attached to my project, not for something with as limited use to my project as multi-threaded event handling.


Qt performs all GUI work in a single thread. Threads may queue events through the main event loop but the events are dispatched on the main GUI thread. You can invoke Qt's signal/slot mechanism from multiple threads, but don't do any GUI work from those slots.

In short, Qt does not have a multi-threaded GUI.

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.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!