/** * @file lleventcoro.h * @author Nat Goodspeed * @date 2009-04-29 * @brief Utilities to interface between coroutines and events. * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, 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 * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #if ! defined(LL_LLEVENTCORO_H) #define LL_LLEVENTCORO_H #include <string> #include "llevents.h" /** * Like LLListenerOrPumpName, this is a class intended for parameter lists: * accept a <tt>const LLEventPumpOrPumpName&</tt> and you can accept either an * <tt>LLEventPump&</tt> or its string name. For a single parameter that could * be either, it's not hard to overload the function -- but as soon as you * want to accept two such parameters, this is cheaper than four overloads. */ class LLEventPumpOrPumpName { public: /// Pass an actual LLEventPump& LLEventPumpOrPumpName(LLEventPump& pump): mPump(pump) {} /// Pass the string name of an LLEventPump LLEventPumpOrPumpName(const std::string& pumpname): mPump(LLEventPumps::instance().obtain(pumpname)) {} /// Pass string constant name of an LLEventPump. This override must be /// explicit, since otherwise passing <tt>const char*</tt> to a function /// accepting <tt>const LLEventPumpOrPumpName&</tt> would require two /// different implicit conversions: <tt>const char*</tt> -> <tt>const /// std::string&</tt> -> <tt>const LLEventPumpOrPumpName&</tt>. LLEventPumpOrPumpName(const char* pumpname): mPump(LLEventPumps::instance().obtain(pumpname)) {} /// Unspecified: "I choose not to identify an LLEventPump." LLEventPumpOrPumpName() {} operator LLEventPump& () const { return *mPump; } LLEventPump& getPump() const { return *mPump; } operator bool() const { return bool(mPump); } bool operator!() const { return ! mPump; } private: boost::optional<LLEventPump&> mPump; }; namespace llcoro { /** * 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(); /** * 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 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 = 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 suspendUntilEventOn() is * entered. Therefore, the coroutine completely misses an immediate reply * event, making it suspend indefinitely. * * By contrast, postAndSuspend() listens on the @a replyPump @em before posting * the specified LLSD event on the specified @a requestPump. * * @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 postAndSuspend() will listen for a * reply. Pass either the LLEventPump& or its string name. The calling * 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 * feature; obviously you can store the name in @a event "by hand" if desired. * @a replyPumpNamePath can be specified in any of four forms: * * @c isUndefined() (default-constructed LLSD object): do nothing. This is * the default behavior if you omit @a replyPumpNamePath. * * @c isInteger(): @a event is an array. Store <tt>replyPump.getName()</tt> * in <tt>event[replyPumpNamePath.asInteger()]</tt>. * * @c isString(): @a event is a map. Store <tt>replyPump.getName()</tt> in * <tt>event[replyPumpNamePath.asString()]</tt>. * * @c isArray(): @a event has several levels of structure, e.g. map of * maps, array of arrays, array of maps, map of arrays, ... Store * <tt>replyPump.getName()</tt> in * <tt>event[replyPumpNamePath[0]][replyPumpNamePath[1]]...</tt> In other * words, examine each array entry in @a replyPumpNamePath in turn. If it's an * <tt>LLSD::String</tt>, the current level of @a event is a map; step down to * that map entry. If it's an <tt>LLSD::Integer</tt>, the current level of @a * event is an array; step down to that array entry. The last array entry in * @a replyPumpNamePath specifies the entry in the lowest-level structure in * @a event into which to store <tt>replyPump.getName()</tt>. */ 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. inline LLSD suspendUntilEventOn(const LLEventPumpOrPumpName& pump) { // This is now a convenience wrapper for postAndSuspend(). return postAndSuspend(LLSD(), LLEventPumpOrPumpName(), pump); } /// Like postAndSuspend(), but if we wait longer than @a timeout seconds, /// stop waiting and return @a timeoutResult instead. LLSD postAndSuspendWithTimeout(const LLSD& event, const LLEventPumpOrPumpName& requestPump, const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath, F32 timeout, const LLSD& timeoutResult); /// Suspend the coroutine until an event is fired on the identified pump /// or the timeout duration has elapsed. If the timeout duration /// elapses the specified LLSD is returned. inline LLSD suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName, F32 timeoutin, const LLSD &timeoutResult) { return postAndSuspendWithTimeout(LLSD(), // event LLEventPumpOrPumpName(), // requestPump suspendPumpOrName, // replyPump LLSD(), // replyPumpNamePath timeoutin, timeoutResult); } } // 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 * LLEventPumps::obtain() instantiate the LLEventPump as a "named singleton," * in a certain sense it's more robust to instantiate a local LLEventPump and * provide its name instead. This class packages the following idiom: * * 1. Instantiate a local LLCoroEventPump, with an optional name prefix. * 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 suspend() method to suspend for the reply. * 5. Let the LLCoroEventPump go out of scope. */ class LL_COMMON_API LLCoroEventPump { public: LLCoroEventPump(const std::string& name="coro"): mPump(name, true) // allow tweaking the pump instance name {} /// It's typical to request the LLEventPump name to direct an event API to /// send its response to this pump. std::string getName() const { return mPump.getName(); } /// Less typically, we'd request the pump itself for some reason. LLEventPump& getPump() { return mPump; } /** * Wait for an event on this LLEventPump. */ LLSD suspend() { return llcoro::suspendUntilEventOn(mPump); } LLSD postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump, const LLSD& replyPumpNamePath=LLSD()) { return llcoro::postAndSuspend(event, requestPump, mPump, replyPumpNamePath); } private: LLEventStream mPump; }; #endif /* ! defined(LL_LLEVENTCORO_H) */