/** * @file llevents.h * @author Kent Quirk, Nat Goodspeed * @date 2008-09-11 * @brief This is an implementation of the event system described at * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Event_System, * originally introduced in llnotifications.h. It has nothing * whatsoever to do with the older system in llevent.h. * * $LicenseInfo:firstyear=2008&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #if ! defined(LL_LLEVENTS_H) #define LL_LLEVENTS_H #include <string> #include <map> #include <set> #include <vector> #include <deque> #include <stdexcept> #if LL_WINDOWS #pragma warning (push) #pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch #pragma warning (disable : 4264) #endif #include <boost/signals2.hpp> #if LL_WINDOWS #pragma warning (pop) #endif #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/utility.hpp> // noncopyable #include <boost/optional/optional.hpp> #include <boost/visit_each.hpp> #include <boost/ref.hpp> // reference_wrapper #include <boost/type_traits/is_pointer.hpp> #include <boost/function.hpp> #include <boost/static_assert.hpp> #include "llsd.h" #include "llsingleton.h" #include "lldependencies.h" #include "ll_template_cast.h" /*==========================================================================*| // override this to allow binding free functions with more parameters #ifndef LLEVENTS_LISTENER_ARITY #define LLEVENTS_LISTENER_ARITY 10 #endif |*==========================================================================*/ // hack for testing #ifndef testable #define testable private #endif /***************************************************************************** * Signal and handler declarations * Using a single handler signature means that we can have a common handler * type, rather than needing a distinct one for each different handler. *****************************************************************************/ /** * A boost::signals Combiner that stops the first time a handler returns true * We need this because we want to have our handlers return bool, so that * we have the option to cause a handler to stop further processing. The * default handler fails when the signal returns a value but has no slots. */ struct LLStopWhenHandled { typedef bool result_type; template<typename InputIterator> result_type operator()(InputIterator first, InputIterator last) const { for (InputIterator si = first; si != last; ++si) { if (*si) { return true; } } return false; } }; /** * We want to have a standard signature for all signals; this way, * we can easily document a protocol for communicating across * dlls and into scripting languages someday. * * We want to return a bool to indicate whether the signal has been * handled and should NOT be passed on to other listeners. * Return true to stop further handling of the signal, and false * to continue. * * We take an LLSD because this way the contents of the signal * are independent of the API used to communicate it. * It is const ref because then there's low cost to pass it; * if you only need to inspect it, it's very cheap. * * @internal * The @c float template parameter indicates that we will internally use @c * float to indicate relative listener order on a given LLStandardSignal. * Don't worry, the @c float values are strictly internal! They are not part * of the interface, for the excellent reason that requiring the caller to * specify a numeric key to establish order means that the caller must know * the universe of possible values. We use LLDependencies for that instead. */ typedef boost::signals2::signal<bool(const LLSD&), LLStopWhenHandled, float> LLStandardSignal; /// Methods that forward listeners (e.g. constructed with /// <tt>boost::bind()</tt>) should accept (const LLEventListener&) typedef LLStandardSignal::slot_type LLEventListener; /// Result of registering a listener, supports <tt>connected()</tt>, /// <tt>disconnect()</tt> and <tt>blocked()</tt> typedef boost::signals2::connection LLBoundListener; /// Storing an LLBoundListener in LLTempBoundListener will disconnect the /// referenced listener when the LLTempBoundListener instance is destroyed. typedef boost::signals2::scoped_connection LLTempBoundListener; /** * A common idiom for event-based code is to accept either a callable -- * directly called on completion -- or the string name of an LLEventPump on * which to post the completion event. Specifying a parameter as <tt>const * LLListenerOrPumpName&</tt> allows either. * * Calling a validly-constructed LLListenerOrPumpName, passing the LLSD * 'event' object, either calls the callable or posts the event to the named * LLEventPump. * * A default-constructed LLListenerOrPumpName is 'empty'. (This is useful as * the default value of an optional method parameter.) Calling it throws * LLListenerOrPumpName::Empty. Test for this condition beforehand using * either <tt>if (param)</tt> or <tt>if (! param)</tt>. */ class LL_COMMON_API LLListenerOrPumpName { public: /// passing string name of LLEventPump LLListenerOrPumpName(const std::string& pumpname); /// passing string literal (overload so compiler isn't forced to infer /// double conversion) LLListenerOrPumpName(const char* pumpname); /// passing listener -- the "anything else" catch-all case. The type of an /// object constructed by boost::bind() isn't intended to be written out. /// Normally we'd just accept 'const LLEventListener&', but that would /// require double implicit conversion: boost::bind() object to /// LLEventListener, LLEventListener to LLListenerOrPumpName. So use a /// template to forward anything. template<typename T> LLListenerOrPumpName(const T& listener): mListener(listener) {} /// for omitted method parameter: uninitialized mListener LLListenerOrPumpName() {} /// test for validity operator bool() const { return bool(mListener); } bool operator! () const { return ! mListener; } /// explicit accessor const LLEventListener& getListener() const { return *mListener; } /// implicit conversion to LLEventListener operator LLEventListener() const { return *mListener; } /// allow calling directly bool operator()(const LLSD& event) const; /// exception if you try to call when empty struct Empty: public std::runtime_error { Empty(const std::string& what): std::runtime_error(std::string("LLListenerOrPumpName::Empty: ") + what) {} }; private: boost::optional<LLEventListener> mListener; }; /***************************************************************************** * LLEventPumps *****************************************************************************/ class LLEventPump; /** * LLEventPumps is a Singleton manager through which one typically accesses * this subsystem. */ class LL_COMMON_API LLEventPumps: public LLSingleton<LLEventPumps> { friend class LLSingleton<LLEventPumps>; public: /** * Find or create an LLEventPump instance with a specific name. We return * a reference so there's no question about ownership. obtain() @em finds * an instance without conferring @em ownership. */ LLEventPump& obtain(const std::string& name); /** * Flush all known LLEventPump instances */ void flush(); /** * Reset all known LLEventPump instances * workaround for DEV-35406 crash on shutdown */ void reset(); private: friend class LLEventPump; /** * Register a new LLEventPump instance (internal) */ std::string registerNew(const LLEventPump&, const std::string& name, bool tweak); /** * Unregister a doomed LLEventPump instance (internal) */ void unregister(const LLEventPump&); private: LLEventPumps(); ~LLEventPumps(); testable: // Map of all known LLEventPump instances, whether or not we instantiated // them. We store a plain old LLEventPump* because this map doesn't claim // ownership of the instances. Though the common usage pattern is to // request an instance using obtain(), it's fair to instantiate an // LLEventPump subclass statically, as a class member, on the stack or on // the heap. In such cases, the instantiating party is responsible for its // lifespan. typedef std::map<std::string, LLEventPump*> PumpMap; PumpMap mPumpMap; // Set of all LLEventPumps we instantiated. Membership in this set means // we claim ownership, and will delete them when this LLEventPumps is // destroyed. typedef std::set<LLEventPump*> PumpSet; PumpSet mOurPumps; // LLEventPump names that should be instantiated as LLEventQueue rather // than as LLEventStream typedef std::set<std::string> PumpNames; PumpNames mQueueNames; }; /***************************************************************************** * details *****************************************************************************/ namespace LLEventDetail { /// Any callable capable of connecting an LLEventListener to an /// LLStandardSignal to produce an LLBoundListener can be mapped to this /// signature. typedef boost::function<LLBoundListener(const LLEventListener&)> ConnectFunc; /// overload of visit_and_connect() when we have a string identifier available template <typename LISTENER> LLBoundListener visit_and_connect(const std::string& name, const LISTENER& listener, const ConnectFunc& connect_func); /** * Utility template function to use Visitor appropriately * * @param listener Callable to connect, typically a boost::bind() * expression. This will be visited by Visitor using boost::visit_each(). * @param connect_func Callable that will connect() @a listener to an * LLStandardSignal, returning LLBoundListener. */ template <typename LISTENER> LLBoundListener visit_and_connect(const LISTENER& listener, const ConnectFunc& connect_func) { return visit_and_connect("", listener, connect_func); } } // namespace LLEventDetail /***************************************************************************** * LLEventTrackable *****************************************************************************/ /** * LLEventTrackable wraps boost::signals2::trackable, which resembles * boost::trackable. Derive your listener class from LLEventTrackable instead, * and use something like * <tt>LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, * instance, _1))</tt>. This will implicitly disconnect when the object * referenced by @c instance is destroyed. * * @note * LLEventTrackable doesn't address a couple of cases: * * Object destroyed during call * - You enter a slot call in thread A. * - Thread B destroys the object, which of course disconnects it from any * future slot calls. * - Thread A's call uses 'this', which now refers to a defunct object. * Undefined behavior results. * * Call during destruction * - @c MySubclass is derived from LLEventTrackable. * - @c MySubclass registers one of its own methods using * <tt>LLEventPump::listen()</tt>. * - The @c MySubclass object begins destruction. <tt>~MySubclass()</tt> * runs, destroying state specific to the subclass. (For instance, a * <tt>Foo*</tt> data member is <tt>delete</tt>d but not zeroed.) * - The listening method will not be disconnected until * <tt>~LLEventTrackable()</tt> runs. * - Before we get there, another thread posts data to the @c LLEventPump * instance, calling the @c MySubclass method. * - The method in question relies on valid @c MySubclass state. (For * instance, it attempts to dereference the <tt>Foo*</tt> pointer that was * <tt>delete</tt>d but not zeroed.) * - Undefined behavior results. * If you suspect you may encounter any such scenario, you're better off * managing the lifespan of your object with <tt>boost::shared_ptr</tt>. * Passing <tt>LLEventPump::listen()</tt> a <tt>boost::bind()</tt> expression * involving a <tt>boost::weak_ptr<Foo></tt> is recognized specially, engaging * thread-safe Boost.Signals2 machinery. */ typedef boost::signals2::trackable LLEventTrackable; /***************************************************************************** * LLEventPump *****************************************************************************/ /** * LLEventPump is the base class interface through which we access the * concrete subclasses LLEventStream and LLEventQueue. * * @NOTE * LLEventPump derives from LLEventTrackable so that when you "chain" * LLEventPump instances together, they will automatically disconnect on * destruction. Please see LLEventTrackable documentation for situations in * which this may be perilous across threads. */ class LL_COMMON_API LLEventPump: public LLEventTrackable { public: /** * Exception thrown by LLEventPump(). You are trying to instantiate an * LLEventPump (subclass) using the same name as some other instance, and * you didn't pass <tt>tweak=true</tt> to permit it to generate a unique * variant. */ struct DupPumpName: public std::runtime_error { DupPumpName(const std::string& what): std::runtime_error(std::string("DupPumpName: ") + what) {} }; /** * Instantiate an LLEventPump (subclass) with the string name by which it * can be found using LLEventPumps::obtain(). * * If you pass (or default) @a tweak to @c false, then a duplicate name * will throw DupPumpName. This won't happen if LLEventPumps::obtain() * instantiates the LLEventPump, because obtain() uses find-or-create * logic. It can only happen if you instantiate an LLEventPump in your own * code -- and a collision with the name of some other LLEventPump is * likely to cause much more subtle problems! * * When you hand-instantiate an LLEventPump, consider passing @a tweak as * @c true. This directs LLEventPump() to append a suffix to the passed @a * name to make it unique. You can retrieve the adjusted name by calling * getName() on your new instance. */ LLEventPump(const std::string& name, bool tweak=false); virtual ~LLEventPump(); /// group exceptions thrown by listen(). We use exceptions because these /// particular errors are likely to be coding errors, found and fixed by /// the developer even before preliminary checkin. struct ListenError: public std::runtime_error { ListenError(const std::string& what): std::runtime_error(what) {} }; /** * exception thrown by listen(). You are attempting to register a * listener on this LLEventPump using the same listener name as an * already-registered listener. */ struct DupListenerName: public ListenError { DupListenerName(const std::string& what): ListenError(std::string("DupListenerName: ") + what) {} }; /** * exception thrown by listen(). The order dependencies specified for your * listener are incompatible with existing listeners. * * Consider listener "a" which specifies before "b" and "b" which * specifies before "c". You are now attempting to register "c" before * "a". There is no order that can satisfy all constraints. */ struct Cycle: public ListenError { Cycle(const std::string& what): ListenError(std::string("Cycle: ") + what) {} }; /** * exception thrown by listen(). This one means that your new listener * would force a change to the order of previously-registered listeners, * and we don't have a good way to implement that. * * Consider listeners "some", "other" and "third". "some" and "other" are * registered earlier without specifying relative order, so "other" * happens to be first. Now you attempt to register "third" after "some" * and before "other". Whoops, that would require swapping "some" and * "other", which we can't do. Instead we throw this exception. * * It may not be possible to change the registration order so we already * know "third"s order requirement by the time we register the second of * "some" and "other". A solution would be to specify that "some" must * come before "other", or equivalently that "other" must come after * "some". */ struct OrderChange: public ListenError { OrderChange(const std::string& what): ListenError(std::string("OrderChange: ") + what) {} }; /// used by listen() typedef std::vector<std::string> NameList; /// convenience placeholder for when you explicitly want to pass an empty /// NameList const static NameList empty; /// Get this LLEventPump's name std::string getName() const { return mName; } /** * Register a new listener with a unique name. Specify an optional list * of other listener names after which this one must be called, likewise * an optional list of other listener names before which this one must be * called. The other listeners mentioned need not yet be registered * themselves. listen() can throw any ListenError; see ListenError * subclasses. * * The listener name must be unique among active listeners for this * LLEventPump, else you get DupListenerName. If you don't care to invent * a name yourself, use inventName(). (I was tempted to recognize e.g. "" * and internally generate a distinct name for that case. But that would * handle badly the scenario in which you want to add, remove, re-add, * etc. the same listener: each new listen() call would necessarily * perform a new dependency sort. Assuming you specify the same * after/before lists each time, using inventName() when you first * instantiate your listener, then passing the same name on each listen() * call, allows us to optimize away the second and subsequent dependency * sorts. * * If (as is typical) you pass a <tt>boost::bind()</tt> expression as @a * listener, listen() will inspect the components of that expression. If a * bound object matches any of several cases, the connection will * automatically be disconnected when that object is destroyed. * * * You bind a <tt>boost::weak_ptr</tt>. * * Binding a <tt>boost::shared_ptr</tt> that way would ensure that the * referenced object would @em never be destroyed, since the @c * shared_ptr stored in the LLEventPump would remain an outstanding * reference. Use the weaken() function to convert your @c shared_ptr to * @c weak_ptr. Because this is easy to forget, binding a @c shared_ptr * will produce a compile error (@c BOOST_STATIC_ASSERT failure). * * You bind a simple pointer or reference to an object derived from * <tt>boost::enable_shared_from_this</tt>. (UNDER CONSTRUCTION) * * You bind a simple pointer or reference to an object derived from * LLEventTrackable. Unlike the cases described above, though, this is * vulnerable to a couple of cross-thread race conditions, as described * in the LLEventTrackable documentation. */ template <typename LISTENER> LLBoundListener listen(const std::string& name, const LISTENER& listener, const NameList& after=NameList(), const NameList& before=NameList()) { // Examine listener, using our listen_impl() method to make the // actual connection. // This is why listen() is a template. Conversion from boost::bind() // to LLEventListener performs type erasure, so it's important to look // at the boost::bind object itself before that happens. return LLEventDetail::visit_and_connect(name, listener, boost::bind(&LLEventPump::listen_impl, this, name, _1, after, before)); } /// Get the LLBoundListener associated with the passed name (dummy /// LLBoundListener if not found) virtual LLBoundListener getListener(const std::string& name) const; /** * Instantiate one of these to block an existing connection: * @code * { // in some local scope * LLEventPump::Blocker block(someLLBoundListener); * // code that needs the connection blocked * } // unblock the connection again * @endcode */ typedef boost::signals2::shared_connection_block Blocker; /// Unregister a listener by name. Prefer this to /// <tt>getListener(name).disconnect()</tt> because stopListening() also /// forgets this name. virtual void stopListening(const std::string& name); /// Post an event to all listeners. The @c bool return is only meaningful /// if the underlying leaf class is LLEventStream -- beware of relying on /// it too much! Truthfully, we return @c bool mostly to permit chaining /// one LLEventPump as a listener on another. virtual bool post(const LLSD&) = 0; /// Enable/disable: while disabled, silently ignore all post() calls virtual void enable(bool enabled=true) { mEnabled = enabled; } /// query virtual bool enabled() const { return mEnabled; } /// Generate a distinct name for a listener -- see listen() static std::string inventName(const std::string& pfx="listener"); private: friend class LLEventPumps; /// flush queued events virtual void flush() {} virtual void reset(); private: virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, const NameList& after, const NameList& before); std::string mName; protected: /// implement the dispatching boost::shared_ptr<LLStandardSignal> mSignal; /// valve open? bool mEnabled; /// Map of named listeners. This tracks the listeners that actually exist /// at this moment. When we stopListening(), we discard the entry from /// this map. typedef std::map<std::string, boost::signals2::connection> ConnectionMap; ConnectionMap mConnections; typedef LLDependencies<std::string, float> DependencyMap; /// Dependencies between listeners. For each listener, track the float /// used to establish its place in mSignal's order. This caches all the /// listeners that have ever registered; stopListening() does not discard /// the entry from this map. This is to avoid a new dependency sort if the /// same listener with the same dependencies keeps hopping on and off this /// LLEventPump. DependencyMap mDeps; }; /***************************************************************************** * LLEventStream *****************************************************************************/ /** * LLEventStream is a thin wrapper around LLStandardSignal. Posting an * event immediately calls all registered listeners. */ class LL_COMMON_API LLEventStream: public LLEventPump { public: LLEventStream(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} virtual ~LLEventStream() {} /// Post an event to all listeners virtual bool post(const LLSD& event); }; /***************************************************************************** * LLEventQueue *****************************************************************************/ /** * LLEventQueue isa LLEventPump whose post() method defers calling registered * listeners until flush() is called. */ class LL_COMMON_API LLEventQueue: public LLEventPump { public: LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} virtual ~LLEventQueue() {} /// Post an event to all listeners virtual bool post(const LLSD& event); private: /// flush queued events virtual void flush(); private: typedef std::deque<LLSD> EventQueue; EventQueue mEventQueue; }; /***************************************************************************** * LLReqID *****************************************************************************/ /** * This class helps the implementer of a given event API to honor the * ["reqid"] convention. By this convention, each event API stamps into its * response LLSD a ["reqid"] key whose value echoes the ["reqid"] value, if * any, from the corresponding request. * * This supports an (atypical, but occasionally necessary) use case in which * two or more asynchronous requests are multiplexed onto the same ["reply"] * LLEventPump. Since the response events could arrive in arbitrary order, the * caller must be able to demux them. It does so by matching the ["reqid"] * value in each response with the ["reqid"] value in the corresponding * request. * * It is the caller's responsibility to ensure distinct ["reqid"] values for * that case. Though LLSD::UUID is guaranteed to work, it might be overkill: * the "namespace" of unique ["reqid"] values is simply the set of requests * specifying the same ["reply"] LLEventPump name. * * Making a given event API echo the request's ["reqid"] into the response is * nearly trivial. This helper is mostly for mnemonic purposes, to serve as a * place to put these comments. We hope that each time a coder implements a * new event API based on some existing one, s/he will say, "Huh, what's an * LLReqID?" and look up this material. * * The hardest part about the convention is deciding where to store the * ["reqid"] value. Ironically, LLReqID can't help with that: you must store * an LLReqID instance in whatever storage will persist until the reply is * sent. For example, if the request ultimately ends up using a Responder * subclass, storing an LLReqID instance in the Responder works. * * @note * The @em implementer of an event API must honor the ["reqid"] convention. * However, the @em caller of an event API need only use it if s/he is sharing * the same ["reply"] LLEventPump for two or more asynchronous event API * requests. * * In most cases, it's far easier for the caller to instantiate a local * LLEventStream and pass its name to the event API in question. Then it's * perfectly reasonable not to set a ["reqid"] key in the request, ignoring * the @c isUndefined() ["reqid"] value in the response. */ class LL_COMMON_API LLReqID { public: /** * If you have the request in hand at the time you instantiate the * LLReqID, pass that request to extract its ["reqid"]. */ LLReqID(const LLSD& request): mReqid(request["reqid"]) {} /// If you don't yet have the request, use setFrom() later. LLReqID() {} /// Extract and store the ["reqid"] value from an incoming request. void setFrom(const LLSD& request) { mReqid = request["reqid"]; } /// Set ["reqid"] key into a pending response LLSD object. void stamp(LLSD& response) const; /// Make a whole new response LLSD object with our ["reqid"]. LLSD makeResponse() const { LLSD response; stamp(response); return response; } /// Not really sure of a use case for this accessor... LLSD getReqID() const { return mReqid; } private: LLSD mReqid; }; /** * Conventionally send a reply to a request event. * * @a reply is the LLSD reply event to send * @a request is the corresponding LLSD request event * @a replyKey is the key in the @a request event, conventionally ["reply"], * whose value is the name of the LLEventPump on which to send the reply. * * Before sending the reply event, sendReply() copies the ["reqid"] item from * the request to the reply. */ LL_COMMON_API bool sendReply(const LLSD& reply, const LLSD& request, const std::string& replyKey="reply"); /** * Base class for LLListenerWrapper. See visit_and_connect() and llwrap(). We * provide virtual @c accept_xxx() methods, customization points allowing a * subclass access to certain data visible at LLEventPump::listen() time. * Example subclass usage: * * @code * myEventPump.listen("somename", * llwrap<MyListenerWrapper>(boost::bind(&MyClass::method, instance, _1))); * @endcode * * Because of the anticipated usage (note the anonymous temporary * MyListenerWrapper instance in the example above), the @c accept_xxx() * methods must be @c const. */ class LL_COMMON_API LLListenerWrapperBase { public: /// New instance. The accept_xxx() machinery makes it important to use /// shared_ptrs for our data. Many copies of this object are made before /// the instance that actually ends up in the signal, yet accept_xxx() /// will later be called on the @em original instance. All copies of the /// same original instance must share the same data. LLListenerWrapperBase(): mName(new std::string), mConnection(new LLBoundListener) { } /// Copy constructor. Copy shared_ptrs to original instance data. LLListenerWrapperBase(const LLListenerWrapperBase& that): mName(that.mName), mConnection(that.mConnection) { } virtual ~LLListenerWrapperBase() {} /// Ask LLEventPump::listen() for the listener name virtual void accept_name(const std::string& name) const { *mName = name; } /// Ask LLEventPump::listen() for the new connection virtual void accept_connection(const LLBoundListener& connection) const { *mConnection = connection; } protected: /// Listener name. boost::shared_ptr<std::string> mName; /// Connection. boost::shared_ptr<LLBoundListener> mConnection; }; /***************************************************************************** * Underpinnings *****************************************************************************/ /** * We originally provided a suite of overloaded * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call * LLEventPump::listen(...) and then pass the returned LLBoundListener to * LLEventTrackable::track(). This was workable but error-prone: the coder * must remember to call listenTo() rather than the more straightforward * listen() method. * * Now we publish only the single canonical listen() method, so there's a * uniform mechanism. Having a single way to do this is good, in that there's * no question in the coder's mind which of several alternatives to choose. * * To support automatic connection management, we use boost::visit_each * (http://www.boost.org/doc/libs/1_37_0/doc/html/boost/visit_each.html) to * inspect each argument of a boost::bind expression. (Although the visit_each * mechanism was first introduced with the original Boost.Signals library, it * was only later documented.) * * Cases: * * At least one of the function's arguments is a boost::weak_ptr<T>. Pass * the corresponding shared_ptr to slot_type::track(). Ideally that would be * the object whose method we want to call, but in fact we do the same for * any weak_ptr we might find among the bound arguments. If we're passing * our bound method a weak_ptr to some object, wouldn't the destruction of * that object invalidate the call? So we disconnect automatically when any * such object is destroyed. This is the mechanism preferred by boost:: * signals2. * * One of the functions's arguments is a boost::shared_ptr<T>. This produces * a compile error: the bound copy of the shared_ptr stored in the * boost_bind object stored in the signal object would make the referenced * T object immortal. We provide a weaken() function. Pass * weaken(your_shared_ptr) instead. (We can inspect, but not modify, the * boost::bind object. Otherwise we'd replace the shared_ptr with weak_ptr * implicitly and just proceed.) * * One of the function's arguments is a plain pointer/reference to an object * derived from boost::enable_shared_from_this. We assume that this object * is managed using boost::shared_ptr, so we implicitly extract a shared_ptr * and track that. (UNDER CONSTRUCTION) * * One of the function's arguments is derived from LLEventTrackable. Pass * the LLBoundListener to its LLEventTrackable::track(). This is vulnerable * to a couple different race conditions, as described in LLEventTrackable * documentation. (NOTE: Now that LLEventTrackable is a typedef for * boost::signals2::trackable, the Signals2 library handles this itself, so * our visitor needs no special logic for this case.) * * Any other argument type is irrelevant to automatic connection management. */ namespace LLEventDetail { template <typename F> const F& unwrap(const F& f) { return f; } template <typename F> const F& unwrap(const boost::reference_wrapper<F>& f) { return f.get(); } // Most of the following is lifted from the Boost.Signals use of // visit_each. template<bool Cond> struct truth {}; /** * boost::visit_each() Visitor, used on a template argument <tt>const F& * f</tt> as follows (see visit_and_connect()): * @code * LLEventListener listener(f); * Visitor visitor(listener); // bind listener so it can track() shared_ptrs * using boost::visit_each; // allow unqualified visit_each() call for ADL * visit_each(visitor, unwrap(f)); * @endcode */ class Visitor { public: /** * Visitor binds a reference to LLEventListener so we can track() any * shared_ptrs we find in the argument list. */ Visitor(LLEventListener& listener): mListener(listener) { } /** * boost::visit_each() calls this method for each component of a * boost::bind() expression. */ template <typename T> void operator()(const T& t) const { decode(t, 0); } private: // decode() decides between a reference wrapper and anything else // boost::ref() variant template<typename T> void decode(const boost::reference_wrapper<T>& t, int) const { // add_if_trackable(t.get_pointer()); } // decode() anything else template<typename T> void decode(const T& t, long) const { typedef truth<(boost::is_pointer<T>::value)> is_a_pointer; maybe_get_pointer(t, is_a_pointer()); } // maybe_get_pointer() decides between a pointer and a non-pointer // plain pointer variant template<typename T> void maybe_get_pointer(const T& t, truth<true>) const { // add_if_trackable(t); } // shared_ptr variant template<typename T> void maybe_get_pointer(const boost::shared_ptr<T>& t, truth<false>) const { // If we have a shared_ptr to this object, it doesn't matter // whether the object is derived from LLEventTrackable, so no // further analysis of T is needed. // mListener.track(t); // Make this case illegal. Passing a bound shared_ptr to // slot_type::track() is useless, since the bound shared_ptr will // keep the object alive anyway! Force the coder to cast to weak_ptr. // Trivial as it is, make the BOOST_STATIC_ASSERT() condition // dependent on template param so the macro is only evaluated if // this method is in fact instantiated, as described here: // http://www.boost.org/doc/libs/1_34_1/doc/html/boost_staticassert.html // ATTENTION: Don't bind a shared_ptr<anything> using // LLEventPump::listen(boost::bind()). Doing so captures a copy of // the shared_ptr, making the referenced object effectively // immortal. Use the weaken() function, e.g.: // somepump.listen(boost::bind(...weaken(my_shared_ptr)...)); // This lets us automatically disconnect when the referenced // object is destroyed. BOOST_STATIC_ASSERT(sizeof(T) == 0); } // weak_ptr variant template<typename T> void maybe_get_pointer(const boost::weak_ptr<T>& t, truth<false>) const { // If we have a weak_ptr to this object, it doesn't matter // whether the object is derived from LLEventTrackable, so no // further analysis of T is needed. mListener.track(t); // std::cout << "Found weak_ptr<" << typeid(T).name() << ">!\n"; } #if 0 // reference to anything derived from boost::enable_shared_from_this template <typename T> inline void maybe_get_pointer(const boost::enable_shared_from_this<T>& ct, truth<false>) const { // Use the slot_type::track(shared_ptr) mechanism. Cast away // const-ness because (in our code base anyway) it's unusual // to find shared_ptr<const T>. boost::enable_shared_from_this<T>& t(const_cast<boost::enable_shared_from_this<T>&>(ct)); std::cout << "Capturing shared_from_this()" << std::endl; boost::shared_ptr<T> sp(t.shared_from_this()); /*==========================================================================*| std::cout << "Capturing weak_ptr" << std::endl; boost::weak_ptr<T> wp(sp); |*==========================================================================*/ std::cout << "Tracking shared__ptr" << std::endl; mListener.track(sp); } #endif // non-pointer variant template<typename T> void maybe_get_pointer(const T& t, truth<false>) const { // Take the address of this object, because the object itself may be // trackable // add_if_trackable(boost::addressof(t)); } /*==========================================================================*| // add_if_trackable() adds LLEventTrackable objects to mTrackables inline void add_if_trackable(const LLEventTrackable* t) const { if (t) { } } // pointer to anything not an LLEventTrackable subclass inline void add_if_trackable(const void*) const { } // pointer to free function // The following construct uses the preprocessor to generate // add_if_trackable() overloads accepting pointer-to-function taking // 0, 1, ..., LLEVENTS_LISTENER_ARITY parameters of arbitrary type. #define BOOST_PP_LOCAL_MACRO(n) \ template <typename R \ BOOST_PP_COMMA_IF(n) \ BOOST_PP_ENUM_PARAMS(n, typename T)> \ inline void \ add_if_trackable(R (*)(BOOST_PP_ENUM_PARAMS(n, T))) const \ { \ } #define BOOST_PP_LOCAL_LIMITS (0, LLEVENTS_LISTENER_ARITY) #include BOOST_PP_LOCAL_ITERATE() #undef BOOST_PP_LOCAL_MACRO #undef BOOST_PP_LOCAL_LIMITS |*==========================================================================*/ /// Bind a reference to the LLEventListener to call its track() method. LLEventListener& mListener; }; /** * Utility template function to use Visitor appropriately * * @param raw_listener Callable to connect, typically a boost::bind() * expression. This will be visited by Visitor using boost::visit_each(). * @param connect_funct Callable that will connect() @a raw_listener to an * LLStandardSignal, returning LLBoundListener. */ template <typename LISTENER> LLBoundListener visit_and_connect(const std::string& name, const LISTENER& raw_listener, const ConnectFunc& connect_func) { // Capture the listener LLEventListener listener(raw_listener); // Define our Visitor, binding the listener so we can call // listener.track() if we discover any shared_ptr<Foo>. LLEventDetail::Visitor visitor(listener); // Allow unqualified visit_each() call for ADL using boost::visit_each; // Visit each component of a boost::bind() expression. Pass // 'raw_listener', our template argument, rather than 'listener' from // which type details have been erased. unwrap() comes from // Boost.Signals, in case we were passed a boost::ref(). visit_each(visitor, LLEventDetail::unwrap(raw_listener)); // Make the connection using passed function. LLBoundListener connection(connect_func(listener)); // If the LISTENER is an LLListenerWrapperBase subclass, pass it the // desired information. It's important that we pass the raw_listener // so the compiler can make decisions based on its original type. const LLListenerWrapperBase* lwb = ll_template_cast<const LLListenerWrapperBase*>(&raw_listener); if (lwb) { lwb->accept_name(name); lwb->accept_connection(connection); } // In any case, show new connection to caller. return connection; } } // namespace LLEventDetail // Somewhat to my surprise, passing boost::bind(...boost::weak_ptr<T>...) to // listen() fails in Boost code trying to instantiate LLEventListener (i.e. // LLStandardSignal::slot_type) because the boost::get_pointer() utility function isn't // specialized for boost::weak_ptr. This remedies that omission. namespace boost { template <typename T> T* get_pointer(const weak_ptr<T>& ptr) { return shared_ptr<T>(ptr).get(); } } /// Since we forbid use of listen(boost::bind(...shared_ptr<T>...)), provide an /// easy way to cast to the corresponding weak_ptr. template <typename T> boost::weak_ptr<T> weaken(const boost::shared_ptr<T>& ptr) { return boost::weak_ptr<T>(ptr); } #endif /* ! defined(LL_LLEVENTS_H) */