Jump to content
  • Advertisement
Sign in to follow this  
Sneftel

Very, very fast event library

This topic is 4083 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

When profiling some code I'd been working on recently, I was surprised and dismayed to see boost::signals functions floating to the top. For those of you who are unaware, boost::signals is a wonderfully useful signal/slot library which can be used alongside boost::bind for delegate-based event handling such as one sees in C#. It is robust, featureful, and flexible. It is also, I have learned, incredibly, terrifyingly slow. For a lot of people who use boost::signals this is fine because they call events very seldom. I was calling several events per frame per object, with predictable results. So I wrote my own. Slightly less flexible and featureful. It's optimized for how everyone tends to actually use events. And event invocation is fifteen to eighty times faster than boost::signals. The library relies upon Don Clugston's excellent FastDelegate library and upon the deliciously evil, horribly useful boost::preprocessor library. The former is included; the latter is not, so you'll need to have Boost installed and its headers available to the compiler. A basic usage guide is as follows:
#include <event.h>
#include <string>
#include <iostream>

class Subject
{
public:
	// events::Event is parameterized by the argument list passed to the event handlers.
	// I like to make events public and mutable. If you want you can hide them behind
	// mutators instead.
	mutable events::Event<std::string const&> NameChangedEvent;

	// I've decided not to make events invocable via operator(). It's too easy to 
	// accidentally invoke an event in this way.
	void SetName(std::string const& name)
	{
		m_name = name;
		NameChangedEvent.Invoke(m_name);
	}

private:
	std::string m_name;
};

class Observer
{
public:
	Observer(Subject const& subject) : m_subject(subject)
	{
		// Registers the OnNameChanged function of this object as a listener, and
		// stores the resultant connection object in the listener's connection set.
		// This is all you need to do for the most common case, where you want the
		// connection to be broken when the Observer is destroyed.
		m_cs.Add(m_subject.NameChangedEvent.Register(*this, &Observer::OnNameChanged));
	}

private:
	// I like to make my event handler functions private, delegating to public functions
	// as necessary.
	void OnNameChanged(std::string const& newName)
	{
		std::cout << "The subject's name is now " << newName << "." << std::endl;
	}

	// The connection set is a utility class which manages connection lifetimes for
	// you. It's similar in functionality to boost::signals::trackable, but provides
	// slightly more flexibility over connection lifetimes. For the normal case,
	// you only need one connection set per listening object (no matter how many
	// things it listens to) and you never need to do anything with it except call Add().
	events::ConnectionSet m_cs;

	Subject const& m_subject;
};

int main()
{
	Subject s;
	Observer o(s);

	s.SetName("Joe");
}













This is beta-quality software, and its interface and implementation are both still in flux. It's intended primarily to get ideas flowing here on what form the ideal event library would take. Is this library too much or too little? Are there widely useful features it's missing? In particular, I'm interested to hear whether the ConnectionSet system is reasonable. download (ver 0.08) [Edited by - Sneftel on July 28, 2007 12:59:58 AM]

Share this post


Link to post
Share on other sites
Advertisement
Could it work with Impossibly fast delegates

I find the above quoted solution to be far too hackish, relying on compiler and architecture specifics, whereas this one is on-par, but only relies on language features.

It also contains an example of event dispatching, although I didn't test it.

Share this post


Link to post
Share on other sites
I waffled over that for some time. Impossibly Fast Delegates is incredibly elegant compared to Fastest Possible. It is, however, considerably less supported by actual compilers (IIRC, it doesn't even work on VC++.NET 2003). But yes, it should be trivial to change from one to the other.

Share this post


Link to post
Share on other sites
I'm not convinced Connection::operator=() handles self assignment properly if m_node && m_node->refCount == 1. It seems like it'd decrease the reference count to 0 then assign 0 to m_node thus annihilating its state.

Share this post


Link to post
Share on other sites
*smacks forehead* I'm not exactly sure how that got off my TODO list without actually getting done. Thanks.

Share this post


Link to post
Share on other sites
For that matter, while I suck horribly at reading boost::preprocessor code, I can't seem to find assignment operators or copy constructor for the Event<> class (or a NonCopyable inheritance).

Share this post


Link to post
Share on other sites
Hmm, you're right. I forgot to copy over the boost::noncopyable designation when I switched to file-based iteration.

Share this post


Link to post
Share on other sites
Both of the above issues (and a couple of others) are now fixed in the latest version.

Share this post


Link to post
Share on other sites
I'm trying to convince myself of the utility of the ConnectionSet class, and to be honest, I'm failing. It doesn't seem to supply any additional invariants over a container of Connection objects that an object could hold anyway. Right now it just seems like a good way to create dangling references in an Event<> delegate chain.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
I'm trying to convince myself of the utility of the ConnectionSet class, and to be honest, I'm failing. It doesn't seem to supply any additional invariants over a container of Connection objects that an object could hold anyway.

Oh, it surely doesn't. It's primarily there as a simplified adapter class, and as a "best practice" container which can have its implementation modified as necessary. In particular, I've been thinking about ways to be able to free the heap-allocated refcount once the Connection is put in a ConnectionSet, to optimize for the case where that's the one and only reference.
Quote:
Right now it just seems like a good way to create dangling references in an Event<> delegate chain.

Hm... how do you mean?

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!