From d3b1859ca3d6c2c2bcc92edba994b522cf9d4e99 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 6 Aug 2024 15:55:58 -0400 Subject: Introduce a custom coroutine/fiber scheduler to prioritize UI. The viewer's main thread's main fiber is responsible for coordinating just about everything. With the default round_robin fiber scheduling algorithm, launching too many additional fibers could starve the main fiber, resulting in visible lag. This custom scheduler tracks when it switches to and from the main fiber, and at each context switch, how long it's been since the last time the main fiber ran. If that exceeds a certain timeslice, it jumps the main fiber to the head of the queue and resumes that instead of any other ready fiber. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/coro_scheduler.cpp | 164 ++++++++++++++++++++++++++++++++++++++ indra/llcommon/coro_scheduler.h | 73 +++++++++++++++++ indra/newview/llstartup.cpp | 5 +- 4 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 indra/llcommon/coro_scheduler.cpp create mode 100644 indra/llcommon/coro_scheduler.h diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 20670d7ebe..221fc2bb3f 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -18,6 +18,7 @@ include(Tracy) set(llcommon_SOURCE_FILES apply.cpp commoncontrol.cpp + coro_scheduler.cpp hbxxh.cpp indra_constants.cpp lazyeventapi.cpp @@ -125,6 +126,7 @@ set(llcommon_HEADER_FILES chrono.h classic_callback.h commoncontrol.h + coro_scheduler.h ctype_workaround.h fix_macros.h fsyspath.h diff --git a/indra/llcommon/coro_scheduler.cpp b/indra/llcommon/coro_scheduler.cpp new file mode 100644 index 0000000000..337162cbd5 --- /dev/null +++ b/indra/llcommon/coro_scheduler.cpp @@ -0,0 +1,164 @@ +/** + * @file coro_scheduler.cpp + * @author Nat Goodspeed + * @date 2024-08-05 + * @brief Implementation for llcoro::scheduler. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "coro_scheduler.h" +// STL headers +// std headers +#include +// external library headers +#include +// other Linden headers +#include "llcallbacklist.h" +#include "lldate.h" +#include "llerror.h" + +namespace llcoro +{ + +const F32 scheduler::DEFAULT_TIMESLICE{ LL::Timers::DEFAULT_TIMESLICE }; + +const std::string qname("General"); + +scheduler::scheduler(): + // Since use_scheduling_algorithm() must be called before any other + // Boost.Fibers operations, we can assume that the calling fiber is in + // fact the main fiber. + mMainID(boost::this_fiber::get_id()), + mStart(LLDate::now().secondsSinceEpoch()), + mQueue(LL::WorkQueue::getInstance(qname)) +{} + +void scheduler::awakened( boost::fibers::context* ctx) noexcept +{ + if (ctx->get_id() == mMainID) + { + // If the fiber that just came ready is the main fiber, record its + // pointer. + llassert(! mMainCtx); + mMainCtx = ctx; + } + // Delegate to round_robin::awakened() as usual, even for the main fiber. + // This way, as long as other fibers don't take too long, we can just let + // normal round_robin processing pass control to the main fiber. + super::awakened(ctx); +} + +boost::fibers::context* scheduler::pick_next() noexcept +{ + // count calls to pick_next() + ++mSwitches; + // pick_next() is called when the previous fiber has suspended, and we + // need to pick another. Did the previous pick_next() call pick the main + // fiber? If so, it's the main fiber that just suspended. + auto now = LLDate::now().secondsSinceEpoch(); + if (mMainRunning) + { + mMainRunning = false; + mMainLast = now; + } + + boost::fibers::context* next; + + // When the main fiber is ready, and it's been more than mTimeslice since + // the main fiber last ran, it's time to intervene. + F32 elapsed(now - mMainLast); + if (mMainCtx && elapsed > mTimeslice) + { + // We claim that the main fiber is not only stored in mMainCtx, but is + // also queued (somewhere) in our ready list. + llassert(mMainCtx->ready_is_linked()); + // The usefulness of a doubly-linked list is that, given only a + // pointer to an item, we can unlink it. + mMainCtx->ready_unlink(); + // Instead of delegating to round_robin::pick_next() to pop the head + // of the queue, override by returning mMainCtx. + next = mMainCtx; + + /*------------------------- logging stuff --------------------------*/ + // Unless this log tag is enabled, don't even bother posting. + LL_DEBUGS("LLCoros.scheduler"); + // This feature is inherently hard to verify. The logging in the + // lambda below seems useful, but also seems like a lot of overhead + // for a coroutine context switch. Try posting the logging lambda to a + // ThreadPool to offload that overhead. However, if this is still + // taking an unreasonable amount of context-switch time, this whole + // passage could be skipped. + + // Record this event for logging, but push it off to a thread pool to + // perform that work. Presumably std::weak_ptr::lock() is cheaper than + // WorkQueue::getInstance(). + LL::WorkQueue::ptr_t queue{ mQueue.lock() }; + // We probably started before the relevant WorkQueue was created. + if (! queue) + { + // Try again to locate the specified WorkQueue. + queue = LL::WorkQueue::getInstance(qname); + mQueue = queue; + } + // Both the lock() call and the getInstance() call might have failed. + if (queue) + { + // Bind values. Do NOT bind 'this' to avoid cross-thread access! + // It would be interesting to know from what queue position we + // unlinked the main fiber, out of how many in the ready list. + // Unfortunately round_robin::rqueue_ is private, not protected, + // so we have no access. + queue->post( + [switches=mSwitches, start=mStart, elapsed, now] + () + { + U32 runtime(U32(now) - U32(start)); + U32 minutes(runtime / 60u); + U32 seconds(runtime % 60u); + // use stringize to avoid lasting side effects to the + // logging ostream + LL_DEBUGS("LLCoros.scheduler") + << "At time " + << stringize(minutes, ":", std::setw(2), std::setfill('0'), seconds) + << " (" << switches << " switches), coroutines took " + << stringize(std::setprecision(4), elapsed) + << " sec, main coroutine jumped queue" + << LL_ENDL; + }); + } + LL_ENDL; + /*----------------------- end logging stuff ------------------------*/ + } + else + { + // Either the main fiber isn't yet ready, or it hasn't yet been + // mTimeslice seconds since the last time the main fiber ran. Business + // as usual. + next = super::pick_next(); + } + + // super::pick_next() could also have returned the main fiber, which is + // why this is a separate test instead of being folded into the override + // case above. + if (next && next->get_id() == mMainID) + { + // we're about to resume the main fiber: it's no longer "ready" + mMainCtx = nullptr; + // instead, it's "running" + mMainRunning = true; + } + return next; +} + +void scheduler::use() +{ + boost::fibers::use_scheduling_algorithm(); +} + +} // namespace llcoro diff --git a/indra/llcommon/coro_scheduler.h b/indra/llcommon/coro_scheduler.h new file mode 100644 index 0000000000..a7572ccf4d --- /dev/null +++ b/indra/llcommon/coro_scheduler.h @@ -0,0 +1,73 @@ +/** + * @file coro_scheduler.h + * @author Nat Goodspeed + * @date 2024-08-05 + * @brief Custom scheduler for viewer's Boost.Fibers (aka coroutines) + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_CORO_SCHEDULER_H) +#define LL_CORO_SCHEDULER_H + +#include "workqueue.h" +#include +#include + +/** + * llcoro::scheduler is specifically intended for the viewer's main thread. + * Its role is to ensure that the main coroutine, responsible for UI + * operations and coordinating everything else, doesn't get starved by + * secondary coroutines -- however many of those there might be. + * + * The simple boost::fibers::algo::round_robin scheduler could result in + * arbitrary time lag between resumptions of the main coroutine. Of course + * every well-behaved viewer coroutine must be coded to yield before too much + * real time has elapsed, but sheer volume of secondary coroutines could still + * consume unreasonable real time before cycling back to the main coroutine. + */ + +namespace llcoro +{ + +class scheduler: public boost::fibers::algo::round_robin +{ + using super = boost::fibers::algo::round_robin; +public: + // If the main fiber is ready, and it's been at least this long since the + // main fiber last ran, jump the main fiber to the head of the queue. + static const F32 DEFAULT_TIMESLICE; + + scheduler(); + void awakened( boost::fibers::context*) noexcept override; + boost::fibers::context* pick_next() noexcept override; + + static void use(); + +private: + // This is the fiber::id of the main fiber. We use this to discover + // whether the fiber passed to awakened() is in fact the main fiber. + boost::fibers::fiber::id mMainID; + // This context* is nullptr until awakened() notices that the main fiber + // has become ready, at which point it contains the main fiber's context*. + boost::fibers::context* mMainCtx{}; + // Set when pick_next() returns the main fiber. + bool mMainRunning{ false }; + // If it's been at least this long since the last time the main fiber got + // control, jump it to the head of the queue. + F32 mTimeslice{ DEFAULT_TIMESLICE }; + // Timestamp as of the last time we suspended the main fiber. + F32 mMainLast{ 0 }; + // Timestamp of start time + F32 mStart{ 0 }; + // count context switches + U64 mSwitches{ 0 }; + // WorkQueue for deferred logging + LL::WorkQueue::weak_t mQueue; +}; + +} // namespace llcoro + +#endif /* ! defined(LL_CORO_SCHEDULER_H) */ diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 3cf0def66e..c7fb734440 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -48,6 +48,7 @@ #include "llaudioengine_openal.h" #endif +#include "coro_scheduler.h" #include "llavatarnamecache.h" #include "llexperiencecache.h" #include "lllandmark.h" @@ -400,10 +401,12 @@ bool idle_startup() static bool first_call = true; if (first_call) { + first_call = false; // Other phases get handled when startup state changes, // need to capture the initial state as well. LLStartUp::getPhases().startPhase(LLStartUp::getStartupStateString()); - first_call = false; + // Use our custom scheduler for coroutine scheduling. + llcoro::scheduler::use(); } gViewerWindow->showCursor(); -- cgit v1.2.3 From 08def53d8bf07beaa56db80e95aa76f3682c557c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 7 Aug 2024 16:01:45 -0400 Subject: Move llcoro::scheduler::use() call from llstartup to llappviewer. Thanks, Maxim. --- indra/newview/llappviewer.cpp | 3 ++- indra/newview/llstartup.cpp | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index ff29f64aeb..3d53200eda 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -765,7 +765,8 @@ bool LLAppViewer::init() //set the max heap size. initMaxHeapSize() ; LLCoros::instance().setStackSize(gSavedSettings.getS32("CoroutineStackSize")); - + // Use our custom scheduler for coroutine scheduling. + llcoro::scheduler::use(); // Although initLoggingAndGetLastDuration() is the right place to mess with // setFatalFunction(), we can't query gSavedSettings until after diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index c7fb734440..82eb59c002 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -405,8 +405,6 @@ bool idle_startup() // Other phases get handled when startup state changes, // need to capture the initial state as well. LLStartUp::getPhases().startPhase(LLStartUp::getStartupStateString()); - // Use our custom scheduler for coroutine scheduling. - llcoro::scheduler::use(); } gViewerWindow->showCursor(); -- cgit v1.2.3 From 3102dc0db472acac7c4007e7423e405a889d665a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 7 Aug 2024 16:12:23 -0400 Subject: Allow smaller minimum timer intervals. Add test_flycam.lua to exercise the smaller intervals. --- indra/llcommon/llcallbacklist.cpp | 2 +- indra/newview/scripts/lua/test_flycam.lua | 38 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 indra/newview/scripts/lua/test_flycam.lua diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index 555c793333..647b268b8b 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -391,7 +391,7 @@ public: TimersListener(const LazyEventAPIParams& params): LLEventAPI(params) {} // Forbid a script from requesting callbacks too quickly. - static constexpr LLSD::Real MINTIMER{ 1.0 }; + static constexpr LLSD::Real MINTIMER{ 0.010 }; void scheduleAfter(const LLSD& params); void scheduleEvery(const LLSD& params); diff --git a/indra/newview/scripts/lua/test_flycam.lua b/indra/newview/scripts/lua/test_flycam.lua new file mode 100644 index 0000000000..4fb5608b33 --- /dev/null +++ b/indra/newview/scripts/lua/test_flycam.lua @@ -0,0 +1,38 @@ +-- Make camera fly around the subject avatar for a few seconds. + +local LLAgent = require 'LLAgent' +local startup = require 'startup' +local timers = require 'timers' + +local initial = LLAgent.getRegionPosition() +local height = 2.0 -- meters +local radius = 4.0 -- meters +local speed = 1.0 -- meters/second along circle +local start = os.clock() +local stop = os.clock() + 30 -- seconds + +local function cameraPos(t) + local radians = speed * t + return { + initial[1] + radius * math.cos(radians), + initial[2] + radius * math.sin(radians), + initial[3] + height + } +end + +local function moveCamera() + if os.clock() < stop then + -- usual case + LLAgent.setCamera{ camera_pos=cameraPos(os.clock() - start), camera_locked=true } + return nil + else + -- last time + LLAgent.removeCamParams() + LLAgent.setFollowCamActive(false) + return true + end +end + +startup.wait('STATE_STARTED') +-- call moveCamera() repeatedly until it returns true +local timer = timers.Timer(0.1, moveCamera, true) -- cgit v1.2.3 From cdc4f5dbd69526906724dc298594906826f6b8b0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 7 Aug 2024 16:34:14 -0400 Subject: Move #include "coro_scheduler.h" from llstartup to llappviewer. --- indra/newview/llappviewer.cpp | 1 + indra/newview/llstartup.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 3d53200eda..c259275d8f 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -29,6 +29,7 @@ #include "llappviewer.h" // Viewer includes +#include "coro_scheduler.h" #include "llversioninfo.h" #include "llfeaturemanager.h" #include "lluictrlfactory.h" diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 82eb59c002..d49e0d9ba2 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -48,7 +48,6 @@ #include "llaudioengine_openal.h" #endif -#include "coro_scheduler.h" #include "llavatarnamecache.h" #include "llexperiencecache.h" #include "lllandmark.h" -- cgit v1.2.3 From b4fe47a5c0abac02d161640e04a9a78afb1c5987 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 8 Aug 2024 09:07:56 -0400 Subject: Ensure that the flycam stays near moving avatar. --- indra/newview/scripts/lua/test_flycam.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/indra/newview/scripts/lua/test_flycam.lua b/indra/newview/scripts/lua/test_flycam.lua index 4fb5608b33..05c3c37b93 100644 --- a/indra/newview/scripts/lua/test_flycam.lua +++ b/indra/newview/scripts/lua/test_flycam.lua @@ -4,7 +4,6 @@ local LLAgent = require 'LLAgent' local startup = require 'startup' local timers = require 'timers' -local initial = LLAgent.getRegionPosition() local height = 2.0 -- meters local radius = 4.0 -- meters local speed = 1.0 -- meters/second along circle @@ -12,11 +11,12 @@ local start = os.clock() local stop = os.clock() + 30 -- seconds local function cameraPos(t) + local agent = LLAgent.getRegionPosition() local radians = speed * t return { - initial[1] + radius * math.cos(radians), - initial[2] + radius * math.sin(radians), - initial[3] + height + agent[1] + radius * math.cos(radians), + agent[2] + radius * math.sin(radians), + agent[3] + height } end -- cgit v1.2.3 From 087cbe553e5bac6fe702200c33acc42baf4eef4f Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 9 Aug 2024 15:00:04 +0300 Subject: Lua api for sending group messages --- indra/newview/groupchatlistener.cpp | 100 +++++++++++++++++++-------- indra/newview/groupchatlistener.h | 21 ++++-- indra/newview/llgroupactions.cpp | 8 ++- indra/newview/lltoastimpanel.cpp | 4 +- indra/newview/scripts/lua/require/LLChat.lua | 25 +++++++ 5 files changed, 119 insertions(+), 39 deletions(-) diff --git a/indra/newview/groupchatlistener.cpp b/indra/newview/groupchatlistener.cpp index 43507f13e9..ab367d9fa1 100644 --- a/indra/newview/groupchatlistener.cpp +++ b/indra/newview/groupchatlistener.cpp @@ -2,11 +2,11 @@ * @file groupchatlistener.cpp * @author Nat Goodspeed * @date 2011-04-11 - * @brief Implementation for groupchatlistener. + * @brief Implementation for LLGroupChatListener. * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. + * Copyright (C) 2024, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -34,43 +34,85 @@ // std headers // external library headers // other Linden headers +#include "llchat.h" #include "llgroupactions.h" #include "llimview.h" +static const F32 GROUP_CHAT_THROTTLE_PERIOD = 1.f; -namespace { - void startIm_wrapper(LLSD const & event) +LLGroupChatListener::LLGroupChatListener(): + LLEventAPI("GroupChat", + "API to enter, leave, send and intercept group chat messages"), + mLastThrottleTime(0) +{ + add("startGroupChat", + "Enter a group chat in group with UUID [\"group_id\"]\n" + "Assumes the logged-in agent is already a member of this group.", + &LLGroupChatListener::startGroupChat, + llsd::map("group_id", LLSD())); + add("leaveGroupChat", + "Leave a group chat in group with UUID [\"group_id\"]\n" + "Assumes a prior successful startIM request.", + &LLGroupChatListener::leaveGroupChat, + llsd::map("group_id", LLSD())); + add("sendGroupIM", + "send a groupchat IM", + &LLGroupChatListener::sendGroupIM, + llsd::map("message", LLSD(), "group_id", LLSD())); +} + +bool is_in_group(LLEventAPI::Response &response, const LLSD &data) +{ + if (!LLGroupActions::isInGroup(data["group_id"])) { - LLUUID session_id = LLGroupActions::startIM(event["id"].asUUID()); - sendReply(LLSDMap("session_id", LLSD(session_id)), event); + response.error(stringize("You are not the member of the group:", std::quoted(data["group_id"].asString()))); + return false; } + return true; +} - void send_message_wrapper(const std::string& text, const LLUUID& session_id, const LLUUID& group_id) +void LLGroupChatListener::startGroupChat(LLSD const &data) +{ + Response response(LLSD(), data); + if (!is_in_group(response, data)) { - LLIMModel::sendMessage(text, session_id, group_id, IM_SESSION_GROUP_START); + return; + } + if (LLGroupActions::startIM(data["group_id"]).isNull()) + { + return response.error(stringize("Failed to start group chat session ", std::quoted(data["group_id"].asString()))); } } +void LLGroupChatListener::leaveGroupChat(LLSD const &data) +{ + Response response(LLSD(), data); + if (is_in_group(response, data)) + { + LLGroupActions::endIM(data["group_id"].asUUID()); + } +} -GroupChatListener::GroupChatListener(): - LLEventAPI("GroupChat", - "API to enter, leave, send and intercept group chat messages") +void LLGroupChatListener::sendGroupIM(LLSD const &data) { - add("startIM", - "Enter a group chat in group with UUID [\"id\"]\n" - "Assumes the logged-in agent is already a member of this group.", - &startIm_wrapper); - add("endIM", - "Leave a group chat in group with UUID [\"id\"]\n" - "Assumes a prior successful startIM request.", - &LLGroupActions::endIM, - llsd::array("id")); - add("sendIM", - "send a groupchat IM", - &send_message_wrapper, - llsd::array("text", "session_id", "group_id")); + Response response(LLSD(), data); + if (!is_in_group(response, 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; + + LLUUID group_id(data["group_id"]); + LLIMModel::sendMessage(LUA_PREFIX + data["message"].asString(), + gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id), + group_id, + IM_SESSION_GROUP_START); } -/* - static void sendMessage(const std::string& utf8_text, const LLUUID& im_session_id, - const LLUUID& other_participant_id, EInstantMessage dialog); -*/ diff --git a/indra/newview/groupchatlistener.h b/indra/newview/groupchatlistener.h index 3819ac59b7..35afc5766c 100644 --- a/indra/newview/groupchatlistener.h +++ b/indra/newview/groupchatlistener.h @@ -4,9 +4,9 @@ * @date 2011-04-11 * @brief * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. + * Copyright (C) 2024, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -26,15 +26,22 @@ * $/LicenseInfo$ */ -#if ! defined(LL_GROUPCHATLISTENER_H) -#define LL_GROUPCHATLISTENER_H +#if ! defined(LL_LLGROUPCHATLISTENER_H) +#define LL_LLGROUPCHATLISTENER_H #include "lleventapi.h" -class GroupChatListener: public LLEventAPI +class LLGroupChatListener: public LLEventAPI { public: - GroupChatListener(); + LLGroupChatListener(); + +private: + void startGroupChat(LLSD const &data); + void leaveGroupChat(LLSD const &data); + void sendGroupIM(LLSD const &data); + + F64 mLastThrottleTime {0.0}; }; -#endif /* ! defined(LL_GROUPCHATLISTENER_H) */ +#endif /* ! defined(LL_LLGROUPCHATLISTENER_H) */ diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index 24ae90e3ae..e901631d94 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -37,6 +37,7 @@ #include "llfloatersidepanelcontainer.h" #include "llgroupmgr.h" #include "llfloaterimcontainer.h" +#include "llfloaterimsession.h" #include "llimview.h" // for gIMMgr #include "llnotificationsutil.h" #include "llstartup.h" @@ -46,7 +47,7 @@ // // Globals // -static GroupChatListener sGroupChatListener; +static LLGroupChatListener sGroupChatListener; class LLGroupHandler : public LLCommandHandler { @@ -519,7 +520,10 @@ void LLGroupActions::endIM(const LLUUID& group_id) LLUUID session_id = gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id); if (session_id != LLUUID::null) { - gIMMgr->leaveSession(session_id); + if (LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(session_id)) + { + LLFloater::onClickClose(conversationFloater); + } } } diff --git a/indra/newview/lltoastimpanel.cpp b/indra/newview/lltoastimpanel.cpp index f7e2d49e13..7fadd8773b 100644 --- a/indra/newview/lltoastimpanel.cpp +++ b/indra/newview/lltoastimpanel.cpp @@ -87,7 +87,9 @@ LLToastIMPanel::LLToastIMPanel(LLToastIMPanel::Params &p) : LLToastPanel(p.notif { LLAvatarName avatar_name; LLAvatarNameCache::get(p.avatar_id, &avatar_name); - p.message = "[From " + avatar_name.getDisplayName() + "]\n" + p.message; + auto [message, is_lua] = LLStringUtil::withoutPrefix(p.message, LUA_PREFIX); + std::string prefix = is_lua ? "LUA - " : ""; + p.message = "[From " + prefix + avatar_name.getDisplayName() + "]\n" + message; } style_params.font.style = "NORMAL"; mMessage->setText(p.message, style_params); diff --git a/indra/newview/scripts/lua/require/LLChat.lua b/indra/newview/scripts/lua/require/LLChat.lua index 78dca765e8..ea6329d574 100644 --- a/indra/newview/scripts/lua/require/LLChat.lua +++ b/indra/newview/scripts/lua/require/LLChat.lua @@ -2,6 +2,10 @@ local leap = require 'leap' local LLChat = {} +-- *************************************************************************** +-- Nearby chat +-- *************************************************************************** + function LLChat.sendNearby(msg) leap.send('LLChatBar', {op='sendChat', message=msg}) end @@ -14,4 +18,25 @@ function LLChat.sendShout(msg) leap.send('LLChatBar', {op='sendChat', type='shout', message=msg}) end +-- 0 is public nearby channel, other channels are used to communicate with LSL scripts +function LLChat.sendChannel(msg, channel) + leap.send('LLChatBar', {op='sendChat', channel=channel, message=msg}) +end + +-- *************************************************************************** +-- Group chat +-- *************************************************************************** + +function LLChat.startGroupChat(group_id) + return leap.request('GroupChat', {op='startGroupChat', group_id=group_id}) +end + +function LLChat.leaveGroupChat(group_id) + leap.send('GroupChat', {op='leaveGroupChat', group_id=group_id}) +end + +function LLChat.sendGroupIM(msg, group_id) + leap.send('GroupChat', {op='sendGroupIM', message=msg, group_id=group_id}) +end + return LLChat -- cgit v1.2.3 From 926a32aa0a9518fe7f19d0c63b30b12a66469f2d Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 9 Aug 2024 18:24:25 +0300 Subject: add demo script for sending group chat messages --- indra/newview/groupchatlistener.cpp | 4 ++-- indra/newview/scripts/lua/require/LLAgent.lua | 11 +++++++++++ indra/newview/scripts/lua/test_group_chat.lua | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 indra/newview/scripts/lua/test_group_chat.lua diff --git a/indra/newview/groupchatlistener.cpp b/indra/newview/groupchatlistener.cpp index ab367d9fa1..09951ba1cc 100644 --- a/indra/newview/groupchatlistener.cpp +++ b/indra/newview/groupchatlistener.cpp @@ -56,7 +56,7 @@ LLGroupChatListener::LLGroupChatListener(): &LLGroupChatListener::leaveGroupChat, llsd::map("group_id", LLSD())); add("sendGroupIM", - "send a groupchat IM", + "send a [\"message\"] to group with UUID [\"group_id\"]", &LLGroupChatListener::sendGroupIM, llsd::map("message", LLSD(), "group_id", LLSD())); } @@ -114,5 +114,5 @@ void LLGroupChatListener::sendGroupIM(LLSD const &data) LLIMModel::sendMessage(LUA_PREFIX + data["message"].asString(), gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id), group_id, - IM_SESSION_GROUP_START); + IM_SESSION_SEND); } diff --git a/indra/newview/scripts/lua/require/LLAgent.lua b/indra/newview/scripts/lua/require/LLAgent.lua index bc9a6b23a0..5ee092f2f6 100644 --- a/indra/newview/scripts/lua/require/LLAgent.lua +++ b/indra/newview/scripts/lua/require/LLAgent.lua @@ -11,6 +11,17 @@ function LLAgent.getGlobalPosition() return leap.request('LLAgent', {op = 'getPosition'}).global end +-- Return array information about the agent's groups +-- id: group id\n" +-- name: group name\n" +-- insignia: group insignia texture id +-- notices: bool indicating if this user accepts notices from this group +-- display: bool indicating if this group is listed in the user's profile +-- contrib: user's land contribution to this group +function LLAgent.getGroups() + return leap.request('LLAgent', {op = 'getGroups'}).groups +end + -- Use LL.leaphelp('LLAgent') and see 'setCameraParams' to get more info about params -- -- TYPE -- DEFAULT -- RANGE -- LLAgent.setCamera{ [, camera_pos] -- vector3 diff --git a/indra/newview/scripts/lua/test_group_chat.lua b/indra/newview/scripts/lua/test_group_chat.lua new file mode 100644 index 0000000000..6299ba535f --- /dev/null +++ b/indra/newview/scripts/lua/test_group_chat.lua @@ -0,0 +1,16 @@ +LLChat = require 'LLChat' +inspect = require 'inspect' +LLAgent = require 'LLAgent' +popup = require 'popup' + +local OK = 'OK_okcancelbuttons' +local GROUPS = LLAgent.getGroups() + +-- Choose one of the groups randomly and send group message +math.randomseed(os.time()) +group_info = GROUPS[math.random(#GROUPS)] +LLChat.startGroupChat(group_info.id) +response = popup:alertYesCancel('Started group chat with ' .. group_info.name .. ' group. Send greetings?') +if next(response) == OK then + LLChat.sendGroupIM('Greetings', group_info.id) +end -- cgit v1.2.3 From a596c1ad2864d639bbab13e7f13fd9f029cf5d93 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Aug 2024 15:58:03 -0400 Subject: Add Throttle and LogThrottle classes to manage throttled APIs. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/throttle.cpp | 34 +++++++++ indra/llcommon/throttle.h | 137 ++++++++++++++++++++++++++++++++++++ indra/newview/groupchatlistener.cpp | 19 +++-- indra/newview/groupchatlistener.h | 4 +- 5 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 indra/llcommon/throttle.cpp create mode 100644 indra/llcommon/throttle.h 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 +#include // 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 +class Throttle: public ThrottleBase +{ +public: + Throttle(const std::string& desc, + const std::function& 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 + Throttle(const std::string& desc, R C::* method, F64 interval): + Throttle(desc, std::mem_fn(method), interval) + {} + template + Throttle(const std::string& desc, R C::* method, C* instance, F64 interval): + Throttle(desc, LL::bind_front(method, instance), interval) + {} + template + Throttle(const std::string& desc, R C::* method, const C* instance, F64 interval): + Throttle(desc, LL::bind_front(method, instance), interval) + {} + + template + auto operator()(ARGS... args) + { + if (too_fast()) + { + suppress(); + using rtype = decltype(mFunc(std::forward(args)...)); + if constexpr (! std::is_same_v) + { + return rtype{}; + } + } + else + { + return mFunc(std::forward(args)...); + } + } + +protected: + // override with desired behavior when calls come too often + virtual void suppress() = 0; + const std::string mDesc; + +private: + std::function 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 +class LogThrottle: public Throttle +{ + using super = Throttle; +public: + LogThrottle(const std::string& desc, + const std::function& func, + F64 interval): + super(desc, func, interval) + {} + template + LogThrottle(const std::string& desc, R C::* method, F64 interval): + super(desc, method, interval) + {} + template + LogThrottle(const std::string& desc, R C::* method, C* instance, F64 interval): + super(desc, method, instance, interval) + {} + template + 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 mIMThrottle; }; #endif /* ! defined(LL_LLGROUPCHATLISTENER_H) */ -- cgit v1.2.3 From 153b0573cd91361a0f674c0ce1b9d7cb47e2bbc0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 12 Aug 2024 16:28:44 -0400 Subject: Add virtual destructor to Throttle class. --- indra/llcommon/throttle.h | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/llcommon/throttle.h b/indra/llcommon/throttle.h index abf595e3c4..481d1d8f72 100644 --- a/indra/llcommon/throttle.h +++ b/indra/llcommon/throttle.h @@ -61,6 +61,7 @@ public: Throttle(const std::string& desc, R C::* method, const C* instance, F64 interval): Throttle(desc, LL::bind_front(method, instance), interval) {} + virtual ~Throttle() {} template auto operator()(ARGS... args) -- cgit v1.2.3 From 9f2e322c7eea6830d372943d74f986d299cd314a Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 13 Aug 2024 14:50:04 +0300 Subject: clean up and add comment --- indra/newview/groupchatlistener.cpp | 2 +- indra/newview/lltoastimpanel.cpp | 1 + indra/newview/scripts/lua/require/LLChat.lua | 10 +++------- indra/newview/scripts/lua/test_group_chat.lua | 1 - 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/indra/newview/groupchatlistener.cpp b/indra/newview/groupchatlistener.cpp index 298f41ff8c..e48cbe824a 100644 --- a/indra/newview/groupchatlistener.cpp +++ b/indra/newview/groupchatlistener.cpp @@ -38,7 +38,7 @@ #include "llgroupactions.h" #include "llimview.h" -static const F32 GROUP_CHAT_THROTTLE_PERIOD = 1.f; +static const F64 GROUP_CHAT_THROTTLE_PERIOD = 1.f; LLGroupChatListener::LLGroupChatListener(): LLEventAPI("GroupChat", diff --git a/indra/newview/lltoastimpanel.cpp b/indra/newview/lltoastimpanel.cpp index 7fadd8773b..b7353d6960 100644 --- a/indra/newview/lltoastimpanel.cpp +++ b/indra/newview/lltoastimpanel.cpp @@ -87,6 +87,7 @@ LLToastIMPanel::LLToastIMPanel(LLToastIMPanel::Params &p) : LLToastPanel(p.notif { LLAvatarName avatar_name; LLAvatarNameCache::get(p.avatar_id, &avatar_name); + // move Lua prefix from the message field to the [From] field auto [message, is_lua] = LLStringUtil::withoutPrefix(p.message, LUA_PREFIX); std::string prefix = is_lua ? "LUA - " : ""; p.message = "[From " + prefix + avatar_name.getDisplayName() + "]\n" + message; diff --git a/indra/newview/scripts/lua/require/LLChat.lua b/indra/newview/scripts/lua/require/LLChat.lua index ea6329d574..bc0fc86d22 100644 --- a/indra/newview/scripts/lua/require/LLChat.lua +++ b/indra/newview/scripts/lua/require/LLChat.lua @@ -6,8 +6,9 @@ local LLChat = {} -- Nearby chat -- *************************************************************************** -function LLChat.sendNearby(msg) - leap.send('LLChatBar', {op='sendChat', message=msg}) +-- 0 is public nearby channel, other channels are used to communicate with LSL scripts +function LLChat.sendNearby(msg, channel) + leap.send('LLChatBar', {op='sendChat', message=msg, channel=channel}) end function LLChat.sendWhisper(msg) @@ -18,11 +19,6 @@ function LLChat.sendShout(msg) leap.send('LLChatBar', {op='sendChat', type='shout', message=msg}) end --- 0 is public nearby channel, other channels are used to communicate with LSL scripts -function LLChat.sendChannel(msg, channel) - leap.send('LLChatBar', {op='sendChat', channel=channel, message=msg}) -end - -- *************************************************************************** -- Group chat -- *************************************************************************** diff --git a/indra/newview/scripts/lua/test_group_chat.lua b/indra/newview/scripts/lua/test_group_chat.lua index 6299ba535f..eaff07ed14 100644 --- a/indra/newview/scripts/lua/test_group_chat.lua +++ b/indra/newview/scripts/lua/test_group_chat.lua @@ -1,5 +1,4 @@ LLChat = require 'LLChat' -inspect = require 'inspect' LLAgent = require 'LLAgent' popup = require 'popup' -- cgit v1.2.3 From 493b8fbe7e435c1a831db2aecf33fd9aac13baf2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 13 Aug 2024 10:47:07 -0400 Subject: Update Luau to v0.638-r2 (2024-08-12 build) --- autobuild.xml | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index bc5c979c95..00f9fb2470 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -1709,11 +1709,11 @@ archive hash - 59bf3d96f9df4b6981c406abac5c46ae276f9b15 + e48f291e2eeb1dbab39f26db5ac9b4c2b2d1c57d hash_algorithm sha1 url - https://github.com/secondlife/3p-luau/releases/download/v0.609-9567db9/luau-0.609-9567db9-darwin64-9567db9.tar.zst + https://github.com/secondlife/3p-luau/releases/download/v0.638-r2/luau-0.638.0-r2-darwin64-10356321602.tar.zst name darwin64 @@ -1723,11 +1723,11 @@ archive hash - 549516ada483ddf276183f66b0a7c3d01e4ef1aa + 3a797d8dae685b25ca787111a370b99d9e57d77c hash_algorithm sha1 url - https://github.com/secondlife/3p-luau/releases/download/v0.609-9567db9/luau-0.609-9567db9-linux64-9567db9.tar.zst + https://github.com/secondlife/3p-luau/releases/download/v0.638-r2/luau-0.638.0-r2-linux64-10356321602.tar.zst name linux64 @@ -1737,11 +1737,11 @@ archive hash - 43e2cc2e6e94299f89655435002864925b640e16 + a8857313496622134b00899141bbe6542f754164 hash_algorithm sha1 url - https://github.com/secondlife/3p-luau/releases/download/v0.609-9567db9/luau-0.609-9567db9-windows64-9567db9.tar.zst + https://github.com/secondlife/3p-luau/releases/download/v0.638-r2/luau-0.638.0-r2-windows64-10356321602.tar.zst name windows64 @@ -1754,7 +1754,7 @@ copyright Copyright (c) 2019-2023 Roblox Corporation, Copyright (c) 1994–2019 Lua.org, PUC-Rio. version - 0.609-9567db9 + 0.638.0-r2 name luau description @@ -1836,18 +1836,6 @@ mikktspace - canonical_repo - https://bitbucket.org/lindenlab/3p-mikktspace - copyright - Copyright (C) 2011 by Morten S. Mikkelsen, Copyright (C) 2022 Blender Authors - description - Mikktspace Tangent Generator - license - Apache 2.0 - license_file - mikktspace.txt - name - mikktspace platforms darwin64 @@ -1893,8 +1881,20 @@ windows64 + license + Apache 2.0 + license_file + mikktspace.txt + copyright + Copyright (C) 2011 by Morten S. Mikkelsen, Copyright (C) 2022 Blender Authors version 1 + name + mikktspace + canonical_repo + https://bitbucket.org/lindenlab/3p-mikktspace + description + Mikktspace Tangent Generator minizip-ng @@ -2811,6 +2811,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors LICENSE copyright Copyright (c) 2000-2012, Linden Research, Inc. + version + 3.0-f14b5ec name viewer-manager description @@ -2819,8 +2821,6 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors https://bitbucket.org/lindenlab/vmp-standalone source_type hg - version - 3.0-f14b5ec vlc-bin -- cgit v1.2.3