diff options
Diffstat (limited to 'indra/llcommon/lleventcoro.cpp')
-rw-r--r-- | indra/llcommon/lleventcoro.cpp | 239 |
1 files changed, 223 insertions, 16 deletions
diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 81cc33fbba..578a2b62c8 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -38,34 +38,60 @@ #include "llsdserialize.h" #include "llerror.h" #include "llcoros.h" +#include "llmake.h" -std::string LLEventDetail::listenerNameForCoroImpl(const void* self_id) +#include "lleventfilter.h" + +namespace { - // First, if this coroutine was launched by LLCoros::launch(), find that name. - std::string name(LLCoros::instance().getNameByID(self_id)); + +/** + * suspendUntilEventOn() 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 suspendUntilEventOn() 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 suspendUntilEventOn() 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. + */ +std::string listenerNameForCoro() +{ + // If this coroutine was launched by LLCoros::launch(), find that name. + std::string name(LLCoros::instance().getName()); if (! name.empty()) { return name; } - // Apparently this coroutine wasn't launched by LLCoros::launch(). Check - // whether we have a memo for this self_id. - typedef std::map<const void*, std::string> MapType; - static MapType memo; - MapType::const_iterator found = memo.find(self_id); - if (found != memo.end()) - { - // this coroutine instance has called us before, reuse same name - return found->second; - } // this is the first time we've been called for this coroutine instance name = LLEventPump::inventName("coro"); - memo[self_id] = name; - LL_INFOS("LLEventCoro") << "listenerNameForCoroImpl(" << self_id << "): inventing coro name '" + LL_INFOS("LLEventCoro") << "listenerNameForCoro(): inventing coro name '" << name << "'" << LL_ENDL; return name; } -void LLEventDetail::storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value) +/** + * Implement behavior described for postAndSuspend()'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. + */ +void storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value) { if (rawPath.isUndefined()) { @@ -118,6 +144,185 @@ void LLEventDetail::storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& *pdest = value; } +/// For LLCoros::Future<LLSD>::make_callback(), the callback has a signature +/// like void callback(LLSD), which isn't a valid LLEventPump listener: such +/// listeners must return bool. +template <typename LISTENER> +class FutureListener +{ +public: + // FutureListener is instantiated on the coroutine stack: the stack, in + // other words, that wants to suspend. + FutureListener(const LISTENER& listener): + mListener(listener), + // Capture the suspending coroutine's flag as a consuming or + // non-consuming listener. + mConsume(LLCoros::get_consuming()) + {} + + // operator()() is called on the main stack: the stack on which the + // expected event is fired. + bool operator()(const LLSD& event) + { + mListener(event); + // tell upstream LLEventPump whether listener consumed + return mConsume; + } + +protected: + LISTENER mListener; + bool mConsume; +}; + +} // anonymous + +void llcoro::suspend() +{ + // By viewer convention, we post an event on the "mainloop" LLEventPump + // each iteration of the main event-handling loop. So waiting for a single + // event on "mainloop" gives us a one-frame suspend. + suspendUntilEventOn("mainloop"); +} + +void llcoro::suspendUntilTimeout(float seconds) +{ + LLEventTimeout timeout; + + timeout.eventAfter(seconds, LLSD()); + llcoro::suspendUntilEventOn(timeout); +} + +LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath) +{ + // declare the future + LLCoros::Future<LLSD> future; + // make a callback that will assign a value to the future, and listen on + // the specified LLEventPump with that callback + std::string listenerName(listenerNameForCoro()); + LLTempBoundListener connection( + replyPump.getPump().listen(listenerName, + llmake<FutureListener>(future.make_callback()))); + // 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); + storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName()); + LL_DEBUGS("lleventcoro") << "postAndSuspend(): 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") << "postAndSuspend(): coroutine " << listenerName + << " about to wait on LLEventPump " << replyPump.getPump().getName() + << LL_ENDL; + // calling get() on the future makes us wait for it + LLSD value(future.get()); + LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName + << " resuming with " << value << LL_ENDL; + // returning should disconnect the connection + return value; +} + +namespace +{ + +/** + * This helper is specifically for postAndSuspend2(). 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 + * FutureListener for the purpose. The basic idea is that we construct a + * distinct instance of FutureListener2 -- binding different instance data -- + * for each of the pumps. Then, when a pump delivers an LLSD value to either + * FutureListener2, it can combine that LLSD with its discriminator to feed + * the future object. + * + * DISCRIM is a template argument so we can use llmake() rather than + * having to write our own argument-deducing helper function. + */ +template <typename LISTENER, typename DISCRIM> +class FutureListener2: public FutureListener<LISTENER> +{ + typedef FutureListener<LISTENER> super; + +public: + // instantiated on coroutine stack: the stack about to suspend + FutureListener2(const LISTENER& listener, DISCRIM discriminator): + super(listener), + mDiscrim(discriminator) + {} + + // called on main stack: the stack on which event is fired + bool operator()(const LLSD& event) + { + // our future object is defined to accept LLEventWithID + super::mListener(LLEventWithID(event, mDiscrim)); + // tell LLEventPump whether or not event was consumed + return super::mConsume; + } + +private: + const DISCRIM mDiscrim; +}; + +} // anonymous + +namespace llcoro +{ + +LLEventWithID postAndSuspend2(const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump0, + const LLEventPumpOrPumpName& replyPump1, + const LLSD& replyPump0NamePath, + const LLSD& replyPump1NamePath) +{ + // declare the future + LLCoros::Future<LLEventWithID> future; + // either callback will assign a value to this future; listen on + // each specified LLEventPump with a callback + std::string name(listenerNameForCoro()); + LLTempBoundListener connection0( + replyPump0.getPump().listen( + name + "a", + llmake<FutureListener2>(future.make_callback(), 0))); + LLTempBoundListener connection1( + replyPump1.getPump().listen( + name + "b", + llmake<FutureListener2>(future.make_callback(), 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); + storeToLLSDPath(modevent, replyPump0NamePath, + replyPump0.getPump().getName()); + storeToLLSDPath(modevent, replyPump1NamePath, + replyPump1.getPump().getName()); + LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name + << " posting to " << requestPump.getPump().getName() + << ": " << modevent << LL_ENDL; + requestPump.getPump().post(modevent); + } + LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name + << " about to wait on LLEventPumps " << replyPump0.getPump().getName() + << ", " << replyPump1.getPump().getName() << LL_ENDL; + // calling get() on the future makes us wait for it + LLEventWithID value(future.get()); + LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << name + << " resuming with (" << value.first << ", " << value.second << ")" + << LL_ENDL; + // returning should disconnect both connections + return value; +} + LLSD errorException(const LLEventWithID& result, const std::string& desc) { // If the result arrived on the error pump (pump 1), instead of @@ -144,3 +349,5 @@ LLSD errorLog(const LLEventWithID& result, const std::string& desc) // A simple return must therefore be from the reply pump (pump 0). return result.first; } + +} // namespace llcoro |