summaryrefslogtreecommitdiff
path: root/indra/llcommon/lleventcoro.h
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/lleventcoro.h')
-rw-r--r--indra/llcommon/lleventcoro.h333
1 files changed, 73 insertions, 260 deletions
diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h
index abbeeaa373..2105faf861 100644
--- a/indra/llcommon/lleventcoro.h
+++ b/indra/llcommon/lleventcoro.h
@@ -29,11 +29,10 @@
#if ! defined(LL_LLEVENTCORO_H)
#define LL_LLEVENTCORO_H
-#include <boost/dcoroutine/coroutine.hpp>
-#include <boost/dcoroutine/future.hpp>
#include <boost/optional.hpp>
#include <string>
#include <stdexcept>
+#include <utility> // std::pair
#include "llevents.h"
#include "llerror.h"
@@ -74,111 +73,46 @@ private:
boost::optional<LLEventPump&> mPump;
};
-/// This is an adapter for a signature like void LISTENER(const LLSD&), which
-/// isn't a valid LLEventPump listener: such listeners should return bool.
-template <typename LISTENER>
-class LLVoidListener
+namespace llcoro
{
-public:
- LLVoidListener(const LISTENER& listener):
- mListener(listener)
- {}
- bool operator()(const LLSD& event)
- {
- mListener(event);
- // don't swallow the event, let other listeners see it
- return false;
- }
-private:
- LISTENER mListener;
-};
-
-/// LLVoidListener helper function to infer the type of the LISTENER
-template <typename LISTENER>
-LLVoidListener<LISTENER> voidlistener(const LISTENER& listener)
-{
- return LLVoidListener<LISTENER>(listener);
-}
-
-namespace LLEventDetail
-{
- /// Implementation for listenerNameForCoro(), see below
- LL_COMMON_API std::string listenerNameForCoroImpl(const void* self_id);
- /**
- * waitForEventOn() permits a coroutine to temporarily listen on an
- * LLEventPump any number of times. We don't really want to have to ask
- * the caller to label each such call with a distinct string; the whole
- * point of waitForEventOn() is to present a nice sequential interface to
- * the underlying LLEventPump-with-named-listeners machinery. So we'll use
- * LLEventPump::inventName() to generate a distinct name for each
- * temporary listener. On the other hand, because a given coroutine might
- * call waitForEventOn() any number of times, we don't really want to
- * consume an arbitrary number of generated inventName()s: that namespace,
- * though large, is nonetheless finite. So we memoize an invented name for
- * each distinct coroutine instance (each different 'self' object). We
- * can't know the type of 'self', because it depends on the coroutine
- * body's signature. So we cast its address to void*, looking for distinct
- * pointer values. Yes, that means that an early coroutine could cache a
- * value here, then be destroyed, only to be supplanted by a later
- * coroutine (of the same or different type), and we'll end up
- * "recognizing" the second one and reusing the listener name -- but
- * that's okay, since it won't collide with any listener name used by the
- * earlier coroutine since that earlier coroutine no longer exists.
- */
- template <typename COROUTINE_SELF>
- std::string listenerNameForCoro(COROUTINE_SELF& self)
- {
- return listenerNameForCoroImpl(self.get_id());
- }
+/**
+ * Yield control from a coroutine for one "mainloop" tick. If your coroutine
+ * runs without suspending for nontrivial time, sprinkle in calls to this
+ * function to avoid stalling the rest of the viewer processing.
+ */
+void suspend();
- /**
- * Implement behavior described for postAndWait()'s @a replyPumpNamePath
- * parameter:
- *
- * * If <tt>path.isUndefined()</tt>, do nothing.
- * * If <tt>path.isString()</tt>, @a dest is an LLSD map: store @a value
- * into <tt>dest[path.asString()]</tt>.
- * * If <tt>path.isInteger()</tt>, @a dest is an LLSD array: store @a
- * value into <tt>dest[path.asInteger()]</tt>.
- * * If <tt>path.isArray()</tt>, iteratively apply the rules above to step
- * down through the structure of @a dest. The last array entry in @a
- * path specifies the entry in the lowest-level structure in @a dest
- * into which to store @a value.
- *
- * @note
- * In the degenerate case in which @a path is an empty array, @a dest will
- * @em become @a value rather than @em containing it.
- */
- LL_COMMON_API void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value);
-} // namespace LLEventDetail
+/**
+ * Yield control from a coroutine for at least the specified number of seconds
+ */
+void suspendUntilTimeout(float seconds);
/**
- * Post specified LLSD event on the specified LLEventPump, then wait for a
+ * Post specified LLSD event on the specified LLEventPump, then suspend for a
* response on specified other LLEventPump. This is more than mere
* convenience: the difference between this function and the sequence
* @code
* requestPump.post(myEvent);
- * LLSD reply = waitForEventOn(self, replyPump);
+ * LLSD reply = suspendUntilEventOn(replyPump);
* @endcode
* is that the sequence above fails if the reply is posted immediately on
* @a replyPump, that is, before <tt>requestPump.post()</tt> returns. In the
* sequence above, the running coroutine isn't even listening on @a replyPump
- * until <tt>requestPump.post()</tt> returns and @c waitForEventOn() is
+ * until <tt>requestPump.post()</tt> returns and @c suspendUntilEventOn() is
* entered. Therefore, the coroutine completely misses an immediate reply
- * event, making it wait indefinitely.
+ * event, making it suspend indefinitely.
*
- * By contrast, postAndWait() listens on the @a replyPump @em before posting
+ * By contrast, postAndSuspend() listens on the @a replyPump @em before posting
* the specified LLSD event on the specified @a requestPump.
*
- * @param self The @c self object passed into a coroutine
* @param event LLSD data to be posted on @a requestPump
* @param requestPump an LLEventPump on which to post @a event. Pass either
* the LLEventPump& or its string name. However, if you pass a
* default-constructed @c LLEventPumpOrPumpName, we skip the post() call.
- * @param replyPump an LLEventPump on which postAndWait() will listen for a
+ * @param replyPump an LLEventPump on which postAndSuspend() will listen for a
* reply. Pass either the LLEventPump& or its string name. The calling
- * coroutine will wait until that reply arrives. (If you're concerned about a
+ * coroutine will suspend until that reply arrives. (If you're concerned about a
* reply that might not arrive, please see also LLEventTimeout.)
* @param replyPumpNamePath specifies the location within @a event in which to
* store <tt>replyPump.getName()</tt>. This is a strictly optional convenience
@@ -201,101 +135,29 @@ namespace LLEventDetail
* @a replyPumpNamePath specifies the entry in the lowest-level structure in
* @a event into which to store <tt>replyPump.getName()</tt>.
*/
-template <typename SELF>
-LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump,
- const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD())
-{
- // declare the future
- boost::dcoroutines::future<LLSD> future(self);
- // make a callback that will assign a value to the future, and listen on
- // the specified LLEventPump with that callback
- std::string listenerName(LLEventDetail::listenerNameForCoro(self));
- LLTempBoundListener connection(
- replyPump.getPump().listen(listenerName,
- voidlistener(boost::dcoroutines::make_callback(future))));
- // skip the "post" part if requestPump is default-constructed
- if (requestPump)
- {
- // If replyPumpNamePath is non-empty, store the replyPump name in the
- // request event.
- LLSD modevent(event);
- LLEventDetail::storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName());
- LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName
- << " posting to " << requestPump.getPump().getName()
- << LL_ENDL;
-
- // *NOTE:Mani - Removed because modevent could contain user's hashed passwd.
- // << ": " << modevent << LL_ENDL;
- requestPump.getPump().post(modevent);
- }
- LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName
- << " about to wait on LLEventPump " << replyPump.getPump().getName()
- << LL_ENDL;
- // trying to dereference ("resolve") the future makes us wait for it
- LLSD value(*future);
- LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName
- << " resuming with " << value << LL_ENDL;
- // returning should disconnect the connection
- return value;
-}
+LLSD postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump,
+ const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD());
/// Wait for the next event on the specified LLEventPump. Pass either the
/// LLEventPump& or its string name.
-template <typename SELF>
-LLSD waitForEventOn(SELF& self, const LLEventPumpOrPumpName& pump)
+inline
+LLSD suspendUntilEventOn(const LLEventPumpOrPumpName& pump)
{
- // This is now a convenience wrapper for postAndWait().
- return postAndWait(self, LLSD(), LLEventPumpOrPumpName(), pump);
+ // This is now a convenience wrapper for postAndSuspend().
+ return postAndSuspend(LLSD(), LLEventPumpOrPumpName(), pump);
}
-/// return type for two-pump variant of waitForEventOn()
+} // namespace llcoro
+
+/// return type for two-pump variant of suspendUntilEventOn()
typedef std::pair<LLSD, int> LLEventWithID;
-namespace LLEventDetail
+namespace llcoro
{
- /**
- * This helper is specifically for the two-pump version of waitForEventOn().
- * We use a single future object, but we want to listen on two pumps with it.
- * Since we must still adapt from (the callable constructed by)
- * boost::dcoroutines::make_callback() (void return) to provide an event
- * listener (bool return), we've adapted LLVoidListener for the purpose. The
- * basic idea is that we construct a distinct instance of WaitForEventOnHelper
- * -- binding different instance data -- for each of the pumps. Then, when a
- * pump delivers an LLSD value to either WaitForEventOnHelper, it can combine
- * that LLSD with its discriminator to feed the future object.
- */
- template <typename LISTENER>
- class WaitForEventOnHelper
- {
- public:
- WaitForEventOnHelper(const LISTENER& listener, int discriminator):
- mListener(listener),
- mDiscrim(discriminator)
- {}
- // this signature is required for an LLEventPump listener
- bool operator()(const LLSD& event)
- {
- // our future object is defined to accept LLEventWithID
- mListener(LLEventWithID(event, mDiscrim));
- // don't swallow the event, let other listeners see it
- return false;
- }
- private:
- LISTENER mListener;
- const int mDiscrim;
- };
-
- /// WaitForEventOnHelper type-inference helper
- template <typename LISTENER>
- WaitForEventOnHelper<LISTENER> wfeoh(const LISTENER& listener, int discriminator)
- {
- return WaitForEventOnHelper<LISTENER>(listener, discriminator);
- }
-} // namespace LLEventDetail
/**
* This function waits for a reply on either of two specified LLEventPumps.
- * Otherwise, it closely resembles postAndWait(); please see the documentation
+ * Otherwise, it closely resembles postAndSuspend(); please see the documentation
* for that function for detailed parameter info.
*
* While we could have implemented the single-pump variant in terms of this
@@ -310,81 +172,41 @@ namespace LLEventDetail
* the index of the pump on which it arrived (0 or 1).
*
* @note
- * I'd have preferred to overload the name postAndWait() for both signatures.
+ * I'd have preferred to overload the name postAndSuspend() for both signatures.
* But consider the following ambiguous call:
* @code
- * postAndWait(self, LLSD(), requestPump, replyPump, "someString");
+ * postAndSuspend(LLSD(), requestPump, replyPump, "someString");
* @endcode
* "someString" could be converted to either LLSD (@a replyPumpNamePath for
* the single-pump function) or LLEventOrPumpName (@a replyPump1 for two-pump
* function).
*
- * It seems less burdensome to write postAndWait2() than to write either
+ * It seems less burdensome to write postAndSuspend2() than to write either
* LLSD("someString") or LLEventOrPumpName("someString").
*/
-template <typename SELF>
-LLEventWithID postAndWait2(SELF& self, const LLSD& event,
+LLEventWithID postAndSuspend2(const LLSD& event,
const LLEventPumpOrPumpName& requestPump,
const LLEventPumpOrPumpName& replyPump0,
const LLEventPumpOrPumpName& replyPump1,
const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD())
-{
- // declare the future
- boost::dcoroutines::future<LLEventWithID> future(self);
- // either callback will assign a value to this future; listen on
- // each specified LLEventPump with a callback
- std::string name(LLEventDetail::listenerNameForCoro(self));
- LLTempBoundListener connection0(
- replyPump0.getPump().listen(name + "a",
- LLEventDetail::wfeoh(boost::dcoroutines::make_callback(future), 0)));
- LLTempBoundListener connection1(
- replyPump1.getPump().listen(name + "b",
- LLEventDetail::wfeoh(boost::dcoroutines::make_callback(future), 1)));
- // skip the "post" part if requestPump is default-constructed
- if (requestPump)
- {
- // If either replyPumpNamePath is non-empty, store the corresponding
- // replyPump name in the request event.
- LLSD modevent(event);
- LLEventDetail::storeToLLSDPath(modevent, replyPump0NamePath,
- replyPump0.getPump().getName());
- LLEventDetail::storeToLLSDPath(modevent, replyPump1NamePath,
- replyPump1.getPump().getName());
- LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name
- << " posting to " << requestPump.getPump().getName()
- << ": " << modevent << LL_ENDL;
- requestPump.getPump().post(modevent);
- }
- LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name
- << " about to wait on LLEventPumps " << replyPump0.getPump().getName()
- << ", " << replyPump1.getPump().getName() << LL_ENDL;
- // trying to dereference ("resolve") the future makes us wait for it
- LLEventWithID value(*future);
- LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << name
- << " resuming with (" << value.first << ", " << value.second << ")"
- << LL_ENDL;
- // returning should disconnect both connections
- return value;
-}
+ const LLSD& replyPump1NamePath=LLSD());
/**
* Wait for the next event on either of two specified LLEventPumps.
*/
-template <typename SELF>
+inline
LLEventWithID
-waitForEventOn(SELF& self,
- const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1)
+suspendUntilEventOn(const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1)
{
- // This is now a convenience wrapper for postAndWait2().
- return postAndWait2(self, LLSD(), LLEventPumpOrPumpName(), pump0, pump1);
+ // This is now a convenience wrapper for postAndSuspend2().
+ return postAndSuspend2(LLSD(), LLEventPumpOrPumpName(), pump0, pump1);
}
/**
- * Helper for the two-pump variant of waitForEventOn(), e.g.:
+ * Helper for the two-pump variant of suspendUntilEventOn(), e.g.:
*
* @code
- * LLSD reply = errorException(waitForEventOn(self, replyPump, errorPump),
+ * LLSD reply = errorException(suspendUntilEventOn(replyPump, errorPump),
* "error response from login.cgi");
* @endcode
*
@@ -400,6 +222,8 @@ waitForEventOn(SELF& self,
*/
LLSD errorException(const LLEventWithID& result, const std::string& desc);
+} // namespace llcoro
+
/**
* Exception thrown by errorException(). We don't call this LLEventError
* because it's not an error in event processing: rather, this exception
@@ -420,12 +244,17 @@ private:
LLSD mData;
};
+namespace llcoro
+{
+
/**
* Like errorException(), save that this trips a fatal error using LL_ERRS
* rather than throwing an exception.
*/
LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc);
+} // namespace llcoro
+
/**
* Certain event APIs require the name of an LLEventPump on which they should
* post results. While it works to invent a distinct name and let
@@ -437,7 +266,7 @@ LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc
* 2. Provide its actual name to the event API in question as the name of the
* reply LLEventPump.
* 3. Initiate the request to the event API.
- * 4. Call your LLEventTempStream's wait() method to wait for the reply.
+ * 4. Call your LLEventTempStream's suspend() method to suspend for the reply.
* 5. Let the LLCoroEventPump go out of scope.
*/
class LL_COMMON_API LLCoroEventPump
@@ -454,26 +283,16 @@ public:
/**
* Wait for an event on this LLEventPump.
- *
- * @note
- * The other major usage pattern we considered was to bind @c self at
- * LLCoroEventPump construction time, which would avoid passing the
- * parameter to each wait() call. But if we were going to bind @c self as
- * a class member, we'd need to specify a class template parameter
- * indicating its type. The big advantage of passing it to the wait() call
- * is that the type can be implicit.
*/
- template <typename SELF>
- LLSD wait(SELF& self)
+ LLSD suspend()
{
- return waitForEventOn(self, mPump);
+ return llcoro::suspendUntilEventOn(mPump);
}
- template <typename SELF>
- LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump,
+ LLSD postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump,
const LLSD& replyPumpNamePath=LLSD())
{
- return ::postAndWait(self, event, requestPump, mPump, replyPumpNamePath);
+ return llcoro::postAndSuspend(event, requestPump, mPump, replyPumpNamePath);
}
private:
@@ -509,57 +328,51 @@ public:
/// request pump 1
LLEventPump& getPump1() { return mPump1; }
- /// waitForEventOn(self, either of our two LLEventPumps)
- template <typename SELF>
- LLEventWithID wait(SELF& self)
+ /// suspendUntilEventOn(either of our two LLEventPumps)
+ LLEventWithID suspend()
{
- return waitForEventOn(self, mPump0, mPump1);
+ return llcoro::suspendUntilEventOn(mPump0, mPump1);
}
- /// errorException(wait(self))
- template <typename SELF>
- LLSD waitWithException(SELF& self)
+ /// errorException(suspend())
+ LLSD suspendWithException()
{
- return errorException(wait(self), std::string("Error event on ") + getName1());
+ return llcoro::errorException(suspend(), std::string("Error event on ") + getName1());
}
- /// errorLog(wait(self))
- template <typename SELF>
- LLSD waitWithLog(SELF& self)
+ /// errorLog(suspend())
+ LLSD suspendWithLog()
{
- return errorLog(wait(self), std::string("Error event on ") + getName1());
+ return llcoro::errorLog(suspend(), std::string("Error event on ") + getName1());
}
- template <typename SELF>
- LLEventWithID postAndWait(SELF& self, const LLSD& event,
+ LLEventWithID postAndSuspend(const LLSD& event,
const LLEventPumpOrPumpName& requestPump,
const LLSD& replyPump0NamePath=LLSD(),
const LLSD& replyPump1NamePath=LLSD())
{
- return postAndWait2(self, event, requestPump, mPump0, mPump1,
- replyPump0NamePath, replyPump1NamePath);
+ return llcoro::postAndSuspend2(event, requestPump, mPump0, mPump1,
+ replyPump0NamePath, replyPump1NamePath);
}
- template <typename SELF>
- LLSD postAndWaitWithException(SELF& self, const LLSD& event,
+ LLSD postAndSuspendWithException(const LLSD& event,
const LLEventPumpOrPumpName& requestPump,
const LLSD& replyPump0NamePath=LLSD(),
const LLSD& replyPump1NamePath=LLSD())
{
- return errorException(postAndWait(self, event, requestPump,
- replyPump0NamePath, replyPump1NamePath),
- std::string("Error event on ") + getName1());
+ return llcoro::errorException(postAndSuspend(event, requestPump,
+ replyPump0NamePath, replyPump1NamePath),
+ std::string("Error event on ") + getName1());
}
- template <typename SELF>
- LLSD postAndWaitWithLog(SELF& self, const LLSD& event,
+ LLSD postAndSuspendWithLog(const LLSD& event,
const LLEventPumpOrPumpName& requestPump,
const LLSD& replyPump0NamePath=LLSD(),
const LLSD& replyPump1NamePath=LLSD())
{
- return errorLog(postAndWait(self, event, requestPump,
- replyPump0NamePath, replyPump1NamePath),
- std::string("Error event on ") + getName1());
+ return llcoro::errorLog(postAndSuspend(event, requestPump,
+ replyPump0NamePath, replyPump1NamePath),
+ std::string("Error event on ") + getName1());
}
private: