diff options
| author | Brad Kittenbrink <brad@lindenlab.com> | 2009-06-04 16:24:21 +0000 | 
|---|---|---|
| committer | Brad Kittenbrink <brad@lindenlab.com> | 2009-06-04 16:24:21 +0000 | 
| commit | 087bd265534b8e3086ae1af441a9cf0eb7c684df (patch) | |
| tree | a0c911515476d4ac4aac9bd984de28eb7573a478 | |
| parent | f9b9372027a41900ad572afcd7ea0d2cc5489b8f (diff) | |
Merge of QAR-1383 event-system-7 into trunk.
svn merge -r 121797:121853 svn+ssh://svn.lindenlab.com/svn/linden/branches/merge-event-system-7
52 files changed, 5071 insertions, 253 deletions
| diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index d6a9e10707..51bd4354df 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -3,6 +3,7 @@  project(llcommon)  include(00-Common) +include(LLAddBuildTest)  include(LLCommon)  include_directories( @@ -22,9 +23,11 @@ set(llcommon_SOURCE_FILES      llcriticaldamp.cpp      llcursortypes.cpp      lldate.cpp +    lldependencies.cpp      llerror.cpp      llerrorthread.cpp      llevent.cpp +    llevents.cpp      llfasttimer.cpp      llfile.cpp      llfindlocale.cpp @@ -95,6 +98,7 @@ set(llcommon_HEADER_FILES      lldarrayptr.h      lldate.h      lldefs.h +    lldependencies.h      lldepthstack.h      lldlinked.h      lldqueueptr.h @@ -105,6 +109,7 @@ set(llcommon_HEADER_FILES      llerrorlegacy.h      llerrorthread.h      llevent.h +    llevents.h      lleventemitter.h      llextendedstatus.h      llfasttimer.h @@ -118,6 +123,7 @@ set(llcommon_HEADER_FILES      llhttpstatuscodes.h      llindexedqueue.h      llkeythrottle.h +    lllazy.h      lllinkedqueue.h      llliveappconfig.h      lllivefile.h @@ -176,6 +182,7 @@ set(llcommon_HEADER_FILES      stdenums.h      stdtypes.h      string_table.h +    stringize.h      timer.h      timing.h      u64.h @@ -194,3 +201,5 @@ target_link_libraries(      ${EXPAT_LIBRARIES}      ${ZLIB_LIBRARIES}      ) + +ADD_BUILD_TEST(lllazy llcommon) diff --git a/indra/llcommon/lldependencies.cpp b/indra/llcommon/lldependencies.cpp new file mode 100644 index 0000000000..ffb5cfbdaa --- /dev/null +++ b/indra/llcommon/lldependencies.cpp @@ -0,0 +1,86 @@ +/** + * @file   lldependencies.cpp + * @author Nat Goodspeed + * @date   2008-09-17 + * @brief  Implementation for lldependencies. + *  + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lldependencies.h" +// STL headers +#include <map> +#include <sstream> +// std headers +// external library headers +#include <boost/graph/graph_traits.hpp>  // for boost::graph_traits +#include <boost/graph/adjacency_list.hpp> +#include <boost/graph/topological_sort.hpp> +#include <boost/graph/exception.hpp> +// other Linden headers + +LLDependenciesBase::VertexList LLDependenciesBase::topo_sort(int vertices, const EdgeList& edges) const +{ +    // Construct a Boost Graph Library graph according to the constraints +    // we've collected. It seems as though we ought to be able to capture +    // the uniqueness of vertex keys using a setS of vertices with a +    // string property -- but I don't yet understand adjacency_list well +    // enough to get there. All the examples I've seen so far use integers +    // for vertices. +    // Define the Graph type. Use a vector for vertices so we can use the +    // default topological_sort vertex lookup by int index. Use a set for +    // edges because the same dependency may be stated twice: Node "a" may +    // specify that it must precede "b", while "b" may also state that it +    // must follow "a". +    typedef boost::adjacency_list<boost::setS, boost::vecS, boost::directedS, +                                  boost::no_property> Graph; +    // Instantiate the graph. Without vertex properties, we need say no +    // more about vertices than the total number. +    Graph g(edges.begin(), edges.end(), vertices); +    // topo sort +    typedef boost::graph_traits<Graph>::vertex_descriptor VertexDesc; +    typedef std::vector<VertexDesc> SortedList; +    SortedList sorted; +    // note that it throws not_a_dag if it finds a cycle +    try +    { +        boost::topological_sort(g, std::back_inserter(sorted)); +    } +    catch (const boost::not_a_dag& e) +    { +        // translate to the exception we define +        std::ostringstream out; +        out << "LLDependencies cycle: " << e.what() << '\n'; +        // Omit independent nodes: display only those that might contribute to +        // the cycle. +        describe(out, false); +        throw Cycle(out.str()); +    } +    // A peculiarity of boost::topological_sort() is that it emits results in +    // REVERSE topological order: to get the result you want, you must +    // traverse the SortedList using reverse iterators. +    return VertexList(sorted.rbegin(), sorted.rend()); +} + +std::ostream& LLDependenciesBase::describe(std::ostream& out, bool full) const +{ +    // Should never encounter this base-class implementation; may mean that +    // the KEY type doesn't have a suitable ostream operator<<(). +    out << "<no description available>"; +    return out; +} + +std::string LLDependenciesBase::describe(bool full) const +{ +    // Just use the ostream-based describe() on a std::ostringstream. The +    // implementation is here mostly so that we can avoid #include <sstream> +    // in the header file. +    std::ostringstream out; +    describe(out, full); +    return out.str(); +} diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h new file mode 100644 index 0000000000..bd4bd7c96a --- /dev/null +++ b/indra/llcommon/lldependencies.h @@ -0,0 +1,779 @@ +/** + * @file   lldependencies.h + * @author Nat Goodspeed + * @date   2008-09-17 + * @brief  LLDependencies: a generic mechanism for expressing "b must follow a, + *         but precede c" + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLDEPENDENCIES_H) +#define LL_LLDEPENDENCIES_H + +#include <string> +#include <vector> +#include <set> +#include <map> +#include <stdexcept> +#include <iosfwd> +#include <boost/iterator/transform_iterator.hpp> +#include <boost/iterator/indirect_iterator.hpp> +#include <boost/range/iterator_range.hpp> +#include <boost/function.hpp> +#include <boost/bind.hpp> + +/***************************************************************************** +*   Utilities +*****************************************************************************/ +/** + * generic range transformer: given a range acceptable to Boost.Range (e.g. a + * standard container, an iterator pair, ...) and a unary function to apply to + * each element of the range, make a corresponding range that lazily applies + * that function to each element on dereferencing. + */ +template<typename FUNCTION, typename RANGE> +inline +boost::iterator_range<boost::transform_iterator<FUNCTION, +                                                typename boost::range_const_iterator<RANGE>::type> > +make_transform_range(const RANGE& range, FUNCTION function) +{ +    // shorthand for the iterator type embedded in our return type +    typedef boost::transform_iterator<FUNCTION, typename boost::range_const_iterator<RANGE>::type> +        transform_iterator; +    return boost::make_iterator_range(transform_iterator(boost::begin(range), function), +                                      transform_iterator(boost::end(range),   function)); +} + +/// non-const version of make_transform_range() +template<typename FUNCTION, typename RANGE> +inline +boost::iterator_range<boost::transform_iterator<FUNCTION, +                                                typename boost::range_iterator<RANGE>::type> > +make_transform_range(RANGE& range, FUNCTION function) +{ +    // shorthand for the iterator type embedded in our return type +    typedef boost::transform_iterator<FUNCTION, typename boost::range_iterator<RANGE>::type> +        transform_iterator; +    return boost::make_iterator_range(transform_iterator(boost::begin(range), function), +                                      transform_iterator(boost::end(range),   function)); +} + +/** + * From any range compatible with Boost.Range, instantiate any class capable + * of accepting an iterator pair. + */ +template<class TYPE> +struct instance_from_range: public TYPE +{ +    template<typename RANGE> +    instance_from_range(RANGE range): +        TYPE(boost::begin(range), boost::end(range)) +    {} +}; + +/***************************************************************************** +*   LLDependencies +*****************************************************************************/ +/** + * LLDependencies components that should not be reinstantiated for each KEY, + * NODE specialization + */ +class LLDependenciesBase +{ +public: +    virtual ~LLDependenciesBase() {} + +    /** +     * Exception thrown by sort() if there's a cycle +     */ +    struct Cycle: public std::runtime_error +    { +        Cycle(const std::string& what): std::runtime_error(what) {} +    }; + +    /** +     * Provide a short description of this LLDependencies instance on the +     * specified output stream, assuming that its KEY type has an operator<<() +     * that works with std::ostream. +     * +     * Pass @a full as @c false to omit any keys without dependency constraints. +     */ +    virtual std::ostream& describe(std::ostream& out, bool full=true) const; + +    /// describe() to a string +    virtual std::string describe(bool full=true) const; + +protected: +    typedef std::vector< std::pair<int, int> > EdgeList; +    typedef std::vector<int> VertexList; +    VertexList topo_sort(int vertices, const EdgeList& edges) const; + +    /** +     * refpair is specifically intended to capture a pair of references. This +     * is better than std::pair<T1&, T2&> because some implementations of +     * std::pair's ctor accept const references to the two types. If the +     * types are themselves references, this results in an illegal reference- +     * to-reference. +     */ +    template<typename T1, typename T2> +    struct refpair +    { +        refpair(T1 value1, T2 value2): +            first(value1), +            second(value2) +        {} +        T1 first; +        T2 second; +    }; +}; + +/// describe() helper: for most types, report the type as usual +template<typename T> +inline +std::ostream& LLDependencies_describe(std::ostream& out, const T& key) +{ +    out << key; +    return out; +} + +/// specialize LLDependencies_describe() for std::string +template<> +inline +std::ostream& LLDependencies_describe(std::ostream& out, const std::string& key) +{ +    out << '"' << key << '"'; +    return out; +} + +/** + * It's reasonable to use LLDependencies in a keys-only way, more or less like + * std::set. For that case, the default NODE type is an empty struct. + */ +struct LLDependenciesEmpty +{ +    LLDependenciesEmpty() {} +    /** +     * Give it a constructor accepting void* so caller can pass placeholder +     * values such as NULL or 0 rather than having to write +     * LLDependenciesEmpty(). +     */ +    LLDependenciesEmpty(void*) {}     +}; + +/** + * This class manages abstract dependencies between node types of your + * choosing. As with a std::map, nodes are copied when add()ed, so the node + * type should be relatively lightweight; to manipulate dependencies between + * expensive objects, use a pointer type. + * + * For a given node, you may state the keys of nodes that must precede it + * and/or nodes that must follow it. The sort() method will produce an order + * that should work, or throw an exception if the constraints are impossible. + * We cache results to minimize the cost of repeated sort() calls. + */ +template<typename KEY = std::string, +         typename NODE = LLDependenciesEmpty> +class LLDependencies: public LLDependenciesBase +{ +    typedef LLDependencies<KEY, NODE> self_type; + +    /** +     * Internally, we bundle the client's NODE with its before/after keys. +     */ +    struct DepNode +    { +        typedef std::set<KEY> dep_set; +        DepNode(const NODE& node_, const dep_set& after_, const dep_set& before_): +            node(node_), +            after(after_), +            before(before_) +        {} +        NODE node; +        dep_set after, before;     +    }; +    typedef std::map<KEY, DepNode> DepNodeMap; +    typedef typename DepNodeMap::value_type DepNodeMapEntry; + +    /// We have various ways to get the dependencies for a given DepNode. +    /// Rather than having to restate each one for 'after' and 'before' +    /// separately, pass a dep_selector so we can apply each to either. +    typedef boost::function<const typename DepNode::dep_set&(const DepNode&)> dep_selector; + +public: +    LLDependencies() {} + +    typedef KEY key_type; +    typedef NODE node_type; + +    /// param type used to express lists of other node keys -- note that such +    /// lists can be initialized with boost::assign::list_of() +    typedef std::vector<KEY> KeyList; + +    /** +     * Add a new node. State its dependencies on other nodes (which may not +     * yet have been added) by listing the keys of nodes this new one must +     * follow, and separately the keys of nodes this new one must precede. +     * +     * The node you pass is @em copied into an internal data structure. If you +     * want to modify the node value after add()ing it, capture the returned +     * NODE& reference. +     * +     * @note +     * Actual dependency analysis is deferred to the sort() method, so  +     * you can add an arbitrary number of nodes without incurring analysis +     * overhead for each. The flip side of this is that add()ing nodes that +     * define a cycle leaves this object in a state in which sort() will +     * always throw the Cycle exception. +     * +     * Two distinct use cases are anticipated: +     * * The data used to load this object are completely known at compile +     * time (e.g. LLEventPump listener names). A Cycle exception represents a +     * bug which can be corrected by the coder. The program need neither catch +     * Cycle nor attempt to salvage the state of this object. +     * * The data are loaded at runtime, therefore the universe of +     * dependencies cannot be known at compile time. The client code should +     * catch Cycle. +     * ** If a Cycle exception indicates fatally-flawed input data, this +     * object can simply be discarded, possibly with the entire program run. +     * ** If it is essential to restore this object to a working state, the +     * simplest workaround is to remove() nodes in LIFO order. +     * *** It may be useful to add functionality to this class to track the +     * add() chronology, providing a pop() method to remove the most recently +     * added node. +     * *** It may further be useful to add a restore() method which would +     * pop() until sort() no longer throws Cycle. This method would be +     * expensive -- but it's not clear that client code could resolve the +     * problem more cheaply. +     */ +    NODE& add(const KEY& key, const NODE& node = NODE(), +              const KeyList& after = KeyList(), +              const KeyList& before = KeyList()) +    { +        // Get the passed-in lists as sets for equality comparison +        typename DepNode::dep_set +            after_set(after.begin(), after.end()), +            before_set(before.begin(), before.end()); +        // Try to insert the new node; if it already exists, find the old +        // node instead. +        std::pair<typename DepNodeMap::iterator, bool> inserted = +            mNodes.insert(typename DepNodeMap::value_type(key, +                                                          DepNode(node, after_set, before_set))); +        if (! inserted.second)      // bool indicating success of insert() +        { +            // We already have a node by this name. Have its dependencies +            // changed? If the existing node's dependencies are identical, the +            // result will be unchanged, so we can leave the cache intact. +            // Regardless of inserted.second, inserted.first is the iterator +            // to the newly-inserted (or existing) map entry. Of course, that +            // entry's second is the DepNode of interest. +            if (inserted.first->second.after  != after_set || +                inserted.first->second.before != before_set) +            { +                // Dependencies have changed: clear the cached result. +                mCache.clear(); +                // save the new dependencies +                inserted.first->second.after  = after_set; +                inserted.first->second.before = before_set; +            } +        } +        else                        // this node is new +        { +            // This will change results. +            mCache.clear(); +        } +        return inserted.first->second.node; +    } + +    /// the value of an iterator, showing both KEY and its NODE +    typedef refpair<const KEY&, NODE&> value_type; +    /// the value of a const_iterator +    typedef refpair<const KEY&, const NODE&> const_value_type; + +private: +    // Extract functors +    static value_type value_extract(DepNodeMapEntry& entry) +    { +        return value_type(entry.first, entry.second.node); +    } + +    static const_value_type const_value_extract(const DepNodeMapEntry& entry) +    { +        return const_value_type(entry.first, entry.second.node); +    } + +    // All the iterator access methods return iterator ranges just to cut down +    // on the friggin' boilerplate!! + +    /// generic mNodes range method +    template<typename ITERATOR, typename FUNCTION> +    boost::iterator_range<ITERATOR> generic_range(FUNCTION function) +    { +        return make_transform_range(mNodes, function); +    } + +    /// generic mNodes const range method +    template<typename ITERATOR, typename FUNCTION> +    boost::iterator_range<ITERATOR> generic_range(FUNCTION function) const +    { +        return make_transform_range(mNodes, function); +    } + +public: +    /// iterator over value_type entries +    typedef boost::transform_iterator<boost::function<value_type(DepNodeMapEntry&)>, +                                      typename DepNodeMap::iterator> iterator; +    /// range over value_type entries +    typedef boost::iterator_range<iterator> range; + +    /// iterate over value_type <i>in @c KEY order</i> rather than dependency order +    range get_range() +    { +        return generic_range<iterator>(value_extract); +    } + +    /// iterator over const_value_type entries +    typedef boost::transform_iterator<boost::function<const_value_type(const DepNodeMapEntry&)>, +                                      typename DepNodeMap::const_iterator> const_iterator; +    /// range over const_value_type entries +    typedef boost::iterator_range<const_iterator> const_range; + +    /// iterate over const_value_type <i>in @c KEY order</i> rather than dependency order +    const_range get_range() const +    { +        return generic_range<const_iterator>(const_value_extract); +    } + +    /// iterator over stored NODEs +    typedef boost::transform_iterator<boost::function<NODE&(DepNodeMapEntry&)>, +                                      typename DepNodeMap::iterator> node_iterator; +    /// range over stored NODEs +    typedef boost::iterator_range<node_iterator> node_range; + +    /// iterate over NODE <i>in @c KEY order</i> rather than dependency order +    node_range get_node_range() +    { +        // First take a DepNodeMapEntry and extract a reference to its +        // DepNode, then from that extract a reference to its NODE. +        return generic_range<node_iterator>( +            boost::bind<NODE&>(&DepNode::node, +                               boost::bind<DepNode&>(&DepNodeMapEntry::second, _1))); +    } + +    /// const iterator over stored NODEs +    typedef boost::transform_iterator<boost::function<const NODE&(const DepNodeMapEntry&)>, +                                      typename DepNodeMap::const_iterator> const_node_iterator; +    /// const range over stored NODEs +    typedef boost::iterator_range<const_node_iterator> const_node_range; + +    /// iterate over const NODE <i>in @c KEY order</i> rather than dependency order +    const_node_range get_node_range() const +    { +        // First take a DepNodeMapEntry and extract a reference to its +        // DepNode, then from that extract a reference to its NODE. +        return generic_range<const_node_iterator>( +            boost::bind<const NODE&>(&DepNode::node, +                                     boost::bind<const DepNode&>(&DepNodeMapEntry::second, _1))); +    } + +    /// const iterator over stored KEYs +    typedef boost::transform_iterator<boost::function<const KEY&(const DepNodeMapEntry&)>, +                                      typename DepNodeMap::const_iterator> const_key_iterator; +    /// const range over stored KEYs +    typedef boost::iterator_range<const_key_iterator> const_key_range; +    // We don't provide a non-const iterator over KEYs because they should be +    // immutable, and in fact our underlying std::map won't give us non-const +    // references. + +    /// iterate over const KEY <i>in @c KEY order</i> rather than dependency order +    const_key_range get_key_range() const +    { +        // From a DepNodeMapEntry, extract a reference to its KEY. +        return generic_range<const_key_iterator>( +            boost::bind<const KEY&>(&DepNodeMapEntry::first, _1)); +    } + +    /** +     * Find an existing NODE, or return NULL. We decided to avoid providing a +     * method analogous to std::map::find(), for a couple of reasons: +     * +     * * For a find-by-key, getting back an iterator to the (key, value) pair +     * is less than useful, since you already have the key in hand. +     * * For a failed request, comparing to end() is problematic. First, we +     * provide range accessors, so it's more code to get end(). Second, we +     * provide a number of different ranges -- quick, to which one's end() +     * should we compare the iterator returned by find()? +     * +     * The returned pointer is solely to allow expressing the not-found +     * condition. LLDependencies still owns the found NODE. +     */ +    const NODE* get(const KEY& key) const +    { +        typename DepNodeMap::const_iterator found = mNodes.find(key); +        if (found != mNodes.end()) +        { +            return &found->second.node; +        } +        return NULL; +    } + +    /** +     * non-const get() +     */ +    NODE* get(const KEY& key) +    { +        // Use const implementation, then cast away const-ness of return +        return const_cast<NODE*>(const_cast<const self_type*>(this)->get(key)); +    } + +    /** +     * Remove a node with specified key. This operation is the major reason +     * we rebuild the graph on the fly instead of storing it. +     */ +    bool remove(const KEY& key) +    { +        typename DepNodeMap::iterator found = mNodes.find(key); +        if (found != mNodes.end()) +        { +            mNodes.erase(found); +            return true; +        } +        return false; +    } + +private: +    /// cached list of iterators +    typedef std::vector<iterator> iterator_list; +    typedef typename iterator_list::iterator iterator_list_iterator; + +public: +    /** +     * The return type of the sort() method needs some explanation. Provide a +     * public typedef to facilitate storing the result. +     * +     * * We will prepare mCache by looking up DepNodeMap iterators. +     * * We want to return a range containing iterators that will walk mCache. +     * * If we simply stored DepNodeMap iterators and returned +     * (mCache.begin(), mCache.end()), dereferencing each iterator would +     * obtain a DepNodeMap iterator. +     * * We want the caller to loop over @c value_type: pair<KEY, NODE>. +     * * This requires two transformations: +     * ** mCache must contain @c LLDependencies::iterator so that +     * dereferencing each entry will obtain an @c LLDependencies::value_type +     * rather than a DepNodeMapEntry. +     * ** We must wrap mCache's iterators in boost::indirect_iterator so that +     * dereferencing one of our returned iterators will also dereference the +     * iterator contained in mCache. +     */ +    typedef boost::iterator_range<boost::indirect_iterator<iterator_list_iterator> > sorted_range; +    /// for convenience in looping over a sorted_range +    typedef typename sorted_range::iterator sorted_iterator; + +    /** +     * Once we've loaded in the dependencies of interest, arrange them into an +     * order that works -- or throw Cycle exception. +     * +     * Return an iterator range over (key, node) pairs that traverses them in +     * the desired order. +     */ +    sorted_range sort() const +    { +        // Changes to mNodes cause us to clear our cache, so empty mCache +        // means it's invalid and should be recomputed. However, if mNodes is +        // also empty, then an empty mCache represents a valid order, so don't +        // bother sorting. +        if (mCache.empty() && ! mNodes.empty()) +        { +            // Construct a map of node keys to distinct vertex numbers -- even for +            // nodes mentioned only in before/after constraints, that haven't yet +            // been explicitly added. Rely on std::map rejecting a second attempt +            // to insert the same key. Use the map's size() as the vertex number +            // to get a distinct value for each successful insertion. +            typedef std::map<KEY, int> VertexMap; +            VertexMap vmap; +            // Nest each of these loops because !@#$%? MSVC warns us that its +            // former broken behavior has finally been fixed -- and our builds +            // treat warnings as errors. +            { +                for (typename DepNodeMap::const_iterator nmi = mNodes.begin(), nmend = mNodes.end(); +                     nmi != nmend; ++nmi) +                { +                    vmap.insert(typename VertexMap::value_type(nmi->first, vmap.size())); +                    for (typename DepNode::dep_set::const_iterator ai = nmi->second.after.begin(), +                                                                   aend = nmi->second.after.end(); +                         ai != aend; ++ai) +                    { +                        vmap.insert(typename VertexMap::value_type(*ai, vmap.size())); +                    } +                    for (typename DepNode::dep_set::const_iterator bi = nmi->second.before.begin(), +                                                                   bend = nmi->second.before.end(); +                         bi != bend; ++bi) +                    { +                        vmap.insert(typename VertexMap::value_type(*bi, vmap.size())); +                    } +                } +            } +            // Define the edges. For this we must traverse mNodes again, mapping +            // all the known key dependencies to integer pairs. +            EdgeList edges; +            { +                for (typename DepNodeMap::const_iterator nmi = mNodes.begin(), nmend = mNodes.end(); +                     nmi != nmend; ++nmi) +                { +                    int thisnode = vmap[nmi->first]; +                    // after dependencies: build edges from the named node to this one +                    for (typename DepNode::dep_set::const_iterator ai = nmi->second.after.begin(), +                                                                   aend = nmi->second.after.end(); +                         ai != aend; ++ai) +                    { +                        edges.push_back(EdgeList::value_type(vmap[*ai], thisnode)); +                    } +                    // before dependencies: build edges from this node to the +                    // named one +                    for (typename DepNode::dep_set::const_iterator bi = nmi->second.before.begin(), +                                                                   bend = nmi->second.before.end(); +                         bi != bend; ++bi) +                    { +                        edges.push_back(EdgeList::value_type(thisnode, vmap[*bi])); +                    } +                } +            } +            // Hide the gory details of our topological sort, since they shouldn't +            // get reinstantiated for each distinct NODE type. +            VertexList sorted(topo_sort(vmap.size(), edges)); +            // Build the reverse of vmap to look up the key for each vertex +            // descriptor. vmap contains exactly one entry for each distinct key, +            // and we're certain that the associated int values are distinct +            // indexes. The fact that they're not in order is irrelevant. +            KeyList vkeys(vmap.size()); +            for (typename VertexMap::const_iterator vmi = vmap.begin(), vmend = vmap.end(); +                 vmi != vmend; ++vmi) +            { +                vkeys[vmi->second] = vmi->first; +            } +            // Walk the sorted output list, building the result into mCache so +            // we'll have it next time someone asks. +            mCache.clear(); +            for (VertexList::const_iterator svi = sorted.begin(), svend = sorted.end(); +                 svi != svend; ++svi) +            { +                // We're certain that vkeys[*svi] exists. However, there might not +                // yet be a corresponding entry in mNodes. +                self_type* non_const_this(const_cast<self_type*>(this)); +                typename DepNodeMap::iterator found = non_const_this->mNodes.find(vkeys[*svi]); +                if (found != non_const_this->mNodes.end()) +                { +                    // Make an iterator of appropriate type. +                    mCache.push_back(iterator(found, value_extract)); +                } +            } +        } +        // Whether or not we've just recomputed mCache, it should now contain +        // the results we want. Return a range of indirect_iterators over it +        // so that dereferencing a returned iterator will dereference the +        // iterator stored in mCache and directly reference the (key, node) +        // pair. +        boost::indirect_iterator<iterator_list_iterator> +            begin(mCache.begin()), +            end(mCache.end()); +        return sorted_range(begin, end); +    } + +    /// Override base-class describe() with actual implementation +    virtual std::ostream& describe(std::ostream& out, bool full=true) const +    { +        typename DepNodeMap::const_iterator dmi(mNodes.begin()), dmend(mNodes.end()); +        if (dmi != dmend) +        { +            std::string sep; +            describe(out, sep, *dmi, full); +            while (++dmi != dmend) +            { +                describe(out, sep, *dmi, full); +            } +        } +        return out; +    } + +    /// describe() helper: report a DepNodeEntry +    static std::ostream& describe(std::ostream& out, std::string& sep, +                                  const DepNodeMapEntry& entry, bool full) +    { +        // If we were asked for a full report, describe every node regardless +        // of whether it has dependencies. If we were asked to suppress +        // independent nodes, describe this one if either after or before is +        // non-empty. +        if (full || (! entry.second.after.empty()) || (! entry.second.before.empty())) +        { +            out << sep; +            sep = "\n"; +            if (! entry.second.after.empty()) +            { +                out << "after "; +                describe(out, entry.second.after); +                out << " -> "; +            } +            LLDependencies_describe(out, entry.first); +            if (! entry.second.before.empty()) +            { +                out << " -> before "; +                describe(out, entry.second.before); +            } +        } +        return out; +    } + +    /// describe() helper: report a dep_set +    static std::ostream& describe(std::ostream& out, const typename DepNode::dep_set& keys) +    { +        out << '('; +        typename DepNode::dep_set::const_iterator ki(keys.begin()), kend(keys.end()); +        if (ki != kend) +        { +            LLDependencies_describe(out, *ki); +            while (++ki != kend) +            { +                out << ", "; +                LLDependencies_describe(out, *ki); +            } +        } +        out << ')'; +        return out; +    } + +    /// Iterator over the before/after KEYs on which a given NODE depends +    typedef typename DepNode::dep_set::const_iterator dep_iterator; +    /// range over the before/after KEYs on which a given NODE depends +    typedef boost::iterator_range<dep_iterator> dep_range; + +    /// dependencies access from key +    dep_range get_dep_range_from_key(const KEY& key, const dep_selector& selector) const +    { +        typename DepNodeMap::const_iterator found = mNodes.find(key); +        if (found != mNodes.end()) +        { +            return dep_range(selector(found->second)); +        } +        // We want to return an empty range. On some platforms a default- +        // constructed range (e.g. dep_range()) does NOT suffice! The client +        // is likely to try to iterate from boost::begin(range) to +        // boost::end(range); yet these iterators might not be valid. Instead +        // return a range over a valid, empty container. +        static const typename DepNode::dep_set empty_deps; +        return dep_range(empty_deps.begin(), empty_deps.end()); +    } + +    /// dependencies access from any one of our key-order iterators +    template<typename ITERATOR> +    dep_range get_dep_range_from_xform(const ITERATOR& iterator, const dep_selector& selector) const +    { +        return dep_range(selector(iterator.base()->second)); +    } + +    /// dependencies access from sorted_iterator +    dep_range get_dep_range_from_sorted(const sorted_iterator& sortiter, +                                        const dep_selector& selector) const +    { +        // sorted_iterator is a boost::indirect_iterator wrapping an mCache +        // iterator, which we can obtain by sortiter.base(). Deferencing that +        // gets us an mCache entry, an 'iterator' -- one of our traversal +        // iterators -- on which we can use get_dep_range_from_xform(). +        return get_dep_range_from_xform(*sortiter.base(), selector); +    } + +    /** +     * Get a range over the after KEYs stored for the passed KEY or iterator, +     * in <i>arbitrary order.</i> If you pass a nonexistent KEY, returns empty +     * range -- same as a KEY with no after KEYs. Detect existence of a KEY +     * using get() instead. +     */ +    template<typename KEY_OR_ITER> +    dep_range get_after_range(const KEY_OR_ITER& key) const; + +    /** +     * Get a range over the before KEYs stored for the passed KEY or iterator, +     * in <i>arbitrary order.</i> If you pass a nonexistent KEY, returns empty +     * range -- same as a KEY with no before KEYs. Detect existence of a KEY +     * using get() instead. +     */ +    template<typename KEY_OR_ITER> +    dep_range get_before_range(const KEY_OR_ITER& key) const; + +private: +    DepNodeMap mNodes; +    mutable iterator_list mCache; +}; + +/** + * Functor to get a dep_range from a KEY or iterator -- generic case. If the + * passed value isn't one of our iterator specializations, assume it's + * convertible to the KEY type. + */ +template<typename KEY_ITER> +struct LLDependencies_dep_range_from +{ +    template<typename KEY, typename NODE, typename SELECTOR> +    typename LLDependencies<KEY, NODE>::dep_range +    operator()(const LLDependencies<KEY, NODE>& deps, +               const KEY_ITER& key, +               const SELECTOR& selector) +    { +        return deps.get_dep_range_from_key(key, selector); +    } +}; + +/// Specialize LLDependencies_dep_range_from for our key-order iterators +template<typename FUNCTION, typename ITERATOR> +struct LLDependencies_dep_range_from< boost::transform_iterator<FUNCTION, ITERATOR> > +{ +    template<typename KEY, typename NODE, typename SELECTOR> +    typename LLDependencies<KEY, NODE>::dep_range +    operator()(const LLDependencies<KEY, NODE>& deps, +               const boost::transform_iterator<FUNCTION, ITERATOR>& iter, +               const SELECTOR& selector) +    { +        return deps.get_dep_range_from_xform(iter, selector); +    } +}; + +/// Specialize LLDependencies_dep_range_from for sorted_iterator +template<typename BASEITER> +struct LLDependencies_dep_range_from< boost::indirect_iterator<BASEITER> > +{ +    template<typename KEY, typename NODE, typename SELECTOR> +    typename LLDependencies<KEY, NODE>::dep_range +    operator()(const LLDependencies<KEY, NODE>& deps, +               const boost::indirect_iterator<BASEITER>& iter, +               const SELECTOR& selector) +    { +        return deps.get_dep_range_from_sorted(iter, selector); +    } +}; + +/// generic get_after_range() implementation +template<typename KEY, typename NODE> +template<typename KEY_OR_ITER> +typename LLDependencies<KEY, NODE>::dep_range +LLDependencies<KEY, NODE>::get_after_range(const KEY_OR_ITER& key_iter) const +{ +    return LLDependencies_dep_range_from<KEY_OR_ITER>()( +        *this, +        key_iter, +        boost::bind<const typename DepNode::dep_set&>(&DepNode::after, _1)); +} + +/// generic get_before_range() implementation +template<typename KEY, typename NODE> +template<typename KEY_OR_ITER> +typename LLDependencies<KEY, NODE>::dep_range +LLDependencies<KEY, NODE>::get_before_range(const KEY_OR_ITER& key_iter) const +{ +    return LLDependencies_dep_range_from<KEY_OR_ITER>()( +        *this, +        key_iter, +        boost::bind<const typename DepNode::dep_set&>(&DepNode::before, _1)); +} + +#endif /* ! defined(LL_LLDEPENDENCIES_H) */ diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index b135dafb3c..8102eddb18 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -433,7 +433,7 @@ namespace LLError  		Settings()  			:	printLocation(false),  				defaultLevel(LLError::LEVEL_DEBUG), -				crashFunction(NULL), +				crashFunction(),  				timeFunction(NULL),  				fileRecorder(NULL),  				fixedBufferRecorder(NULL), @@ -601,12 +601,18 @@ namespace LLError  		s.printLocation = print;  	} -	void setFatalFunction(FatalFunction f) +	void setFatalFunction(const FatalFunction& f)  	{  		Settings& s = Settings::get();  		s.crashFunction = f;  	} +    FatalFunction getFatalFunction() +    { +        Settings& s = Settings::get(); +        return s.crashFunction; +    } +  	void setTimeFunction(TimeFunction f)  	{  		Settings& s = Settings::get(); diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index a55d706d2e..c9424f8a5e 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -35,7 +35,7 @@  #define LL_LLERRORCONTROL_H  #include "llerror.h" - +#include "boost/function.hpp"  #include <string>  class LLFixedBuffer; @@ -83,16 +83,38 @@ namespace LLError  		Control functions.  	*/ -	typedef void(*FatalFunction)(const std::string& message); +	typedef boost::function<void(const std::string&)> FatalFunction;  	void crashAndLoop(const std::string& message); -		// Default fatal funtion: access null pointer and loops forever +		// Default fatal function: access null pointer and loops forever -	void setFatalFunction(FatalFunction); +	void setFatalFunction(const FatalFunction&);  		// The fatal function will be called when an message of LEVEL_ERROR  		// is logged.  Note: supressing a LEVEL_ERROR message from being logged  		// (by, for example, setting a class level to LEVEL_NONE), will keep  		// the that message from causing the fatal funciton to be invoked. -		 + +    FatalFunction getFatalFunction(); +        // Retrieve the previously-set FatalFunction + +    /// temporarily override the FatalFunction for the duration of a +    /// particular scope, e.g. for unit tests +    class OverrideFatalFunction +    { +    public: +        OverrideFatalFunction(const FatalFunction& func): +            mPrev(getFatalFunction()) +        { +            setFatalFunction(func); +        } +        ~OverrideFatalFunction() +        { +            setFatalFunction(mPrev); +        } + +    private: +        FatalFunction mPrev; +    }; +  	typedef std::string (*TimeFunction)();  	std::string utcTime(); diff --git a/indra/llcommon/llevent.cpp b/indra/llcommon/llevent.cpp index 24be6e8b34..f669d0e13f 100644 --- a/indra/llcommon/llevent.cpp +++ b/indra/llcommon/llevent.cpp @@ -34,6 +34,8 @@  #include "llevent.h" +using namespace LLOldEvents; +  /************************************************      Events  ************************************************/ diff --git a/indra/llcommon/llevent.h b/indra/llcommon/llevent.h index 60887a060a..a74ddbd091 100644 --- a/indra/llcommon/llevent.h +++ b/indra/llcommon/llevent.h @@ -38,6 +38,9 @@  #include "llmemory.h"  #include "llthread.h" +namespace LLOldEvents +{ +  class LLEventListener;  class LLEvent;  class LLEventDispatcher; @@ -194,4 +197,6 @@ public:  	LLSD mValue;  }; +} // LLOldEvents +  #endif // LL_EVENT_H diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp new file mode 100644 index 0000000000..eb380ba7c8 --- /dev/null +++ b/indra/llcommon/llevents.cpp @@ -0,0 +1,501 @@ +/** + * @file   llevents.cpp + * @author Nat Goodspeed + * @date   2008-09-12 + * @brief  Implementation for llevents. + *  + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// associated header +#include "llevents.h" +// STL headers +#include <set> +#include <sstream> +#include <algorithm> +// std headers +#include <typeinfo> +#include <cassert> +#include <cmath> +#include <cctype> +// external library headers +#include <boost/range/iterator_range.hpp> +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable : 4701) // compiler thinks might use uninitialized var, but no +#endif +#include <boost/lexical_cast.hpp> +#if LL_WINDOWS +#pragma warning (pop) +#endif +// other Linden headers + +/***************************************************************************** +*   queue_names: specify LLEventPump names that should be instantiated as +*   LLEventQueue +*****************************************************************************/ +/** + * At present, we recognize particular requested LLEventPump names as needing + * LLEventQueues. Later on we'll migrate this information to an external + * configuration file. + */ +const char* queue_names[] = +{ +    "placeholder - replace with first real name string" +}; + +/***************************************************************************** +*   If there's a "mainloop" pump, listen on that to flush all LLEventQueues +*****************************************************************************/ +struct RegisterFlush +{ +    RegisterFlush(): +        pumps(LLEventPumps::instance()), +        mainloop(pumps.obtain("mainloop")), +        name("flushLLEventQueues") +    { +        mainloop.listen(name, boost::bind(&RegisterFlush::flush, this, _1)); +    } +    bool flush(const LLSD&) +    { +        pumps.flush(); +        return false; +    } +    ~RegisterFlush() +    { +        mainloop.stopListening(name); +    } +    LLEventPumps& pumps; +    LLEventPump& mainloop; +    const std::string name; +}; +static RegisterFlush registerFlush; + +/***************************************************************************** +*   LLEventPumps +*****************************************************************************/ +LLEventPumps::LLEventPumps(): +    // Until we migrate this information to an external config file, +    // initialize mQueueNames from the static queue_names array. +    mQueueNames(boost::begin(queue_names), boost::end(queue_names)) +{ +} + +LLEventPump& LLEventPumps::obtain(const std::string& name) +{ +    PumpMap::iterator found = mPumpMap.find(name); +    if (found != mPumpMap.end()) +    { +        // Here we already have an LLEventPump instance with the requested +        // name. +        return *found->second; +    } +    // Here we must instantiate an LLEventPump subclass.  +    LLEventPump* newInstance; +    // Should this name be an LLEventQueue? +    PumpNames::const_iterator nfound = mQueueNames.find(name); +    if (nfound != mQueueNames.end()) +        newInstance = new LLEventQueue(name); +    else +        newInstance = new LLEventStream(name); +    // LLEventPump's constructor implicitly registers each new instance in +    // mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll +    // delete it later. +    mOurPumps.insert(newInstance); +    return *newInstance; +} + +void LLEventPumps::flush() +{ +    // Flush every known LLEventPump instance. Leave it up to each instance to +    // decide what to do with the flush() call. +    for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi) +    { +        pmi->second->flush(); +    } +} + +std::string LLEventPumps::registerNew(const LLEventPump& pump, const std::string& name, bool tweak) +{ +    std::pair<PumpMap::iterator, bool> inserted = +        mPumpMap.insert(PumpMap::value_type(name, const_cast<LLEventPump*>(&pump))); +    // If the insert worked, then the name is unique; return that. +    if (inserted.second) +        return name; +    // Here the new entry was NOT inserted, and therefore name isn't unique. +    // Unless we're permitted to tweak it, that's Bad. +    if (! tweak) +    { +        throw LLEventPump::DupPumpName(std::string("Duplicate LLEventPump name '") + name + "'"); +    } +    // The passed name isn't unique, but we're permitted to tweak it. Find the +    // first decimal-integer suffix not already taken. The insert() attempt +    // above will have set inserted.first to the iterator of the existing +    // entry by that name. Starting there, walk forward until we reach an +    // entry that doesn't start with 'name'. For each entry consisting of name +    // + integer suffix, capture the integer suffix in a set. Use a set +    // because we're going to encounter string suffixes in the order: name1, +    // name10, name11, name2, ... Walking those possibilities in that order +    // isn't convenient to detect the first available "hole." +    std::set<int> suffixes; +    PumpMap::iterator pmi(inserted.first), pmend(mPumpMap.end()); +    // We already know inserted.first references the existing entry with +    // 'name' as the key; skip that one and start with the next. +    while (++pmi != pmend) +    { +        if (pmi->first.substr(0, name.length()) != name) +        { +            // Found the first entry beyond the entries starting with 'name': +            // stop looping. +            break; +        } +        // Here we're looking at an entry that starts with 'name'. Is the rest +        // of it an integer? +        // Dubious (?) assumption: in the local character set, decimal digits +        // are in increasing order such that '9' is the last of them. This +        // test deals with 'name' values such as 'a', where there might be a +        // very large number of entries starting with 'a' whose suffixes +        // aren't integers. A secondary assumption is that digit characters +        // precede most common name characters (true in ASCII, false in +        // EBCDIC). The test below is correct either way, but it's worth more +        // if the assumption holds. +        if (pmi->first[name.length()] > '9') +            break; +        // It should be cheaper to detect that we're not looking at a digit +        // character -- and therefore the suffix can't possibly be an integer +        // -- than to attempt the lexical_cast and catch the exception. +        if (! std::isdigit(pmi->first[name.length()])) +            continue; +        // Okay, the first character of the suffix is a digit, it's worth at +        // least attempting to convert to int. +        try +        { +            suffixes.insert(boost::lexical_cast<int>(pmi->first.substr(name.length()))); +        } +        catch (const boost::bad_lexical_cast&) +        { +            // If the rest of pmi->first isn't an int, just ignore it. +        } +    } +    // Here we've accumulated in 'suffixes' all existing int suffixes of the +    // entries starting with 'name'. Find the first unused one. +    int suffix = 1; +    for ( ; suffixes.find(suffix) != suffixes.end(); ++suffix) +        ; +    // Here 'suffix' is not in 'suffixes'. Construct a new name based on that +    // suffix, insert it and return it. +    std::ostringstream out; +    out << name << suffix; +    return registerNew(pump, out.str(), tweak); +} + +void LLEventPumps::unregister(const LLEventPump& pump) +{ +    // Remove this instance from mPumpMap +    PumpMap::iterator found = mPumpMap.find(pump.getName()); +    if (found != mPumpMap.end()) +    { +        mPumpMap.erase(found); +    } +    // If this instance is one we created, also remove it from mOurPumps so we +    // won't try again to delete it later! +    PumpSet::iterator psfound = mOurPumps.find(const_cast<LLEventPump*>(&pump)); +    if (psfound != mOurPumps.end()) +    { +        mOurPumps.erase(psfound); +    } +} + +LLEventPumps::~LLEventPumps() +{ +    // On destruction, delete every LLEventPump we instantiated (via +    // obtain()). CAREFUL: deleting an LLEventPump calls its destructor, which +    // calls unregister(), which removes that LLEventPump instance from +    // mOurPumps. So an iterator loop over mOurPumps to delete contained +    // LLEventPump instances is dangerous! Instead, delete them one at a time +    // until mOurPumps is empty. +    while (! mOurPumps.empty()) +    { +        delete *mOurPumps.begin(); +    } +} + +/***************************************************************************** +*   LLEventPump +*****************************************************************************/ +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +LLEventPump::LLEventPump(const std::string& name, bool tweak): +    // Register every new instance with LLEventPumps +    mName(LLEventPumps::instance().registerNew(*this, name, tweak)), +    mEnabled(true) +{} + +#if LL_WINDOWS +#pragma warning (pop) +#endif + +LLEventPump::~LLEventPump() +{ +    // Unregister this doomed instance from LLEventPumps +    LLEventPumps::instance().unregister(*this); +} + +// static data member +const LLEventPump::NameList LLEventPump::empty; + +LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventListener& listener, +                                         const NameList& after, +                                         const NameList& before) +{ +    // Check for duplicate name before connecting listener to mSignal +    ConnectionMap::const_iterator found = mConnections.find(name); +    // In some cases the user might disconnect a connection explicitly -- or +    // might use LLEventTrackable to disconnect implicitly. Either way, we can +    // end up retaining in mConnections a zombie connection object that's +    // already been disconnected. Such a connection object can't be +    // reconnected -- nor, in the case of LLEventTrackable, would we want to +    // try, since disconnection happens with the destruction of the listener +    // object. That means it's safe to overwrite a disconnected connection +    // object with the new one we're attempting. The case we want to prevent +    // is only when the existing connection object is still connected. +    if (found != mConnections.end() && found->second.connected()) +    { +        throw DupListenerName(std::string("Attempt to register duplicate listener name '") + name + +                              "' on " + typeid(*this).name() + " '" + getName() + "'"); +    } +    // Okay, name is unique, try to reconcile its dependencies. Specify a new +    // "node" value that we never use for an mSignal placement; we'll fix it +    // later. +    DependencyMap::node_type& newNode = mDeps.add(name, -1.0, after, before); +    // What if this listener has been added, removed and re-added? In that +    // case newNode already has a non-negative value because we never remove a +    // listener from mDeps. But keep processing uniformly anyway in case the +    // listener was added back with different dependencies. Then mDeps.sort() +    // would put it in a different position, and the old newNode placement +    // value would be wrong, so we'd have to reassign it anyway. Trust that +    // re-adding a listener with the same dependencies is the trivial case for +    // mDeps.sort(): it can just replay its cache. +    DependencyMap::sorted_range sorted_range; +    try +    { +        // Can we pick an order that works including this new entry? +        sorted_range = mDeps.sort(); +    } +    catch (const DependencyMap::Cycle& e) +    { +        // No: the new node's after/before dependencies have made mDeps +        // unsortable. If we leave the new node in mDeps, it will continue +        // to screw up all future attempts to sort()! Pull it out. +        mDeps.remove(name); +        throw Cycle(std::string("New listener '") + name + "' on " + typeid(*this).name() + +                    " '" + getName() + "' would cause cycle: " + e.what()); +    } +    // Walk the list to verify that we haven't changed the order. +    float previous = 0.0, myprev = 0.0; +    DependencyMap::sorted_iterator mydmi = sorted_range.end(); // need this visible after loop +    for (DependencyMap::sorted_iterator dmi = sorted_range.begin(); +         dmi != sorted_range.end(); ++dmi) +    { +        // Since we've added the new entry with an invalid placement, +        // recognize it and skip it. +        if (dmi->first == name) +        { +            // Remember the iterator belonging to our new node, and which +            // placement value was 'previous' at that point. +            mydmi = dmi; +            myprev = previous; +            continue; +        } +        // If the new node has rearranged the existing nodes, we'll find +        // that their placement values are no longer in increasing order. +        if (dmi->second < previous) +        { +            // This is another scenario in which we'd better back out the +            // newly-added node from mDeps -- but don't do it yet, we want to +            // traverse the existing mDeps to report on it! +            // Describe the change to the order of our listeners. Copy +            // everything but the newest listener to a vector we can sort to +            // obtain the old order. +            typedef std::vector< std::pair<float, std::string> > SortNameList; +            SortNameList sortnames; +            for (DependencyMap::sorted_iterator cdmi(sorted_range.begin()), cdmend(sorted_range.end()); +                 cdmi != cdmend; ++cdmi) +            { +                if (cdmi->first != name) +                { +                    sortnames.push_back(SortNameList::value_type(cdmi->second, cdmi->first)); +                } +            } +            std::sort(sortnames.begin(), sortnames.end()); +            std::ostringstream out; +            out << "New listener '" << name << "' on " << typeid(*this).name() << " '" << getName() +                << "' would move previous listener '" << dmi->first << "'\nwas: "; +            SortNameList::const_iterator sni(sortnames.begin()), snend(sortnames.end()); +            if (sni != snend) +            { +                out << sni->second; +                while (++sni != snend) +                { +                    out << ", " << sni->second; +                } +            } +            out << "\nnow: "; +            DependencyMap::sorted_iterator ddmi(sorted_range.begin()), ddmend(sorted_range.end()); +            if (ddmi != ddmend) +            { +                out << ddmi->first; +                while (++ddmi != ddmend) +                { +                    out << ", " << ddmi->first; +                } +            } +            // NOW remove the offending listener node. +            mDeps.remove(name); +            // Having constructed a description of the order change, inform caller. +            throw OrderChange(out.str()); +        } +        // This node becomes the previous one. +        previous = dmi->second; +    } +    // We just got done with a successful mDeps.add(name, ...) call. We'd +    // better have found 'name' somewhere in that sorted list! +    assert(mydmi != sorted_range.end()); +    // Four cases: +    // 0. name is the only entry: placement 1.0 +    // 1. name is the first of several entries: placement (next placement)/2 +    // 2. name is between two other entries: placement (myprev + (next placement))/2 +    // 3. name is the last entry: placement ceil(myprev) + 1.0 +    // Since we've cleverly arranged for myprev to be 0.0 if name is the +    // first entry, this folds down to two cases. Case 1 is subsumed by +    // case 2, and case 0 is subsumed by case 3. So we need only handle +    // cases 2 and 3, which means we need only detect whether name is the +    // last entry. Increment mydmi to see if there's anything beyond. +    if (++mydmi != sorted_range.end()) +    { +        // The new node isn't last. Place it between the previous node and +        // the successor. +        newNode = (myprev + mydmi->second)/2.0; +    } +    else +    { +        // The new node is last. Bump myprev up to the next integer, add +        // 1.0 and use that. +        newNode = std::ceil(myprev) + 1.0; +    } +    // Now that newNode has a value that places it appropriately in mSignal, +    // connect it. +    LLBoundListener bound = mSignal.connect(newNode, listener); +    mConnections[name] = bound; +    return bound; +} + +LLBoundListener LLEventPump::getListener(const std::string& name) const +{ +    ConnectionMap::const_iterator found = mConnections.find(name); +    if (found != mConnections.end()) +    { +        return found->second; +    } +    // not found, return dummy LLBoundListener +    return LLBoundListener(); +} + +void LLEventPump::stopListening(const std::string& name) +{ +    ConnectionMap::iterator found = mConnections.find(name); +    if (found != mConnections.end()) +    { +        found->second.disconnect(); +        mConnections.erase(found); +    } +    // We intentionally do NOT remove this name from mDeps. It may happen that +    // the same listener with the same name and dependencies will jump on and +    // off this LLEventPump repeatedly. Keeping a cache of dependencies will +    // avoid a new dependency sort in such cases. +} + +/***************************************************************************** +*   LLEventStream +*****************************************************************************/ +bool LLEventStream::post(const LLSD& event) +{ +    if (! mEnabled) +        return false; +    // Let caller know if any one listener handled the event. This is mostly +    // useful when using LLEventStream as a listener for an upstream +    // LLEventPump. +    return mSignal(event); +} + +/***************************************************************************** +*   LLEventQueue +*****************************************************************************/ +bool LLEventQueue::post(const LLSD& event) +{ +    if (mEnabled) +    { +        // Defer sending this event by queueing it until flush() +        mEventQueue.push_back(event); +    } +    // Unconditionally return false. We won't know until flush() whether a +    // listener claims to have handled the event -- meanwhile, don't block +    // other listeners. +    return false; +} + +void LLEventQueue::flush() +{ +    // Consider the case when a given listener on this LLEventQueue posts yet +    // another event on the same queue. If we loop over mEventQueue directly, +    // we'll end up processing all those events during the same flush() call +    // -- rather like an EventStream. Instead, copy mEventQueue and clear it, +    // so that any new events posted to this LLEventQueue during flush() will +    // be processed in the *next* flush() call. +    EventQueue queue(mEventQueue); +    mEventQueue.clear(); +    for ( ; ! queue.empty(); queue.pop_front()) +    { +        mSignal(queue.front()); +    } +} + +/***************************************************************************** +*   LLListenerOrPumpName +*****************************************************************************/ +LLListenerOrPumpName::LLListenerOrPumpName(const std::string& pumpname): +    // Look up the specified pumpname, and bind its post() method as our listener +    mListener(boost::bind(&LLEventPump::post, +                          boost::ref(LLEventPumps::instance().obtain(pumpname)), +                          _1)) +{ +} + +LLListenerOrPumpName::LLListenerOrPumpName(const char* pumpname): +    // Look up the specified pumpname, and bind its post() method as our listener +    mListener(boost::bind(&LLEventPump::post, +                          boost::ref(LLEventPumps::instance().obtain(pumpname)), +                          _1)) +{ +} + +bool LLListenerOrPumpName::operator()(const LLSD& event) const +{ +    if (! mListener) +    { +        throw Empty("attempting to call uninitialized"); +    } +    return (*mListener)(event); +} diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h new file mode 100644 index 0000000000..f70d532e4c --- /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 "llmemory.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) */ diff --git a/indra/llcommon/lllazy.cpp b/indra/llcommon/lllazy.cpp new file mode 100644 index 0000000000..215095bc27 --- /dev/null +++ b/indra/llcommon/lllazy.cpp @@ -0,0 +1,23 @@ +/** + * @file   lllazy.cpp + * @author Nat Goodspeed + * @date   2009-01-28 + * @brief  Implementation for lllazy. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lllazy.h" +// STL headers +// std headers +// external library headers +// other Linden headers + +// lllazy.h is presently header-only. This file exists only because our CMake +// test macro ADD_BUILD_TEST requires it. +int dummy = 0; diff --git a/indra/llcommon/lllazy.h b/indra/llcommon/lllazy.h new file mode 100644 index 0000000000..2240954d98 --- /dev/null +++ b/indra/llcommon/lllazy.h @@ -0,0 +1,382 @@ +/** + * @file   lllazy.h + * @author Nat Goodspeed + * @date   2009-01-22 + * @brief  Lazy instantiation of specified type. Useful in conjunction with + *         Michael Feathers's "Extract and Override Getter" ("Working + *         Effectively with Legacy Code", p. 352). + * + * Quoting his synopsis of steps on p.355: + * + * 1. Identify the object you need a getter for. + * 2. Extract all of the logic needed to create the object into a getter. + * 3. Replace all uses of the object with calls to the getter, and initialize + *    the reference that holds the object to null in all constructors. + * 4. Add the first-time logic to the getter so that the object is constructed + *    and assigned to the reference whenever the reference is null. + * 5. Subclass the class and override the getter to provide an alternative + *    object for testing. + * + * It's the second half of bullet 3 (3b, as it were) that bothers me. I find + * it all too easy to imagine adding pointer initializers to all but one + * constructor... the one not exercised by my tests. That suggested using + * (e.g.) boost::scoped_ptr<MyObject> so you don't have to worry about + * destroying it either. + * + * However, introducing additional machinery allows us to encapsulate bullet 4 + * as well. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLLAZY_H) +#define LL_LLLAZY_H + +#include <boost/function.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/lambda/construct.hpp> +#include <stdexcept> + +/// LLLazyCommon simply factors out of LLLazy<T> things that don't depend on +/// its template parameter. +class LLLazyCommon +{ +public: +    /** +     * This exception is thrown if you try to replace an LLLazy<T>'s factory +     * (or T* instance) after it already has an instance in hand. Since T +     * might well be stateful, we can't know the effect of silently discarding +     * and replacing an existing instance, so we disallow it. This facility is +     * intended for testing, and in a test scenario we can definitely control +     * that. +     */ +    struct InstanceChange: public std::runtime_error +    { +        InstanceChange(const std::string& what): std::runtime_error(what) {} +    }; + +protected: +    /** +     * InstanceChange might be appropriate in a couple of different LLLazy<T> +     * methods. Factor out the common logic. +     */ +    template <typename PTR> +    static void ensureNoInstance(const PTR& ptr) +    { +        if (ptr) +        { +            // Too late: we've already instantiated the lazy object. We don't +            // know whether it's stateful or not, so it's not safe to discard +            // the existing instance in favor of a replacement. +            throw InstanceChange("Too late to replace LLLazy instance"); +        } +    } +}; + +/** + * LLLazy<T> is useful when you have an outer class Outer that you're trying + * to bring under unit test, that contains a data member difficult to + * instantiate in a test harness. Typically the data member's class Inner has + * many thorny dependencies. Feathers generally advocates "Extract and + * Override Factory Method" (p. 350). But in C++, you can't call a derived + * class override of a virtual method from the derived class constructor, + * which limits applicability of "Extract and Override Factory Method." For + * such cases Feathers presents "Extract and Override Getter" (p. 352). + * + * So we'll assume that your class Outer contains a member like this: + * @code + * Inner mInner; + * @endcode + * + * LLLazy<Inner> can be used to replace this member. You can directly declare: + * @code + * LLLazy<Inner> mInner; + * @endcode + * and change references to mInner accordingly. + * + * (Alternatively, you can add a base class of the form + * <tt>LLLazyBase<Inner></tt>. This is discussed further in the LLLazyBase<T> + * documentation.) + * + * LLLazy<T> binds a <tt>boost::scoped_ptr<T></tt> and a factory functor + * returning T*. You can either bind that functor explicitly or let it default + * to the expression <tt>new T()</tt>. + * + * As long as LLLazy<T> remains unreferenced, its T remains uninstantiated. + * The first time you use get(), <tt>operator*()</tt> or <tt>operator->()</tt> + * it will instantiate its T and thereafter behave like a pointer to it. + * + * Thus, any existing reference to <tt>mInner.member</tt> should be replaced + * with <tt>mInner->member</tt>. Any simple reference to @c mInner should be + * replaced by <tt>*mInner</tt>. + * + * (If the original declaration was a pointer initialized in Outer's + * constructor, e.g. <tt>Inner* mInner</tt>, so much the better. In that case + * you should be able to drop in <tt>LLLazy<Inner></tt> without much change.) + * + * The support for "Extract and Override Getter" lies in the fact that you can + * replace the factory functor -- or provide an explicit T*. Presumably this + * is most useful from a test subclass -- which suggests that your @c mInner + * member should be @c protected. + * + * Note that <tt>boost::lambda::new_ptr<T>()</tt> makes a dandy factory + * functor, for either the set() method or LLLazy<T>'s constructor. If your T + * requires constructor arguments, use an expression more like + * <tt>boost::lambda::bind(boost::lambda::new_ptr<T>(), arg1, arg2, ...)</tt>. + * + * Of course the point of replacing the functor is to substitute a class that, + * though referenced as Inner*, is not an Inner; presumably this is a testing + * subclass of Inner (e.g. TestInner). Thus your test subclass TestOuter for + * the containing class Outer will contain something like this: + * @code + * class TestOuter: public Outer + * { + * public: + *     TestOuter() + *     { + *         // mInner must be 'protected' rather than 'private' + *         mInner.set(boost::lambda::new_ptr<TestInner>()); + *     } + *     ... + * }; + * @endcode + */ +template <typename T> +class LLLazy: public LLLazyCommon +{ +public: +    /// Any nullary functor returning T* will work as a Factory +    typedef boost::function<T* ()> Factory; + +    /// The default LLLazy constructor uses <tt>new T()</tt> as its Factory +    LLLazy(): +        mFactory(boost::lambda::new_ptr<T>()) +    {} + +    /// Bind an explicit Factory functor +    LLLazy(const Factory& factory): +        mFactory(factory) +    {} + +    /// Reference T, instantiating it if this is the first access +    const T& get() const +    { +        if (! mInstance) +        { +            // use the bound Factory functor +            mInstance.reset(mFactory()); +        } +        return *mInstance; +    } + +    /// non-const get() +    T& get() +    { +        return const_cast<T&>(const_cast<const LLLazy<T>*>(this)->get()); +    } + +    /// operator*() is equivalent to get() +    const T& operator*() const { return get(); } +    /// operator*() is equivalent to get() +    T& operator*() { return get(); } + +    /** +     * operator->() must return (something resembling) T*. It's tempting to +     * return the underlying boost::scoped_ptr<T>, but that would require +     * breaking out the lazy-instantiation logic from get() into a common +     * private method. Assume the pointer used for operator->() access is very +     * short-lived. +     */ +    const T* operator->() const { return &get(); } +    /// non-const operator->() +    T* operator->() { return &get(); } + +    /// set(Factory). This will throw InstanceChange if mInstance has already +    /// been set. +    void set(const Factory& factory) +    { +        ensureNoInstance(mInstance); +        mFactory = factory; +    } + +    /// set(T*). This will throw InstanceChange if mInstance has already been +    /// set. +    void set(T* instance) +    { +        ensureNoInstance(mInstance); +        mInstance.reset(instance); +    } + +private: +    Factory mFactory; +    // Consider an LLLazy<T> member of a class we're accessing by const +    // reference. We want to allow even const methods to touch the LLLazy<T> +    // member. Hence the actual pointer must be mutable because such access +    // might assign it. +    mutable boost::scoped_ptr<T> mInstance; +}; + +#if (! defined(__GNUC__)) || (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ > 3) +// Not gcc at all, or a gcc more recent than gcc 3.3 +#define GCC33 0 +#else +#define GCC33 1 +#endif + +/** + * LLLazyBase<T> wraps LLLazy<T>, giving you an alternative way to replace + * <tt>Inner mInner;</tt>. Instead of coding <tt>LLLazy<Inner> mInner</tt>, + * you can add LLLazyBase<Inner> to your Outer class's bases, e.g.: + * @code + * class Outer: public LLLazyBase<Inner> + * { + *     ... + * }; + * @endcode + * + * This gives you @c public get() and @c protected set() methods without + * having to make your LLLazy<Inner> member @c protected. The tradeoff is that + * you must access the wrapped LLLazy<Inner> using get() and set() rather than + * with <tt>operator*()</tt> or <tt>operator->()</tt>. + * + * This mechanism can be used for more than one member, but only if they're of + * different types. That is, you can replace: + * @code + * DifficultClass mDifficult; + * AwkwardType    mAwkward; + * @endcode + * with: + * @code + * class Outer: public LLLazyBase<DifficultClass>, public LLLazyBase<AwkwardType> + * { + *     ... + * }; + * @endcode + * but for a situation like this: + * @code + * DifficultClass mMainDifficult, mAuxDifficult; + * @endcode + * you should directly embed LLLazy<DifficultClass> (q.v.). + * + * For multiple LLLazyBase bases, e.g. the <tt>LLLazyBase<DifficultClass>, + * LLLazyBase<AwkwardType></tt> example above, access the relevant get()/set() + * as (e.g.) <tt>LLLazyBase<DifficultClass>::get()</tt>. (This is why you + * can't have multiple LLLazyBase<T> of the same T.) For a bit of syntactic + * sugar, please see getLazy()/setLazy(). + */ +template <typename T> +class LLLazyBase +{ +public: +    /// invoke default LLLazy constructor +    LLLazyBase() {} +    /// make wrapped LLLazy bind an explicit Factory +    LLLazyBase(const typename LLLazy<T>::Factory& factory): +        mInstance(factory) +    {} + +    /// access to LLLazy::get() +    T& get() { return *mInstance; } +    /// access to LLLazy::get() +    const T& get() const { return *mInstance; } + +protected: +    // see getLazy()/setLazy() +    #if (! GCC33) +    template <typename T2, class MYCLASS> friend T2& getLazy(MYCLASS* this_); +    template <typename T2, class MYCLASS> friend const T2& getLazy(const MYCLASS* this_); +    #else // gcc 3.3 +    template <typename T2, class MYCLASS> friend T2& getLazy(const MYCLASS* this_); +    #endif // gcc 3.3 +    template <typename T2, class MYCLASS> friend void setLazy(MYCLASS* this_, T2* instance); +    template <typename T2, class MYCLASS> +    friend void setLazy(MYCLASS* this_, const typename LLLazy<T2>::Factory& factory); + +    /// access to LLLazy::set(Factory) +    void set(const typename LLLazy<T>::Factory& factory) +    { +        mInstance.set(factory); +    } + +    /// access to LLLazy::set(T*) +    void set(T* instance) +    { +        mInstance.set(instance); +    } + +private: +    LLLazy<T> mInstance; +}; + +/** + * @name getLazy()/setLazy() + * Suppose you have something like the following: + * @code + * class Outer: public LLLazyBase<DifficultClass>, public LLLazyBase<AwkwardType> + * { + *     ... + * }; + * @endcode + * + * Your methods can reference the @c DifficultClass instance using + * <tt>LLLazyBase<DifficultClass>::get()</tt>, which is admittedly a bit ugly. + * Alternatively, you can write <tt>getLazy<DifficultClass>(this)</tt>, which + * is somewhat more straightforward to read. + * + * Similarly, + * @code + * LLLazyBase<DifficultClass>::set(new TestDifficultClass()); + * @endcode + * could instead be written: + * @code + * setLazy<DifficultClass>(this, new TestDifficultClass()); + * @endcode + * + * @note + * I wanted to provide getLazy() and setLazy() without explicitly passing @c + * this. That would imply making them methods on a base class rather than free + * functions. But if <tt>LLLazyBase<T></tt> derives normally from (say) @c + * LLLazyGrandBase providing those methods, then unqualified getLazy() would + * be ambiguous: you'd have to write <tt>LLLazyBase<T>::getLazy<T>()</tt>, + * which is even uglier than <tt>LLLazyBase<T>::get()</tt>, and therefore + * pointless. You can make the compiler not care which @c LLLazyGrandBase + * instance you're talking about by making @c LLLazyGrandBase a @c virtual + * base class of @c LLLazyBase. But in that case, + * <tt>LLLazyGrandBase::getLazy<T>()</tt> can't access + * <tt>LLLazyBase<T>::get()</tt>! + * + * We want <tt>getLazy<T>()</tt> to access <tt>LLLazyBase<T>::get()</tt> as if + * in the lexical context of some subclass method. Ironically, free functions + * let us do that better than methods on a @c virtual base class -- but that + * implies passing @c this explicitly. So be it. + */ +//@{ +#if (! GCC33) +template <typename T, class MYCLASS> +T& getLazy(MYCLASS* this_) { return this_->LLLazyBase<T>::get(); } +template <typename T, class MYCLASS> +const T& getLazy(const MYCLASS* this_) { return this_->LLLazyBase<T>::get(); } +#else // gcc 3.3 +// For const-correctness, we really should have two getLazy() variants: one +// accepting const MYCLASS* and returning const T&, the other accepting +// non-const MYCLASS* and returning non-const T&. This works fine on the Mac +// (gcc 4.0.1) and Windows (MSVC 8.0), but fails on our Linux 32-bit Debian +// Sarge stations (gcc 3.3.5). Since I really don't know how to beat that aging +// compiler over the head to make it do the right thing, I'm going to have to +// move forward with the wrong thing: a single getLazy() function that accepts +// const MYCLASS* and returns non-const T&. +template <typename T, class MYCLASS> +T& getLazy(const MYCLASS* this_) { return const_cast<MYCLASS*>(this_)->LLLazyBase<T>::get(); } +#endif // gcc 3.3 +template <typename T, class MYCLASS> +void setLazy(MYCLASS* this_, T* instance) { this_->LLLazyBase<T>::set(instance); } +template <typename T, class MYCLASS> +void setLazy(MYCLASS* this_, const typename LLLazy<T>::Factory& factory) +{ +    this_->LLLazyBase<T>::set(factory); +} +//@} + +#endif /* ! defined(LL_LLLAZY_H) */ diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h new file mode 100644 index 0000000000..1b2958020f --- /dev/null +++ b/indra/llcommon/stringize.h @@ -0,0 +1,75 @@ +/** + * @file   stringize.h + * @author Nat Goodspeed + * @date   2008-12-17 + * @brief  stringize(item) template function and STRINGIZE(expression) macro + *  + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_STRINGIZE_H) +#define LL_STRINGIZE_H + +#include <sstream> + +/** + * stringize(item) encapsulates an idiom we use constantly, using + * operator<<(std::ostringstream&, TYPE) followed by std::ostringstream::str() + * to render a string expressing some item. + */ +template <typename T> +std::string stringize(const T& item) +{ +    std::ostringstream out; +    out << item; +    return out.str(); +} + +/** + * STRINGIZE(item1 << item2 << item3 ...) effectively expands to the + * following: + * @code + * std::ostringstream out; + * out << item1 << item2 << item3 ... ; + * return out.str(); + * @endcode + */ +#define STRINGIZE(EXPRESSION) (static_cast<std::ostringstream&>(Stringize() << EXPRESSION).str()) + +/** + * Helper class for STRINGIZE() macro. Ideally the body of + * STRINGIZE(EXPRESSION) would look something like this: + * @code + * (std::ostringstream() << EXPRESSION).str() + * @endcode + * That doesn't work because each of the relevant operator<<() functions + * accepts a non-const std::ostream&, to which you can't pass a temp instance + * of std::ostringstream. Stringize plays the necessary const tricks to make + * the whole thing work. + */ +class Stringize +{ +public: +    /** +     * This is the essence of Stringize. The leftmost << operator (the one +     * coded in the STRINGIZE() macro) engages this operator<<() const method +     * on the temp Stringize instance. Every other << operator (ones embedded +     * in EXPRESSION) simply sees the std::ostream& returned by the first one. +     * +     * Finally, the STRINGIZE() macro downcasts that std::ostream& to +     * std::ostringstream&. +     */ +    template <typename T> +    std::ostream& operator<<(const T& item) const +    { +        mOut << item; +        return mOut; +    } + +private: +    mutable std::ostringstream mOut; +}; + +#endif /* ! defined(LL_STRINGIZE_H) */ diff --git a/indra/llcommon/tests/lllazy_test.cpp b/indra/llcommon/tests/lllazy_test.cpp new file mode 100644 index 0000000000..db581d650f --- /dev/null +++ b/indra/llcommon/tests/lllazy_test.cpp @@ -0,0 +1,227 @@ +/** + * @file   lllazy_test.cpp + * @author Nat Goodspeed + * @date   2009-01-28 + * @brief  Tests of lllazy.h. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lllazy.h" +// STL headers +#include <iostream> +// std headers +// external library headers +#include <boost/lambda/construct.hpp> +#include <boost/lambda/bind.hpp> +// other Linden headers +#include "../test/lltut.h" + +namespace bll = boost::lambda; + +/***************************************************************************** +*   Test classes +*****************************************************************************/ + +// Let's say that because of its many external dependencies, YuckyFoo is very +// hard to instantiate in a test harness. +class YuckyFoo +{ +public: +    virtual ~YuckyFoo() {} +    virtual std::string whoami() const { return "YuckyFoo"; } +}; + +// Let's further suppose that YuckyBar is another hard-to-instantiate class. +class YuckyBar +{ +public: +    YuckyBar(const std::string& which): +        mWhich(which) +    {} +    virtual ~YuckyBar() {} + +    virtual std::string identity() const { return std::string("YuckyBar(") + mWhich + ")"; } + +private: +    const std::string mWhich; +}; + +// Pretend that this class would be tough to test because, up until we started +// trying to test it, it contained instances of both YuckyFoo and YuckyBar. +// Now we've refactored so it contains LLLazy<YuckyFoo> and LLLazy<YuckyBar>. +// More than that, it contains them by virtue of deriving from +// LLLazyBase<YuckyFoo> and LLLazyBase<YuckyBar>. +// We postulate two different LLLazyBases because, with only one, you need not +// specify *which* get()/set() method you're talking about. That's a simpler +// case. +class NeedsTesting: public LLLazyBase<YuckyFoo>, public LLLazyBase<YuckyBar> +{ +public: +    NeedsTesting(): +        // mYuckyBar("RealYuckyBar") +        LLLazyBase<YuckyBar>(bll::bind(bll::new_ptr<YuckyBar>(), "RealYuckyBar")) +    {} +    virtual ~NeedsTesting() {} + +    virtual std::string describe() const +    { +        return std::string("NeedsTesting(") + getLazy<YuckyFoo>(this).whoami() + ", " + +            getLazy<YuckyBar>(this).identity() + ")"; +    } + +private: +    // These instance members were moved to LLLazyBases: +    // YuckyFoo mYuckyFoo; +    // YuckyBar mYuckyBar; +}; + +// Fake up a test YuckyFoo class +class TestFoo: public YuckyFoo +{ +public: +    virtual std::string whoami() const { return "TestFoo"; } +}; + +// and a test YuckyBar +class TestBar: public YuckyBar +{ +public: +    TestBar(const std::string& which): YuckyBar(which) {} +    virtual std::string identity() const +    { +        return std::string("TestBar(") + YuckyBar::identity() + ")"; +    } +}; + +// So here's a test subclass of NeedsTesting that uses TestFoo and TestBar +// instead of YuckyFoo and YuckyBar. +class TestNeedsTesting: public NeedsTesting +{ +public: +    TestNeedsTesting() +    { +        // Exercise setLazy(T*) +        setLazy<YuckyFoo>(this, new TestFoo()); +        // Exercise setLazy(Factory) +        setLazy<YuckyBar>(this, bll::bind(bll::new_ptr<TestBar>(), "TestYuckyBar")); +    } + +    virtual std::string describe() const +    { +        return std::string("TestNeedsTesting(") + NeedsTesting::describe() + ")"; +    } + +    void toolate() +    { +        setLazy<YuckyFoo>(this, new TestFoo()); +    } +}; + +// This class tests having an explicit LLLazy<T> instance as a named member, +// rather than deriving from LLLazyBase<T>. +class LazyMember +{ +public: +    YuckyFoo& getYuckyFoo() { return *mYuckyFoo; } +    std::string whoisit() const { return mYuckyFoo->whoami(); } + +protected: +    LLLazy<YuckyFoo> mYuckyFoo; +}; + +// This is a test subclass of the above, dynamically replacing the +// LLLazy<YuckyFoo> member. +class TestLazyMember: public LazyMember +{ +public: +    // use factory setter +    TestLazyMember() +    { +        mYuckyFoo.set(bll::new_ptr<TestFoo>()); +    } + +    // use instance setter +    TestLazyMember(YuckyFoo* instance) +    { +        mYuckyFoo.set(instance); +    } +}; + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct lllazy_data +    { +    }; +    typedef test_group<lllazy_data> lllazy_group; +    typedef lllazy_group::object lllazy_object; +    lllazy_group lllazygrp("lllazy"); + +    template<> template<> +    void lllazy_object::test<1>() +    { +        // Instantiate an official one, just because we can +        NeedsTesting nt; +        // and a test one +        TestNeedsTesting tnt; +//      std::cout << nt.describe() << '\n'; +        ensure_equals(nt.describe(), "NeedsTesting(YuckyFoo, YuckyBar(RealYuckyBar))"); +//      std::cout << tnt.describe() << '\n'; +        ensure_equals(tnt.describe(), +                      "TestNeedsTesting(NeedsTesting(TestFoo, TestBar(YuckyBar(TestYuckyBar))))"); +    } + +    template<> template<> +    void lllazy_object::test<2>() +    { +        TestNeedsTesting tnt; +        std::string threw; +        try +        { +            tnt.toolate(); +        } +        catch (const LLLazyCommon::InstanceChange& e) +        { +            threw = e.what(); +        } +        ensure_contains("InstanceChange exception", threw, "replace LLLazy instance"); +    } + +    template<> template<> +    void lllazy_object::test<3>() +    { +        { +            LazyMember lm; +            // operator*() on-demand instantiation +            ensure_equals(lm.getYuckyFoo().whoami(), "YuckyFoo"); +        } +        { +            LazyMember lm; +            // operator->() on-demand instantiation +            ensure_equals(lm.whoisit(), "YuckyFoo"); +        } +    } + +    template<> template<> +    void lllazy_object::test<4>() +    { +        { +            // factory setter +            TestLazyMember tlm; +            ensure_equals(tlm.whoisit(), "TestFoo"); +        } +        { +            // instance setter +            TestLazyMember tlm(new TestFoo()); +            ensure_equals(tlm.whoisit(), "TestFoo"); +        } +    } +} // namespace tut diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 0f3e159802..c0f7a4d335 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -3,6 +3,7 @@  project(llmessage)  include(00-Common) +include(LLAddBuildTest)  include(LLCommon)  include(LLMath)  include(LLMessage) @@ -63,6 +64,7 @@ set(llmessage_SOURCE_FILES      llregionpresenceverifier.cpp      llsdappservices.cpp      llsdhttpserver.cpp +    llsdmessage.cpp      llsdmessagebuilder.cpp      llsdmessagereader.cpp      llsdrpcclient.cpp @@ -156,6 +158,7 @@ set(llmessage_HEADER_FILES      llregionpresenceverifier.h      llsdappservices.h      llsdhttpserver.h +    llsdmessage.h      llsdmessagebuilder.h      llsdmessagereader.h      llsdrpcclient.h @@ -217,5 +220,6 @@ IF (NOT LINUX AND VIEWER)      #ADD_BUILD_TEST(llhttpclientadapter llmessage)      ADD_BUILD_TEST(lltrustedmessageservice llmessage)      ADD_BUILD_TEST(lltemplatemessagedispatcher llmessage) +    # Don't make llmessage depend on llsdmessage_test because ADD_COMM_BUILD_TEST depends on llmessage! +    ADD_COMM_BUILD_TEST(llsdmessage "" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py")  ENDIF (NOT LINUX AND VIEWER) - diff --git a/indra/llmessage/llsdmessage.cpp b/indra/llmessage/llsdmessage.cpp new file mode 100644 index 0000000000..f663268466 --- /dev/null +++ b/indra/llmessage/llsdmessage.cpp @@ -0,0 +1,150 @@ +/** + * @file   llsdmessage.cpp + * @author Nat Goodspeed + * @date   2008-10-31 + * @brief  Implementation for llsdmessage. + *  + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llsdmessage.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llevents.h" +#include "llsdserialize.h" +#include "llhttpclient.h" +#include "llmessageconfig.h" +#include "llhost.h" +#include "message.h" +#include "llsdutil.h" + +// Declare a static LLSDMessage instance to ensure that we have a listener as +// soon as someone tries to post on our canonical LLEventPump name. +static LLSDMessage httpListener; + +LLSDMessage::LLSDMessage(): +    // Instantiating our own local LLEventPump with a string name the +    // constructor is NOT allowed to tweak is a way of ensuring Singleton +    // semantics: attempting to instantiate a second LLSDMessage object would +    // throw LLEventPump::DupPumpName. +    mEventPump("LLHTTPClient") +{ +    mEventPump.listen("self", boost::bind(&LLSDMessage::httpListener, this, _1)); +} + +bool LLSDMessage::httpListener(const LLSD& request) +{ +    // Extract what we want from the request object. We do it all up front +    // partly to document what we expect. +    LLSD::String url(request["url"]); +    LLSD payload(request["payload"]); +    LLSD::String reply(request["reply"]); +    LLSD::String error(request["error"]); +    LLSD::Real timeout(request["timeout"]); +    // If the LLSD doesn't even have a "url" key, we doubt it was intended for +    // this listener. +    if (url.empty()) +    { +        std::ostringstream out; +        out << "request event without 'url' key to '" << mEventPump.getName() << "'"; +        throw ArgError(out.str()); +    } +    // Establish default timeout. This test relies on LLSD::asReal() returning +    // exactly 0.0 for an undef value. +    if (! timeout) +    { +        timeout = HTTP_REQUEST_EXPIRY_SECS; +    } +    LLHTTPClient::post(url, payload, +                       new LLSDMessage::EventResponder(LLEventPumps::instance(), +                                                       url, "POST", reply, error), +                       LLSD(),      // headers +                       timeout); +    return false; +} + +void LLSDMessage::EventResponder::result(const LLSD& data) +{ +    // If our caller passed an empty replyPump name, they're not +    // listening: this is a fire-and-forget message. Don't bother posting +    // to the pump whose name is "". +    if (! mReplyPump.empty()) +    { +        mPumps.obtain(mReplyPump).post(data); +    } +    else                            // default success handling +    { +        LL_INFOS("LLSDMessage::EventResponder") +            << "'" << mMessage << "' to '" << mTarget << "' succeeded" +            << LL_ENDL; +    } +} + +void LLSDMessage::EventResponder::error(U32 status, const std::string& reason, const LLSD& content) +{ +    // If our caller passed an empty errorPump name, they're not +    // listening: "default error handling is acceptable." Only post to an +    // explicit pump name. +    if (! mErrorPump.empty()) +    { +        LLSD info; +        info["target"]  = mTarget; +        info["message"] = mMessage; +        info["status"]  = LLSD::Integer(status); +        info["reason"]  = reason; +        info["content"] = content; +        mPumps.obtain(mErrorPump).post(info); +    } +    else                        // default error handling +    { +        // convention seems to be to use llinfos, but that seems a bit casual? +        LL_WARNS("LLSDMessage::EventResponder") +            << "'" << mMessage << "' to '" << mTarget +            << "' failed with code " << status << ": " << reason << '\n' +            << ll_pretty_print_sd(content) +            << LL_ENDL; +    } +} + +LLSDMessage::ResponderAdapter::ResponderAdapter(LLHTTPClient::ResponderPtr responder, +                                                const std::string& name): +    mResponder(responder), +    mReplyPump(name + ".reply", true), // tweak name for uniqueness +    mErrorPump(name + ".error", true) +{ +    mReplyPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, true)); +    mErrorPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, false)); +} + +bool LLSDMessage::ResponderAdapter::listener(const LLSD& payload, bool success) +{ +    if (success) +    { +        mResponder->result(payload); +    } +    else +    { +        mResponder->error(payload["status"].asInteger(), payload["reason"], payload["content"]); +    } + +    /*---------------- MUST BE LAST STATEMENT BEFORE RETURN ----------------*/ +    delete this; +    // Destruction of mResponder will usually implicitly free its referent as well +    /*------------------------- NOTHING AFTER THIS -------------------------*/ +    return false; +} + +void LLSDMessage::link() +{ +} diff --git a/indra/llmessage/llsdmessage.h b/indra/llmessage/llsdmessage.h new file mode 100644 index 0000000000..8ae9451243 --- /dev/null +++ b/indra/llmessage/llsdmessage.h @@ -0,0 +1,146 @@ +/** + * @file   llsdmessage.h + * @author Nat Goodspeed + * @date   2008-10-30 + * @brief  API intended to unify sending capability, UDP and TCP messages: + *         https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes + *  + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLSDMESSAGE_H) +#define LL_LLSDMESSAGE_H + +#include "llerror.h"                // LOG_CLASS() +#include "llevents.h"               // LLEventPumps +#include "llhttpclient.h" +#include <string> +#include <stdexcept> + +class LLSD; + +/** + * Class managing the messaging API described in + * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes + */ +class LLSDMessage +{ +    LOG_CLASS(LLSDMessage); + +public: +    LLSDMessage(); + +    /// Exception if you specify arguments badly +    struct ArgError: public std::runtime_error +    { +        ArgError(const std::string& what): +            std::runtime_error(std::string("ArgError: ") + what) {} +    }; + +    /** +     * The response idiom used by LLSDMessage -- LLEventPump names on which to +     * post reply or error -- is designed for the case in which your +     * reply/error handlers are methods on the same class as the method +     * sending the message. Any state available to the sending method that +     * must be visible to the reply/error methods can conveniently be stored +     * on that class itself, if it's not already. +     * +     * The LLHTTPClient::Responder idiom requires a separate instance of a +     * separate class so that it can dispatch to the code of interest by +     * calling canonical virtual methods. Interesting state must be copied +     * into that new object.  +     * +     * With some trepidation, because existing response code is packaged in +     * LLHTTPClient::Responder subclasses, we provide this adapter class +     * <i>for transitional purposes only.</i> Instantiate a new heap +     * ResponderAdapter with your new LLHTTPClient::ResponderPtr. Pass +     * ResponderAdapter::getReplyName() and/or getErrorName() in your +     * LLSDMessage (or LLViewerRegion::getCapAPI()) request event. The +     * ResponderAdapter will call the appropriate Responder method, then +     * @c delete itself. +     */ +    class ResponderAdapter +    { +    public: +        /** +         * Bind the new LLHTTPClient::Responder subclass instance. +         * +         * Passing the constructor a name other than the default is only +         * interesting if you suspect some usage will lead to an exception or +         * log message. +         */ +        ResponderAdapter(LLHTTPClient::ResponderPtr responder, +                         const std::string& name="ResponderAdapter"); + +        /// EventPump name on which LLSDMessage should post reply event +        std::string getReplyName() const { return mReplyPump.getName(); } +        /// EventPump name on which LLSDMessage should post error event +        std::string getErrorName() const { return mErrorPump.getName(); } + +    private: +        // We have two different LLEventStreams, though we route them both to +        // the same listener, so that we can bind an extra flag identifying +        // which case (reply or error) reached that listener. +        bool listener(const LLSD&, bool success); + +        LLHTTPClient::ResponderPtr mResponder; +        LLEventStream mReplyPump, mErrorPump; +    }; + +    /** +     * Force our implementation file to be linked with caller. The .cpp file +     * contains a static instance of this class, which must be linked into the +     * executable to support the canonical listener. But since the primary +     * interface to that static instance is via a named LLEventPump rather +     * than by direct reference, the linker doesn't necessarily perceive the +     * necessity to bring in the translation unit. Referencing this dummy +     * method forces the issue. +     */ +    static void link(); + +private: +    friend class LLCapabilityListener; +    /// Responder used for internal purposes by LLSDMessage and +    /// LLCapabilityListener. Others should use higher-level APIs. +    class EventResponder: public LLHTTPClient::Responder +    { +    public: +        /** +         * LLHTTPClient::Responder that dispatches via named LLEventPump instances. +         * We bind LLEventPumps, even though it's an LLSingleton, for testability. +         * We bind the string names of the desired LLEventPump instances rather +         * than actually obtain()ing them so we only obtain() the one we're going +         * to use. If the caller doesn't bother to listen() on it, the other pump +         * may never materialize at all. +         * @a target and @a message are only to clarify error processing. +         * For a capability message, @a target should be the region description, +         * @a message should be the capability name. +         * For a service with a visible URL, pass the URL as @a target and the HTTP verb +         * (e.g. "POST") as @a message. +         */ +        EventResponder(LLEventPumps& pumps, +                       const std::string& target, const std::string& message, +                       const std::string& replyPump, const std::string& errorPump): +            mPumps(pumps), +            mTarget(target), +            mMessage(message), +            mReplyPump(replyPump), +            mErrorPump(errorPump) +        {} +     +        virtual void result(const LLSD& data); +        virtual void error(U32 status, const std::string& reason, const LLSD& content); +     +    private: +        LLEventPumps& mPumps; +        const std::string mTarget, mMessage, mReplyPump, mErrorPump; +    }; + +private: +    bool httpListener(const LLSD&); +    LLEventStream mEventPump; +}; + +#endif /* ! defined(LL_LLSDMESSAGE_H) */ diff --git a/indra/llmessage/tests/llsdmessage_test.cpp b/indra/llmessage/tests/llsdmessage_test.cpp new file mode 100644 index 0000000000..2957d7cc4f --- /dev/null +++ b/indra/llmessage/tests/llsdmessage_test.cpp @@ -0,0 +1,113 @@ +/** + * @file   llsdmessage_tut.cpp + * @author Nat Goodspeed + * @date   2008-12-22 + * @brief  Test of llsdmessage.h + *  + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llsdmessage.h" +// STL headers +#include <iostream> +// std headers +#include <stdexcept> +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "llsdserialize.h" +#include "llevents.h" +#include "stringize.h" +#include "llhost.h" +#include "tests/networkio.h" +#include "tests/commtest.h" + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct llsdmessage_data: public commtest_data +    { +        LLEventPump& httpPump; + +        llsdmessage_data(): +            httpPump(pumps.obtain("LLHTTPClient")) +        { +            LLSDMessage::link(); +        } +    }; +    typedef test_group<llsdmessage_data> llsdmessage_group; +    typedef llsdmessage_group::object llsdmessage_object; +    llsdmessage_group llsdmgr("llsdmessage"); + +    template<> template<> +    void llsdmessage_object::test<1>() +    { +        bool threw = false; +        // This should fail... +        try +        { +            LLSDMessage localListener; +        } +        catch (const LLEventPump::DupPumpName&) +        { +            threw = true; +        } +        ensure("second LLSDMessage should throw", threw); +    } + +    template<> template<> +    void llsdmessage_object::test<2>() +    { +        LLSD request, body; +        body["data"] = "yes"; +        request["payload"] = body; +        request["reply"] = replyPump.getName(); +        request["error"] = errorPump.getName(); +        bool threw = false; +        try +        { +            httpPump.post(request); +        } +        catch (const LLSDMessage::ArgError&) +        { +            threw = true; +        } +        ensure("missing URL", threw); +    } + +    template<> template<> +    void llsdmessage_object::test<3>() +    { +        LLSD request, body; +        body["data"] = "yes"; +        request["url"] = server + "got-message"; +        request["payload"] = body; +        request["reply"] = replyPump.getName(); +        request["error"] = errorPump.getName(); +        httpPump.post(request); +        ensure("got response", netio.pump()); +        ensure("success response", success); +        ensure_equals(result.asString(), "success"); + +        body["status"] = 499; +        body["reason"] = "custom error message"; +        request["url"] = server + "fail"; +        request["payload"] = body; +        httpPump.post(request); +        ensure("got response", netio.pump()); +        ensure("failure response", ! success); +        ensure_equals(result["status"].asInteger(), body["status"].asInteger()); +        ensure_equals(result["reason"].asString(),  body["reason"].asString()); +    } +} // namespace tut diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 0762d7c12d..a8d06643ff 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -67,6 +67,8 @@  #include <set>  #include <boost/tokenizer.hpp> +using namespace LLOldEvents; +  // static  LLMenuHolderGL *LLMenuGL::sMenuContainer = NULL; diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 030b81b58c..e62402d617 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -224,7 +224,7 @@ private:  // calls a user defined callback.  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLMenuItemCallGL : public LLMenuItemGL, public LLObservable +class LLMenuItemCallGL : public LLMenuItemGL, public LLOldEvents::LLObservable  {  public:  	// normal constructor diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 6b7dd0a3de..fe1ea95070 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -714,7 +714,7 @@ std::string LLNotification::getLabel() const  // =========================================================  // LLNotificationChannel implementation  // --- -void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type& slot) +LLBoundListener LLNotificationChannelBase::connectChangedImpl(const LLEventListener& slot)  {  	// when someone wants to connect to a channel, we first throw them  	// all of the notifications that are already in the channel @@ -722,23 +722,23 @@ void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type  	// only about new notifications  	for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)  	{ -		slot.get_slot_function()(LLSD().insert("sigtype", "load").insert("id", (*it)->id())); +		slot(LLSD().insert("sigtype", "load").insert("id", (*it)->id()));  	}  	// and then connect the signal so that all future notifications will also be  	// forwarded. -	mChanged.connect(slot); +	return mChanged.connect(slot);  } -void LLNotificationChannelBase::connectPassedFilter(const LLStandardSignal::slot_type& slot) +LLBoundListener LLNotificationChannelBase::connectPassedFilterImpl(const LLEventListener& slot)  {  	// these two filters only fire for notifications added after the current one, because  	// they don't participate in the hierarchy. -	mPassedFilter.connect(slot); +	return mPassedFilter.connect(slot);  } -void LLNotificationChannelBase::connectFailedFilter(const LLStandardSignal::slot_type& slot) +LLBoundListener LLNotificationChannelBase::connectFailedFilterImpl(const LLEventListener& slot)  { -	mFailedFilter.connect(slot); +	return mFailedFilter.connect(slot);  }  // external call, conforms to our standard signature @@ -896,8 +896,7 @@ mParent(parent)  	else  	{  		LLNotificationChannelPtr p = LLNotifications::instance().getChannel(parent); -		LLStandardSignal::slot_type f = boost::bind(&LLNotificationChannelBase::updateItem, this, _1); -		p->connectChanged(f); +		p->connectChanged(boost::bind(&LLNotificationChannelBase::updateItem, this, _1));  	}  } @@ -1093,11 +1092,11 @@ void LLNotifications::createDefaultChannels()  	// connect action methods to these channels  	LLNotifications::instance().getChannel("Expiration")-> -		connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); +        connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1));  	LLNotifications::instance().getChannel("Unique")-> -		connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); +        connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1));  	LLNotifications::instance().getChannel("Unique")-> -		connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); +        connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1));  	LLNotifications::instance().getChannel("Ignore")->  		connectFailedFilter(&handleIgnoredNotification);  } diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index bb379121cc..d01296c89e 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -91,49 +91,20 @@  #include <boost/utility.hpp>  #include <boost/shared_ptr.hpp> -#include <boost/signal.hpp> +#include <boost/enable_shared_from_this.hpp>  #include <boost/type_traits.hpp>  // we want to minimize external dependencies, but this one is important  #include "llsd.h"  // and we need this to manage the notification callbacks +#include "llevents.h"  #include "llfunctorregistry.h"  #include "llui.h"  class LLNotification;  typedef boost::shared_ptr<LLNotification> LLNotificationPtr; -/***************************************************************************** -*   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; -    } -}; -  typedef enum e_notification_priority  { @@ -144,27 +115,11 @@ typedef enum e_notification_priority  	NOTIFICATION_PRIORITY_CRITICAL  } ENotificationPriority; -/** - * 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. - */ -  typedef boost::function<void (const LLSD&, const LLSD&)> LLNotificationResponder;  typedef LLFunctorRegistry<LLNotificationResponder> LLNotificationFunctorRegistry;  typedef LLFunctorRegistration<LLNotificationResponder> LLNotificationFunctorRegistration; -typedef boost::signal<bool(const LLSD&), LLStopWhenHandled>  LLStandardSignal; -  // context data that can be looked up via a notification's payload by the display logic  // derive from this class to implement specific contexts  class LLNotificationContext : public LLInstanceTracker<LLNotificationContext, LLUUID> @@ -699,7 +654,7 @@ typedef std::multimap<std::string, LLNotificationPtr> LLNotificationMap;  // all of the built-in tests should attach to the "Visible" channel  //  class LLNotificationChannelBase : -	public boost::signals::trackable +	public LLEventTrackable  {  	LOG_CLASS(LLNotificationChannelBase);  public: @@ -709,15 +664,45 @@ public:  	virtual ~LLNotificationChannelBase() {}  	// you can also connect to a Channel, so you can be notified of  	// changes to this channel -	virtual void connectChanged(const LLStandardSignal::slot_type& slot); -	virtual void connectPassedFilter(const LLStandardSignal::slot_type& slot); -	virtual void connectFailedFilter(const LLStandardSignal::slot_type& slot); +	template <typename LISTENER> +    LLBoundListener connectChanged(const LISTENER& slot) +    { +        // Examine slot to see if it binds an LLEventTrackable subclass, or a +        // boost::shared_ptr to something, or a boost::weak_ptr to something. +        // Call this->connectChangedImpl() to actually connect it. +        return LLEventDetail::visit_and_connect(slot, +                                  boost::bind(&LLNotificationChannelBase::connectChangedImpl, +                                              this, +                                              _1)); +    } +    template <typename LISTENER> +	LLBoundListener connectPassedFilter(const LISTENER& slot) +    { +        // see comments in connectChanged() +        return LLEventDetail::visit_and_connect(slot, +                                  boost::bind(&LLNotificationChannelBase::connectPassedFilterImpl, +                                              this, +                                              _1)); +    } +    template <typename LISTENER> +	LLBoundListener connectFailedFilter(const LISTENER& slot) +    { +        // see comments in connectChanged() +        return LLEventDetail::visit_and_connect(slot, +                                  boost::bind(&LLNotificationChannelBase::connectFailedFilterImpl, +                                              this, +                                              _1)); +    }  	// use this when items change or to add a new one  	bool updateItem(const LLSD& payload);  	const LLNotificationFilter& getFilter() { return mFilter; }  protected: +    LLBoundListener connectChangedImpl(const LLEventListener& slot); +    LLBoundListener connectPassedFilterImpl(const LLEventListener& slot); +    LLBoundListener connectFailedFilterImpl(const LLEventListener& slot); +  	LLNotificationSet mItems;  	LLStandardSignal mChanged;  	LLStandardSignal mPassedFilter; diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h index 756d02ef7d..a7c9579030 100644 --- a/indra/llui/llpanel.h +++ b/indra/llui/llpanel.h @@ -42,6 +42,7 @@  #include "llviewborder.h"  #include "lluistring.h"  #include "v4color.h" +#include "llevents.h"  #include <list>  #include <queue> @@ -56,7 +57,7 @@ const BOOL BORDER_NO = FALSE;   * With or without border,   * Can contain LLUICtrls.   */ -class LLPanel : public LLUICtrl, public boost::signals::trackable +class LLPanel : public LLUICtrl, public LLEventTrackable  {  public: diff --git a/indra/llui/llui.h b/indra/llui/llui.h index ebcc7304b1..6d6ce7a97c 100644 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -43,7 +43,7 @@  #include "llgl.h"			// *TODO: break this dependency  #include <stack>  //#include "llimagegl.h" -#include <boost/signal.hpp> +#include <boost/signals2.hpp>  // LLUIFactory  #include "llsd.h" @@ -661,7 +661,7 @@ template <typename T> std::set<T*> LLInstanceTracker<T, T*>::sInstances;  class LLCallbackRegistry  {  public: -	typedef boost::signal<void()> callback_signal_t; +	typedef boost::signals2::signal<void()> callback_signal_t;  	void registerCallback(const callback_signal_t::slot_type& slot)  	{ diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 2350ea6050..8ec681fcaf 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -56,6 +56,8 @@  #include "lltexteditor.h"  #include "lltextbox.h" +using namespace LLOldEvents; +  //HACK: this allows you to instantiate LLView from xml with "<view/>" which we don't want  static LLRegisterWidget<LLView> r("view"); diff --git a/indra/llui/llview.h b/indra/llui/llview.h index e0e0f6ba47..721fe99f4a 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -441,11 +441,11 @@ public:  	void localRectToScreen( const LLRect& local, LLRect* screen ) const;  	// Listener dispatching functions (Dispatcher deletes pointers to listeners on deregistration or destruction) -	LLSimpleListener* getListenerByName(const std::string& callback_name); -	void registerEventListener(std::string name, LLSimpleListener* function); +    LLOldEvents::LLSimpleListener* getListenerByName(const std::string& callback_name); +	void registerEventListener(std::string name, LLOldEvents::LLSimpleListener* function);  	void deregisterEventListener(std::string name); -	std::string findEventListener(LLSimpleListener *listener) const; -	void addListenerToControl(LLEventDispatcher *observer, const std::string& name, LLSD filter, LLSD userdata); +	std::string findEventListener(LLOldEvents::LLSimpleListener *listener) const; +	void addListenerToControl(LLOldEvents::LLEventDispatcher *observer, const std::string& name, LLSD filter, LLSD userdata);  	void addBoolControl(const std::string& name, bool initial_value);  	LLControlVariable *getControl(const std::string& name); @@ -651,7 +651,7 @@ private:  	static LLWindow* sWindow;	// All root views must know about their window. -	typedef std::map<std::string, LLPointer<LLSimpleListener> > dispatch_list_t; +	typedef std::map<std::string, LLPointer<LLOldEvents::LLSimpleListener> > dispatch_list_t;  	dispatch_list_t mDispatchList;  	std::string		mControlName; @@ -659,7 +659,7 @@ private:  	typedef std::map<std::string, LLView*> dummy_widget_map_t;  	mutable dummy_widget_map_t mDummyWidgets; -	boost::signals::connection mControlConnection; +	boost::signals2::connection mControlConnection;  	ECursorType mHoverCursor; diff --git a/indra/llxml/CMakeLists.txt b/indra/llxml/CMakeLists.txt index dc7787beea..c5fb44e721 100644 --- a/indra/llxml/CMakeLists.txt +++ b/indra/llxml/CMakeLists.txt @@ -39,6 +39,5 @@ add_library (llxml ${llxml_SOURCE_FILES})  # Sort by high-level to low-level  target_link_libraries( llxml      llmath -    ${BOOST_SIGNALS_LIBRARY}      ${EXPAT_LIBRARIES}      ) diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h index ba0a1c7cbf..a39a56aacd 100644 --- a/indra/llxml/llcontrol.h +++ b/indra/llxml/llcontrol.h @@ -53,7 +53,7 @@  #endif  #include <boost/bind.hpp> -#include <boost/signal.hpp> +#include <boost/signals2.hpp>  #if LL_WINDOWS  # if (_MSC_VER >= 1300 && _MSC_VER < 1400) @@ -89,7 +89,7 @@ typedef enum e_control_type  class LLControlVariable : public LLRefCount  {  	friend class LLControlGroup; -	typedef boost::signal<void(const LLSD&)> signal_t; +	typedef boost::signals2::signal<void(const LLSD&)> signal_t;  private:  	std::string		mName; diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 7b1c3003f0..57f1716db5 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -78,6 +78,7 @@ set(viewer_SOURCE_FILES      llbox.cpp      llcallbacklist.cpp      llcallingcard.cpp +    llcapabilitylistener.cpp      llcaphttpsender.cpp      llchatbar.cpp      llclassifiedinfo.cpp @@ -481,6 +482,8 @@ set(viewer_HEADER_FILES      llbox.h      llcallbacklist.h      llcallingcard.h +    llcapabilitylistener.h +    llcapabilityprovider.h      llcaphttpsender.h      llchatbar.h      llclassifiedinfo.h @@ -1391,3 +1394,5 @@ if (INSTALL)  endif (INSTALL)  ADD_VIEWER_BUILD_TEST(llagentaccess viewer) +ADD_VIEWER_COMM_BUILD_TEST(llcapabilitylistener viewer  +  ${CMAKE_CURRENT_SOURCE_DIR}/../llmessage/tests/test_llsdmessage_peer.py) diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 98526c8a57..07503404dc 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -134,6 +134,8 @@  #include "llappviewer.h"  #include "llviewerjoystick.h"  #include "llfollowcam.h" +#include "stringize.h" +#include "llcapabilitylistener.h"  using namespace LLVOAvatarDefines; @@ -914,7 +916,7 @@ LLViewerRegion *LLAgent::getRegion() const  } -const LLHost& LLAgent::getRegionHost() const +LLHost LLAgent::getRegionHost() const  {  	if (mRegionp)  	{ @@ -4674,109 +4676,128 @@ void LLAgent::lookAtLastChat()  const F32 SIT_POINT_EXTENTS = 0.2f; +LLSD ll_sdmap_from_vector3(const LLVector3& vec) +{ +    LLSD ret; +    ret["X"] = vec.mV[VX]; +    ret["Y"] = vec.mV[VY]; +    ret["Z"] = vec.mV[VZ]; +    return ret; +} + +LLVector3 ll_vector3_from_sdmap(const LLSD& sd) +{ +    LLVector3 ret; +    ret.mV[VX] = F32(sd["X"].asReal()); +    ret.mV[VY] = F32(sd["Y"].asReal()); +    ret.mV[VZ] = F32(sd["Z"].asReal()); +    return ret; +} +  void LLAgent::setStartPosition( U32 location_id )  { -  LLViewerObject          *object; +    LLViewerObject          *object; -  if ( !(gAgentID == LLUUID::null) ) -  { +    if (gAgentID == LLUUID::null) +    { +        return; +    }      // we've got an ID for an agent viewerobject      object = gObjectList.findObject(gAgentID); -    if (object) +    if (! object)      { -      // we've got the viewer object -      // Sometimes the agent can be velocity interpolated off of -      // this simulator.  Clamp it to the region the agent is -      // in, a little bit in on each side. -      const F32 INSET = 0.5f; //meters -      const F32 REGION_WIDTH = LLWorld::getInstance()->getRegionWidthInMeters(); - -      LLVector3 agent_pos = getPositionAgent(); -      LLVector3 agent_look_at = mFrameAgent.getAtAxis(); - -      if (mAvatarObject.notNull()) -      { -	// the z height is at the agent's feet -	agent_pos.mV[VZ] -= 0.5f * mAvatarObject->mBodySize.mV[VZ]; -      } - -      agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET ); -      agent_pos.mV[VY] = llclamp( agent_pos.mV[VY], INSET, REGION_WIDTH - INSET ); - -      // Don't let them go below ground, or too high. -      agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ], -				  mRegionp->getLandHeightRegion( agent_pos ), -				  LLWorld::getInstance()->getRegionMaxHeight() ); -      // Send the CapReq - -      LLSD body; - -      std::string url = gAgent.getRegion()->getCapability("HomeLocation"); -      std::ostringstream strBuffer; -      if( url.empty() ) -      { -	LLMessageSystem* msg = gMessageSystem; -	msg->newMessageFast(_PREHASH_SetStartLocationRequest); -	msg->nextBlockFast( _PREHASH_AgentData); -	msg->addUUIDFast(_PREHASH_AgentID, getID()); -	msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); -	msg->nextBlockFast( _PREHASH_StartLocationData); -	// corrected by sim -	msg->addStringFast(_PREHASH_SimName, ""); -	msg->addU32Fast(_PREHASH_LocationID, location_id); -	msg->addVector3Fast(_PREHASH_LocationPos, agent_pos); -	msg->addVector3Fast(_PREHASH_LocationLookAt,mFrameAgent.getAtAxis()); -	 -	// Reliable only helps when setting home location.  Last -	// location is sent on quit, and we don't have time to ack -	// the packets. -	msg->sendReliable(mRegionp->getHost()); - -	const U32 HOME_INDEX = 1; -	if( HOME_INDEX == location_id ) -	  { -	    setHomePosRegion( mRegionp->getHandle(), getPositionAgent() ); -	  } -      } -      else -      { -	strBuffer << location_id; -	body["HomeLocation"]["LocationId"] = strBuffer.str(); - -	strBuffer.str(""); -	strBuffer << agent_pos.mV[VX]; -	body["HomeLocation"]["LocationPos"]["X"] = strBuffer.str(); - -	strBuffer.str(""); -	strBuffer << agent_pos.mV[VY]; -	body["HomeLocation"]["LocationPos"]["Y"] = strBuffer.str(); - -	strBuffer.str(""); -	strBuffer << agent_pos.mV[VZ]; -	body["HomeLocation"]["LocationPos"]["Z"] = strBuffer.str(); - -	strBuffer.str(""); -	strBuffer << agent_look_at.mV[VX]; -	body["HomeLocation"]["LocationLookAt"]["X"] = strBuffer.str(); - -	strBuffer.str(""); -	strBuffer << agent_look_at.mV[VY]; -	body["HomeLocation"]["LocationLookAt"]["Y"] = strBuffer.str(); - -	strBuffer.str(""); -	strBuffer << agent_look_at.mV[VZ]; -	body["HomeLocation"]["LocationLookAt"]["Z"] = strBuffer.str(); - -	LLHTTPClient::post( url, body, new LLHomeLocationResponder() ); -      } +        llinfos << "setStartPosition - Can't find agent viewerobject id " << gAgentID << llendl; +        return; +    } +    // we've got the viewer object +    // Sometimes the agent can be velocity interpolated off of +    // this simulator.  Clamp it to the region the agent is +    // in, a little bit in on each side. +    const F32 INSET = 0.5f; //meters +    const F32 REGION_WIDTH = LLWorld::getInstance()->getRegionWidthInMeters(); + +    LLVector3 agent_pos = getPositionAgent(); + +    if (mAvatarObject.notNull()) +    { +        // the z height is at the agent's feet +        agent_pos.mV[VZ] -= 0.5f * mAvatarObject->mBodySize.mV[VZ];      } -    else + +    agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET ); +    agent_pos.mV[VY] = llclamp( agent_pos.mV[VY], INSET, REGION_WIDTH - INSET ); + +    // Don't let them go below ground, or too high. +    agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ], +                                mRegionp->getLandHeightRegion( agent_pos ), +                                LLWorld::getInstance()->getRegionMaxHeight() ); +    // Send the CapReq +    LLSD request; +    LLSD body; +    LLSD homeLocation; + +    homeLocation["LocationId"] = LLSD::Integer(location_id); +    homeLocation["LocationPos"] = ll_sdmap_from_vector3(agent_pos); +    homeLocation["LocationLookAt"] = ll_sdmap_from_vector3(mFrameAgent.getAtAxis()); + +    body["HomeLocation"] = homeLocation; + +    // This awkward idiom warrants explanation. +    // For starters, LLSDMessage::ResponderAdapter is ONLY for testing the new +    // LLSDMessage functionality with a pre-existing LLHTTPClient::Responder. +    // In new code, define your reply/error methods on the same class as the +    // sending method, bind them to local LLEventPump objects and pass those +    // LLEventPump names in the request LLSD object. +    // When testing old code, the new LLHomeLocationResponder object +    // is referenced by an LLHTTPClient::ResponderPtr, so when the +    // ResponderAdapter is deleted, the LLHomeLocationResponder will be too. +    // We must trust that the underlying LLHTTPClient code will eventually +    // fire either the reply callback or the error callback; either will cause +    // the ResponderAdapter to delete itself. +    LLSDMessage::ResponderAdapter* +        adapter(new LLSDMessage::ResponderAdapter(new LLHomeLocationResponder())); + +    request["message"] = "HomeLocation"; +    request["payload"] = body; +    request["reply"]   = adapter->getReplyName(); +    request["error"]   = adapter->getErrorName(); + +    gAgent.getRegion()->getCapAPI().post(request); + +    const U32 HOME_INDEX = 1; +    if( HOME_INDEX == location_id )      { -      llinfos << "setStartPosition - Can't find agent viewerobject id " << gAgentID << llendl; +        setHomePosRegion( mRegionp->getHandle(), getPositionAgent() );      } -  }  } +struct HomeLocationMapper: public LLCapabilityListener::CapabilityMapper +{ +    // No reply message expected +    HomeLocationMapper(): LLCapabilityListener::CapabilityMapper("HomeLocation") {} +    virtual void buildMessage(LLMessageSystem* msg, +                              const LLUUID& agentID, +                              const LLUUID& sessionID, +                              const std::string& capabilityName, +                              const LLSD& payload) const +    { +        msg->newMessageFast(_PREHASH_SetStartLocationRequest); +        msg->nextBlockFast( _PREHASH_AgentData); +        msg->addUUIDFast(_PREHASH_AgentID, agentID); +        msg->addUUIDFast(_PREHASH_SessionID, sessionID); +        msg->nextBlockFast( _PREHASH_StartLocationData); +        // corrected by sim +        msg->addStringFast(_PREHASH_SimName, ""); +        msg->addU32Fast(_PREHASH_LocationID, payload["HomeLocation"]["LocationId"].asInteger()); +        msg->addVector3Fast(_PREHASH_LocationPos, +                            ll_vector3_from_sdmap(payload["HomeLocation"]["LocationPos"])); +        msg->addVector3Fast(_PREHASH_LocationLookAt, +                            ll_vector3_from_sdmap(payload["HomeLocation"]["LocationLookAt"])); +    } +}; +// Need an instance of this class so it will self-register +static HomeLocationMapper homeLocationMapper; +  void LLAgent::requestStopMotion( LLMotion* motion )  {  	// Notify all avatars that a motion has stopped. @@ -5400,7 +5421,7 @@ void update_group_floaters(const LLUUID& group_id)  		gIMMgr->refresh();  	} -	gAgent.fireEvent(new LLEvent(&gAgent, "new group"), ""); +	gAgent.fireEvent(new LLOldEvents::LLEvent(&gAgent, "new group"), "");  }  // static diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index 226c78e631..27fcb07fd5 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -119,7 +119,7 @@ inline bool operator==(const LLGroupData &a, const LLGroupData &b)  // -class LLAgent : public LLObservable +class LLAgent : public LLOldEvents::LLObservable  {  	LOG_CLASS(LLAgent); @@ -176,7 +176,7 @@ public:  	// Set the home data  	void			setRegion(LLViewerRegion *regionp);  	LLViewerRegion	*getRegion() const; -	const LLHost&	getRegionHost() const; +	LLHost			getRegionHost() const;  	std::string		getSLURL() const;  	void			updateAgentPosition(const F32 dt, const F32 yaw, const S32 mouse_x, const S32 mouse_y);		// call once per frame to update position, angles radians diff --git a/indra/newview/llagentlanguage.h b/indra/newview/llagentlanguage.h index 6bc7250c6e..596c584232 100644 --- a/indra/newview/llagentlanguage.h +++ b/indra/newview/llagentlanguage.h @@ -36,7 +36,7 @@  #include "llmemory.h"	// LLSingleton<>  #include "llevent.h" -class LLAgentLanguage: public LLSingleton<LLAgentLanguage>, public LLSimpleListener +class LLAgentLanguage: public LLSingleton<LLAgentLanguage>, public LLOldEvents::LLSimpleListener  {   public:  	LLAgentLanguage(); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 0c34d018a2..b66b514597 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -91,6 +91,7 @@  #include "lltexturecache.h"  #include "lltexturefetch.h"  #include "llimageworker.h" +#include "llevents.h"  // The files below handle dependencies from cleanup.  #include "llkeyframemotion.h" @@ -841,7 +842,14 @@ bool LLAppViewer::mainLoop()  	LLTimer debugTime;  	LLViewerJoystick* joystick(LLViewerJoystick::getInstance());  	joystick->setNeedsReset(true); - 	 + +    LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); +    // As we do not (yet) send data on the mainloop LLEventPump that varies +    // with each frame, no need to instantiate a new LLSD event object each +    // time. Obviously, if that changes, just instantiate the LLSD at the +    // point of posting. +    LLSD newFrame; +  	// Handle messages  	while (!LLApp::isExiting())  	{ @@ -882,6 +890,9 @@ bool LLAppViewer::mainLoop()  				LLFloaterMemLeak::getInstance()->idle() ;				  			}			 +            // canonical per-frame event +            mainloop.post(newFrame); +  			if (!LLApp::isExiting())  			{  				pingMainloopTimeout("Main:JoystickKeyboard"); diff --git a/indra/newview/llcapabilitylistener.cpp b/indra/newview/llcapabilitylistener.cpp new file mode 100644 index 0000000000..3277da8930 --- /dev/null +++ b/indra/newview/llcapabilitylistener.cpp @@ -0,0 +1,183 @@ +/** + * @file   llcapabilitylistener.cpp + * @author Nat Goodspeed + * @date   2009-01-07 + * @brief  Implementation for llcapabilitylistener. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "llviewerprecompiledheaders.h" +// associated header +#include "llcapabilitylistener.h" +// STL headers +#include <map> +// std headers +// external library headers +#include <boost/bind.hpp> +// other Linden headers +#include "stringize.h" +#include "llcapabilityprovider.h" + +class LLCapabilityListener::CapabilityMappers: public LLSingleton<LLCapabilityListener::CapabilityMappers> +{ +public: +    void registerMapper(const LLCapabilityListener::CapabilityMapper*); +    void unregisterMapper(const LLCapabilityListener::CapabilityMapper*); +    const LLCapabilityListener::CapabilityMapper* find(const std::string& cap) const; + +    struct DupCapMapper: public std::runtime_error +    { +        DupCapMapper(const std::string& what): +            std::runtime_error(std::string("DupCapMapper: ") + what) +        {} +    }; + +private: +    friend class LLSingleton<LLCapabilityListener::CapabilityMappers>; +    CapabilityMappers(); + +    typedef std::map<std::string, const LLCapabilityListener::CapabilityMapper*> CapabilityMap; +    CapabilityMap mMap; +}; + +LLCapabilityListener::LLCapabilityListener(const std::string& name, +                                           LLMessageSystem* messageSystem, +                                           const LLCapabilityProvider& provider, +                                           const LLUUID& agentID, +                                           const LLUUID& sessionID): +    mEventPump(name), +    mMessageSystem(messageSystem), +    mProvider(provider), +    mAgentID(agentID), +    mSessionID(sessionID) +{ +    mEventPump.listen("self", boost::bind(&LLCapabilityListener::capListener, this, _1)); +} + +bool LLCapabilityListener::capListener(const LLSD& request) +{ +    // Extract what we want from the request object. We do it all up front +    // partly to document what we expect. +    LLSD::String cap(request["message"]); +    LLSD payload(request["payload"]); +    LLSD::String reply(request["reply"]); +    LLSD::String error(request["error"]); +    LLSD::Real timeout(request["timeout"]); +    // If the LLSD doesn't even have a "message" key, we doubt it was intended +    // for this listener. +    if (cap.empty()) +    { +        LL_ERRS("capListener") << "capability request event without 'message' key to '" +                               << getCapAPI().getName() +                               << "' on region\n" << mProvider.getDescription() +                               << LL_ENDL; +        return false;               // in case fatal-error function isn't +    } +    // Establish default timeout. This test relies on LLSD::asReal() returning +    // exactly 0.0 for an undef value. +    if (! timeout) +    { +        timeout = HTTP_REQUEST_EXPIRY_SECS; +    } +    // Look up the url for the requested capability name. +    std::string url = mProvider.getCapability(cap); +    if (! url.empty()) +    { +        // This capability is supported by the region to which we're talking. +        LLHTTPClient::post(url, payload, +                           new LLSDMessage::EventResponder(LLEventPumps::instance(), +                                                           mProvider.getDescription(), +                                                           cap, reply, error), +                           LLSD(),  // headers +                           timeout); +    } +    else +    { +        // Capability not supported -- do we have a registered mapper? +        const CapabilityMapper* mapper = CapabilityMappers::instance().find(cap); +        if (! mapper)               // capability neither supported nor mapped +        { +            LL_ERRS("capListener") << "unsupported capability '" << cap << "' request to '" +                                   << getCapAPI().getName() << "' on region\n" +                                   << mProvider.getDescription() +                                   << LL_ENDL; +        } +        else if (! mapper->getReplyName().empty()) // mapper expects reply support +        { +            LL_ERRS("capListener") << "Mapper for capability '" << cap +                                   << "' requires unimplemented support for reply message '" +                                   << mapper->getReplyName() +                                   << "' on '" << getCapAPI().getName() << "' on region\n" +                                   << mProvider.getDescription() +                                   << LL_ENDL; +        } +        else +        { +            LL_INFOS("capListener") << "fallback invoked for capability '" << cap +                                    << "' request to '" << getCapAPI().getName() +                                    << "' on region\n" << mProvider.getDescription() +                                    << LL_ENDL; +            mapper->buildMessage(mMessageSystem, mAgentID, mSessionID, cap, payload); +            mMessageSystem->sendReliable(mProvider.getHost()); +        } +    } +    return false; +} + +LLCapabilityListener::CapabilityMapper::CapabilityMapper(const std::string& cap, const std::string& reply): +    mCapName(cap), +    mReplyName(reply) +{ +    LLCapabilityListener::CapabilityMappers::instance().registerMapper(this); +} + +LLCapabilityListener::CapabilityMapper::~CapabilityMapper() +{ +    LLCapabilityListener::CapabilityMappers::instance().unregisterMapper(this); +} + +LLSD LLCapabilityListener::CapabilityMapper::readResponse(LLMessageSystem* messageSystem) const +{ +    return LLSD(); +} + +LLCapabilityListener::CapabilityMappers::CapabilityMappers() {} + +void LLCapabilityListener::CapabilityMappers::registerMapper(const LLCapabilityListener::CapabilityMapper* mapper) +{ +    // Try to insert a new map entry by which we can look up the passed mapper +    // instance. +    std::pair<CapabilityMap::iterator, bool> inserted = +        mMap.insert(CapabilityMap::value_type(mapper->getCapName(), mapper)); +    // If we already have a mapper for that name, insert() merely located the +    // existing iterator and returned false. It is a coding error to try to +    // register more than one mapper for the same capability name. +    if (! inserted.second) +    { +        throw DupCapMapper(std::string("Duplicate capability name ") + mapper->getCapName()); +    } +} + +void LLCapabilityListener::CapabilityMappers::unregisterMapper(const LLCapabilityListener::CapabilityMapper* mapper) +{ +    CapabilityMap::iterator found = mMap.find(mapper->getCapName()); +    if (found != mMap.end()) +    { +        mMap.erase(found); +    } +} + +const LLCapabilityListener::CapabilityMapper* +LLCapabilityListener::CapabilityMappers::find(const std::string& cap) const +{ +    CapabilityMap::const_iterator found = mMap.find(cap); +    if (found != mMap.end()) +    { +        return found->second; +    } +    return NULL; +} diff --git a/indra/newview/llcapabilitylistener.h b/indra/newview/llcapabilitylistener.h new file mode 100644 index 0000000000..061227e04d --- /dev/null +++ b/indra/newview/llcapabilitylistener.h @@ -0,0 +1,113 @@ +/** + * @file   llcapabilitylistener.h + * @author Nat Goodspeed + * @date   2009-01-07 + * @brief  Provide an event-based API for capability requests + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLCAPABILITYLISTENER_H) +#define LL_LLCAPABILITYLISTENER_H + +#include "llevents.h"               // LLEventPump +#include "llsdmessage.h"            // LLSDMessage::ArgError +#include "llerror.h"                // LOG_CLASS() + +class LLCapabilityProvider; +class LLSD; + +class LLCapabilityListener +{ +    LOG_CLASS(LLCapabilityListener); +public: +    LLCapabilityListener(const std::string& name, LLMessageSystem* messageSystem, +                         const LLCapabilityProvider& provider, +                         const LLUUID& agentID, const LLUUID& sessionID); + +    /// Capability-request exception +    typedef LLSDMessage::ArgError ArgError; +    /// Get LLEventPump on which we listen for capability requests +    /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) +    LLEventPump& getCapAPI() { return mEventPump; } + +    /** +     * Base class for mapping an as-yet-undeployed capability name to a (pair +     * of) LLMessageSystem message(s). To map a capability name to such +     * messages, derive a subclass of CapabilityMapper and declare a static +     * instance in a translation unit known to be loaded. The mapping is not +     * region-specific. If an LLViewerRegion's capListener() receives a +     * request for a supported capability, it will use the capability's URL. +     * If not, it will look for an applicable CapabilityMapper subclass +     * instance. +     */ +    class CapabilityMapper +    { +    public: +        /** +         * Base-class constructor. Typically your subclass constructor will +         * pass these parameters as literals. +         * @param cap the capability name handled by this (subclass) instance +         * @param reply the name of the response LLMessageSystem message. Omit +         * if the LLMessageSystem message you intend to send doesn't prompt a +         * reply message, or if you already handle that message in some other +         * way. +         */ +        CapabilityMapper(const std::string& cap, const std::string& reply = ""); +        virtual ~CapabilityMapper(); +        /// query the capability name +        std::string getCapName() const { return mCapName; } +        /// query the reply message name +        std::string getReplyName() const { return mReplyName; } +        /** +         * Override this method to build the LLMessageSystem message we should +         * send instead of the requested capability message. DO NOT send that +         * message: that will be handled by the caller. +         */ +        virtual void buildMessage(LLMessageSystem* messageSystem, +                                  const LLUUID& agentID, +                                  const LLUUID& sessionID, +                                  const std::string& capabilityName, +                                  const LLSD& payload) const = 0; +        /** +         * Override this method if you pass a non-empty @a reply +         * LLMessageSystem message name to the constructor: that is, if you +         * expect to receive an LLMessageSystem message in response to the +         * message you constructed in buildMessage(). If you don't pass a @a +         * reply message name, you need not override this method as it won't +         * be called. +         * +         * Using LLMessageSystem message-reading operations, your +         * readResponse() override should construct and return an LLSD object +         * of the form you expect to receive from the real implementation of +         * the capability you intend to invoke, when it finally goes live. +         */ +        virtual LLSD readResponse(LLMessageSystem* messageSystem) const; + +    private: +        const std::string mCapName; +        const std::string mReplyName; +    }; + +private: +    /// Bind the LLCapabilityProvider passed to our ctor +    const LLCapabilityProvider& mProvider; + +    /// Post an event to this LLEventPump to invoke a capability message on +    /// the bound LLCapabilityProvider's server +    /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) +    LLEventStream mEventPump; + +    LLMessageSystem* mMessageSystem; +    LLUUID mAgentID, mSessionID; + +    /// listener to process capability requests +    bool capListener(const LLSD&); + +    /// helper class for capListener() +    class CapabilityMappers; +}; + +#endif /* ! defined(LL_LLCAPABILITYLISTENER_H) */ diff --git a/indra/newview/llcapabilityprovider.h b/indra/newview/llcapabilityprovider.h new file mode 100644 index 0000000000..0ddb2b6cb9 --- /dev/null +++ b/indra/newview/llcapabilityprovider.h @@ -0,0 +1,39 @@ +/** + * @file   llcapabilityprovider.h + * @author Nat Goodspeed + * @date   2009-01-07 + * @brief  Interface by which to reference (e.g.) LLViewerRegion to obtain a + *         capability. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLCAPABILITYPROVIDER_H) +#define LL_LLCAPABILITYPROVIDER_H + +#include "llhost.h" +#include <string> + +/// Interface for obtaining a capability URL, given a capability name +class LLCapabilityProvider +{ +public: +    virtual ~LLCapabilityProvider() {} +    /** +     * Get a capability URL, given a capability name. Returns empty string if +     * no such capability is defined on this provider. +     */ +    virtual std::string getCapability(const std::string& name) const = 0; +    /** +     * Get host to which to send that capability request. +     */ +    virtual LLHost getHost() const = 0; +    /** +     * Describe this LLCapabilityProvider for logging etc. +     */ +    virtual std::string getDescription() const = 0; +}; + +#endif /* ! defined(LL_LLCAPABILITYPROVIDER_H) */ diff --git a/indra/newview/llfloatergroups.cpp b/indra/newview/llfloatergroups.cpp index 8cd7297447..9e0b0d7230 100644 --- a/indra/newview/llfloatergroups.cpp +++ b/indra/newview/llfloatergroups.cpp @@ -57,6 +57,8 @@  #include "llviewerwindow.h"  #include "llimview.h" +using namespace LLOldEvents; +  // static  std::map<const LLUUID, LLFloaterGroupPicker*> LLFloaterGroupPicker::sInstances; diff --git a/indra/newview/llfloatergroups.h b/indra/newview/llfloatergroups.h index da1c4e23dd..3eb3e5af52 100644 --- a/indra/newview/llfloatergroups.h +++ b/indra/newview/llfloatergroups.h @@ -84,14 +84,14 @@ protected:  	static instance_map_t sInstances;  }; -class LLPanelGroups : public LLPanel, public LLSimpleListener +class LLPanelGroups : public LLPanel, public LLOldEvents::LLSimpleListener  {  public:  	LLPanelGroups();  	virtual ~LLPanelGroups();  	//LLEventListener -	/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); +	/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);  	// clear the group list, and get a fresh set of info.  	void reset(); diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index e4459e38bd..5d09d8748f 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -87,6 +87,8 @@  #include "llselectmgr.h"  #include "llfloateropenobject.h" +using namespace LLOldEvents; +  // Helpers  // bug in busy count inc/dec right now, logic is complex... do we really need it?  void inc_busy_count() diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp index 2eee54df40..23d32fee81 100644 --- a/indra/newview/llnetmap.cpp +++ b/indra/newview/llnetmap.cpp @@ -68,6 +68,8 @@  #include "llglheaders.h" +using namespace LLOldEvents; +  const F32 MAP_SCALE_MIN = 64;  const F32 MAP_SCALE_MID = 172;  const F32 MAP_SCALE_MAX = 512; diff --git a/indra/newview/llnetmap.h b/indra/newview/llnetmap.h index a5bf9f0fb5..9fbd5097fd 100644 --- a/indra/newview/llnetmap.h +++ b/indra/newview/llnetmap.h @@ -109,31 +109,31 @@ private:  	class LLScaleMap : public LLMemberListener<LLNetMap>  	{  	public: -		/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); +		/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);  	};  	class LLStopTracking : public LLMemberListener<LLNetMap>  	{  	public: -		/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); +		/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);  	};  	class LLEnableTracking : public LLMemberListener<LLNetMap>  	{  	public: -		/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); +		/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);  	};  	class LLShowAgentProfile : public LLMemberListener<LLNetMap>  	{  	public: -		/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); +		/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);  	};  	class LLEnableProfile : public LLMemberListener<LLNetMap>  	{  	public: -		/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); +		/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);  	};  }; diff --git a/indra/newview/llviewercontrol.h b/indra/newview/llviewercontrol.h index d0dc80cb9a..c4003111d3 100644 --- a/indra/newview/llviewercontrol.h +++ b/indra/newview/llviewercontrol.h @@ -84,7 +84,7 @@ class LLCachedControl  {      T mCachedValue;      LLPointer<LLControlVariable> mControl; -    boost::signals::connection mConnection; +    boost::signals2::scoped_connection mConnection;  public:  	LLCachedControl(const std::string& name,  @@ -109,17 +109,13 @@ public:  		}  		// Add a listener to the controls signal... -		mControl->getSignal()->connect( +		mConnection = mControl->getSignal()->connect(  			boost::bind(&LLCachedControl<T>::handleValueChange, this, _1)  			);  	}  	~LLCachedControl()  	{ -		if(mConnection.connected()) -		{ -			mConnection.disconnect(); -		}  	}  	LLCachedControl& operator =(const T& newvalue) diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 7f1f0fe6d0..f47d0777b0 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -764,56 +764,39 @@ void move_inventory_item(  	gAgent.sendReliableMessage();  } -class LLCopyInventoryFromNotecardResponder : public LLHTTPClient::Responder -{ -public: -	//If we get back a normal response, handle it here -	virtual void result(const LLSD& content) -	{ -		// What do we do here? -		llinfos << "CopyInventoryFromNotecard request successful." << llendl; -	} - -	//If we get back an error (not found, etc...), handle it here -	virtual void error(U32 status, const std::string& reason) -	{ -		llinfos << "LLCopyInventoryFromNotecardResponder::error " -			<< status << ": " << reason << llendl; -	} -}; -  void copy_inventory_from_notecard(const LLUUID& object_id, const LLUUID& notecard_inv_id, const LLInventoryItem *src, U32 callback_id)  { -	LLSD body;  	LLViewerRegion* viewer_region = NULL; -	if(object_id.notNull()) -	{ -		LLViewerObject* vo = gObjectList.findObject(object_id); -		if(vo) -		{ -			viewer_region = vo->getRegion(); -		} +    LLViewerObject* vo = NULL; +	if (object_id.notNull() && (vo = gObjectList.findObject(object_id)) != NULL) +    { +        viewer_region = vo->getRegion();  	}  	// Fallback to the agents region if for some reason the   	// object isn't found in the viewer. -	if(!viewer_region) +	if (! viewer_region)  	{  		viewer_region = gAgent.getRegion();  	} -	if(viewer_region) +	if (! viewer_region)  	{ -		std::string url = viewer_region->getCapability("CopyInventoryFromNotecard"); -		if (!url.empty()) -		{ -			body["notecard-id"] = notecard_inv_id; -			body["object-id"] = object_id; -			body["item-id"] = src->getUUID(); -			body["folder-id"] = gInventory.findCategoryUUIDForType(src->getType()); -			body["callback-id"] = (LLSD::Integer)callback_id; - -			LLHTTPClient::post(url, body, new LLCopyInventoryFromNotecardResponder()); -		} -	} +        LL_WARNS("copy_inventory_from_notecard") << "Can't find region from object_id " +                                                 << object_id << " or gAgent" +                                                 << LL_ENDL; +        return; +    } + +    LLSD request, body; +    body["notecard-id"] = notecard_inv_id; +    body["object-id"] = object_id; +    body["item-id"] = src->getUUID(); +    body["folder-id"] = gInventory.findCategoryUUIDForType(src->getType()); +    body["callback-id"] = (LLSD::Integer)callback_id; + +    request["message"] = "CopyInventoryFromNotecard"; +    request["payload"] = body; + +    viewer_region->getCapAPI().post(request);  } diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 70fc78d277..82f8359732 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -46,7 +46,7 @@  #include "lluuid.h"  #include <boost/bind.hpp>	// for SkinFolder listener -#include <boost/signal.hpp> +#include <boost/signals2.hpp>  // Implementation functions not exported into header file diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index fe91da05fa..ac36cf7bb6 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -1,4 +1,3 @@ -  /**    * @file llviewermenu.cpp   * @brief Builds menus out of items. @@ -217,6 +216,8 @@  #include "lltexlayer.h"  using namespace LLVOAvatarDefines; +using namespace LLOldEvents; +  void init_client_menu(LLMenuGL* menu);  void init_server_menu(LLMenuGL* menu); diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 5240fd3211..d1f50212db 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -71,6 +71,8 @@  // system libraries  #include <boost/tokenizer.hpp> +using namespace LLOldEvents; +  typedef LLMemberListener<LLView> view_listener_t; diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 12d9c1a992..d4ee7a7e50 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -65,6 +65,11 @@  #include "llvoclouds.h"  #include "llworld.h"  #include "llspatialpartition.h" +#include "stringize.h" + +#ifdef LL_WINDOWS +	#pragma warning(disable:4355) +#endif  // Viewer object cache version, change if object update  // format changes. JC @@ -172,7 +177,18 @@ LLViewerRegion::LLViewerRegion(const U64 &handle,  	mCacheEntriesCount(0),  	mCacheID(),  	mEventPoll(NULL), -	mReleaseNotesRequested(FALSE) +	mReleaseNotesRequested(FALSE), +    // I'd prefer to set the LLCapabilityListener name to match the region +    // name -- it's disappointing that's not available at construction time. +    // We could instead store an LLCapabilityListener*, making +    // setRegionNameAndZone() replace the instance. Would that pose +    // consistency problems? Can we even request a capability before calling +    // setRegionNameAndZone()? +    // For testability -- the new Michael Feathers paradigm -- +    // LLCapabilityListener binds all the globals it expects to need at +    // construction time. +    mCapabilityListener(host.getString(), gMessageSystem, *this, +                        gAgent.getID(), gAgent.getSessionID())  {  	mWidth = region_width_meters;  	mOriginGlobal = from_region_handle(handle);  @@ -223,7 +239,6 @@ LLViewerRegion::LLViewerRegion(const U64 &handle,  	mObjectPartition.push_back(new LLBridgePartition());	//PARTITION_BRIDGE  	mObjectPartition.push_back(new LLHUDParticlePartition());//PARTITION_HUD_PARTICLE  	mObjectPartition.push_back(NULL);						//PARTITION_NONE -	  } @@ -774,6 +789,15 @@ std::ostream& operator<<(std::ostream &s, const LLViewerRegion ®ion)  	s << "{ ";  	s << region.mHost;  	s << " mOriginGlobal = " << region.getOriginGlobal()<< "\n"; +    std::string name(region.getName()), zone(region.getZoning()); +    if (! name.empty()) +    { +        s << " mName         = " << name << '\n'; +    } +    if (! zone.empty()) +    { +        s << " mZoning       = " << zone << '\n'; +    }  	s << "}";  	return s;  } @@ -1518,3 +1542,8 @@ void LLViewerRegion::showReleaseNotes()  	LLWeb::loadURL(url);  	mReleaseNotesRequested = FALSE;  } + +std::string LLViewerRegion::getDescription() const +{ +    return stringize(*this); +} diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index b0a98a99b8..4d345c811a 100644 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -49,6 +49,8 @@  #include "lldatapacker.h"  #include "llvocache.h"  #include "llweb.h" +#include "llcapabilityprovider.h" +#include "llcapabilitylistener.h"  // Surface id's  #define LAND  1 @@ -66,8 +68,9 @@ class LLSurface;  class LLVOCache;  class LLVOCacheEntry;  class LLSpatialPartition; +class LLEventPump; -class LLViewerRegion  +class LLViewerRegion: public LLCapabilityProvider // implements this interface  {  public:  	//MUST MATCH THE ORDER OF DECLARATION IN CONSTRUCTOR @@ -226,11 +229,19 @@ public:  	// Get/set named capability URLs for this region.  	void setSeedCapability(const std::string& url);  	void setCapability(const std::string& name, const std::string& url); -	std::string getCapability(const std::string& name) const; +	// implements LLCapabilityProvider +    virtual std::string getCapability(const std::string& name) const;  	static bool isSpecialCapabilityName(const std::string &name);  	void logActiveCapabilities() const; -	const LLHost	&getHost() const			{ return mHost; } +    /// Capability-request exception +    typedef LLCapabilityListener::ArgError ArgError; +    /// Get LLEventPump on which we listen for capability requests +    /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) +    LLEventPump& getCapAPI() { return mCapabilityListener.getCapAPI(); } + +    /// implements LLCapabilityProvider +	virtual LLHost	getHost() const				{ return mHost; }  	const U64 		&getHandle() const 			{ return mHandle; }  	LLSurface		&getLand() const			{ return *mLandp; } @@ -274,6 +285,8 @@ public:  	void calculateCameraDistance();  	friend std::ostream& operator<<(std::ostream &s, const LLViewerRegion ®ion); +    /// implements LLCapabilityProvider +    virtual std::string getDescription() const;  	// used by LCD to get details for debug screen  	U32 getNetDetailsForLCD(); @@ -394,6 +407,11 @@ private:  	LLEventPoll* mEventPoll; +    /// Post an event to this LLCapabilityListener to invoke a capability message on +    /// this LLViewerRegion's server +    /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) +    LLCapabilityListener mCapabilityListener; +  private:  	bool	mAlive;					// can become false if circuit disconnects @@ -461,5 +479,3 @@ inline BOOL LLViewerRegion::getReleaseNotesRequested() const  }  #endif - - diff --git a/indra/newview/llviewerthrottle.cpp b/indra/newview/llviewerthrottle.cpp index bf779c427a..73065c5c00 100644 --- a/indra/newview/llviewerthrottle.cpp +++ b/indra/newview/llviewerthrottle.cpp @@ -40,6 +40,8 @@  #include "llviewerstats.h"  #include "lldatapacker.h" +using namespace LLOldEvents; +  // consts  // The viewer is allowed to set the under-the-hood bandwidth to 50% diff --git a/indra/newview/tests/llcapabilitylistener_test.cpp b/indra/newview/tests/llcapabilitylistener_test.cpp new file mode 100644 index 0000000000..3c5f6fad2d --- /dev/null +++ b/indra/newview/tests/llcapabilitylistener_test.cpp @@ -0,0 +1,274 @@ +/** + * @file   llcapabilitylistener_test.cpp + * @author Nat Goodspeed + * @date   2008-12-31 + * @brief  Test for llcapabilitylistener.cpp. + *  + * $LicenseInfo:firstyear=2008&license=internal$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "../llviewerprecompiledheaders.h" +// Own header +#include "../llcapabilitylistener.h" +// STL headers +#include <stdexcept> +#include <map> +#include <vector> +// std headers +// external library headers +#include "boost/bind.hpp" +// other Linden headers +#include "../test/lltut.h" +#include "../llcapabilityprovider.h" +#include "lluuid.h" +#include "llerrorcontrol.h" +#include "tests/networkio.h" +#include "tests/commtest.h" +#include "stringize.h" + +#if defined(LL_WINDOWS) +#pragma warning(disable: 4355)      // using 'this' in base-class ctor initializer expr +#endif + +/***************************************************************************** +*   TestCapabilityProvider +*****************************************************************************/ +struct TestCapabilityProvider: public LLCapabilityProvider +{ +    TestCapabilityProvider(const LLHost& host): +        mHost(host) +    {} + +    std::string getCapability(const std::string& cap) const +    { +        CapMap::const_iterator found = mCaps.find(cap); +        if (found != mCaps.end()) +            return found->second; +        // normal LLViewerRegion lookup failure mode +        return ""; +    } +    void setCapability(const std::string& cap, const std::string& url) +    { +        mCaps[cap] = url; +    } +    LLHost getHost() const { return mHost; } +    std::string getDescription() const { return "TestCapabilityProvider"; } + +    LLHost mHost; +    typedef std::map<std::string, std::string> CapMap; +    CapMap mCaps; +}; + +/***************************************************************************** +*   Dummy LLMessageSystem methods +*****************************************************************************/ +/*==========================================================================*| +// This doesn't work because we're already linking in llmessage.a, and we get +// duplicate-symbol errors from the linker. Perhaps if I wanted to go through +// the exercise of providing dummy versions of every single symbol defined in +// message.o -- maybe some day. +typedef std::vector< std::pair<std::string, std::string> > StringPairVector; +StringPairVector call_history; + +S32 LLMessageSystem::sendReliable(const LLHost& host) +{ +    call_history.push_back(StringPairVector::value_type("sendReliable", stringize(host))); +    return 0; +} +|*==========================================================================*/ + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct llcapears_data: public commtest_data +    { +        TestCapabilityProvider provider; +        LLCapabilityListener regionListener; +        LLEventPump& regionPump; + +        llcapears_data(): +            provider(host), +            regionListener("testCapabilityListener", NULL, provider, LLUUID(), LLUUID()), +            regionPump(regionListener.getCapAPI()) +        { +            provider.setCapability("good", server + "capability-test"); +            provider.setCapability("fail", server + "fail"); +        } +    }; +    typedef test_group<llcapears_data> llcapears_group; +    typedef llcapears_group::object llcapears_object; +    llcapears_group llsdmgr("llcapabilitylistener"); + +    struct CaptureError: public LLError::OverrideFatalFunction +    { +        CaptureError(): +            LLError::OverrideFatalFunction(boost::bind(&CaptureError::operator(), this, _1)) +        { +            LLError::setPrintLocation(false); +        } + +        struct FatalException: public std::runtime_error +        { +            FatalException(const std::string& what): std::runtime_error(what) {} +        }; + +        void operator()(const std::string& message) +        { +            error = message; +            throw FatalException(message); +        } + +        std::string error; +    }; + +    template<> template<> +    void llcapears_object::test<1>() +    { +        LLSD request, body; +        body["data"] = "yes"; +        request["payload"] = body; +        request["reply"] = replyPump.getName(); +        request["error"] = errorPump.getName(); +        std::string threw; +        try +        { +            CaptureError capture; +            regionPump.post(request); +        } +        catch (const CaptureError::FatalException& e) +        { +            threw = e.what(); +        } +        ensure_contains("missing capability name", threw, "without 'message' key"); +    } + +    template<> template<> +    void llcapears_object::test<2>() +    { +        LLSD request, body; +        body["data"] = "yes"; +        request["message"] = "good"; +        request["payload"] = body; +        request["reply"] = replyPump.getName(); +        request["error"] = errorPump.getName(); +        regionPump.post(request); +        ensure("got response", netio.pump()); +        ensure("success response", success); +        ensure_equals(result.asString(), "success"); + +        body["status"] = 499; +        body["reason"] = "custom error message"; +        request["message"] = "fail"; +        request["payload"] = body; +        regionPump.post(request); +        ensure("got response", netio.pump()); +        ensure("failure response", ! success); +        ensure_equals(result["status"].asInteger(), body["status"].asInteger()); +        ensure_equals(result["reason"].asString(),  body["reason"].asString()); +    } + +    template<> template<> +    void llcapears_object::test<3>() +    { +        LLSD request, body; +        body["data"] = "yes"; +        request["message"] = "unknown"; +        request["payload"] = body; +        request["reply"] = replyPump.getName(); +        request["error"] = errorPump.getName(); +        std::string threw; +        try +        { +            CaptureError capture; +            regionPump.post(request); +        } +        catch (const CaptureError::FatalException& e) +        { +            threw = e.what(); +        } +        ensure_contains("bad capability name", threw, "unsupported capability"); +    } + +    struct TestMapper: public LLCapabilityListener::CapabilityMapper +    { +        // Instantiator gets to specify whether mapper expects a reply. +        // I'd really like to be able to test CapabilityMapper::buildMessage() +        // functionality, too, but -- even though LLCapabilityListener accepts +        // the LLMessageSystem* that it passes to CapabilityMapper -- +        // LLMessageSystem::sendReliable(const LLHost&) isn't virtual, so it's +        // not helpful to pass a subclass instance. I suspect that making any +        // LLMessageSystem methods virtual would provoke howls of outrage, +        // given how heavily it's used. Nor can I just provide a local +        // definition of LLMessageSystem::sendReliable(const LLHost&) because +        // we're already linking in the rest of message.o via llmessage.a, and +        // that produces duplicate-symbol link errors. +        TestMapper(const std::string& replyMessage = std::string()): +            LLCapabilityListener::CapabilityMapper("test", replyMessage) +        {} +        virtual void buildMessage(LLMessageSystem* msg, +                                  const LLUUID& agentID, +                                  const LLUUID& sessionID, +                                  const std::string& capabilityName, +                                  const LLSD& payload) const +        { +            msg->newMessageFast(_PREHASH_SetStartLocationRequest); +            msg->nextBlockFast( _PREHASH_AgentData); +            msg->addUUIDFast(_PREHASH_AgentID, agentID); +            msg->addUUIDFast(_PREHASH_SessionID, sessionID); +            msg->nextBlockFast( _PREHASH_StartLocationData); +            // corrected by sim +            msg->addStringFast(_PREHASH_SimName, ""); +            msg->addU32Fast(_PREHASH_LocationID, payload["HomeLocation"]["LocationId"].asInteger()); +/*==========================================================================*| +            msg->addVector3Fast(_PREHASH_LocationPos, +                                ll_vector3_from_sdmap(payload["HomeLocation"]["LocationPos"])); +            msg->addVector3Fast(_PREHASH_LocationLookAt, +                                ll_vector3_from_sdmap(payload["HomeLocation"]["LocationLookAt"])); +|*==========================================================================*/ +        } +    }; + +    template<> template<> +    void llcapears_object::test<4>() +    { +        TestMapper testMapper("WantReply"); +        LLSD request, body; +        body["data"] = "yes"; +        request["message"] = "test"; +        request["payload"] = body; +        request["reply"] = replyPump.getName(); +        request["error"] = errorPump.getName(); +        std::string threw; +        try +        { +            CaptureError capture; +            regionPump.post(request); +        } +        catch (const CaptureError::FatalException& e) +        { +            threw = e.what(); +        } +        ensure_contains("capability mapper wants reply", threw, "unimplemented support for reply message"); +    } + +    template<> template<> +    void llcapears_object::test<5>() +    { +        TestMapper testMapper; +        std::string threw; +        try +        { +            TestMapper testMapper2; +        } +        catch (const std::runtime_error& e) +        { +            threw = e.what(); +        } +        ensure_contains("no dup cap mapper", threw, "DupCapMapper"); +    } +} diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index c8682c8ea7..49a0a8f361 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -13,6 +13,7 @@ include(LLXML)  include(LScript)  include(Linking)  include(Tut) +include(Boost)  include_directories(      ${LLCOMMON_INCLUDE_DIRS} @@ -34,7 +35,9 @@ set(test_SOURCE_FILES      llblowfish_tut.cpp      llbuffer_tut.cpp      lldate_tut.cpp +    lldependencies_tut.cpp      llerror_tut.cpp +    llevents_tut.cpp      llhost_tut.cpp      llhttpdate_tut.cpp      llhttpclient_tut.cpp @@ -71,6 +74,7 @@ set(test_SOURCE_FILES      math.cpp      message_tut.cpp      reflection_tut.cpp +    stringize_tut.cpp      test.cpp      v2math_tut.cpp      v3color_tut.cpp @@ -104,7 +108,7 @@ endif (NOT DARWIN)  set_source_files_properties(${test_HEADER_FILES}                              PROPERTIES HEADER_FILE_ONLY TRUE) -list(APPEND test_SOURC_FILES ${test_HEADER_FILES}) +list(APPEND test_SOURCE_FILES ${test_HEADER_FILES})  add_executable(test ${test_SOURCE_FILES}) diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp new file mode 100644 index 0000000000..e401f89b22 --- /dev/null +++ b/indra/test/llevents_tut.cpp @@ -0,0 +1,793 @@ +/** + * @file   llevents_tut.cpp + * @author Nat Goodspeed + * @date   2008-09-12 + * @brief  Test of llevents.h + *  + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +// Precompiled header +#include "linden_common.h" +// associated header +// UGLY HACK! We want to verify state internal to the classes without +// providing public accessors. +#define testable public +#include "llevents.h" +#undef testable +// STL headers +// std headers +#include <iostream> +#include <typeinfo> +// external library headers +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/assign/list_of.hpp> +// other Linden headers +#include "lltut.h" +#include "stringize.h" + +using boost::assign::list_of; + +/***************************************************************************** +*   test listener class +*****************************************************************************/ +class Listener; +std::ostream& operator<<(std::ostream&, const Listener&); + +class Listener +{ +public: +    Listener(const std::string& name): +        mName(name) +    { +//      std::cout << *this << ": ctor\n"; +    } +    Listener(const Listener& that): +        mName(that.mName), +        mLastEvent(that.mLastEvent) +    { +//      std::cout << *this << ": copy\n"; +    } +    virtual ~Listener() +    { +//      std::cout << *this << ": dtor\n"; +    } +    std::string getName() const { return mName; } +    bool call(const LLSD& event) +    { +//      std::cout << *this << "::call(" << event << ")\n"; +        mLastEvent = event; +        return false; +    } +    bool callstop(const LLSD& event) +    { +//      std::cout << *this << "::callstop(" << event << ")\n"; +        mLastEvent = event; +        return true; +    } +    LLSD getLastEvent() const +    { +//      std::cout << *this << "::getLastEvent() -> " << mLastEvent << "\n"; +        return mLastEvent; +    } +    void reset(const LLSD& to = LLSD()) +    { +//      std::cout << *this << "::reset(" << to << ")\n"; +        mLastEvent = to; +    } + +private: +    std::string mName; +    LLSD mLastEvent; +}; + +std::ostream& operator<<(std::ostream& out, const Listener& listener) +{ +    out << "Listener(" << listener.getName() /* << "@" << &listener */ << ')'; +    return out; +} + +struct Collect +{ +    bool add(const std::string& bound, const LLSD& event) +    { +        result.push_back(bound); +        return false; +    } +    void clear() { result.clear(); } +    typedef std::vector<std::string> StringList; +    StringList result; +}; + +std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings) +{ +    out << '('; +    Collect::StringList::const_iterator begin(strings.begin()), end(strings.end()); +    if (begin != end) +    { +        out << '"' << *begin << '"'; +        while (++begin != end) +        { +            out << ", \"" << *begin << '"'; +        } +    } +    out << ')'; +    return out; +} + +template<typename T> +T make(const T& value) { return value; } + +/***************************************************************************** +*   tut test group +*****************************************************************************/ +namespace tut +{ +    struct events_data +    { +        events_data(): +            pumps(LLEventPumps::instance()), +            listener0("first"), +            listener1("second") +        {} +        LLEventPumps& pumps; +        Listener listener0; +        Listener listener1; + +        void check_listener(const std::string& desc, const Listener& listener, LLSD::Integer got) +        { +            ensure_equals(STRINGIZE(listener << ' ' << desc), +                          listener.getLastEvent().asInteger(), got); +        } +    }; +    typedef test_group<events_data> events_group; +    typedef events_group::object events_object; +    tut::events_group evgr("events"); + +    template<> template<> +    void events_object::test<1>() +    { +        set_test_name("basic operations"); +        // Now there's a static constructor in llevents.cpp that registers on +        // the "mainloop" pump to call LLEventPumps::flush(). +        // Actually -- having to modify this to track the statically- +        // constructed pumps in other TUT modules in this giant monolithic test +        // executable isn't such a hot idea. +//      ensure_equals("initial pump", pumps.mPumpMap.size(), 1); +        size_t initial_pumps(pumps.mPumpMap.size()); +        LLEventPump& per_frame(pumps.obtain("per-frame")); +        ensure_equals("first explicit pump", pumps.mPumpMap.size(), initial_pumps+1); +        // Verify that per_frame was instantiated as an LLEventStream. +        ensure("LLEventStream leaf class", dynamic_cast<LLEventStream*>(&per_frame)); +        ensure("enabled", per_frame.enabled()); +        // Trivial test, but posting an event to an EventPump with no +        // listeners should not blow up. The test is relevant because defining +        // a boost::signal with a non-void return signature, using the default +        // combiner, blows up if there are no listeners. This is because the +        // default combiner is defined to return the value returned by the +        // last listener, which is meaningless if there were no listeners. +        per_frame.post(0); +        // NOTE: boost::bind() saves its arguments by VALUE! If you pass an +        // object instance rather than a pointer, you'll end up binding to an +        // internal copy of that instance! Use boost::ref() to capture a +        // reference instead. +        LLBoundListener connection = per_frame.listen(listener0.getName(), +                                                      boost::bind(&Listener::call, +                                                                  boost::ref(listener0), +                                                                  _1)); +        ensure("connected", connection.connected()); +        ensure("not blocked", ! connection.blocked()); +        per_frame.post(1); +        check_listener("received", listener0, 1); +        { // block the connection +            LLEventPump::Blocker block(connection); +            ensure("blocked", connection.blocked()); +            per_frame.post(2); +            check_listener("not updated", listener0, 1); +        } // unblock +        ensure("unblocked", ! connection.blocked()); +        per_frame.post(3); +        check_listener("unblocked", listener0, 3); +        LLBoundListener sameConnection = per_frame.getListener(listener0.getName()); +        ensure("still connected", sameConnection.connected()); +        ensure("still not blocked", ! sameConnection.blocked()); +        { // block it again +            LLEventPump::Blocker block(sameConnection); +            ensure("re-blocked", sameConnection.blocked()); +            per_frame.post(4); +            check_listener("re-blocked", listener0, 3); +        } // unblock +        bool threw = false; +        try +        { +            per_frame.listen(listener0.getName(), // note bug, dup name +                             boost::bind(&Listener::call, boost::ref(listener1), _1)); +        } +        catch (const LLEventPump::DupListenerName& e) +        { +            threw = true; +            ensure_equals(e.what(), +                          std::string("DupListenerName: " +                                      "Attempt to register duplicate listener name '") + +                          listener0.getName() + +                          "' on " + typeid(per_frame).name() + " '" + per_frame.getName() + "'"); +        } +        ensure("threw DupListenerName", threw); +        // do it right this time +        per_frame.listen(listener1.getName(), +                         boost::bind(&Listener::call, boost::ref(listener1), _1)); +        per_frame.post(5); +        check_listener("got", listener0, 5); +        check_listener("got", listener1, 5); +        per_frame.enable(false); +        per_frame.post(6); +        check_listener("didn't get", listener0, 5); +        check_listener("didn't get", listener1, 5); +        per_frame.enable(); +        per_frame.post(7); +        check_listener("got", listener0, 7); +        check_listener("got", listener1, 7); +        per_frame.stopListening(listener0.getName()); +        ensure("disconnected 0", ! connection.connected()); +        ensure("disconnected 1", ! sameConnection.connected()); +        per_frame.post(8); +        check_listener("disconnected", listener0, 7); +        check_listener("still connected", listener1, 8); +        per_frame.stopListening(listener1.getName()); +        per_frame.post(9); +        check_listener("disconnected", listener1, 8); +    } + +    template<> template<> +    void events_object::test<2>() +    { +        set_test_name("callstop() returning true"); +        LLEventPump& per_frame(pumps.obtain("per-frame")); +        listener0.reset(0); +        listener1.reset(0); +        LLBoundListener bound0 = per_frame.listen(listener0.getName(), +                                                  boost::bind(&Listener::callstop, +                                                              boost::ref(listener0), +                                                              _1)); +        LLBoundListener bound1 = per_frame.listen(listener1.getName(), +                                                  boost::bind(&Listener::call, +                                                              boost::ref(listener1), +                                                              _1), +                                                  // after listener0 +                                                  make<LLEventPump::NameList>(list_of(listener0.getName()))); +        ensure("enabled", per_frame.enabled()); +        ensure("connected 0", bound0.connected()); +        ensure("unblocked 0", ! bound0.blocked()); +        ensure("connected 1", bound1.connected()); +        ensure("unblocked 1", ! bound1.blocked()); +        per_frame.post(1); +        check_listener("got", listener0, 1); +        // Because listener0.callstop() returns true, control never reaches listener1.call(). +        check_listener("got", listener1, 0); +    } + +    bool chainEvents(Listener& someListener, const LLSD& event) +    { +        // Make this call so we can watch for side effects for test purposes. +        someListener.call(event); +        // This function represents a recursive event chain -- or some other +        // scenario in which an event handler raises additional events. +        int value = event.asInteger(); +        if (value) +        { +            LLEventPumps::instance().obtain("login").post(value - 1); +        } +        return false; +    } + +    template<> template<> +    void events_object::test<3>() +    { +        set_test_name("LLEventQueue delayed action"); +        // This access is NOT legal usage: we can do it only because we're +        // hacking private for test purposes. Normally we'd either compile in +        // a particular name, or (later) edit a config file. +        pumps.mQueueNames.insert("login"); +        LLEventPump& login(pumps.obtain("login")); +        // The "mainloop" pump is special: posting on that implicitly calls +        // LLEventPumps::flush(), which in turn should flush our "login" +        // LLEventQueue. +        LLEventPump& mainloop(pumps.obtain("mainloop")); +        ensure("LLEventQueue leaf class", dynamic_cast<LLEventQueue*>(&login)); +        login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1)); +        listener0.reset(0); +        login.post(1); +        check_listener("waiting for queued event", listener0, 0); +        mainloop.post(LLSD()); +        check_listener("got queued event", listener0, 1); +        login.stopListening(listener0.getName()); +        // Verify that when an event handler posts a new event on the same +        // LLEventQueue, it doesn't get processed in the same flush() call -- +        // it waits until the next flush() call. +        listener0.reset(17); +        login.listen("chainEvents", boost::bind(chainEvents, boost::ref(listener0), _1)); +        login.post(1); +        check_listener("chainEvents(1) not yet called", listener0, 17); +        mainloop.post(LLSD()); +        check_listener("chainEvents(1) called", listener0, 1); +        mainloop.post(LLSD()); +        check_listener("chainEvents(0) called", listener0, 0); +        mainloop.post(LLSD()); +        check_listener("chainEvents(-1) not called", listener0, 0); +        login.stopListening("chainEvents"); +    } + +    template<> template<> +    void events_object::test<4>() +    { +        set_test_name("explicitly-instantiated LLEventStream"); +        // Explicitly instantiate an LLEventStream, and verify that it +        // self-registers with LLEventPumps +        size_t registered = pumps.mPumpMap.size(); +        size_t owned = pumps.mOurPumps.size(); +        LLEventPump* localInstance; +        { +            LLEventStream myEventStream("stream"); +            localInstance = &myEventStream; +            LLEventPump& stream(pumps.obtain("stream")); +            ensure("found named LLEventStream instance", &stream == localInstance); +            ensure_equals("registered new instance", pumps.mPumpMap.size(), registered + 1); +            ensure_equals("explicit instance not owned", pumps.mOurPumps.size(), owned); +        } // destroy myEventStream -- should unregister +        ensure_equals("destroyed instance unregistered", pumps.mPumpMap.size(), registered); +        ensure_equals("destroyed instance not owned", pumps.mOurPumps.size(), owned); +        LLEventPump& stream(pumps.obtain("stream")); +        ensure("new LLEventStream instance", &stream != localInstance); +        ensure_equals("obtain()ed instance registered", pumps.mPumpMap.size(), registered + 1); +        ensure_equals("obtain()ed instance owned", pumps.mOurPumps.size(), owned + 1); +    } + +    template<> template<> +    void events_object::test<5>() +    { +        set_test_name("stopListening()"); +        LLEventPump& login(pumps.obtain("login")); +        login.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1)); +        login.stopListening(listener0.getName()); +        // should not throw because stopListening() should have removed name +        login.listen(listener0.getName(), +                     boost::bind(&Listener::callstop, boost::ref(listener0), _1)); +        LLBoundListener wrong = login.getListener("bogus"); +        ensure("bogus connection disconnected", ! wrong.connected()); +        ensure("bogus connection blocked", wrong.blocked()); +    } + +    template<> template<> +    void events_object::test<6>() +    { +        set_test_name("chaining LLEventPump instances"); +        LLEventPump& upstream(pumps.obtain("upstream")); +        // One potentially-useful construct is to chain LLEventPumps together. +        // Among other things, this allows you to turn subsets of listeners on +        // and off in groups. +        LLEventPump& filter0(pumps.obtain("filter0")); +        LLEventPump& filter1(pumps.obtain("filter1")); +        upstream.listen(filter0.getName(), +                        boost::bind(&LLEventPump::post, boost::ref(filter0), _1)); +        upstream.listen(filter1.getName(), +                        boost::bind(&LLEventPump::post, boost::ref(filter1), _1)); +        filter0.listen(listener0.getName(), +                       boost::bind(&Listener::call, boost::ref(listener0), _1)); +        filter1.listen(listener1.getName(), +                       boost::bind(&Listener::call, boost::ref(listener1), _1)); +        listener0.reset(0); +        listener1.reset(0); +        upstream.post(1); +        check_listener("got unfiltered", listener0, 1); +        check_listener("got unfiltered", listener1, 1); +        filter0.enable(false); +        upstream.post(2); +        check_listener("didn't get filtered", listener0, 1); +        check_listener("got filtered", listener1, 2); +    } + +    template<> template<> +    void events_object::test<7>() +    { +        set_test_name("listener dependency order"); +        typedef LLEventPump::NameList NameList; +        typedef Collect::StringList StringList; +        LLEventPump& button(pumps.obtain("button")); +        Collect collector; +        button.listen("Mary", +                      boost::bind(&Collect::add, boost::ref(collector), "Mary", _1), +                      // state that "Mary" must come after "checked" +                      make<NameList>(list_of("checked"))); +        button.listen("checked", +                      boost::bind(&Collect::add, boost::ref(collector), "checked", _1), +                      // "checked" must come after "spot" +                      make<NameList>(list_of("spot"))); +        button.listen("spot", +                      boost::bind(&Collect::add, boost::ref(collector), "spot", _1)); +        button.post(1); +        ensure_equals(collector.result, make<StringList>(list_of("spot")("checked")("Mary"))); +        collector.clear(); +        button.stopListening("Mary"); +        button.listen("Mary", +                      boost::bind(&Collect::add, boost::ref(collector), "Mary", _1), +                      LLEventPump::empty, // no after dependencies +                      // now "Mary" must come before "spot" +                      make<NameList>(list_of("spot"))); +        button.post(2); +        ensure_equals(collector.result, make<StringList>(list_of("Mary")("spot")("checked"))); +        collector.clear(); +        button.stopListening("spot"); +        std::string threw; +        try +        { +            button.listen("spot", +                          boost::bind(&Collect::add, boost::ref(collector), "spot", _1), +                          // after "Mary" and "checked" -- whoops! +                          make<NameList>(list_of("Mary")("checked"))); +        } +        catch (const LLEventPump::Cycle& e) +        { +            threw = e.what(); +//          std::cout << "Caught: " << e.what() << '\n'; +        } +        // Obviously the specific wording of the exception text can +        // change; go ahead and change the test to match. +        // Establish that it contains: +        // - the name and runtime type of the LLEventPump +        ensure_contains("LLEventPump type", threw, typeid(button).name()); +        ensure_contains("LLEventPump name", threw, "'button'"); +        // - the name of the new listener that caused the problem +        ensure_contains("new listener name", threw, "'spot'"); +        // - a synopsis of the problematic dependencies. +        ensure_contains("cyclic dependencies", threw, +                        "\"Mary\" -> before (\"spot\")"); +        ensure_contains("cyclic dependencies", threw, +                        "after (\"spot\") -> \"checked\""); +        ensure_contains("cyclic dependencies", threw, +                        "after (\"Mary\", \"checked\") -> \"spot\""); +        button.listen("yellow", +                      boost::bind(&Collect::add, boost::ref(collector), "yellow", _1), +                      make<NameList>(list_of("checked"))); +        button.listen("shoelaces", +                      boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1), +                      make<NameList>(list_of("checked"))); +        button.post(3); +        ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces"))); +        collector.clear(); +        threw.clear(); +        try +        { +            button.listen("of", +                          boost::bind(&Collect::add, boost::ref(collector), "of", _1), +                          make<NameList>(list_of("shoelaces")), +                          make<NameList>(list_of("yellow"))); +        } +        catch (const LLEventPump::OrderChange& e) +        { +            threw = e.what(); +//          std::cout << "Caught: " << e.what() << '\n'; +        } +        // Same remarks about the specific wording of the exception. Just +        // ensure that it contains enough information to clarify the +        // problem and what must be done to resolve it. +        ensure_contains("LLEventPump type", threw, typeid(button).name()); +        ensure_contains("LLEventPump name", threw, "'button'"); +        ensure_contains("new listener name", threw, "'of'"); +        ensure_contains("prev listener name", threw, "'yellow'"); +        ensure_contains("old order", threw, "was: Mary, checked, yellow, shoelaces"); +        ensure_contains("new order", threw, "now: Mary, checked, shoelaces, of, yellow"); +        button.post(4); +        ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces"))); +    } + +    template<> template<> +    void events_object::test<8>() +    { +        set_test_name("tweaked and untweaked LLEventPump instance names"); +        {   // nested scope +            // Hand-instantiate an LLEventStream... +            LLEventStream bob("bob"); +            bool threw = false; +            try +            { +                // then another with a duplicate name. +                LLEventStream bob2("bob"); +            } +            catch (const LLEventPump::DupPumpName& /*e*/) +            { +                threw = true; +//              std::cout << "Caught: " << e.what() << '\n'; +            } +            ensure("Caught DupPumpName", threw); +        }   // delete first 'bob' +        LLEventStream bob("bob");   // should work, previous one unregistered +        LLEventStream bob1("bob", true); // allowed to tweak name +        ensure_equals("tweaked LLEventStream name", bob1.getName(), "bob1"); +        std::vector< boost::shared_ptr<LLEventStream> > streams; +        for (int i = 2; i <= 10; ++i) +        { +            streams.push_back(boost::shared_ptr<LLEventStream>(new LLEventStream("bob", true))); +        } +        ensure_equals("last tweaked LLEventStream name", streams.back()->getName(), "bob10"); +    } + +    // Define a function that accepts an LLListenerOrPumpName +    void eventSource(const LLListenerOrPumpName& listener) +    { +        // Pretend that some time has elapsed. Call listener immediately. +        listener(17); +    } + +    template<> template<> +    void events_object::test<9>() +    { +        set_test_name("LLListenerOrPumpName"); +        // Passing a boost::bind() expression to LLListenerOrPumpName +        listener0.reset(0); +        eventSource(boost::bind(&Listener::call, boost::ref(listener0), _1)); +        check_listener("got by listener", listener0, 17); +        // Passing a string LLEventPump name to LLListenerOrPumpName +        listener0.reset(0); +        LLEventStream random("random"); +        random.listen(listener0.getName(), boost::bind(&Listener::call, boost::ref(listener0), _1)); +        eventSource("random"); +        check_listener("got by pump name", listener0, 17); +        bool threw = false; +        try +        { +            LLListenerOrPumpName empty; +            empty(17); +        } +        catch (const LLListenerOrPumpName::Empty&) +        { +            threw = true; +        } +        ensure("threw Empty", threw); +    } + +    class TempListener: public Listener +    { +    public: +        TempListener(const std::string& name, bool& liveFlag): +            Listener(name), +            mLiveFlag(liveFlag) +        { +            mLiveFlag = true; +        } + +        virtual ~TempListener() +        { +            mLiveFlag = false; +        } + +    private: +        bool& mLiveFlag; +    }; + +    template<> template<> +    void events_object::test<10>() +    { +        set_test_name("listen(boost::bind(...TempListener...))"); +        // listen() can't do anything about a plain TempListener instance: +        // it's not managed with shared_ptr, nor is it an LLEventTrackable subclass +        bool live = false; +        LLEventPump& heaptest(pumps.obtain("heaptest")); +        LLBoundListener connection; +        { +            TempListener tempListener("temp", live); +            ensure("TempListener constructed", live); +            connection = heaptest.listen(tempListener.getName(), +                                         boost::bind(&Listener::call, +                                                     boost::ref(tempListener), +                                                     _1)); +            heaptest.post(1); +            check_listener("received", tempListener, 1); +        } // presumably this will make newListener go away? +        // verify that +        ensure("TempListener destroyed", ! live); +        // This is the case against which we can't defend. Don't even try to +        // post to heaptest -- that would engage Undefined Behavior. +        // Cautiously inspect connection... +        ensure("misleadingly connected", connection.connected()); +        // then disconnect by hand. +        heaptest.stopListening("temp"); +    } + +    template<> template<> +    void events_object::test<11>() +    { +        set_test_name("listen(boost::bind(...weak_ptr...))"); +        // listen() detecting weak_ptr<TempListener> in boost::bind() object +        bool live = false; +        LLEventPump& heaptest(pumps.obtain("heaptest")); +        LLBoundListener connection; +        ensure("default state", ! connection.connected()); +        { +            boost::shared_ptr<TempListener> newListener(new TempListener("heap", live)); +            newListener->reset(); +            ensure("TempListener constructed", live); +            connection = heaptest.listen(newListener->getName(), +                                         boost::bind(&Listener::call, weaken(newListener), _1)); +            ensure("new connection", connection.connected()); +            heaptest.post(1); +            check_listener("received", *newListener, 1); +        } // presumably this will make newListener go away? +        // verify that +        ensure("TempListener destroyed", ! live); +        ensure("implicit disconnect", ! connection.connected()); +        // now just make sure we don't blow up trying to access a freed object! +        heaptest.post(2); +    } + +    template<> template<> +    void events_object::test<12>() +    { +        set_test_name("listen(boost::bind(...shared_ptr...))"); +/*==========================================================================*| +        // DISABLED because I've made this case produce a compile error. +        // Following the error leads the disappointed dev to a comment +        // instructing her to use the weaken() function to bind a weak_ptr<T> +        // instead of binding a shared_ptr<T>, and explaining why. I know of +        // no way to use TUT to code a repeatable test in which the expected +        // outcome is a compile error. The interested reader is invited to +        // uncomment this block and build to see for herself. + +        // listen() detecting shared_ptr<TempListener> in boost::bind() object +        bool live = false; +        LLEventPump& heaptest(pumps.obtain("heaptest")); +        LLBoundListener connection; +        std::string listenerName("heap"); +        ensure("default state", ! connection.connected()); +        { +            boost::shared_ptr<TempListener> newListener(new TempListener(listenerName, live)); +            ensure_equals("use_count", newListener.use_count(), 1); +            newListener->reset(); +            ensure("TempListener constructed", live); +            connection = heaptest.listen(newListener->getName(), +                                         boost::bind(&Listener::call, newListener, _1)); +            ensure("new connection", connection.connected()); +            ensure_equals("use_count", newListener.use_count(), 2); +            heaptest.post(1); +            check_listener("received", *newListener, 1); +        } // this should make newListener go away... +        // Unfortunately, the fact that we've bound a shared_ptr by value into +        // our LLEventPump means that copy will keep the referenced object alive. +        ensure("TempListener still alive", live); +        ensure("still connected", connection.connected()); +        // disconnecting explicitly should delete the TempListener... +        heaptest.stopListening(listenerName); +#if 0   // however, in my experience, it does not. I don't know why not. +        // Ah: on 2009-02-19, Frank Mori Hess, author of the Boost.Signals2 +        // library, stated on the boost-users mailing list: +        // http://www.nabble.com/Re%3A--signals2--review--The-review-of-the-signals2-library-(formerly-thread_safe_signals)-begins-today%2C-Nov-1st-p22102367.html +        // "It will get destroyed eventually. The signal cleans up its slot +        // list little by little during connect/invoke. It doesn't immediately +        // remove disconnected slots from the slot list since other threads +        // might be using the same slot list concurrently. It might be +        // possible to make it immediately reset the shared_ptr owning the +        // slot though, leaving an empty shared_ptr in the slot list, since +        // that wouldn't invalidate any iterators." +        ensure("TempListener destroyed", ! live); +        ensure("implicit disconnect", ! connection.connected()); +#endif  // 0 +        // now just make sure we don't blow up trying to access a freed object! +        heaptest.post(2); +|*==========================================================================*/ +    } + +    class TempTrackableListener: public TempListener, public LLEventTrackable +    { +    public: +        TempTrackableListener(const std::string& name, bool& liveFlag): +            TempListener(name, liveFlag) +        {} +    }; + +    template<> template<> +    void events_object::test<13>() +    { +        set_test_name("listen(boost::bind(...TempTrackableListener ref...))"); +        bool live = false; +        LLEventPump& heaptest(pumps.obtain("heaptest")); +        LLBoundListener connection; +        { +            TempTrackableListener tempListener("temp", live); +            ensure("TempTrackableListener constructed", live); +            connection = heaptest.listen(tempListener.getName(), +                                         boost::bind(&TempTrackableListener::call, +                                                     boost::ref(tempListener), _1)); +            heaptest.post(1); +            check_listener("received", tempListener, 1); +        } // presumably this will make tempListener go away? +        // verify that +        ensure("TempTrackableListener destroyed", ! live); +        ensure("implicit disconnect", ! connection.connected()); +        // now just make sure we don't blow up trying to access a freed object! +        heaptest.post(2); +    } + +    template<> template<> +    void events_object::test<14>() +    { +        set_test_name("listen(boost::bind(...TempTrackableListener pointer...))"); +        bool live = false; +        LLEventPump& heaptest(pumps.obtain("heaptest")); +        LLBoundListener connection; +        { +            TempTrackableListener* newListener(new TempTrackableListener("temp", live)); +            ensure("TempTrackableListener constructed", live); +            connection = heaptest.listen(newListener->getName(), +                                         boost::bind(&TempTrackableListener::call, +                                                     newListener, _1)); +            heaptest.post(1); +            check_listener("received", *newListener, 1); +            // explicitly destroy newListener +            delete newListener; +        } +        // verify that +        ensure("TempTrackableListener destroyed", ! live); +        ensure("implicit disconnect", ! connection.connected()); +        // now just make sure we don't blow up trying to access a freed object! +        heaptest.post(2); +    } + +    class TempSharedListener: public TempListener, +                              public boost::enable_shared_from_this<TempSharedListener> +    { +    public: +        TempSharedListener(const std::string& name, bool& liveFlag): +            TempListener(name, liveFlag) +        {} +    }; + +    template<> template<> +    void events_object::test<15>() +    { +        set_test_name("listen(boost::bind(...TempSharedListener ref...))"); +#if 0 +        bool live = false; +        LLEventPump& heaptest(pumps.obtain("heaptest")); +        LLBoundListener connection; +        { +            // We MUST have at least one shared_ptr to an +            // enable_shared_from_this subclass object before +            // shared_from_this() can work. +            boost::shared_ptr<TempSharedListener> +                tempListener(new TempSharedListener("temp", live)); +            ensure("TempSharedListener constructed", live); +            // However, we're not passing either the shared_ptr or its +            // corresponding weak_ptr -- instead, we're passing a reference to +            // the TempSharedListener. +/*==========================================================================*| +            std::cout << "Capturing const ref" << std::endl; +            const boost::enable_shared_from_this<TempSharedListener>& cref(*tempListener); +            std::cout << "Capturing const ptr" << std::endl; +            const boost::enable_shared_from_this<TempSharedListener>* cp(&cref); +            std::cout << "Capturing non-const ptr" << std::endl; +            boost::enable_shared_from_this<TempSharedListener>* p(const_cast<boost::enable_shared_from_this<TempSharedListener>*>(cp)); +            std::cout << "Capturing shared_from_this()" << std::endl; +            boost::shared_ptr<TempSharedListener> sp(p->shared_from_this()); +            std::cout << "Capturing weak_ptr" << std::endl; +            boost::weak_ptr<TempSharedListener> wp(weaken(sp)); +            std::cout << "Binding weak_ptr" << std::endl; +|*==========================================================================*/ +            connection = heaptest.listen(tempListener->getName(), +                                         boost::bind(&TempSharedListener::call, *tempListener, _1)); +            heaptest.post(1); +            check_listener("received", *tempListener, 1); +        } // presumably this will make tempListener go away? +        // verify that +        ensure("TempSharedListener destroyed", ! live); +        ensure("implicit disconnect", ! connection.connected()); +        // now just make sure we don't blow up trying to access a freed object! +        heaptest.post(2); +#endif // 0 +    } +} // namespace tut diff --git a/install.xml b/install.xml index 33875aee86..a0d53dbcc0 100644 --- a/install.xml +++ b/install.xml @@ -207,30 +207,30 @@            <key>darwin</key>            <map>              <key>md5sum</key> -            <string>279834a12a0ed4531fd602594eac8509</string> +            <string>081ef195a856c708cc473c4421b4b290</string>              <key>url</key> -            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.32.0-darwin-20080812.tar.bz2</uri> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-darwin-20090223.tar.bz2</uri>            </map>            <key>linux</key>            <map>              <key>md5sum</key> -            <string>b9a943052e5525da5417d6f471d70bc5</string> +            <string>b516a8576ecad0f957db7fc2cd972aac</string>              <key>url</key> -            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.32.0-linux-20080812.tar.bz2</uri> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux-20090223a.tar.bz2</uri>            </map>            <key>linux64</key>            <map>              <key>md5sum</key> -            <string>b97ae0855e77cc25b37ca63df093bb9b</string> +            <string>6db62bb7f141b3a1b3107e1f9aad0eb0</string>              <key>url</key> -            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.0-linux64-20080909.tar.bz2</uri> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-linux64-20090223a.tar.bz2</uri>            </map>            <key>windows</key>            <map>              <key>md5sum</key> -            <string>d2b2ad9b46b9981c2a6be7c912bd17b4</string> +            <string>3b56fe9e8d2975c612639d0a5370ffe7</string>              <key>url</key> -            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-windows-20080723.tar.bz2</uri> +            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-windows-20090225.tar.bz2</uri>            </map>          </map>        </map> | 
