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