summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llcommon/CMakeLists.txt2
-rw-r--r--indra/llcommon/throttle.cpp34
-rw-r--r--indra/llcommon/throttle.h137
-rw-r--r--indra/newview/groupchatlistener.cpp19
-rw-r--r--indra/newview/groupchatlistener.h4
5 files changed, 184 insertions, 12 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 20670d7ebe..4c4a676531 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -112,6 +112,7 @@ set(llcommon_SOURCE_FILES
lua_function.cpp
lualistener.cpp
threadpool.cpp
+ throttle.cpp
u64.cpp
workqueue.cpp
StackWalker.cpp
@@ -263,6 +264,7 @@ set(llcommon_HEADER_FILES
threadpool.h
threadpool_fwd.h
threadsafeschedule.h
+ throttle.h
timer.h
tuple.h
u64.h
diff --git a/indra/llcommon/throttle.cpp b/indra/llcommon/throttle.cpp
new file mode 100644
index 0000000000..d5f7d5dcab
--- /dev/null
+++ b/indra/llcommon/throttle.cpp
@@ -0,0 +1,34 @@
+/**
+ * @file throttle.cpp
+ * @author Nat Goodspeed
+ * @date 2024-08-12
+ * @brief Implementation for ThrottleBase.
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "throttle.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "lltimer.h"
+
+bool ThrottleBase::too_fast()
+{
+ F64 now = LLTimer::getElapsedSeconds();
+ if (now < mNext)
+ {
+ return true;
+ }
+ else
+ {
+ mNext = now + mInterval;
+ return false;
+ }
+}
diff --git a/indra/llcommon/throttle.h b/indra/llcommon/throttle.h
new file mode 100644
index 0000000000..abf595e3c4
--- /dev/null
+++ b/indra/llcommon/throttle.h
@@ -0,0 +1,137 @@
+/**
+ * @file throttle.h
+ * @author Nat Goodspeed
+ * @date 2024-08-12
+ * @brief Throttle class
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_THROTTLE_H)
+#define LL_THROTTLE_H
+
+#include "apply.h" // LL::bind_front()
+#include "llerror.h"
+#include <functional>
+#include <iomanip> // std::quoted()
+
+class ThrottleBase
+{
+public:
+ ThrottleBase(F64 interval):
+ mInterval(interval)
+ {}
+
+protected:
+ bool too_fast(); // not const: we update mNext
+ F64 mInterval, mNext{ 0. };
+};
+
+/**
+ * An instance of Throttle mediates calls to some other specified function,
+ * ensuring that it's called no more often than the specified time interval.
+ * Throttle is an abstract base class that delegates the behavior when the
+ * specified interval is exceeded.
+ */
+template <typename SIGNATURE>
+class Throttle: public ThrottleBase
+{
+public:
+ Throttle(const std::string& desc,
+ const std::function<SIGNATURE>& func,
+ F64 interval):
+ ThrottleBase(interval),
+ mDesc(desc),
+ mFunc(func)
+ {}
+ // Constructing Throttle with a member function pointer but without an
+ // instance pointer requires you to pass the instance pointer/reference as
+ // the first argument to operator()().
+ template <typename R, class C>
+ Throttle(const std::string& desc, R C::* method, F64 interval):
+ Throttle(desc, std::mem_fn(method), interval)
+ {}
+ template <typename R, class C>
+ Throttle(const std::string& desc, R C::* method, C* instance, F64 interval):
+ Throttle(desc, LL::bind_front(method, instance), interval)
+ {}
+ template <typename R, class C>
+ Throttle(const std::string& desc, R C::* method, const C* instance, F64 interval):
+ Throttle(desc, LL::bind_front(method, instance), interval)
+ {}
+
+ template <typename... ARGS>
+ auto operator()(ARGS... args)
+ {
+ if (too_fast())
+ {
+ suppress();
+ using rtype = decltype(mFunc(std::forward<ARGS>(args)...));
+ if constexpr (! std::is_same_v<rtype, void>)
+ {
+ return rtype{};
+ }
+ }
+ else
+ {
+ return mFunc(std::forward<ARGS>(args)...);
+ }
+ }
+
+protected:
+ // override with desired behavior when calls come too often
+ virtual void suppress() = 0;
+ const std::string mDesc;
+
+private:
+ std::function<SIGNATURE> mFunc;
+};
+
+/**
+ * An instance of LogThrottle mediates calls to some other specified function,
+ * ensuring that it's called no more often than the specified time interval.
+ * When that interval is exceeded, it logs a message at the specified log
+ * level. It uses LL_MUMBLES_ONCE() logic to prevent spamming, since a too-
+ * frequent call may well be spammy.
+ */
+template <LLError::ELevel LOGLEVEL, typename SIGNATURE>
+class LogThrottle: public Throttle<SIGNATURE>
+{
+ using super = Throttle<SIGNATURE>;
+public:
+ LogThrottle(const std::string& desc,
+ const std::function<SIGNATURE>& func,
+ F64 interval):
+ super(desc, func, interval)
+ {}
+ template <typename R, class C>
+ LogThrottle(const std::string& desc, R C::* method, F64 interval):
+ super(desc, method, interval)
+ {}
+ template <typename R, class C>
+ LogThrottle(const std::string& desc, R C::* method, C* instance, F64 interval):
+ super(desc, method, instance, interval)
+ {}
+ template <typename R, class C>
+ LogThrottle(const std::string& desc, R C::* method, const C* instance, F64 interval):
+ super(desc, method, instance, interval)
+ {}
+
+private:
+ void suppress() override
+ {
+ // Using lllog(), the macro underlying LL_WARNS() et al., allows
+ // specifying compile-time LOGLEVEL. It does NOT support a variable
+ // LOGLEVEL, which is why LOGLEVEL is a non-type template parameter.
+ // See llvlog() for variable support, which is a bit more expensive.
+ // true = only print the log message once
+ lllog(LOGLEVEL, true, "LogThrottle") << std::quoted(super::mDesc)
+ << " called more than once per "
+ << super::mInterval
+ << LL_ENDL;
+ }
+};
+
+#endif /* ! defined(LL_THROTTLE_H) */
diff --git a/indra/newview/groupchatlistener.cpp b/indra/newview/groupchatlistener.cpp
index 09951ba1cc..298f41ff8c 100644
--- a/indra/newview/groupchatlistener.cpp
+++ b/indra/newview/groupchatlistener.cpp
@@ -43,7 +43,8 @@ static const F32 GROUP_CHAT_THROTTLE_PERIOD = 1.f;
LLGroupChatListener::LLGroupChatListener():
LLEventAPI("GroupChat",
"API to enter, leave, send and intercept group chat messages"),
- mLastThrottleTime(0)
+ mIMThrottle("sendGroupIM", &LLGroupChatListener::sendGroupIM_, this,
+ GROUP_CHAT_THROTTLE_PERIOD)
{
add("startGroupChat",
"Enter a group chat in group with UUID [\"group_id\"]\n"
@@ -101,18 +102,14 @@ void LLGroupChatListener::sendGroupIM(LLSD const &data)
return;
}
- F64 cur_time = LLTimer::getElapsedSeconds();
-
- if (cur_time < mLastThrottleTime + GROUP_CHAT_THROTTLE_PERIOD)
- {
- LL_DEBUGS("LLGroupChatListener") << "'sendGroupIM' was throttled" << LL_ENDL;
- return;
- }
- mLastThrottleTime = cur_time;
+ mIMThrottle(data["group_id"], data["message"]);
+}
- LLUUID group_id(data["group_id"]);
- LLIMModel::sendMessage(LUA_PREFIX + data["message"].asString(),
+void LLGroupChatListener::sendGroupIM_(const LLUUID& group_id, const std::string& message)
+{
+ LLIMModel::sendMessage(LUA_PREFIX + message,
gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id),
group_id,
IM_SESSION_SEND);
}
+
diff --git a/indra/newview/groupchatlistener.h b/indra/newview/groupchatlistener.h
index 35afc5766c..a75fecb254 100644
--- a/indra/newview/groupchatlistener.h
+++ b/indra/newview/groupchatlistener.h
@@ -30,6 +30,7 @@
#define LL_LLGROUPCHATLISTENER_H
#include "lleventapi.h"
+#include "throttle.h"
class LLGroupChatListener: public LLEventAPI
{
@@ -40,8 +41,9 @@ private:
void startGroupChat(LLSD const &data);
void leaveGroupChat(LLSD const &data);
void sendGroupIM(LLSD const &data);
+ void sendGroupIM_(const LLUUID& group_id, const std::string& message);
- F64 mLastThrottleTime {0.0};
+ LogThrottle<LLError::LEVEL_DEBUG, void(const LLUUID&, const std::string&)> mIMThrottle;
};
#endif /* ! defined(LL_LLGROUPCHATLISTENER_H) */