EVENT-DRIVEN PROGRAMMING& BOOST SIGNALSAlex GoldbergSpring 2008Signals and Slots Signals and Slots is an event-driven programming strategy, intended to reduce interdependencies without loss of flexibility Several widely-used implementations: Boost Signals Qt Signals & SlotsBoost Boost (http://boost.org) is an open-source collection of C++ libraries and header files, providing many useful utilities: Smart pointers Additional data structures (circular buffer, etc) Powerful iterators Callbacks & signals Quaternions Threads Much more…Boost Contd. Most Boost components are implemented entirely in header files Simply add the few required headers to your project Quite a few Boost components will very likely be added to the next C++ standard library revision (see Technical Report 1)Boost Signals Signals & Slots and event-driven programming are very powerful techniques, but it may not be clear how best to use them Most tutorials are centered around GUI widgets or abstract concepts I‟ll present a game design example that should give you a good startLet‟s Blow Stuff Up! The goal: We‟re making an action game and want some cool explosions Several occurrences trigger explosions: Detonated rockets/grenades Exploding barrels Destroyed tanks Etc. Lot‟s of things need to happen when an explosion occurs: A fancy explosion particle system must be created A sound must play A crater texture must be applied at the blast site Players near the explosion need to take damage Nearby barrels should explodeThe Explosion Manager A logical approach might involve a singleton class that notifies the appropriate parties about explosions Let‟s create an explosion manager class: ExplosionMgr Objects that trigger explosions call ExplosionMgr::triggerExplosion() The manager then calls functions on all the objects that care about explosionsExplosionMgrclass ExplosionMgr { public: //Receive an explosion message static void triggerExplosion(const Vector3 &pos, float radius) { handleExplosion(pos, radius); } private: //Notify others about the explosion static void handleExplosion(const Vector3 &pos, float radius) { //Notify sound managerSoundMgr::PlaySound(“Explosion.wav”);//Notify particle system //Notify players //Notify barrels //Etc. } };Barrel We could then define an explodable barrel as:class Barrel { public: void onDestroyed() { //Trigger explosion ExplosionMgr::triggerExplosion(m_myPos, 10); //Do some other stuff... } void handleExplosion(const Vector3 &pos, float radius) { //Destroy this barrel if a nearby explosion occurred if (pos != m_myPos && myPos.distTo(pos) <= radius) onDestroyed(); } };Are We Done? This approach is functional, but quickly grows out of control and violates the low coupling principle The explosion manager must be aware of many other game components, and many components must be aware of the explosion manager This makes it very hard to maintain clean boundaries between modules… Consider the Barrel objects: Barrels must send a message when they blow up, but also need to know when nearby objects explode This means that barrels rely on the ExplosionMgr, which in turn relies on barrels. Ugly…Alternatives… We could store a list of recent explosions in ExplosionMgr (a „polling station‟ approach), but that requires many objects to check for explosions each frame Explosions might only occur every 1000 frames – lots of wasted checks Also doesn‟t make sense for things like the sound manager to know about the explosion managerAlternatives Contd. Another approach: have objects that receive explosions derive from some IExplosionReceiverinterface ExplosionMgr stores a list of these objects, and notifies them about explosions through a virtual IExplosionReceiver::Notify() function This actually isn‟t a bad approach for this simple case However, some objects might need to know about dozens of different events in the game It would be nice if we didn‟t need to inherit dozens of interfaces…Signals To The Rescue This scenario is very common in software design, and Signals & Slots is one way of tackling it Each class may have one or more „signals‟, which define a type-safe set of messages that class can send Similarly, each class can have one or more „slots‟, defining messages that can be receivedBoost Signals & Slots Boost Signals are represented by boost::signal objects, which internally store a list of function pointers that are called sequentially when the signal is invoked Slots are simply global, static, or member functions –no special treatment requiredExplosionMgr Signals & Slots Thinking in terms of signals/slots, we see that ExplosionMgr could be amended to include one signal – a signal notifying objects about an explosion event Any class that wants to be notified by ExplosionMgrneeds to have a slot (member function) taking the appropriate parametersModified ExplosionMgrclass ExplosionMgr { typedef boost::signal<void (const Vector3&, float)> ExplosionSignalType; public: //Notify listeners about explosionstatic void triggerExplosion(const Vector3 &pos, float radius) { m_signalExplosion(pos, radius);} //Connect a slot to the explosion signalstatic void connectSlot(const ExplosionSignalType::slot_type &slot) {m_signalExplosion.connect(slot);}private: ExplosionSignalType m_signalExplosion;};ExplosionMgr Changes Let‟s look at each change: typedef boost::signal<void (const Vector3&, float)> ExplosionSignalType; This defines the type of our signal. It states that our signal can only be connected to slot functions that take as parameters a const reference to a Vector3 and a float, and have return type void. Attempting to connect to the wrong slot type will be a compile-time error m_signalExplosion(pos, radius); This calls each slot function that has been connected to the m_signalExplosion signal. This works because signal objects define the operator() function, taking the appropriate parameter types and returning the desired return typeExplosionMgr Changes Contd. static void connectExplosionSlot(const ExplosionSignalType::slot_type &slot){m_signalExplosion.connect(slot); } This function links a slot to the explosion signal. slot is a function pointer (or Boost function object) with the appropriate prototypeModified Barrel Let‟s now redefine Barrel accordingly:class Barrel :
View Full Document