diff options
Diffstat (limited to 'indra/llcommon/lleventcoro.h')
-rw-r--r-- | indra/llcommon/lleventcoro.h | 333 |
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: |