From 7af4a49835880fc92461882d5354c0ff12a9672a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 23 Mar 2017 22:39:31 -0400 Subject: MAINT-6789: Add LLEventBatch, LLEventThrottle, LLEventBatchThrottle. These classes are as yet untested: they are straw people for API review, based on email conversations with Caladbolg and Rider. --- indra/llcommon/lleventfilter.cpp | 115 +++++++++++++++++++++++++++++++++++++++ indra/llcommon/lleventfilter.h | 104 +++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp index 64ab58adcd..1fd29ea9e5 100644 --- a/indra/llcommon/lleventfilter.cpp +++ b/indra/llcommon/lleventfilter.cpp @@ -148,6 +148,11 @@ bool LLEventTimeoutBase::tick(const LLSD&) return false; // show event to other listeners } +bool LLEventTimeoutBase::running() const +{ + return mMainloop.connected(); +} + LLEventTimeout::LLEventTimeout() {} LLEventTimeout::LLEventTimeout(LLEventPump& source): @@ -164,3 +169,113 @@ bool LLEventTimeout::countdownElapsed() const { return mTimer.hasExpired(); } + +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; +} + +LLEventThrottle::LLEventThrottle(F32 interval): + LLEventFilter("throttle"), + mInterval(interval), + mPosts(0) +{} + +LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval): + LLEventFilter(source, "throttle"), + mInterval(interval), + mPosts(0) +{} + +void LLEventThrottle::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; + mAlarm.cancel(); + // 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. + mTimer.setTimerExpirySec(mInterval); + // copy and clear mPending BEFORE posting to avoid weird circularity + // effects + LLSD pending = mPending; + mPending.clear(); + LLEventStream::post(pending); + } +} + +LLSD LLEventThrottle::pending() const +{ + return mPending; +} + +bool LLEventThrottle::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 = mTimer.getRemainingTimeF32(); + if (! timeRemaining) + { + // more than enough time has elapsed, immediately flush() + flush(); + } + else + { + // still within mInterval of the last flush() call: have to defer + if (! mAlarm.running()) + { + // timeRemaining tells us how much longer it will be until + // mInterval seconds since the last flush() call. At that time, + // flush() deferred events. + mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this)); + } + } +} + +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 + return LLEventThrottle::post(pending().append(event)); +} diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index 66f3c14869..1445b8a3b7 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,105 @@ 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); + +private: + LLSD mBatch; + std::size_t mBatchSize; +}; + +/** + * LLEventThrottle: 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, you may obtain the previous event + * blob by calling pending(), modify it however you want and post() 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. + */ +class LL_COMMON_API LLEventThrottle: public LLEventFilter +{ +public: + // pass time interval + LLEventThrottle(F32 interval); + // construct and connect + LLEventThrottle(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); + +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; + // 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) */ -- cgit v1.3 From 2fe4f6b9187d9153e335bf54ea43fc89a7987459 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 24 Mar 2017 18:25:16 -0400 Subject: MAINT-6789: Add LLEventThrottle::getInterval(), setInterval() plus LLEventBatch::getSize(), setSize() plus LLEventThrottle::getPostCount() and getDelay(). The interesting thing about LLEventThrottle::setInterval() and LLEventBatch::setSize() is that either might cause an immediate flush(). --- indra/llcommon/lleventfilter.cpp | 49 ++++++++++++++++++++++++++++++++++++++++ indra/llcommon/lleventfilter.h | 14 ++++++++++++ 2 files changed, 63 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp index 1fd29ea9e5..87eb583cb6 100644 --- a/indra/llcommon/lleventfilter.cpp +++ b/indra/llcommon/lleventfilter.cpp @@ -198,6 +198,16 @@ bool LLEventBatch::post(const LLSD& event) 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(); + } +} + LLEventThrottle::LLEventThrottle(F32 interval): LLEventFilter("throttle"), mInterval(interval), @@ -264,6 +274,45 @@ bool LLEventThrottle::post(const LLSD& event) mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this)); } } + return false; +} + +void LLEventThrottle::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 = mTimer.getRemainingTimeF32(); + 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 + mTimer.setTimerExpirySec(timeRemaining); + // and if mAlarm is running, reset that too + if (mAlarm.running()) + { + mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this)); + } + } + } } LLEventBatchThrottle::LLEventBatchThrottle(F32 interval): diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index 1445b8a3b7..68890846a7 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -237,6 +237,10 @@ public: // 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; @@ -289,6 +293,16 @@ public: // 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 { return mTimer.getRemainingTimeF32(); } + private: // remember throttle interval F32 mInterval; -- cgit v1.3 From c27dbc62148f51ff5848f63573f5816235f0cc35 Mon Sep 17 00:00:00 2001 From: mnikolenko Date: Mon, 3 Apr 2017 02:21:18 +0300 Subject: MAINT-6404 FIXED When pasting text with mac linebreak into a notecard, it shouldn't be removed --- indra/llcommon/llstring.h | 23 +++++++++++++++++++++++ indra/llwindow/llwindowwin32.cpp | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') 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::removeCRLF(string_type& string) //static template +void LLStringUtilBase::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 void LLStringUtilBase::replaceChar( string_type& string, T target, T replacement ) { size_type found_pos = 0; diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 321792eb14..b5ed53fd4f 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -2801,7 +2801,7 @@ BOOL LLWindowWin32::pasteTextFromClipboard(LLWString &dst) if (utf16str) { dst = utf16str_to_wstring(utf16str); - LLWStringUtil::removeCRLF(dst); + LLWStringUtil::removeWindowsCR(dst); GlobalUnlock(h_data); success = TRUE; } -- cgit v1.3 From 10213f994f64de1f50e1ff6b9b5f43be549d19cb Mon Sep 17 00:00:00 2001 From: andreykproductengine Date: Fri, 7 Apr 2017 20:29:24 +0300 Subject: MAINT-6283 Names that contain 'http:' or 'https:' were causing new line in chat history --- indra/llcommon/lluri.cpp | 7 ++++--- indra/llcommon/lluri.h | 8 ++++++++ indra/newview/lllogchat.cpp | 35 ++++++++++++++++++++++++++++------- 3 files changed, 40 insertions(+), 10 deletions(-) (limited to 'indra/llcommon') 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 #include -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 @@ -120,6 +120,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 * diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index b9194c6c67..e310bbb062 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -67,7 +67,8 @@ const std::string LL_IM_FROM("from"); const std::string LL_IM_FROM_ID("from_id"); const std::string LL_TRANSCRIPT_FILE_EXTENSION("txt"); -const static std::string IM_SEPARATOR(": "); +const static char IM_SYMBOL_SEPARATOR(':'); +const static std::string IM_SEPARATOR(std::string() + IM_SYMBOL_SEPARATOR + " "); const static std::string NEW_LINE("\n"); const static std::string NEW_LINE_SPACE_PREFIX("\n "); const static std::string TWO_SPACES(" "); @@ -838,7 +839,7 @@ void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const } if (im[LL_IM_TIME].isDefined()) -{ + { std::string timestamp = im[LL_IM_TIME].asString(); boost::trim(timestamp); ostr << '[' << timestamp << ']' << TWO_SPACES; @@ -851,9 +852,29 @@ void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const { std::string from = im[LL_IM_FROM].asString(); boost::trim(from); - if (from.size()) + + std::size_t found = from.find(IM_SYMBOL_SEPARATOR); + std::size_t len = from.size(); + std::size_t start = 0; + while (found != std::string::npos) + { + std::size_t sub_len = found - start; + if (sub_len > 0) + { + ostr << from.substr(start, sub_len); + } + LLURI::encodeCharacter(ostr, IM_SYMBOL_SEPARATOR); + start = found + 1; + found = from.find(IM_SYMBOL_SEPARATOR, start); + } + if (start < len) + { + std::string str_end = from.substr(start, len - start); + ostr << str_end; + } + if (len > 0) { - ostr << from << IM_SEPARATOR; + ostr << IM_SEPARATOR; } } @@ -865,7 +886,7 @@ void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const boost::replace_all(im_text, NEW_LINE, NEW_LINE_SPACE_PREFIX); ostr << im_text; } - } +} bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params) { @@ -912,7 +933,7 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params if (!boost::regex_match(stuff, name_and_text, NAME_AND_TEXT)) return false; bool has_name = name_and_text[IDX_NAME].matched; - std::string name = name_and_text[IDX_NAME]; + std::string name = LLURI::unescape(name_and_text[IDX_NAME]); //we don't need a name/text separator if (has_name && name.length() && name[name.length()-1] == ':') @@ -933,7 +954,7 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params U32 divider_pos = stuff.find(NAME_TEXT_DIVIDER); if (divider_pos != std::string::npos && divider_pos < (stuff.length() - NAME_TEXT_DIVIDER.length())) { - im[LL_IM_FROM] = stuff.substr(0, divider_pos); + im[LL_IM_FROM] = LLURI::unescape(stuff.substr(0, divider_pos)); im[LL_IM_TEXT] = stuff.substr(divider_pos + NAME_TEXT_DIVIDER.length()); return true; } -- cgit v1.3 From 0dcb423cf3dc42e11621eece972801b036657e91 Mon Sep 17 00:00:00 2001 From: andreykproductengine Date: Tue, 25 Apr 2017 17:48:34 +0300 Subject: MAINT-7145 Eliminate LLSingleton circular references --- indra/llcommon/llsingleton.cpp | 32 +++++++++++++++++++++++++++----- indra/llui/llnotifications.cpp | 18 ++++++------------ indra/newview/lldaycyclemanager.cpp | 22 ---------------------- indra/newview/llenvmanager.cpp | 21 ++++++++++++++++++++- indra/newview/llsky.cpp | 2 -- indra/newview/llstartup.cpp | 1 + indra/newview/llwlparammanager.cpp | 25 ++++++++----------------- indra/newview/llwlparammanager.h | 5 +++-- 8 files changed, 65 insertions(+), 61 deletions(-) (limited to 'indra/llcommon') 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/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 0cb959a315..604092d536 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -1405,20 +1405,14 @@ void LLNotifications::createDefaultChannels() mDefaultChannels.push_back(new LLPersistentNotificationChannel()); // connect action methods to these channels - LLNotifications::instance().getChannel("Enabled")-> - connectFailedFilter(&defaultResponse); - LLNotifications::instance().getChannel("Expiration")-> - connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); + getChannel("Enabled")->connectFailedFilter(&defaultResponse); + getChannel("Expiration")->connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); // uniqueHandler slot should be added as first slot of the signal due to // usage LLStopWhenHandled combiner in LLStandardSignal - LLNotifications::instance().getChannel("Unique")-> - connectAtFrontChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); - LLNotifications::instance().getChannel("Unique")-> - connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); - LLNotifications::instance().getChannel("Ignore")-> - connectFailedFilter(&handleIgnoredNotification); - LLNotifications::instance().getChannel("VisibilityRules")-> - connectFailedFilter(&visibilityRuleMached); + getChannel("Unique")->connectAtFrontChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); + getChannel("Unique")->connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); + getChannel("Ignore")->connectFailedFilter(&handleIgnoredNotification); + getChannel("VisibilityRules")->connectFailedFilter(&visibilityRuleMached); } diff --git a/indra/newview/lldaycyclemanager.cpp b/indra/newview/lldaycyclemanager.cpp index e7bf989850..803e2b2fb2 100644 --- a/indra/newview/lldaycyclemanager.cpp +++ b/indra/newview/lldaycyclemanager.cpp @@ -163,28 +163,6 @@ void LLDayCycleManager::initSingleton() { LL_DEBUGS("Windlight") << "Loading all day cycles" << LL_ENDL; loadAllPresets(); - - // presets loaded, can set params - if (LLEnvManagerNew::instance().getUseDayCycle()) - { - LLSD params; - std::string preferred_day = LLEnvManagerNew::getInstance()->getDayCycleName(); - if (getPreset(preferred_day, params)) - { - // Same as through useDayCycle(), but it will call LLDayCycleManager and we are initializing - // so no need to cycle - LLWLParamManager::getInstance()->applyDayCycleParams(params, LLEnvKey::SCOPE_LOCAL); - } - else - { - LL_WARNS() << "No day cycle named " << preferred_day << ", reverting LLWLParamManager to defaults" << LL_ENDL; - LLWLParamManager::getInstance()->setDefaultDay(); - } - } - else - { - LLWLParamManager::getInstance()->setDefaultDay(); - } } void LLDayCycleManager::loadAllPresets() diff --git a/indra/newview/llenvmanager.cpp b/indra/newview/llenvmanager.cpp index b97e483fd9..12c3070474 100644 --- a/indra/newview/llenvmanager.cpp +++ b/indra/newview/llenvmanager.cpp @@ -476,7 +476,7 @@ void LLEnvManagerNew::onRegionSettingsResponse(const LLSD& content) mCachedRegionPrefs = new_settings; // Load region sky presets. - LLWLParamManager::instance().refreshRegionPresets(); + LLWLParamManager::instance().refreshRegionPresets(getRegionSettings().getSkyMap()); // If using server settings, update managers. if (getUseRegionSettings()) @@ -509,6 +509,25 @@ void LLEnvManagerNew::initSingleton() LL_DEBUGS("Windlight") << "Initializing LLEnvManagerNew" << LL_ENDL; loadUserPrefs(); + + // preferences loaded, can set params + std::string preferred_day = getDayCycleName(); + if (!useDayCycle(preferred_day, LLEnvKey::SCOPE_LOCAL)) + { + LL_WARNS() << "No day cycle named " << preferred_day << ", reverting LLWLParamManager to defaults" << LL_ENDL; + LLWLParamManager::instance().setDefaultDay(); + } + + std::string sky = getSkyPresetName(); + if (!useSkyPreset(sky)) + { + LL_WARNS() << "No sky preset named " << sky << ", falling back to defaults" << LL_ENDL; + LLWLParamManager::instance().setDefaultSky(); + + // *TODO: Fix user preferences accordingly. + } + + LLWLParamManager::instance().resetAnimator(0.5 /*noon*/, getUseDayCycle()); } void LLEnvManagerNew::updateSkyFromPrefs() diff --git a/indra/newview/llsky.cpp b/indra/newview/llsky.cpp index 7a292fe0ef..3ef89ba920 100644 --- a/indra/newview/llsky.cpp +++ b/indra/newview/llsky.cpp @@ -341,8 +341,6 @@ void LLSky::init(const LLVector3 &sun_direction) LLGLState::checkTextureChannels(); mUpdatedThisFrame = TRUE; - - LLEnvManagerNew::instance().usePrefs(); //inits day cycles and water } diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 0a85344025..33b6352bf5 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -1462,6 +1462,7 @@ bool idle_startup() LLGLState::checkStates(); LLGLState::checkTextureChannels(); + LLEnvManagerNew::getInstance()->usePrefs(); // Load all presets and settings gSky.init(initial_sun_direction); LLGLState::checkStates(); diff --git a/indra/newview/llwlparammanager.cpp b/indra/newview/llwlparammanager.cpp index debe467a9a..4b4393b07b 100644 --- a/indra/newview/llwlparammanager.cpp +++ b/indra/newview/llwlparammanager.cpp @@ -50,7 +50,6 @@ #include "llagent.h" #include "llviewerregion.h" -#include "llenvmanager.h" #include "llwlparamset.h" #include "llpostprocess.h" @@ -251,13 +250,13 @@ void LLWLParamManager::addAllSkies(const LLWLParamKey::EScope scope, const LLSD& } } -void LLWLParamManager::refreshRegionPresets() +void LLWLParamManager::refreshRegionPresets(const LLSD& region_sky_presets) { // Remove all region sky presets because they may belong to a previously visited region. clearParamSetsOfScope(LLEnvKey::SCOPE_REGION); // Add all sky presets belonging to the current region. - addAllSkies(LLEnvKey::SCOPE_REGION, LLEnvManagerNew::instance().getRegionSettings().getSkyMap()); + addAllSkies(LLEnvKey::SCOPE_REGION, region_sky_presets); } void LLWLParamManager::loadAllPresets() @@ -489,7 +488,6 @@ bool LLWLParamManager::applyDayCycleParams(const LLSD& params, LLEnvKey::EScope void LLWLParamManager::setDefaultDay() { mDay.loadDayCycleFromFile("Default.xml"); - resetAnimator(0.5, LLEnvManagerNew::getInstance()->getUseDayCycle()); } bool LLWLParamManager::applySkyParams(const LLSD& params) @@ -499,6 +497,12 @@ bool LLWLParamManager::applySkyParams(const LLSD& params) return true; } +void LLWLParamManager::setDefaultSky() +{ + getParamSet(LLWLParamKey("Default", LLWLParamKey::SCOPE_LOCAL), mCurParams); +} + + void LLWLParamManager::resetAnimator(F32 curTime, bool run) { mAnimator.setTrack(mDay.mTimeMap, mDay.mDayRate, @@ -677,19 +681,6 @@ void LLWLParamManager::initSingleton() loadAllPresets(); - // *HACK - sets cloud scrolling to what we want... fix this better in the future - std::string sky = LLEnvManagerNew::instance().getSkyPresetName(); - if (!getParamSet(LLWLParamKey(sky, LLWLParamKey::SCOPE_LOCAL), mCurParams)) - { - LL_WARNS() << "No sky preset named " << sky << ", falling back to defaults" << LL_ENDL; - getParamSet(LLWLParamKey("Default", LLWLParamKey::SCOPE_LOCAL), mCurParams); - - // *TODO: Fix user preferences accordingly. - } - - // set it to noon - resetAnimator(0.5, LLEnvManagerNew::instance().getUseDayCycle()); - // but use linden time sets it to what the estate is mAnimator.setTimeType(LLWLAnimator::TIME_LINDEN); } diff --git a/indra/newview/llwlparammanager.h b/indra/newview/llwlparammanager.h index db3c285e76..a8029839a7 100644 --- a/indra/newview/llwlparammanager.h +++ b/indra/newview/llwlparammanager.h @@ -29,7 +29,6 @@ #include #include -#include "llenvmanager.h" #include "llwlparamset.h" #include "llwlanimator.h" #include "llwldaycycle.h" @@ -246,6 +245,8 @@ public: /// apply specified fixed sky params bool applySkyParams(const LLSD& params); + void setDefaultSky(); + // get where the light is pointing inline LLVector4 getLightDir(void) const; @@ -305,7 +306,7 @@ public: void addAllSkies(LLEnvKey::EScope scope, const LLSD& preset_map); /// refresh region-scope presets - void refreshRegionPresets(); + void refreshRegionPresets(const LLSD& region_sky_presets); // returns all skies referenced by the current day cycle (in mDay), with their final names // side effect: applies changes to all internal structures! (trashes all unreferenced skies in scope, keys in day cycle rescoped to scope, etc.) -- cgit v1.3 From 9c66072cacae0b3b86a277780e3a19e94accd6bc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 10 May 2017 15:04:18 -0400 Subject: Add LLEventThrottle tests; actually *all* lleventfilter.cpp tests. For some reason there wasn't an entry in indra/llcommon/CMakeLists.txt to run the tests in indra/llcommon/tests/lleventfilter_test.cpp. It seems likely that at some point it existed, since all previous tests built and ran successfully. In any case, (re-)add lleventfilter_test.cpp to the set of llcommon tests. Also alphabetize them to make it easier to find a particular test invocation. Also add new tests for LLEventThrottle. To support this, refactor the concrete LLEventThrottle class into LLEventThrottleBase containing all the tricky logic, with pure virtual methods for access to LLTimer and LLEventTimeout, and an LLEventThrottle subclass containing the LLTimer and LLEventTimeout instances and corresponding implementations of the new pure virtual methods. That permits us to introduce TestEventThrottle, an alternate subclass with dummy implementations of the methods related to LLTimer and LLEventTimeout. In particular, we can explicitly advance simulated realtime to simulate particular LLTimer and LLEventTimeout behaviors. Finally, introduce Concat, a test LLEventPump listener class whose function is to concatenate received string event data into a composite string so we can readily test for particular sequences of events. --- indra/llcommon/CMakeLists.txt | 15 ++-- indra/llcommon/lleventfilter.cpp | 96 +++++++++++++++++---- indra/llcommon/lleventfilter.h | 49 +++++++++-- indra/llcommon/tests/listener.h | 11 +++ indra/llcommon/tests/lleventfilter_test.cpp | 124 +++++++++++++++++++++++++++- 5 files changed, 262 insertions(+), 33 deletions(-) (limited to 'indra/llcommon') 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 87eb583cb6..ddd7d5547a 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") { @@ -153,6 +162,9 @@ bool LLEventTimeoutBase::running() const return mMainloop.connected(); } +/***************************************************************************** +* LLEventTimeout +*****************************************************************************/ LLEventTimeout::LLEventTimeout() {} LLEventTimeout::LLEventTimeout(LLEventPump& source): @@ -170,6 +182,9 @@ bool LLEventTimeout::countdownElapsed() const return mTimer.hasExpired(); } +/***************************************************************************** +* LLEventBatch +*****************************************************************************/ LLEventBatch::LLEventBatch(std::size_t size): LLEventFilter("batch"), mBatchSize(size) @@ -208,19 +223,22 @@ void LLEventBatch::setSize(std::size_t size) } } -LLEventThrottle::LLEventThrottle(F32 interval): +/***************************************************************************** +* LLEventThrottleBase +*****************************************************************************/ +LLEventThrottleBase::LLEventThrottleBase(F32 interval): LLEventFilter("throttle"), mInterval(interval), mPosts(0) {} -LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval): +LLEventThrottleBase::LLEventThrottleBase(LLEventPump& source, F32 interval): LLEventFilter(source, "throttle"), mInterval(interval), mPosts(0) {} -void LLEventThrottle::flush() +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 @@ -228,11 +246,11 @@ void LLEventThrottle::flush() if (mPosts) { mPosts = 0; - mAlarm.cancel(); + 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. - mTimer.setTimerExpirySec(mInterval); + timerSet(mInterval); // copy and clear mPending BEFORE posting to avoid weird circularity // effects LLSD pending = mPending; @@ -241,12 +259,12 @@ void LLEventThrottle::flush() } } -LLSD LLEventThrottle::pending() const +LLSD LLEventThrottleBase::pending() const { return mPending; } -bool LLEventThrottle::post(const LLSD& event) +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 @@ -257,7 +275,7 @@ bool LLEventThrottle::post(const LLSD& event) ++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 = mTimer.getRemainingTimeF32(); + F32 timeRemaining = timerGetRemaining(); if (! timeRemaining) { // more than enough time has elapsed, immediately flush() @@ -266,24 +284,24 @@ bool LLEventThrottle::post(const LLSD& event) else { // still within mInterval of the last flush() call: have to defer - if (! mAlarm.running()) + 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. - mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this)); + alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this)); } } return false; } -void LLEventThrottle::setInterval(F32 interval) +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 = mTimer.getRemainingTimeF32(); + F32 timeRemaining = timerGetRemaining(); if (timeRemaining) { // We are currently within oldInterval of the last flush(). Figure out @@ -305,16 +323,60 @@ void LLEventThrottle::setInterval(F32 interval) else { // immediately reset mTimer - mTimer.setTimerExpirySec(timeRemaining); + timerSet(timeRemaining); // and if mAlarm is running, reset that too - if (mAlarm.running()) + if (alarmRunning()) { - mAlarm.actionAfter(timeRemaining, boost::bind(&LLEventThrottle::flush, this)); + 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) {} @@ -326,5 +388,7 @@ LLEventBatchThrottle::LLEventBatchThrottle(LLEventPump& source, F32 interval): bool LLEventBatchThrottle::post(const LLSD& event) { // simply retrieve pending value and append the new event to it - return LLEventThrottle::post(pending().append(event)); + LLSD partial = pending(); + partial.append(event); + return LLEventThrottle::post(partial); } diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index 68890846a7..cae18bfd86 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -247,7 +247,7 @@ private: }; /** - * LLEventThrottle: construct with a time interval. Regardless of how + * 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. * @@ -268,21 +268,24 @@ private: * 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, you may obtain the previous event - * blob by calling pending(), modify it however you want and post() the new - * value. Each time an event is delivered to listeners, the pending() value is - * reset to isUndefined(). + * 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 LLEventThrottle: public LLEventFilter +class LL_COMMON_API LLEventThrottleBase: public LLEventFilter { public: // pass time interval - LLEventThrottle(F32 interval); + LLEventThrottleBase(F32 interval); // construct and connect - LLEventThrottle(LLEventPump& source, F32 interval); + LLEventThrottleBase(LLEventPump& source, F32 interval); // force out any deferred events void flush(); @@ -301,7 +304,17 @@ public: std::size_t getPostCount() const { return mPosts; } // time until next event would be passed through, 0.0 if now - F32 getDelay() const { return mTimer.getRemainingTimeF32(); } + 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 @@ -310,6 +323,24 @@ private: 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() 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..712864bf63 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 /***************************************************************************** -- cgit v1.3 From 4d87ded8862f032682a66704b9c68ef5626779ea Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 10 May 2017 17:37:06 -0400 Subject: Add size limit to LLEventBatchThrottle like LLEventBatch. The new behavior is that it will flush when either the pending batch has grown to the specified size, or the time interval has expired. --- indra/llcommon/lleventfilter.cpp | 35 ++++++++++++++++++++++++++--------- indra/llcommon/lleventfilter.h | 30 +++++++++++++++++++++--------- 2 files changed, 47 insertions(+), 18 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp index ddd7d5547a..9fb18dc67d 100644 --- a/indra/llcommon/lleventfilter.cpp +++ b/indra/llcommon/lleventfilter.cpp @@ -206,10 +206,8 @@ void LLEventBatch::flush() bool LLEventBatch::post(const LLSD& event) { mBatch.append(event); - if (mBatch.size() >= mBatchSize) - { - flush(); - } + // calling setSize(same) performs the very check we want + setSize(mBatchSize); return false; } @@ -377,12 +375,14 @@ F32 LLEventThrottle::timerGetRemaining() const /***************************************************************************** * LLEventBatchThrottle *****************************************************************************/ -LLEventBatchThrottle::LLEventBatchThrottle(F32 interval): - LLEventThrottle(interval) +LLEventBatchThrottle::LLEventBatchThrottle(F32 interval, std::size_t size): + LLEventThrottle(interval), + mBatchSize(size) {} -LLEventBatchThrottle::LLEventBatchThrottle(LLEventPump& source, F32 interval): - LLEventThrottle(source, interval) +LLEventBatchThrottle::LLEventBatchThrottle(LLEventPump& source, F32 interval, std::size_t size): + LLEventThrottle(source, interval), + mBatchSize(size) {} bool LLEventBatchThrottle::post(const LLSD& event) @@ -390,5 +390,22 @@ 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); + 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 cae18bfd86..ccbbc8bd25 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -270,14 +270,15 @@ private: * 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(). + * 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. + * alternate "timer" that doesn't actually consume real time. See + * LLEventThrottle. */ class LL_COMMON_API LLEventThrottleBase: public LLEventFilter { @@ -348,20 +349,31 @@ private: }; /** - * 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. + * 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 - LLEventBatchThrottle(F32 interval); + // 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); + 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) */ -- cgit v1.3 From 4a90678cc7406b0c2c8dad5691cdc2555186931b Mon Sep 17 00:00:00 2001 From: AndreyL ProductEngine Date: Thu, 18 May 2017 03:13:57 +0300 Subject: Linux buildfix; this should be reverted after gcc update to 4.7+ --- indra/llcommon/lleventfilter.h | 10 +++++----- indra/llcommon/tests/lleventfilter_test.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index ccbbc8bd25..ff8fc9bc7f 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -336,11 +336,11 @@ public: 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; + 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; diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp index 712864bf63..eb98b12ef5 100644 --- a/indra/llcommon/tests/lleventfilter_test.cpp +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -88,29 +88,29 @@ public: {} /*----- implementation of LLEventThrottleBase timing functionality -----*/ - virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) override + virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) /*override*/ { mAlarmRemaining = interval; mAlarmAction = action; } - virtual bool alarmRunning() const override + virtual bool alarmRunning() const /*override*/ { // decrementing to exactly 0 should mean the alarm fires return mAlarmRemaining > 0; } - virtual void alarmCancel() override + virtual void alarmCancel() /*override*/ { mAlarmRemaining = -1; } - virtual void timerSet(F32 interval) override + virtual void timerSet(F32 interval) /*override*/ { mTimerRemaining = interval; } - virtual F32 timerGetRemaining() const override + virtual F32 timerGetRemaining() const /*override*/ { // LLTimer.getRemainingTimeF32() never returns negative; 0.0 means expired return (mTimerRemaining > 0.0)? mTimerRemaining : 0.0; -- cgit v1.3