diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2009-05-08 21:08:08 +0000 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2009-05-08 21:08:08 +0000 |
commit | 3800c0df910c83e987184d541b868168fc2b5bec (patch) | |
tree | 91bcf4e13972ae02b9d6500c1d14de7bb8d37dc4 /indra/llcommon/llevents.h | |
parent | 5da967dc744f35d5270c7cb0b8b23b993ecda3e1 (diff) |
svn merge -r114679:114681 svn+ssh://svn.lindenlab.com/svn/linden/branches/event-system/event-system-7 svn+ssh://svn.lindenlab.com/svn/linden/branches/event-system/event-system-8
Diffstat (limited to 'indra/llcommon/llevents.h')
-rw-r--r-- | indra/llcommon/llevents.h | 822 |
1 files changed, 822 insertions, 0 deletions
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h new file mode 100644 index 0000000000..2f6515a4cb --- /dev/null +++ b/indra/llcommon/llevents.h @@ -0,0 +1,822 @@ +/** + * @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=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTS_H) +#define LL_LLEVENTS_H + +#include <string> +#include <map> +#include <set> +#include <vector> +#include <list> +#include <deque> +#include <stdexcept> +#include <boost/signals2.hpp> +#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/ptr_container/ptr_vector.hpp> +#include <boost/visit_each.hpp> +#include <boost/ref.hpp> // reference_wrapper +#include <boost/type_traits/is_pointer.hpp> +#include <boost/utility/addressof.hpp> +#include <boost/preprocessor/repetition/enum_params.hpp> +#include <boost/preprocessor/iteration/local.hpp> +#include <boost/function.hpp> +#include <boost/static_assert.hpp> +#include "llsd.h" +#include "llsingleton.h" +#include "lldependencies.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; + +/** + * 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 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 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(); + +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; + + /** + * 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); +} // namespace LLEventDetail + +/***************************************************************************** +* LLEventPump +*****************************************************************************/ +/** + * LLEventPump is the base class interface through which we access the + * concrete subclasses LLEventStream and LLEventQueue. + */ +class LLEventPump: boost::noncopyable +{ +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. + * + * If (as is typical) you pass a <tt>boost::bind()</tt> expression, + * 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(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; } + +private: + friend class LLEventPumps; + /// flush queued events + virtual void flush() {} + +private: + virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + const NameList& after, + const NameList& before); + std::string mName; + +protected: + /// implement the dispatching + 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 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 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; +}; + +/***************************************************************************** +* LLEventTrackable and underpinnings +*****************************************************************************/ +/** + * 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; + +/** + * 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 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. At present, wrapping + // this functionality into this function is a bit silly: we don't + // really need a visit_and_connect() function any more, just a visit() + // function. The definition of this function dates from when, after + // visit_each(), after establishing the connection, we had to + // postprocess the new connection with the visitor object. That's no + // longer necessary. + return connect_func(listener); + } +} // 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) */ |