summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/CMakeLists.txt15
-rw-r--r--indra/llcommon/lleventfilter.cpp245
-rw-r--r--indra/llcommon/lleventfilter.h161
-rw-r--r--indra/llcommon/llsingleton.cpp32
-rw-r--r--indra/llcommon/llstring.h23
-rw-r--r--indra/llcommon/lluri.cpp7
-rw-r--r--indra/llcommon/lluri.h8
-rw-r--r--indra/llcommon/tests/listener.h11
-rw-r--r--indra/llcommon/tests/lleventfilter_test.cpp124
9 files changed, 610 insertions, 16 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 3493f80556..aa76a57f1d 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -324,26 +324,27 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llerror "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(lleventfilter "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llframetimer "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltrace "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llunits "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(lleventcoro "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")
- LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
## llexception_test.cpp isn't a regression test, and doesn't need to be run
## every build. It's to help a developer make implementation choices about
diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp
index 64ab58adcd..9fb18dc67d 100644
--- a/indra/llcommon/lleventfilter.cpp
+++ b/indra/llcommon/lleventfilter.cpp
@@ -38,12 +38,18 @@
#include "llerror.h" // LL_ERRS
#include "llsdutil.h" // llsd_matches()
+/*****************************************************************************
+* LLEventFilter
+*****************************************************************************/
LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak):
LLEventStream(name, tweak),
mSource(source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1)))
{
}
+/*****************************************************************************
+* LLEventMatching
+*****************************************************************************/
LLEventMatching::LLEventMatching(const LLSD& pattern):
LLEventFilter("matching"),
mPattern(pattern)
@@ -64,6 +70,9 @@ bool LLEventMatching::post(const LLSD& event)
return LLEventStream::post(event);
}
+/*****************************************************************************
+* LLEventTimeoutBase
+*****************************************************************************/
LLEventTimeoutBase::LLEventTimeoutBase():
LLEventFilter("timeout")
{
@@ -148,6 +157,14 @@ bool LLEventTimeoutBase::tick(const LLSD&)
return false; // show event to other listeners
}
+bool LLEventTimeoutBase::running() const
+{
+ return mMainloop.connected();
+}
+
+/*****************************************************************************
+* LLEventTimeout
+*****************************************************************************/
LLEventTimeout::LLEventTimeout() {}
LLEventTimeout::LLEventTimeout(LLEventPump& source):
@@ -164,3 +181,231 @@ bool LLEventTimeout::countdownElapsed() const
{
return mTimer.hasExpired();
}
+
+/*****************************************************************************
+* LLEventBatch
+*****************************************************************************/
+LLEventBatch::LLEventBatch(std::size_t size):
+ LLEventFilter("batch"),
+ mBatchSize(size)
+{}
+
+LLEventBatch::LLEventBatch(LLEventPump& source, std::size_t size):
+ LLEventFilter(source, "batch"),
+ mBatchSize(size)
+{}
+
+void LLEventBatch::flush()
+{
+ // copy and clear mBatch BEFORE posting to avoid weird circularity effects
+ LLSD batch(mBatch);
+ mBatch.clear();
+ LLEventStream::post(batch);
+}
+
+bool LLEventBatch::post(const LLSD& event)
+{
+ mBatch.append(event);
+ // calling setSize(same) performs the very check we want
+ setSize(mBatchSize);
+ return false;
+}
+
+void LLEventBatch::setSize(std::size_t size)
+{
+ mBatchSize = size;
+ // changing the size might mean that we have to flush NOW
+ if (mBatch.size() >= mBatchSize)
+ {
+ flush();
+ }
+}
+
+/*****************************************************************************
+* LLEventThrottleBase
+*****************************************************************************/
+LLEventThrottleBase::LLEventThrottleBase(F32 interval):
+ LLEventFilter("throttle"),
+ mInterval(interval),
+ mPosts(0)
+{}
+
+LLEventThrottleBase::LLEventThrottleBase(LLEventPump& source, F32 interval):
+ LLEventFilter(source, "throttle"),
+ mInterval(interval),
+ mPosts(0)
+{}
+
+void LLEventThrottleBase::flush()
+{
+ // flush() is a no-op unless there's something pending.
+ // Don't test mPending because there's no requirement that the consumer
+ // post() anything but an isUndefined(). This is what mPosts is for.
+ if (mPosts)
+ {
+ mPosts = 0;
+ alarmCancel();
+ // This is not to set our alarm; we are not yet requesting
+ // any notification. This is just to track whether subsequent post()
+ // calls fall within this mInterval or not.
+ timerSet(mInterval);
+ // copy and clear mPending BEFORE posting to avoid weird circularity
+ // effects
+ LLSD pending = mPending;
+ mPending.clear();
+ LLEventStream::post(pending);
+ }
+}
+
+LLSD LLEventThrottleBase::pending() const
+{
+ return mPending;
+}
+
+bool LLEventThrottleBase::post(const LLSD& event)
+{
+ // Always capture most recent post() event data. If caller wants to
+ // aggregate multiple events, let them retrieve pending() and modify
+ // before calling post().
+ mPending = event;
+ // Always increment mPosts. Unless we count this call, flush() does
+ // nothing.
+ ++mPosts;
+ // We reset mTimer on every flush() call to let us know if we're still
+ // within the same mInterval. So -- are we?
+ F32 timeRemaining = timerGetRemaining();
+ if (! timeRemaining)
+ {
+ // more than enough time has elapsed, immediately flush()
+ flush();
+ }
+ else
+ {
+ // still within mInterval of the last flush() call: have to defer
+ if (! alarmRunning())
+ {
+ // timeRemaining tells us how much longer it will be until
+ // mInterval seconds since the last flush() call. At that time,
+ // flush() deferred events.
+ alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this));
+ }
+ }
+ return false;
+}
+
+void LLEventThrottleBase::setInterval(F32 interval)
+{
+ F32 oldInterval = mInterval;
+ mInterval = interval;
+ // If we are not now within oldInterval of the last flush(), we're done:
+ // this will only affect behavior starting with the next flush().
+ F32 timeRemaining = timerGetRemaining();
+ if (timeRemaining)
+ {
+ // We are currently within oldInterval of the last flush(). Figure out
+ // how much time remains until (the new) mInterval of the last
+ // flush(). Bt we don't actually store a timestamp for the last
+ // flush(); it's implicit. There are timeRemaining seconds until what
+ // used to be the end of the interval. Move that endpoint by the
+ // difference between the new interval and the old.
+ timeRemaining += (mInterval - oldInterval);
+ // If we're called with a larger interval, the difference is positive
+ // and timeRemaining increases.
+ // If we're called with a smaller interval, the difference is negative
+ // and timeRemaining decreases. The interesting case is when it goes
+ // nonpositive: when the new interval means we can flush immediately.
+ if (timeRemaining <= 0.0f)
+ {
+ flush();
+ }
+ else
+ {
+ // immediately reset mTimer
+ timerSet(timeRemaining);
+ // and if mAlarm is running, reset that too
+ if (alarmRunning())
+ {
+ alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this));
+ }
+ }
+ }
+}
+
+F32 LLEventThrottleBase::getDelay() const
+{
+ return timerGetRemaining();
+}
+
+/*****************************************************************************
+* LLEventThrottle implementation
+*****************************************************************************/
+LLEventThrottle::LLEventThrottle(F32 interval):
+ LLEventThrottleBase(interval)
+{}
+
+LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval):
+ LLEventThrottleBase(source, interval)
+{}
+
+void LLEventThrottle::alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action)
+{
+ mAlarm.actionAfter(interval, action);
+}
+
+bool LLEventThrottle::alarmRunning() const
+{
+ return mAlarm.running();
+}
+
+void LLEventThrottle::alarmCancel()
+{
+ return mAlarm.cancel();
+}
+
+void LLEventThrottle::timerSet(F32 interval)
+{
+ mTimer.setTimerExpirySec(interval);
+}
+
+F32 LLEventThrottle::timerGetRemaining() const
+{
+ return mTimer.getRemainingTimeF32();
+}
+
+/*****************************************************************************
+* LLEventBatchThrottle
+*****************************************************************************/
+LLEventBatchThrottle::LLEventBatchThrottle(F32 interval, std::size_t size):
+ LLEventThrottle(interval),
+ mBatchSize(size)
+{}
+
+LLEventBatchThrottle::LLEventBatchThrottle(LLEventPump& source, F32 interval, std::size_t size):
+ LLEventThrottle(source, interval),
+ mBatchSize(size)
+{}
+
+bool LLEventBatchThrottle::post(const LLSD& event)
+{
+ // simply retrieve pending value and append the new event to it
+ LLSD partial = pending();
+ partial.append(event);
+ bool ret = LLEventThrottle::post(partial);
+ // The post() call above MIGHT have called flush() already. If it did,
+ // then pending() was reset to empty. If it did not, though, but the batch
+ // size has grown to the limit, flush() anyway. If there's a limit at all,
+ // of course. Calling setSize(same) performs the very check we want.
+ setSize(mBatchSize);
+ return ret;
+}
+
+void LLEventBatchThrottle::setSize(std::size_t size)
+{
+ mBatchSize = size;
+ // Changing the size might mean that we have to flush NOW. Don't forget
+ // that 0 means unlimited.
+ if (mBatchSize && pending().size() >= mBatchSize)
+ {
+ flush();
+ }
+}
diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h
index 66f3c14869..ff8fc9bc7f 100644
--- a/indra/llcommon/lleventfilter.h
+++ b/indra/llcommon/lleventfilter.h
@@ -177,6 +177,9 @@ public:
/// Cancel timer without event
void cancel();
+ /// Is this timer currently running?
+ bool running() const;
+
protected:
virtual void setCountdown(F32 seconds) = 0;
virtual bool countdownElapsed() const = 0;
@@ -215,4 +218,162 @@ private:
LLTimer mTimer;
};
+/**
+ * LLEventBatch: accumulate post() events (LLSD blobs) into an LLSD Array
+ * until the array reaches a certain size, then call listeners with the Array
+ * and clear it back to empty.
+ */
+class LL_COMMON_API LLEventBatch: public LLEventFilter
+{
+public:
+ // pass batch size
+ LLEventBatch(std::size_t size);
+ // construct and connect
+ LLEventBatch(LLEventPump& source, std::size_t size);
+
+ // force out the pending batch
+ void flush();
+
+ // accumulate an event and flush() when big enough
+ virtual bool post(const LLSD& event);
+
+ // query or reset batch size
+ std::size_t getSize() const { return mBatchSize; }
+ void setSize(std::size_t size);
+
+private:
+ LLSD mBatch;
+ std::size_t mBatchSize;
+};
+
+/**
+ * LLEventThrottleBase: construct with a time interval. Regardless of how
+ * frequently you call post(), LLEventThrottle will pass on an event to
+ * its listeners no more often than once per specified interval.
+ *
+ * A new event after more than the specified interval will immediately be
+ * passed along to listeners. But subsequent events will be delayed until at
+ * least one time interval since listeners were last called. Consider the
+ * sequence below. Suppose we have an LLEventThrottle constructed with an
+ * interval of 3 seconds. The numbers on the left are timestamps in seconds
+ * relative to an arbitrary reference point.
+ *
+ * 1: post(): event immediately passed to listeners, next no sooner than 4
+ * 2: post(): deferred: waiting for 3 seconds to elapse
+ * 3: post(): deferred
+ * 4: no post() call, but event delivered to listeners; next no sooner than 7
+ * 6: post(): deferred
+ * 7: no post() call, but event delivered; next no sooner than 10
+ * 12: post(): immediately passed to listeners, next no sooner than 15
+ * 17: post(): immediately passed to listeners, next no sooner than 20
+ *
+ * For a deferred event, the LLSD blob delivered to listeners is from the most
+ * recent deferred post() call. However, a sender may obtain the previous
+ * event blob by calling pending(), modifying it as desired and post()ing the
+ * new value. (See LLEventBatchThrottle.) Each time an event is delivered to
+ * listeners, the pending() value is reset to isUndefined().
+ *
+ * You may also call flush() to immediately pass along any deferred events to
+ * all listeners.
+ *
+ * @NOTE This is an abstract base class so that, for testing, we can use an
+ * alternate "timer" that doesn't actually consume real time. See
+ * LLEventThrottle.
+ */
+class LL_COMMON_API LLEventThrottleBase: public LLEventFilter
+{
+public:
+ // pass time interval
+ LLEventThrottleBase(F32 interval);
+ // construct and connect
+ LLEventThrottleBase(LLEventPump& source, F32 interval);
+
+ // force out any deferred events
+ void flush();
+
+ // retrieve (aggregate) deferred event since last event sent to listeners
+ LLSD pending() const;
+
+ // register an event, may be either passed through or deferred
+ virtual bool post(const LLSD& event);
+
+ // query or reset interval
+ F32 getInterval() const { return mInterval; }
+ void setInterval(F32 interval);
+
+ // deferred posts
+ std::size_t getPostCount() const { return mPosts; }
+
+ // time until next event would be passed through, 0.0 if now
+ F32 getDelay() const;
+
+protected:
+ // Implement these time-related methods for a valid LLEventThrottleBase
+ // subclass (see LLEventThrottle). For testing, we use a subclass that
+ // doesn't involve actual elapsed time.
+ virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) = 0;
+ virtual bool alarmRunning() const = 0;
+ virtual void alarmCancel() = 0;
+ virtual void timerSet(F32 interval) = 0;
+ virtual F32 timerGetRemaining() const = 0;
+
+private:
+ // remember throttle interval
+ F32 mInterval;
+ // count post() calls since last flush()
+ std::size_t mPosts;
+ // pending event data from most recent deferred event
+ LLSD mPending;
+};
+
+/**
+ * Production implementation of LLEventThrottle.
+ */
+class LLEventThrottle: public LLEventThrottleBase
+{
+public:
+ LLEventThrottle(F32 interval);
+ LLEventThrottle(LLEventPump& source, F32 interval);
+
+private:
+ virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) /*override*/;
+ virtual bool alarmRunning() const /*override*/;
+ virtual void alarmCancel() /*override*/;
+ virtual void timerSet(F32 interval) /*override*/;
+ virtual F32 timerGetRemaining() const /*override*/;
+
+ // use this to arrange a deferred flush() call
+ LLEventTimeout mAlarm;
+ // use this to track whether we're within mInterval of last flush()
+ LLTimer mTimer;
+};
+
+/**
+ * LLEventBatchThrottle: like LLEventThrottle, it's reluctant to pass events
+ * to listeners more often than once per specified time interval -- but only
+ * reluctant, since exceeding the specified batch size limit can cause it to
+ * deliver accumulated events sooner. Like LLEventBatch, it accumulates
+ * pending events into an LLSD Array, optionally flushing when the batch grows
+ * to a certain size.
+ */
+class LLEventBatchThrottle: public LLEventThrottle
+{
+public:
+ // pass time interval and (optionally) max batch size; 0 means batch can
+ // grow arbitrarily large
+ LLEventBatchThrottle(F32 interval, std::size_t size = 0);
+ // construct and connect
+ LLEventBatchThrottle(LLEventPump& source, F32 interval, std::size_t size = 0);
+
+ // append a new event to current batch
+ virtual bool post(const LLSD& event);
+
+ // query or reset batch size
+ std::size_t getSize() const { return mBatchSize; }
+ void setSize(std::size_t size);
+
+private:
+ std::size_t mBatchSize;
+};
+
#endif /* ! defined(LL_LLEVENTFILTER_H) */
diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp
index 9025e53bb2..a3a87edd88 100644
--- a/indra/llcommon/llsingleton.cpp
+++ b/indra/llcommon/llsingleton.cpp
@@ -220,6 +220,9 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt
std::find(initializing.begin(), initializing.end(), this);
if (found != initializing.end())
{
+ list_t::const_iterator it_next = found;
+ it_next++;
+
// Report the circularity. Requiring the coder to dig through the
// logic to diagnose exactly how we got here is less than helpful.
std::ostringstream out;
@@ -238,11 +241,30 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt
// otherwise we'd be returning a pointer to a partially-
// constructed object! But from initSingleton() is okay: that
// method exists specifically to support circularity.
- // Decide which log helper to call based on initState. They have
- // identical signatures.
- ((initState == CONSTRUCTING)? logerrs : logwarns)
- ("LLSingleton circularity: ", out.str().c_str(),
- demangle(typeid(*this).name()).c_str(), "");
+ // Decide which log helper to call.
+ if (initState == CONSTRUCTING)
+ {
+ logerrs("LLSingleton circularity in Constructor: ", out.str().c_str(),
+ demangle(typeid(*this).name()).c_str(), "");
+ }
+ else if (it_next == initializing.end())
+ {
+ // Points to self after construction, but during initialization.
+ // Singletons can initialize other classes that depend onto them,
+ // so this is expected.
+ //
+ // Example: LLNotifications singleton initializes default channels.
+ // Channels register themselves with singleton once done.
+ logdebugs("LLSingleton circularity: ", out.str().c_str(),
+ demangle(typeid(*this).name()).c_str(), "");
+ }
+ else
+ {
+ // Actual circularity with other singleton (or single singleton is used extensively).
+ // Dependency can be unclear.
+ logwarns("LLSingleton circularity: ", out.str().c_str(),
+ demangle(typeid(*this).name()).c_str(), "");
+ }
}
else
{
diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h
index a40db0f8cc..2255e638c2 100644
--- a/indra/llcommon/llstring.h
+++ b/indra/llcommon/llstring.h
@@ -336,6 +336,7 @@ public:
static void addCRLF(string_type& string);
static void removeCRLF(string_type& string);
+ static void removeWindowsCR(string_type& string);
static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab );
static void replaceNonstandardASCII( string_type& string, T replacement );
@@ -1322,6 +1323,28 @@ void LLStringUtilBase<T>::removeCRLF(string_type& string)
//static
template<class T>
+void LLStringUtilBase<T>::removeWindowsCR(string_type& string)
+{
+ const T LF = 10;
+ const T CR = 13;
+
+ size_type cr_count = 0;
+ size_type len = string.size();
+ size_type i;
+ for( i = 0; i < len - cr_count - 1; i++ )
+ {
+ if( string[i+cr_count] == CR && string[i+cr_count+1] == LF)
+ {
+ cr_count++;
+ }
+
+ string[i] = string[i+cr_count];
+ }
+ string.erase(i, cr_count);
+}
+
+//static
+template<class T>
void LLStringUtilBase<T>::replaceChar( string_type& string, T target, T replacement )
{
size_type found_pos = 0;
diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp
index 9f12d49244..758b98e143 100644
--- a/indra/llcommon/lluri.cpp
+++ b/indra/llcommon/lluri.cpp
@@ -40,7 +40,8 @@
#include <boost/algorithm/string/find_iterator.hpp>
#include <boost/algorithm/string/finder.hpp>
-void encode_character(std::ostream& ostr, std::string::value_type val)
+// static
+void LLURI::encodeCharacter(std::ostream& ostr, std::string::value_type val)
{
ostr << "%"
@@ -95,7 +96,7 @@ std::string LLURI::escape(
}
else
{
- encode_character(ostr, c);
+ encodeCharacter(ostr, c);
}
}
}
@@ -106,7 +107,7 @@ std::string LLURI::escape(
c = *it;
if(allowed.find(c) == std::string::npos)
{
- encode_character(ostr, c);
+ encodeCharacter(ostr, c);
}
else
{
diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h
index c82a666e48..9e44cc7da2 100644
--- a/indra/llcommon/lluri.h
+++ b/indra/llcommon/lluri.h
@@ -121,6 +121,14 @@ public:
/** @name Escaping Utilities */
//@{
/**
+ * @brief 'Escape' symbol into stream
+ *
+ * @param ostr Output stream.
+ * @param val Symbol to encode.
+ */
+ static void encodeCharacter(std::ostream& ostr, std::string::value_type val);
+
+ /**
* @brief Escape the string passed except for unreserved
*
* ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
diff --git a/indra/llcommon/tests/listener.h b/indra/llcommon/tests/listener.h
index 9c5c18a150..6072060bb6 100644
--- a/indra/llcommon/tests/listener.h
+++ b/indra/llcommon/tests/listener.h
@@ -138,4 +138,15 @@ struct Collect
StringVec result;
};
+struct Concat
+{
+ bool operator()(const LLSD& event)
+ {
+ result += event.asString();
+ return false;
+ }
+ void clear() { result.clear(); }
+ std::string result;
+};
+
#endif /* ! defined(LL_LISTENER_H) */
diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp
index 2cdfb52f2f..eb98b12ef5 100644
--- a/indra/llcommon/tests/lleventfilter_test.cpp
+++ b/indra/llcommon/tests/lleventfilter_test.cpp
@@ -70,6 +70,85 @@ private:
bool mElapsed;
};
+// Similar remarks about LLEventThrottle: we're actually testing the logic in
+// LLEventThrottleBase, dummying out the LLTimer and LLEventTimeout used by
+// the production LLEventThrottle class.
+class TestEventThrottle: public LLEventThrottleBase
+{
+public:
+ TestEventThrottle(F32 interval):
+ LLEventThrottleBase(interval),
+ mAlarmRemaining(-1),
+ mTimerRemaining(-1)
+ {}
+ TestEventThrottle(LLEventPump& source, F32 interval):
+ LLEventThrottleBase(source, interval),
+ mAlarmRemaining(-1),
+ mTimerRemaining(-1)
+ {}
+
+ /*----- implementation of LLEventThrottleBase timing functionality -----*/
+ virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) /*override*/
+ {
+ mAlarmRemaining = interval;
+ mAlarmAction = action;
+ }
+
+ virtual bool alarmRunning() const /*override*/
+ {
+ // decrementing to exactly 0 should mean the alarm fires
+ return mAlarmRemaining > 0;
+ }
+
+ virtual void alarmCancel() /*override*/
+ {
+ mAlarmRemaining = -1;
+ }
+
+ virtual void timerSet(F32 interval) /*override*/
+ {
+ mTimerRemaining = interval;
+ }
+
+ virtual F32 timerGetRemaining() const /*override*/
+ {
+ // LLTimer.getRemainingTimeF32() never returns negative; 0.0 means expired
+ return (mTimerRemaining > 0.0)? mTimerRemaining : 0.0;
+ }
+
+ /*------------------- methods for manipulating time --------------------*/
+ void alarmAdvance(F32 delta)
+ {
+ bool wasRunning = alarmRunning();
+ mAlarmRemaining -= delta;
+ if (wasRunning && ! alarmRunning())
+ {
+ mAlarmAction();
+ }
+ }
+
+ void timerAdvance(F32 delta)
+ {
+ // This simple implementation, like alarmAdvance(), completely ignores
+ // HOW negative mTimerRemaining might go. All that matters is whether
+ // it's negative. We trust that no test method in this source will
+ // drive it beyond the capacity of an F32. Seems like a safe assumption.
+ mTimerRemaining -= delta;
+ }
+
+ void advance(F32 delta)
+ {
+ // Advance the timer first because it has no side effects.
+ // alarmAdvance() might call flush(), which will need to see the
+ // change in the timer.
+ timerAdvance(delta);
+ alarmAdvance(delta);
+ }
+
+ F32 mAlarmRemaining, mTimerRemaining;
+ LLEventTimeoutBase::Action mAlarmAction;
+};
+
/*****************************************************************************
* TUT
*****************************************************************************/
@@ -116,7 +195,9 @@ namespace tut
listener0.listenTo(driver));
// Construct a pattern LLSD: desired Event must have a key "foo"
// containing string "bar"
- LLEventMatching filter(driver, LLSD().insert("foo", "bar"));
+ LLSD pattern;
+ pattern.insert("foo", "bar");
+ LLEventMatching filter(driver, pattern);
listener1.reset(0);
LLTempBoundListener temp2(
listener1.listenTo(filter));
@@ -285,6 +366,47 @@ namespace tut
mainloop.post(17);
check_listener("no timeout 3", listener0, LLSD(0));
}
+
+ template<> template<>
+ void filter_object::test<5>()
+ {
+ set_test_name("LLEventThrottle");
+ TestEventThrottle throttle(3);
+ Concat cat;
+ throttle.listen("concat", boost::ref(cat));
+
+ // (sequence taken from LLEventThrottleBase Doxygen comments)
+ // 1: post(): event immediately passed to listeners, next no sooner than 4
+ throttle.advance(1);
+ throttle.post("1");
+ ensure_equals("1", cat.result, "1"); // delivered immediately
+ // 2: post(): deferred: waiting for 3 seconds to elapse
+ throttle.advance(1);
+ throttle.post("2");
+ ensure_equals("2", cat.result, "1"); // "2" not yet delivered
+ // 3: post(): deferred
+ throttle.advance(1);
+ throttle.post("3");
+ ensure_equals("3", cat.result, "1"); // "3" not yet delivered
+ // 4: no post() call, but event delivered to listeners; next no sooner than 7
+ throttle.advance(1);
+ ensure_equals("4", cat.result, "13"); // "3" delivered
+ // 6: post(): deferred
+ throttle.advance(2);
+ throttle.post("6");
+ ensure_equals("6", cat.result, "13"); // "6" not yet delivered
+ // 7: no post() call, but event delivered; next no sooner than 10
+ throttle.advance(1);
+ ensure_equals("7", cat.result, "136"); // "6" delivered
+ // 12: post(): immediately passed to listeners, next no sooner than 15
+ throttle.advance(5);
+ throttle.post(";12");
+ ensure_equals("12", cat.result, "136;12"); // "12" delivered
+ // 17: post(): immediately passed to listeners, next no sooner than 20
+ throttle.advance(5);
+ throttle.post(";17");
+ ensure_equals("17", cat.result, "136;12;17"); // "17" delivered
+ }
} // namespace tut
/*****************************************************************************