Skip to content
Snippets Groups Projects
Commit 820d4a20 authored by Nat Goodspeed's avatar Nat Goodspeed
Browse files

DEV-32777: Use a canonical boost::coroutines::coroutine signature, relying on

boost::bind() to pass any other coroutine arguments. This allows us to remove
the LLCoroBase and LLCoro constructs, directly storing a coroutine object in
our ptr_map. It also allows us to remove the multiple launch() overloads for
multiple arguments. Finally, it lets us move most launch() functionality into
a non-template method.
parent a3d54c48
No related branches found
No related tags found
No related merge requests found
...@@ -54,6 +54,15 @@ bool LLCoros::cleanup(const LLSD&) ...@@ -54,6 +54,15 @@ bool LLCoros::cleanup(const LLSD&)
return false; return false;
} }
std::string LLCoros::launchImpl(const std::string& prefix, coro* newCoro)
{
std::string name(generateDistinctName(prefix));
mCoros.insert(name, newCoro);
/* Run the coroutine until its first wait, then return here */
(*newCoro)(std::nothrow);
return name;
}
std::string LLCoros::generateDistinctName(const std::string& prefix) const std::string LLCoros::generateDistinctName(const std::string& prefix) const
{ {
// Allowing empty name would make getName()'s not-found return ambiguous. // Allowing empty name would make getName()'s not-found return ambiguous.
...@@ -86,8 +95,8 @@ bool LLCoros::kill(const std::string& name) ...@@ -86,8 +95,8 @@ bool LLCoros::kill(const std::string& name)
return false; return false;
} }
// Because this is a boost::ptr_map, erasing the map entry also destroys // Because this is a boost::ptr_map, erasing the map entry also destroys
// the referenced heap object, in this case an LLCoro. That will destroy // the referenced heap object, in this case the boost::coroutine object,
// the contained boost::coroutine object, which will terminate the coroutine. // which will terminate the coroutine.
mCoros.erase(found); mCoros.erase(found);
return true; return true;
} }
...@@ -98,7 +107,9 @@ std::string LLCoros::getNameByID(const void* self_id) const ...@@ -98,7 +107,9 @@ std::string LLCoros::getNameByID(const void* self_id) const
// passed to us comes. // passed to us comes.
for (CoroMap::const_iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; ++mi) for (CoroMap::const_iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; ++mi)
{ {
if (mi->second->owns_self_id(self_id)) namespace coro_private = boost::coroutines::detail;
if (static_cast<void*>(coro_private::coroutine_accessor::get_impl(const_cast<coro&>(*mi->second)).get())
== self_id)
{ {
return mi->first; return mi->first;
} }
......
...@@ -21,53 +21,16 @@ ...@@ -21,53 +21,16 @@
#include <boost/preprocessor/iteration/local.hpp> #include <boost/preprocessor/iteration/local.hpp>
#include <stdexcept> #include <stdexcept>
/// Base class for each coroutine
struct LLCoroBase
{
LLCoroBase() {}
virtual ~LLCoroBase() {}
virtual bool exited() const = 0;
template <typename COROUTINE_SELF>
bool owns_self(const COROUTINE_SELF& self) const
{
return owns_self_id(self.get_id());
}
virtual bool owns_self_id(const void* self_id) const = 0;
};
/// Template subclass to accommodate different boost::coroutine signatures
template <typename COROUTINE>
struct LLCoro: public LLCoroBase
{
template <typename CALLABLE>
LLCoro(const CALLABLE& callable):
mCoro(callable)
{}
virtual bool exited() const { return mCoro.exited(); }
COROUTINE mCoro;
virtual bool owns_self_id(const void* self_id) const
{
namespace coro_private = boost::coroutines::detail;
return static_cast<void*>(coro_private::coroutine_accessor::get_impl(const_cast<COROUTINE&>(mCoro)).get())
== self_id;
}
};
/** /**
* Registry of named Boost.Coroutine instances * Registry of named Boost.Coroutine instances
* *
* The Boost.Coroutine library supports the general case of a coroutine accepting * The Boost.Coroutine library supports the general case of a coroutine
* arbitrary parameters and yielding multiple (sets of) results. For such use * accepting arbitrary parameters and yielding multiple (sets of) results. For
* cases, it's natural for the invoking code to retain the coroutine instance: * such use cases, it's natural for the invoking code to retain the coroutine
* the consumer repeatedly calls back into the coroutine until it yields its * instance: the consumer repeatedly calls into the coroutine, perhaps passing
* next result. * new parameter values, prompting it to yield its next result.
* *
* Our typical coroutine usage is a bit different, though. For us, coroutines * Our typical coroutine usage is different, though. For us, coroutines
* provide an alternative to the @c Responder pattern. Our typical coroutine * provide an alternative to the @c Responder pattern. Our typical coroutine
* has @c void return, invoked in fire-and-forget mode: the handler for some * has @c void return, invoked in fire-and-forget mode: the handler for some
* user gesture launches the coroutine and promptly returns to the main loop. * user gesture launches the coroutine and promptly returns to the main loop.
...@@ -98,7 +61,11 @@ struct LLCoro: public LLCoroBase ...@@ -98,7 +61,11 @@ struct LLCoro: public LLCoroBase
class LLCoros: public LLSingleton<LLCoros> class LLCoros: public LLSingleton<LLCoros>
{ {
public: public:
/*------------------------------ launch() ------------------------------*/ /// Canonical boost::coroutines::coroutine signature we use
typedef boost::coroutines::coroutine<void()> coro;
/// Canonical 'self' type
typedef coro::self self;
/** /**
* Create and start running a new coroutine with specified name. The name * Create and start running a new coroutine with specified name. The name
* string you pass is a suggestion; it will be tweaked for uniqueness. The * string you pass is a suggestion; it will be tweaked for uniqueness. The
...@@ -106,68 +73,44 @@ class LLCoros: public LLSingleton<LLCoros> ...@@ -106,68 +73,44 @@ class LLCoros: public LLSingleton<LLCoros>
* *
* Usage looks like this, for (e.g.) two coroutine parameters: * Usage looks like this, for (e.g.) two coroutine parameters:
* @code * @code
* typedef boost::coroutines::coroutine<void(const std::string&, const LLSD&)> coro_type; * class MyClass
* std::string name = LLCoros::instance().launch<coro_type>( * {
* "mycoro", boost::bind(&MyClass::method, this, _1, _2, _3), * public:
* "somestring", LLSD(17)); * ...
* // Do NOT NOT NOT accept reference params other than 'self'!
* // Pass by value only!
* void myCoroutineMethod(LLCoros::self& self, std::string, LLSD);
* ...
* };
* ...
* std::string name = LLCoros::instance().launch(
* "mycoro", boost::bind(&MyClass::myCoroutineMethod, this, _1,
* "somestring", LLSD(17));
* @endcode * @endcode
* *
* In other words, you must specify: * Your function/method must accept LLCoros::self& as its first parameter.
* It can accept any other parameters you want -- but ONLY BY VALUE!
* Other reference parameters are a BAD IDEA! You Have Been Warned. See
* DEV-32777 comments for an explanation.
* *
* * the desired <tt>boost::coroutines::coroutine</tt> type, to whose * Pass a callable that accepts the single LLCoros::self& parameter. It
* signature the initial <tt>coro_type::self&</tt> parameter is * may work to pass a free function whose only parameter is 'self'; for
* implicitly added * all other cases use boost::bind(). Of course, for a non-static class
* * the suggested name string for the new coroutine instance * method, the first parameter must be the class instance. Use the
* * the callable to be run, e.g. <tt>boost::bind()</tt> expression for a * placeholder _1 for the 'self' parameter. Any other parameters should be
* class method -- not forgetting to add _1 for the * passed via the bind() expression.
* <tt>coro_type::self&</tt> parameter
* * the actual parameters to be passed to that callable after the
* implicit <tt>coro_type::self&</tt> parameter
* *
* launch() tweaks the suggested name so it won't collide with any * launch() tweaks the suggested name so it won't collide with any
* existing coroutine instance, creates the coroutine instance, registers * existing coroutine instance, creates the coroutine instance, registers
* it with the tweaked name and runs it until its first wait. At that * it with the tweaked name and runs it until its first wait. At that
* point it returns the tweaked name. * point it returns the tweaked name.
*
* Use of a typedef for the coroutine type is recommended, because you
* must restate it for the callable's first parameter.
*
* @note
* launch() only accepts const-reference parameters. Once we can assume
* C++0x features on every platform, we'll have so-called "perfect
* forwarding" and variadic templates and other such ponies, and can
* support an arbitrary number of truly arbitrary parameter types. But for
* now, we'll stick with const reference params. N.B. Passing a non-const
* reference to a local variable into a coroutine seems like a @em really
* bad idea: the local variable will be destroyed during the lifetime of
* the coroutine.
*/ */
// Use the preprocessor to generate launch() overloads accepting 0, 1, template <typename CALLABLE>
// ..., BOOST_COROUTINE_ARG_MAX const ref params of arbitrary type. std::string launch(const std::string& prefix, const CALLABLE& callable)
#define BOOST_PP_LOCAL_MACRO(n) \ {
template <typename COROUTINE, typename CALLABLE \ return launchImpl(prefix, new coro(callable));
BOOST_PP_COMMA_IF(n) \
BOOST_PP_ENUM_PARAMS(n, typename T)> \
std::string launch(const std::string& prefix, const CALLABLE& callable \
BOOST_PP_COMMA_IF(n) \
BOOST_PP_ENUM_BINARY_PARAMS(n, const T, & p)) \
{ \
std::string name(generateDistinctName(prefix)); \
LLCoro<COROUTINE>* ptr = new LLCoro<COROUTINE>(callable); \
mCoros.insert(name, ptr); \
/* Run the coroutine until its first wait, then return here */ \
ptr->mCoro(std::nothrow \
BOOST_PP_COMMA_IF(n) \
BOOST_PP_ENUM_PARAMS(n, p)); \
return name; \
} }
#define BOOST_PP_LOCAL_LIMITS (0, BOOST_COROUTINE_ARG_MAX)
#include BOOST_PP_LOCAL_ITERATE()
#undef BOOST_PP_LOCAL_MACRO
#undef BOOST_PP_LOCAL_LIMITS
/*----------------------- end of launch() family -----------------------*/
/** /**
* Abort a running coroutine by name. Normally, when a coroutine either * Abort a running coroutine by name. Normally, when a coroutine either
* runs to completion or terminates with an exception, LLCoros quietly * runs to completion or terminates with an exception, LLCoros quietly
...@@ -195,10 +138,11 @@ class LLCoros: public LLSingleton<LLCoros> ...@@ -195,10 +138,11 @@ class LLCoros: public LLSingleton<LLCoros>
private: private:
friend class LLSingleton<LLCoros>; friend class LLSingleton<LLCoros>;
LLCoros(); LLCoros();
std::string launchImpl(const std::string& prefix, coro* newCoro);
std::string generateDistinctName(const std::string& prefix) const; std::string generateDistinctName(const std::string& prefix) const;
bool cleanup(const LLSD&); bool cleanup(const LLSD&);
typedef boost::ptr_map<std::string, LLCoroBase> CoroMap; typedef boost::ptr_map<std::string, coro> CoroMap;
CoroMap mCoros; CoroMap mCoros;
}; };
......
...@@ -105,12 +105,10 @@ class LLLogin::Impl ...@@ -105,12 +105,10 @@ class LLLogin::Impl
return response; return response;
} }
typedef boost::coroutines::coroutine<void(std::string, LLSD)> coroutine_type;
// In a coroutine's top-level function args, do NOT NOT NOT accept // In a coroutine's top-level function args, do NOT NOT NOT accept
// references (const or otherwise) to anything but the self argument! Pass // references (const or otherwise) to anything but the self argument! Pass
// by value only! // by value only!
void login_(coroutine_type::self& self, std::string uri, LLSD credentials); void login_(LLCoros::self& self, std::string uri, LLSD credentials);
LLEventStream mPump; LLEventStream mPump;
LLSD mAuthResponse, mValidAuthResponse; LLSD mAuthResponse, mValidAuthResponse;
...@@ -121,11 +119,11 @@ void LLLogin::Impl::connect(const std::string& uri, const LLSD& credentials) ...@@ -121,11 +119,11 @@ void LLLogin::Impl::connect(const std::string& uri, const LLSD& credentials)
// Launch a coroutine with our login_() method. Run the coroutine until // Launch a coroutine with our login_() method. Run the coroutine until
// its first wait; at that point, return here. // its first wait; at that point, return here.
std::string coroname = std::string coroname =
LLCoros::instance().launch<coroutine_type>("LLLogin::Impl::login_", LLCoros::instance().launch("LLLogin::Impl::login_",
boost::bind(&Impl::login_, this, _1, uri, credentials)); boost::bind(&Impl::login_, this, _1, uri, credentials));
} }
void LLLogin::Impl::login_(coroutine_type::self& self, std::string uri, LLSD credentials) void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD credentials)
{ {
LL_INFOS("LLLogin") << "Entering coroutine " << LLCoros::instance().getName(self) LL_INFOS("LLLogin") << "Entering coroutine " << LLCoros::instance().getName(self)
<< " with uri '" << uri << "', credentials " << credentials << LL_ENDL; << " with uri '" << uri << "', credentials " << credentials << LL_ENDL;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment