From 35ee96709ef2704a2636a11c67d61190dd6bdd50 Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Thu, 18 Jul 2024 12:55:38 -0400
Subject: Make `LLEventPump::listen()` also accept new `LLAwareListener`.

`listen()` still takes `LLEventListener`, a `callable(const LLSD&)`, but now
also accepts `LLAwareListener`, a `callable(const LLBoundListener&, const LLSD&)`.
This uses `boost::signals2::signal::connect_extended()`, which, when the
signal is called, passes to a connected listener the `LLBoundListener` (aka
`boost::signals2::connection`) representing its own connection. This allows a
listener to disconnect itself when done.

Internally, `listen_impl()` now always uses `connect_extended()`. When passed
a classic `LLEventListener`, `listen()` wraps it in a lambda that ignores the
passed `LLBoundListener`.

`listen()` also now accepts `LLVoidListener`, and internally wraps it in a lambda
that returns `false` on its behalf.
---
 indra/llcommon/lleventfilter.cpp          | 13 +++---
 indra/llcommon/lleventfilter.h            |  7 ++--
 indra/llcommon/llevents.cpp               | 11 +++--
 indra/llcommon/llevents.h                 | 70 +++++++++++++++++++++++++++----
 indra/newview/tests/llluamanager_test.cpp | 25 ++++-------
 5 files changed, 86 insertions(+), 40 deletions(-)

diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp
index da19946e3b..2b5401e9f7 100644
--- a/indra/llcommon/lleventfilter.cpp
+++ b/indra/llcommon/lleventfilter.cpp
@@ -365,21 +365,22 @@ bool LLEventLogProxy::post(const LLSD& event) /* override */
 }
 
 LLBoundListener LLEventLogProxy::listen_impl(const std::string& name,
-                                             const LLEventListener& target,
+                                             const LLAwareListener& target,
                                              const NameList& after,
                                              const NameList& before)
 {
     LL_DEBUGS("LogProxy") << "LLEventLogProxy('" << getName() << "').listen('"
                           << name << "')" << LL_ENDL;
     return mPump.listen(name,
-                        [this, name, target](const LLSD& event)->bool
-                        { return listener(name, target, event); },
+                        [this, name, target](const LLBoundListener& conn, const LLSD& event)
+                        { return listener(conn, name, target, event); },
                         after,
                         before);
 }
 
-bool LLEventLogProxy::listener(const std::string& name,
-                               const LLEventListener& target,
+bool LLEventLogProxy::listener(const LLBoundListener& conn,
+                               const std::string& name,
+                               const LLAwareListener& target,
                                const LLSD& event) const
 {
     auto eventminus = event;
@@ -391,7 +392,7 @@ bool LLEventLogProxy::listener(const std::string& name,
     }
     std::string hdr{STRINGIZE(getName() << " to " << name << " " << counter)};
     LL_INFOS("LogProxy") << hdr << ": " << eventminus << LL_ENDL;
-    bool result = target(eventminus);
+    bool result = target(conn, eventminus);
     LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL;
     return result;
 }
diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h
index 9988459aae..de706d72eb 100644
--- a/indra/llcommon/lleventfilter.h
+++ b/indra/llcommon/lleventfilter.h
@@ -459,7 +459,7 @@ public:
     LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak=false);
 
     /// register a new listener
-    LLBoundListener listen_impl(const std::string& name, const LLEventListener& target,
+    LLBoundListener listen_impl(const std::string& name, const LLAwareListener& target,
                                 const NameList& after, const NameList& before);
 
     /// Post an event to all listeners
@@ -469,8 +469,9 @@ private:
     /// This method intercepts each call to any target listener. We pass it
     /// the listener name and the caller's intended target listener plus the
     /// posted LLSD event.
-    bool listener(const std::string& name,
-                  const LLEventListener& target,
+    bool listener(const LLBoundListener& conn,
+                  const std::string& name,
+                  const LLAwareListener& target,
                   const LLSD& event) const;
 
     LLEventPump& mPump;
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index 5a6e13cb7d..98bd990f31 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -415,7 +415,7 @@ void LLEventPump::reset()
     //mDeps.clear();
 }
 
-LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventListener& listener,
+LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLAwareListener& listener,
                                          const NameList& after,
                                          const NameList& before)
 {
@@ -575,7 +575,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL
     }
     // Now that newNode has a value that places it appropriately in mSignal,
     // connect it.
-    LLBoundListener bound = mSignal->connect(nodePosition, listener);
+    LLBoundListener bound = mSignal->connect_extended(nodePosition, listener);
     
     if (!name.empty())
     {   // note that we are not tracking anonymous listeners here either.
@@ -659,7 +659,7 @@ bool LLEventMailDrop::post(const LLSD& event)
 }
 
 LLBoundListener LLEventMailDrop::listen_impl(const std::string& name,
-                                    const LLEventListener& listener,
+                                    const LLAwareListener& listener,
                                     const NameList& after,
                                     const NameList& before)
 {
@@ -668,7 +668,10 @@ LLBoundListener LLEventMailDrop::listen_impl(const std::string& name,
     // Remove any that this listener consumes -- Effective STL, Item 9.
     for (auto hi(mEventHistory.begin()), hend(mEventHistory.end()); hi != hend; )
     {
-        if (listener(*hi))
+        // We don't actually have an LLBoundListener in hand, and we won't
+        // until the base-class listen_impl() call below. Pass an empty
+        // instance.
+        if (listener({}, *hi))
         {
             // new listener consumed this event, erase it
             hi = mEventHistory.erase(hi);
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h
index d0686bd8b5..8ef3a5af95 100644
--- a/indra/llcommon/llevents.h
+++ b/indra/llcommon/llevents.h
@@ -32,12 +32,13 @@
 #if ! defined(LL_LLEVENTS_H)
 #define LL_LLEVENTS_H
 
-#include <string>
+#include <deque>
+#include <functional>
 #include <map>
 #include <set>
+#include <string>
+#include <type_traits>
 #include <vector>
-#include <deque>
-#include <functional>
 #if LL_WINDOWS
     #pragma warning (push)
     #pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch
@@ -137,6 +138,10 @@ typedef boost::signals2::signal<bool(const LLSD&), LLStopWhenHandled, float>  LL
 /// Methods that forward listeners (e.g. constructed with
 /// <tt>boost::bind()</tt>) should accept (const LLEventListener&)
 typedef LLStandardSignal::slot_type LLEventListener;
+/// Support a listener accepting (const LLBoundListener&, const LLSD&).
+/// Note that LLBoundListener::disconnect() is a const method: this feature is
+/// specifically intended to allow a listener to disconnect itself when done.
+typedef LLStandardSignal::extended_slot_type LLAwareListener;
 /// Accept a void listener too
 typedef std::function<void(const LLSD&)> LLVoidListener;
 /// Result of registering a listener, supports <tt>connected()</tt>,
@@ -522,18 +527,37 @@ public:
      * call, allows us to optimize away the second and subsequent dependency
      * sorts.
      *
-     * If name is set to LLEventPump::ANONYMOUS listen will bypass the entire
+     * If name is set to LLEventPump::ANONYMOUS, listen() will bypass the entire
      * dependency and ordering calculation. In this case, it is critical that
      * the result be assigned to a LLTempBoundListener or the listener is
-     * manually disconnected when no longer needed since there will be no
+     * manually disconnected when no longer needed, since there will be no
      * way to later find and disconnect this listener manually.
      */
+    template <typename LISTENER>
     LLBoundListener listen(const std::string& name,
-                           const LLEventListener& listener,
+                           LISTENER&& listener,
                            const NameList& after=NameList(),
                            const NameList& before=NameList())
     {
-        return listen_impl(name, listener, after, before);
+        if constexpr (std::is_invocable_v<LISTENER, const LLSD&>)
+        {
+            // wrap classic LLEventListener in LLAwareListener lambda
+            return listenb(
+                name,
+                [listener=std::move(listener)]
+                (const LLBoundListener&, const LLSD& event)
+                {
+                    return listener(event);
+                },
+                after,
+                before);
+        }
+        else
+        {
+            static_assert(std::is_invocable_v<LISTENER, LLBoundListener, const LLSD&>,
+                          "LLEventPump::listen() listener has bad parameter signature");
+            return listenb(name, std::forward<LISTENER>(listener), after, before);
+        }
     }
 
     /// Get the LLBoundListener associated with the passed name (dummy
@@ -579,7 +603,35 @@ private:
     LLMutex mConnectionListMutex;
 
 protected:
-    virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&,
+    template <typename LISTENER>
+    LLBoundListener listenb(const std::string& name,
+                            LISTENER&& listener,
+                            const NameList& after=NameList(),
+                            const NameList& before=NameList())
+    {
+        using result_t = std::decay_t<decltype(listener(LLBoundListener(), LLSD()))>;
+        if constexpr (std::is_same_v<bool, result_t>)
+        {
+            return listen_impl(name, std::forward<LISTENER>(listener), after, before);
+        }
+        else
+        {
+            static_assert(std::is_same_v<void, result_t>,
+                          "LLEventPump::listen() listener has bad return type");
+            // wrap void listener in one that returns bool
+            return listen_impl(
+                name,
+                [listener=std::move(listener)]
+                (const LLBoundListener& conn, const LLSD& event)
+                {
+                    listener(conn, event);
+                    return false;
+                },
+                after,
+                before);
+        }
+    }
+    virtual LLBoundListener listen_impl(const std::string& name, const LLAwareListener&,
                                         const NameList& after,
                                         const NameList& before);
 
@@ -654,7 +706,7 @@ public:
     void discard();
 
 protected:
-    virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&,
+    virtual LLBoundListener listen_impl(const std::string& name, const LLAwareListener&,
                                         const NameList& after,
                                         const NameList& before) override;
 
diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp
index 26a4ac95e3..3209d93d39 100644
--- a/indra/newview/tests/llluamanager_test.cpp
+++ b/indra/newview/tests/llluamanager_test.cpp
@@ -42,17 +42,6 @@ public:
 
 LLControlGroup gSavedSettings("Global");
 
-template <typename CALLABLE>
-auto listener(CALLABLE&& callable)
-{
-    return [callable=std::forward<CALLABLE>(callable)]
-    (const LLSD& data)
-    {
-        callable(data);
-        return false;
-    };
-}
-
 /*****************************************************************************
 *   TUT
 *****************************************************************************/
@@ -138,7 +127,7 @@ namespace tut
     {
         LLSD fromlua;
         LLStreamListener pump("testpump",
-                              listener([&fromlua](const LLSD& data){ fromlua = data; }));
+                              [&fromlua](const LLSD& data){ fromlua = data; });
         const std::string lua(stringize(
             "data = ", construct, "\n"
             "LL.post_on('testpump', data)\n"
@@ -167,8 +156,8 @@ namespace tut
         set_test_name("test post_on(), get_event_pumps(), get_event_next()");
         StringVec posts;
         LLStreamListener pump("testpump",
-                              listener([&posts](const LLSD& data)
-                              { posts.push_back(data.asString()); }));
+                              [&posts](const LLSD& data)
+                              { posts.push_back(data.asString()); });
         const std::string lua(
             "-- test post_on,get_event_pumps,get_event_next\n"
             "LL.post_on('testpump', 'entry')\n"
@@ -346,11 +335,11 @@ namespace tut
 
         LLStreamListener pump(
             "echo",
-            listener([](const LLSD& data)
+            [](const LLSD& data)
             {
                 LL_DEBUGS("Lua") << "echo pump got: " << data << LL_ENDL;
                 sendReply(data, data);
-            }));
+            });
 
         auto [count, result] = LLLUAmanager::waitScriptLine(lua);
         ensure_equals("Lua script didn't return item", count, 1);
@@ -424,11 +413,11 @@ namespace tut
         LLSD requests;
         LLStreamListener pump(
             "testpump",
-            listener([&requests](const LLSD& data)
+            [&requests](const LLSD& data)
             {
                 LL_DEBUGS("Lua") << "testpump got: " << data << LL_ENDL;
                 requests.append(data);
-            }));
+            });
 
         auto future = LLLUAmanager::startScriptLine(lua);
         // LuaState::expr() periodically interrupts a running chunk to ensure
-- 
cgit v1.2.3