Skip to content
Snippets Groups Projects
Commit 51b05920 authored by Nat Goodspeed's avatar Nat Goodspeed
Browse files
parents bb3380b2 9c66072c
No related branches found
No related tags found
No related merge requests found
...@@ -324,26 +324,27 @@ if (LL_TESTS) ...@@ -324,26 +324,27 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llerror "" "${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(llframetimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${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(llprocessor "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llsingleton "" "${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(llstring "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltrace "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lltrace "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llunits "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llunits "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(stringize "" "${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 ## 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 ## every build. It's to help a developer make implementation choices about
......
...@@ -38,12 +38,18 @@ ...@@ -38,12 +38,18 @@
#include "llerror.h" // LL_ERRS #include "llerror.h" // LL_ERRS
#include "llsdutil.h" // llsd_matches() #include "llsdutil.h" // llsd_matches()
/*****************************************************************************
* LLEventFilter
*****************************************************************************/
LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak): LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak):
LLEventStream(name, tweak), LLEventStream(name, tweak),
mSource(source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1))) mSource(source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1)))
{ {
} }
/*****************************************************************************
* LLEventMatching
*****************************************************************************/
LLEventMatching::LLEventMatching(const LLSD& pattern): LLEventMatching::LLEventMatching(const LLSD& pattern):
LLEventFilter("matching"), LLEventFilter("matching"),
mPattern(pattern) mPattern(pattern)
...@@ -64,6 +70,9 @@ bool LLEventMatching::post(const LLSD& event) ...@@ -64,6 +70,9 @@ bool LLEventMatching::post(const LLSD& event)
return LLEventStream::post(event); return LLEventStream::post(event);
} }
/*****************************************************************************
* LLEventTimeoutBase
*****************************************************************************/
LLEventTimeoutBase::LLEventTimeoutBase(): LLEventTimeoutBase::LLEventTimeoutBase():
LLEventFilter("timeout") LLEventFilter("timeout")
{ {
...@@ -148,6 +157,14 @@ bool LLEventTimeoutBase::tick(const LLSD&) ...@@ -148,6 +157,14 @@ bool LLEventTimeoutBase::tick(const LLSD&)
return false; // show event to other listeners return false; // show event to other listeners
} }
bool LLEventTimeoutBase::running() const
{
return mMainloop.connected();
}
/*****************************************************************************
* LLEventTimeout
*****************************************************************************/
LLEventTimeout::LLEventTimeout() {} LLEventTimeout::LLEventTimeout() {}
LLEventTimeout::LLEventTimeout(LLEventPump& source): LLEventTimeout::LLEventTimeout(LLEventPump& source):
...@@ -164,3 +181,214 @@ bool LLEventTimeout::countdownElapsed() const ...@@ -164,3 +181,214 @@ bool LLEventTimeout::countdownElapsed() const
{ {
return mTimer.hasExpired(); 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);
if (mBatch.size() >= mBatchSize)
{
flush();
}
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):
LLEventThrottle(interval)
{}
LLEventBatchThrottle::LLEventBatchThrottle(LLEventPump& source, F32 interval):
LLEventThrottle(source, interval)
{}
bool LLEventBatchThrottle::post(const LLSD& event)
{
// simply retrieve pending value and append the new event to it
LLSD partial = pending();
partial.append(event);
return LLEventThrottle::post(partial);
}
...@@ -177,6 +177,9 @@ class LL_COMMON_API LLEventTimeoutBase: public LLEventFilter ...@@ -177,6 +177,9 @@ class LL_COMMON_API LLEventTimeoutBase: public LLEventFilter
/// Cancel timer without event /// Cancel timer without event
void cancel(); void cancel();
/// Is this timer currently running?
bool running() const;
protected: protected:
virtual void setCountdown(F32 seconds) = 0; virtual void setCountdown(F32 seconds) = 0;
virtual bool countdownElapsed() const = 0; virtual bool countdownElapsed() const = 0;
...@@ -215,4 +218,150 @@ class LL_COMMON_API LLEventTimeout: public LLEventTimeoutBase ...@@ -215,4 +218,150 @@ class LL_COMMON_API LLEventTimeout: public LLEventTimeoutBase
LLTimer mTimer; 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. 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.
*/
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 refuses to pass events to
* listeners more often than once per specified time interval.
* Like LLEventBatch, it accumulates pending events into an LLSD Array.
*/
class LLEventBatchThrottle: public LLEventThrottle
{
public:
// pass time interval
LLEventBatchThrottle(F32 interval);
// construct and connect
LLEventBatchThrottle(LLEventPump& source, F32 interval);
// append a new event to current batch
virtual bool post(const LLSD& event);
};
#endif /* ! defined(LL_LLEVENTFILTER_H) */ #endif /* ! defined(LL_LLEVENTFILTER_H) */
...@@ -138,4 +138,15 @@ struct Collect ...@@ -138,4 +138,15 @@ struct Collect
StringVec result; 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) */ #endif /* ! defined(LL_LISTENER_H) */
...@@ -70,6 +70,85 @@ class TestEventTimeout: public LLEventTimeoutBase ...@@ -70,6 +70,85 @@ class TestEventTimeout: public LLEventTimeoutBase
bool mElapsed; 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 * TUT
*****************************************************************************/ *****************************************************************************/
...@@ -116,7 +195,9 @@ namespace tut ...@@ -116,7 +195,9 @@ namespace tut
listener0.listenTo(driver)); listener0.listenTo(driver));
// Construct a pattern LLSD: desired Event must have a key "foo" // Construct a pattern LLSD: desired Event must have a key "foo"
// containing string "bar" // containing string "bar"
LLEventMatching filter(driver, LLSD().insert("foo", "bar")); LLSD pattern;
pattern.insert("foo", "bar");
LLEventMatching filter(driver, pattern);
listener1.reset(0); listener1.reset(0);
LLTempBoundListener temp2( LLTempBoundListener temp2(
listener1.listenTo(filter)); listener1.listenTo(filter));
...@@ -285,6 +366,47 @@ namespace tut ...@@ -285,6 +366,47 @@ namespace tut
mainloop.post(17); mainloop.post(17);
check_listener("no timeout 3", listener0, LLSD(0)); 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 } // namespace tut
/***************************************************************************** /*****************************************************************************
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment