summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
authorBrad Kittenbrink <brad@lindenlab.com>2009-06-04 16:24:21 +0000
committerBrad Kittenbrink <brad@lindenlab.com>2009-06-04 16:24:21 +0000
commit087bd265534b8e3086ae1af441a9cf0eb7c684df (patch)
treea0c911515476d4ac4aac9bd984de28eb7573a478 /indra/llcommon
parentf9b9372027a41900ad572afcd7ea0d2cc5489b8f (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
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/CMakeLists.txt9
-rw-r--r--indra/llcommon/lldependencies.cpp86
-rw-r--r--indra/llcommon/lldependencies.h779
-rw-r--r--indra/llcommon/llerror.cpp10
-rw-r--r--indra/llcommon/llerrorcontrol.h32
-rw-r--r--indra/llcommon/llevent.cpp2
-rw-r--r--indra/llcommon/llevent.h5
-rw-r--r--indra/llcommon/llevents.cpp501
-rw-r--r--indra/llcommon/llevents.h822
-rw-r--r--indra/llcommon/lllazy.cpp23
-rw-r--r--indra/llcommon/lllazy.h382
-rw-r--r--indra/llcommon/stringize.h75
-rw-r--r--indra/llcommon/tests/lllazy_test.cpp227
13 files changed, 2946 insertions, 7 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