summaryrefslogtreecommitdiff
path: root/indra/llcommon/llevents.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/llevents.cpp')
-rw-r--r--indra/llcommon/llevents.cpp501
1 files changed, 501 insertions, 0 deletions
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);
+}