LLViewerRegion WIP: Added all headers that are dragged in.
Most in particular llevents.h, which comes along with the demand that the old events in llevent.h are put in a namespace LLOldEvents. Made all changes necessary to compile the rest of the code again (without changing the actual code: it's still using the old events). This patch also removes LLStopWhenHandled and LLStandardSignal from indra/llui/llnotifications.h because those are moved to llevents.h. That seems to be the only change to indra/llui/llnotifications.h that isn't floater related, so I left the rest of that file alone.
This commit is contained in:
@@ -29,14 +29,21 @@ set(llcommon_SOURCE_FILES
|
||||
llbase64.cpp
|
||||
llcommon.cpp
|
||||
llcommonutils.cpp
|
||||
llcoros.cpp
|
||||
llcrc.cpp
|
||||
llcriticaldamp.cpp
|
||||
llcursortypes.cpp
|
||||
lldate.cpp
|
||||
lldependencies.cpp
|
||||
lldictionary.cpp
|
||||
llerror.cpp
|
||||
llerrorthread.cpp
|
||||
llevent.cpp
|
||||
lleventapi.cpp
|
||||
lleventcoro.cpp
|
||||
lleventdispatcher.cpp
|
||||
lleventfilter.cpp
|
||||
llevents.cpp
|
||||
lleventtimer.cpp
|
||||
llfasttimer_class.cpp
|
||||
llfile.cpp
|
||||
@@ -84,6 +91,7 @@ set(llcommon_SOURCE_FILES
|
||||
lluri.cpp
|
||||
lluuid.cpp
|
||||
llworkerthread.cpp
|
||||
ll_template_cast.h
|
||||
metaclass.cpp
|
||||
metaproperty.cpp
|
||||
reflective.cpp
|
||||
@@ -121,6 +129,7 @@ set(llcommon_HEADER_FILES
|
||||
llclickaction.h
|
||||
llcommon.h
|
||||
llcommonutils.h
|
||||
llcoros.h
|
||||
llcrc.h
|
||||
llcriticaldamp.h
|
||||
llcursortypes.h
|
||||
@@ -128,6 +137,7 @@ set(llcommon_HEADER_FILES
|
||||
lldarrayptr.h
|
||||
lldate.h
|
||||
lldefs.h
|
||||
lldependencies.h
|
||||
lldeleteutils.h
|
||||
lldepthstack.h
|
||||
lldictionary.h
|
||||
@@ -140,6 +150,11 @@ set(llcommon_HEADER_FILES
|
||||
llerrorlegacy.h
|
||||
llerrorthread.h
|
||||
llevent.h
|
||||
lleventapi.h
|
||||
lleventcoro.h
|
||||
lleventdispatcher.h
|
||||
lleventfilter.h
|
||||
llevents.h
|
||||
lleventemitter.h
|
||||
llextendedstatus.h
|
||||
lleventtimer.h
|
||||
|
||||
177
indra/llcommon/ll_template_cast.h
Normal file
177
indra/llcommon/ll_template_cast.h
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* @file ll_template_cast.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-11-21
|
||||
* @brief Define ll_template_cast function
|
||||
*
|
||||
* $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_LL_TEMPLATE_CAST_H)
|
||||
#define LL_LL_TEMPLATE_CAST_H
|
||||
|
||||
/**
|
||||
* Implementation for ll_template_cast() (q.v.).
|
||||
*
|
||||
* Default implementation: trying to cast two completely unrelated types
|
||||
* returns 0. Typically you'd specify T and U as pointer types, but in fact T
|
||||
* can be any type that can be initialized with 0.
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
struct ll_template_cast_impl
|
||||
{
|
||||
T operator()(U)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ll_template_cast<T>(some_value) is for use in a template function when
|
||||
* some_value might be of arbitrary type, but you want to recognize type T
|
||||
* specially.
|
||||
*
|
||||
* It's designed for use with pointer types. Example:
|
||||
* @code
|
||||
* struct SpecialClass
|
||||
* {
|
||||
* void someMethod(const std::string&) const;
|
||||
* };
|
||||
*
|
||||
* template <class REALCLASS>
|
||||
* void somefunc(const REALCLASS& instance)
|
||||
* {
|
||||
* const SpecialClass* ptr = ll_template_cast<const SpecialClass*>(&instance);
|
||||
* if (ptr)
|
||||
* {
|
||||
* ptr->someMethod("Call method only available on SpecialClass");
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Why is this better than dynamic_cast<>? Because unless OtherClass is
|
||||
* polymorphic, the following won't even compile (gcc 4.0.1):
|
||||
* @code
|
||||
* OtherClass other;
|
||||
* SpecialClass* ptr = dynamic_cast<SpecialClass*>(&other);
|
||||
* @endcode
|
||||
* to say nothing of this:
|
||||
* @code
|
||||
* void function(int);
|
||||
* SpecialClass* ptr = dynamic_cast<SpecialClass*>(&function);
|
||||
* @endcode
|
||||
* ll_template_cast handles these kinds of cases by returning 0.
|
||||
*/
|
||||
template <typename T, typename U>
|
||||
T ll_template_cast(U value)
|
||||
{
|
||||
return ll_template_cast_impl<T, U>()(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation for ll_template_cast() (q.v.).
|
||||
*
|
||||
* Implementation for identical types: return same value.
|
||||
*/
|
||||
template <typename T>
|
||||
struct ll_template_cast_impl<T, T>
|
||||
{
|
||||
T operator()(T value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of
|
||||
* type @c source, <tt>ll_template_cast<dest>(s)</tt> will return @c s --
|
||||
* presuming that @c source can be converted to @c dest by the normal rules of
|
||||
* C++.
|
||||
*
|
||||
* By default, <tt>ll_template_cast<dest>(s)</tt> will return 0 unless @c s's
|
||||
* type is literally identical to @c dest. (This is because of the
|
||||
* straightforward application of template specialization rules.) That can
|
||||
* lead to surprising results, e.g.:
|
||||
*
|
||||
* @code
|
||||
* Foo myFoo;
|
||||
* const Foo* fooptr = ll_template_cast<const Foo*>(&myFoo);
|
||||
* @endcode
|
||||
*
|
||||
* Here @c fooptr will be 0 because <tt>&myFoo</tt> is of type <tt>Foo*</tt>
|
||||
* -- @em not <tt>const Foo*</tt>. (Declaring <tt>const Foo myFoo;</tt> would
|
||||
* force the compiler to do the right thing.)
|
||||
*
|
||||
* More disappointingly:
|
||||
* @code
|
||||
* struct Base {};
|
||||
* struct Subclass: public Base {};
|
||||
* Subclass object;
|
||||
* Base* ptr = ll_template_cast<Base*>(&object);
|
||||
* @endcode
|
||||
*
|
||||
* Here @c ptr will be 0 because <tt>&object</tt> is of type
|
||||
* <tt>Subclass*</tt> rather than <tt>Base*</tt>. We @em want this cast to
|
||||
* succeed, but without our help ll_template_cast can't recognize it.
|
||||
*
|
||||
* The following would suffice:
|
||||
* @code
|
||||
* LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*);
|
||||
* ...
|
||||
* Base* ptr = ll_template_cast<Base*>(&object);
|
||||
* @endcode
|
||||
*
|
||||
* However, as noted earlier, this is easily fooled:
|
||||
* @code
|
||||
* const Base* ptr = ll_template_cast<const Base*>(&object);
|
||||
* @endcode
|
||||
* would still produce 0 because we haven't yet seen:
|
||||
* @code
|
||||
* LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*);
|
||||
* @endcode
|
||||
*
|
||||
* @TODO
|
||||
* This macro should use Boost type_traits facilities for stripping and
|
||||
* re-adding @c const and @c volatile qualifiers so that invoking
|
||||
* LL_TEMPLATE_CONVERTIBLE(dest, source) will automatically generate all
|
||||
* permitted permutations. It's really not fair to the coder to require
|
||||
* separate:
|
||||
* @code
|
||||
* LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*);
|
||||
* LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*);
|
||||
* LL_TEMPLATE_CONVERTIBLE(const Base*, const Subclass*);
|
||||
* @endcode
|
||||
*
|
||||
* (Naturally we omit <tt>LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*)</tt>
|
||||
* because that's not permitted by normal C++ assignment anyway.)
|
||||
*/
|
||||
#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \
|
||||
template <> \
|
||||
struct ll_template_cast_impl<DEST, SOURCE> \
|
||||
{ \
|
||||
DEST operator()(SOURCE wrapper) \
|
||||
{ \
|
||||
return wrapper; \
|
||||
} \
|
||||
}
|
||||
|
||||
#endif /* ! defined(LL_LL_TEMPLATE_CAST_H) */
|
||||
154
indra/llcommon/llcoros.cpp
Normal file
154
indra/llcommon/llcoros.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* @file llcoros.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-06-03
|
||||
* @brief Implementation for llcoros.
|
||||
*
|
||||
* $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$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llcoros.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/bind.hpp>
|
||||
// other Linden headers
|
||||
#include "llevents.h"
|
||||
#include "llerror.h"
|
||||
#include "stringize.h"
|
||||
|
||||
LLCoros::LLCoros()
|
||||
{
|
||||
// Register our cleanup() method for "mainloop" ticks
|
||||
LLEventPumps::instance().obtain("mainloop").listen(
|
||||
"LLCoros", boost::bind(&LLCoros::cleanup, this, _1));
|
||||
}
|
||||
|
||||
bool LLCoros::cleanup(const LLSD&)
|
||||
{
|
||||
// Walk the mCoros map, checking and removing completed coroutines.
|
||||
for (CoroMap::iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; )
|
||||
{
|
||||
// Has this coroutine exited (normal return, exception, exit() call)
|
||||
// since last tick?
|
||||
if (mi->second->exited())
|
||||
{
|
||||
LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL;
|
||||
// The erase() call will invalidate its passed iterator value --
|
||||
// so increment mi FIRST -- but pass its original value to
|
||||
// erase(). This is what postincrement is all about.
|
||||
mCoros.erase(mi++);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Still live, just skip this entry as if incrementing at the top
|
||||
// of the loop as usual.
|
||||
++mi;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string LLCoros::generateDistinctName(const std::string& prefix) const
|
||||
{
|
||||
// Allowing empty name would make getName()'s not-found return ambiguous.
|
||||
if (prefix.empty())
|
||||
{
|
||||
LL_ERRS("LLCoros") << "LLCoros::launch(): pass non-empty name string" << LL_ENDL;
|
||||
}
|
||||
|
||||
// If the specified name isn't already in the map, just use that.
|
||||
std::string name(prefix);
|
||||
|
||||
// Find the lowest numeric suffix that doesn't collide with an existing
|
||||
// entry. Start with 2 just to make it more intuitive for any interested
|
||||
// parties: e.g. "joe", "joe2", "joe3"...
|
||||
for (int i = 2; ; name = STRINGIZE(prefix << i++))
|
||||
{
|
||||
if (mCoros.find(name) == mCoros.end())
|
||||
{
|
||||
LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LLCoros::kill(const std::string& name)
|
||||
{
|
||||
CoroMap::iterator found = mCoros.find(name);
|
||||
if (found == mCoros.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Because this is a boost::ptr_map, erasing the map entry also destroys
|
||||
// the referenced heap object, in this case the boost::coroutine object,
|
||||
// which will terminate the coroutine.
|
||||
mCoros.erase(found);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string LLCoros::getNameByID(const void* self_id) const
|
||||
{
|
||||
// Walk the existing coroutines, looking for one from which the 'self_id'
|
||||
// passed to us comes.
|
||||
for (CoroMap::const_iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; ++mi)
|
||||
{
|
||||
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 "";
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* MUST BE LAST
|
||||
*****************************************************************************/
|
||||
// Turn off MSVC optimizations for just LLCoros::launchImpl() -- see
|
||||
// DEV-32777. But MSVC doesn't support push/pop for optimization flags as it
|
||||
// does for warning suppression, and we really don't want to force
|
||||
// optimization ON for other code even in Debug or RelWithDebInfo builds.
|
||||
|
||||
#if LL_MSVC
|
||||
// work around broken optimizations
|
||||
#pragma warning(disable: 4748)
|
||||
#pragma optimize("", off)
|
||||
#endif // LL_MSVC
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#if LL_MSVC
|
||||
// reenable optimizations
|
||||
#pragma optimize("", on)
|
||||
#endif // LL_MSVC
|
||||
166
indra/llcommon/llcoros.h
Normal file
166
indra/llcommon/llcoros.h
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @file llcoros.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-06-02
|
||||
* @brief Manage running boost::coroutine instances
|
||||
*
|
||||
* $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_LLCOROS_H)
|
||||
#define LL_LLCOROS_H
|
||||
|
||||
#include <boost/coroutine/coroutine.hpp>
|
||||
#include "llsingleton.h"
|
||||
#include <boost/ptr_container/ptr_map.hpp>
|
||||
#include <string>
|
||||
#include <boost/preprocessor/repetition/enum_params.hpp>
|
||||
#include <boost/preprocessor/repetition/enum_binary_params.hpp>
|
||||
#include <boost/preprocessor/iteration/local.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
/**
|
||||
* Registry of named Boost.Coroutine instances
|
||||
*
|
||||
* The Boost.Coroutine library supports the general case of a coroutine
|
||||
* accepting arbitrary parameters and yielding multiple (sets of) results. For
|
||||
* such use cases, it's natural for the invoking code to retain the coroutine
|
||||
* instance: the consumer repeatedly calls into the coroutine, perhaps passing
|
||||
* new parameter values, prompting it to yield its next result.
|
||||
*
|
||||
* Our typical coroutine usage is different, though. For us, coroutines
|
||||
* 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
|
||||
* user gesture launches the coroutine and promptly returns to the main loop.
|
||||
* The coroutine initiates some action that will take multiple frames (e.g. a
|
||||
* capability request), waits for its result, processes it and silently steals
|
||||
* away.
|
||||
*
|
||||
* This usage poses two (related) problems:
|
||||
*
|
||||
* # Who should own the coroutine instance? If it's simply local to the
|
||||
* handler code that launches it, return from the handler will destroy the
|
||||
* coroutine object, terminating the coroutine.
|
||||
* # Once the coroutine terminates, in whatever way, who's responsible for
|
||||
* cleaning up the coroutine object?
|
||||
*
|
||||
* LLCoros is a Singleton collection of currently-active coroutine instances.
|
||||
* Each has a name. You ask LLCoros to launch a new coroutine with a suggested
|
||||
* name prefix; from your prefix it generates a distinct name, registers the
|
||||
* new coroutine and returns the actual name.
|
||||
*
|
||||
* The name can be used to kill off the coroutine prematurely, if needed. It
|
||||
* can also provide diagnostic info: we can look up the name of the
|
||||
* currently-running coroutine.
|
||||
*
|
||||
* Finally, the next frame ("mainloop" event) after the coroutine terminates,
|
||||
* LLCoros will notice its demise and destroy it.
|
||||
*/
|
||||
class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
|
||||
{
|
||||
public:
|
||||
/// 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
|
||||
* string you pass is a suggestion; it will be tweaked for uniqueness. The
|
||||
* actual name is returned to you.
|
||||
*
|
||||
* Usage looks like this, for (e.g.) two coroutine parameters:
|
||||
* @code
|
||||
* class MyClass
|
||||
* {
|
||||
* public:
|
||||
* ...
|
||||
* // 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
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Pass a callable that accepts the single LLCoros::self& parameter. It
|
||||
* may work to pass a free function whose only parameter is 'self'; for
|
||||
* all other cases use boost::bind(). Of course, for a non-static class
|
||||
* method, the first parameter must be the class instance. Use the
|
||||
* placeholder _1 for the 'self' parameter. Any other parameters should be
|
||||
* passed via the bind() expression.
|
||||
*
|
||||
* launch() tweaks the suggested name so it won't collide with any
|
||||
* existing coroutine instance, creates the coroutine instance, registers
|
||||
* it with the tweaked name and runs it until its first wait. At that
|
||||
* point it returns the tweaked name.
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
std::string launch(const std::string& prefix, const CALLABLE& callable)
|
||||
{
|
||||
return launchImpl(prefix, new coro(callable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort a running coroutine by name. Normally, when a coroutine either
|
||||
* runs to completion or terminates with an exception, LLCoros quietly
|
||||
* cleans it up. This is for use only when you must explicitly interrupt
|
||||
* one prematurely. Returns @c true if the specified name was found and
|
||||
* still running at the time.
|
||||
*/
|
||||
bool kill(const std::string& name);
|
||||
|
||||
/**
|
||||
* From within a coroutine, pass its @c self object to look up the
|
||||
* (tweaked) name string by which this coroutine is registered. Returns
|
||||
* the empty string if not found (e.g. if the coroutine was launched by
|
||||
* hand rather than using LLCoros::launch()).
|
||||
*/
|
||||
template <typename COROUTINE_SELF>
|
||||
std::string getName(const COROUTINE_SELF& self) const
|
||||
{
|
||||
return getNameByID(self.get_id());
|
||||
}
|
||||
|
||||
/// getName() by self.get_id()
|
||||
std::string getNameByID(const void* self_id) const;
|
||||
|
||||
private:
|
||||
friend class LLSingleton<LLCoros>;
|
||||
LLCoros();
|
||||
std::string launchImpl(const std::string& prefix, coro* newCoro);
|
||||
std::string generateDistinctName(const std::string& prefix) const;
|
||||
bool cleanup(const LLSD&);
|
||||
|
||||
typedef boost::ptr_map<std::string, coro> CoroMap;
|
||||
CoroMap mCoros;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLCOROS_H) */
|
||||
103
indra/llcommon/lldependencies.cpp
Normal file
103
indra/llcommon/lldependencies.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* @file lldependencies.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2008-09-17
|
||||
* @brief Implementation for lldependencies.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&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$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "lldependencies.h"
|
||||
// STL headers
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/graph/graph_traits.hpp> // for boost::graph_traits
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
#include <boost/graph/topological_sort.hpp>
|
||||
#include <boost/graph/exception.hpp>
|
||||
// other Linden headers
|
||||
|
||||
LLDependenciesBase::VertexList LLDependenciesBase::topo_sort(int vertices, const EdgeList& edges) const
|
||||
{
|
||||
// Construct a Boost Graph Library graph according to the constraints
|
||||
// we've collected. It seems as though we ought to be able to capture
|
||||
// the uniqueness of vertex keys using a setS of vertices with a
|
||||
// string property -- but I don't yet understand adjacency_list well
|
||||
// enough to get there. All the examples I've seen so far use integers
|
||||
// for vertices.
|
||||
// Define the Graph type. Use a vector for vertices so we can use the
|
||||
// default topological_sort vertex lookup by int index. Use a set for
|
||||
// edges because the same dependency may be stated twice: Node "a" may
|
||||
// specify that it must precede "b", while "b" may also state that it
|
||||
// must follow "a".
|
||||
typedef boost::adjacency_list<boost::setS, boost::vecS, boost::directedS,
|
||||
boost::no_property> Graph;
|
||||
// Instantiate the graph. Without vertex properties, we need say no
|
||||
// more about vertices than the total number.
|
||||
Graph g(edges.begin(), edges.end(), vertices);
|
||||
// topo sort
|
||||
typedef boost::graph_traits<Graph>::vertex_descriptor VertexDesc;
|
||||
typedef std::vector<VertexDesc> SortedList;
|
||||
SortedList sorted;
|
||||
// note that it throws not_a_dag if it finds a cycle
|
||||
try
|
||||
{
|
||||
boost::topological_sort(g, std::back_inserter(sorted));
|
||||
}
|
||||
catch (const boost::not_a_dag& e)
|
||||
{
|
||||
// translate to the exception we define
|
||||
std::ostringstream out;
|
||||
out << "LLDependencies cycle: " << e.what() << '\n';
|
||||
// Omit independent nodes: display only those that might contribute to
|
||||
// the cycle.
|
||||
describe(out, false);
|
||||
throw Cycle(out.str());
|
||||
}
|
||||
// A peculiarity of boost::topological_sort() is that it emits results in
|
||||
// REVERSE topological order: to get the result you want, you must
|
||||
// traverse the SortedList using reverse iterators.
|
||||
return VertexList(sorted.rbegin(), sorted.rend());
|
||||
}
|
||||
|
||||
std::ostream& LLDependenciesBase::describe(std::ostream& out, bool full) const
|
||||
{
|
||||
// Should never encounter this base-class implementation; may mean that
|
||||
// the KEY type doesn't have a suitable ostream operator<<().
|
||||
out << "<no description available>";
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string LLDependenciesBase::describe(bool full) const
|
||||
{
|
||||
// Just use the ostream-based describe() on a std::ostringstream. The
|
||||
// implementation is here mostly so that we can avoid #include <sstream>
|
||||
// in the header file.
|
||||
std::ostringstream out;
|
||||
describe(out, full);
|
||||
return out.str();
|
||||
}
|
||||
799
indra/llcommon/lldependencies.h
Normal file
799
indra/llcommon/lldependencies.h
Normal file
@@ -0,0 +1,799 @@
|
||||
/**
|
||||
* @file lldependencies.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2008-09-17
|
||||
* @brief LLDependencies: a generic mechanism for expressing "b must follow a,
|
||||
* but precede c"
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&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_LLDEPENDENCIES_H)
|
||||
#define LL_LLDEPENDENCIES_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <iosfwd>
|
||||
#include <boost/iterator/transform_iterator.hpp>
|
||||
#include <boost/iterator/indirect_iterator.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
/*****************************************************************************
|
||||
* Utilities
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* generic range transformer: given a range acceptable to Boost.Range (e.g. a
|
||||
* standard container, an iterator pair, ...) and a unary function to apply to
|
||||
* each element of the range, make a corresponding range that lazily applies
|
||||
* that function to each element on dereferencing.
|
||||
*/
|
||||
template<typename FUNCTION, typename RANGE>
|
||||
inline
|
||||
boost::iterator_range<boost::transform_iterator<FUNCTION,
|
||||
typename boost::range_const_iterator<RANGE>::type> >
|
||||
make_transform_range(const RANGE& range, FUNCTION function)
|
||||
{
|
||||
// shorthand for the iterator type embedded in our return type
|
||||
typedef boost::transform_iterator<FUNCTION, typename boost::range_const_iterator<RANGE>::type>
|
||||
transform_iterator;
|
||||
return boost::make_iterator_range(transform_iterator(boost::begin(range), function),
|
||||
transform_iterator(boost::end(range), function));
|
||||
}
|
||||
|
||||
/// non-const version of make_transform_range()
|
||||
template<typename FUNCTION, typename RANGE>
|
||||
inline
|
||||
boost::iterator_range<boost::transform_iterator<FUNCTION,
|
||||
typename boost::range_iterator<RANGE>::type> >
|
||||
make_transform_range(RANGE& range, FUNCTION function)
|
||||
{
|
||||
// shorthand for the iterator type embedded in our return type
|
||||
typedef boost::transform_iterator<FUNCTION, typename boost::range_iterator<RANGE>::type>
|
||||
transform_iterator;
|
||||
return boost::make_iterator_range(transform_iterator(boost::begin(range), function),
|
||||
transform_iterator(boost::end(range), function));
|
||||
}
|
||||
|
||||
/**
|
||||
* From any range compatible with Boost.Range, instantiate any class capable
|
||||
* of accepting an iterator pair.
|
||||
*/
|
||||
template<class TYPE>
|
||||
struct instance_from_range: public TYPE
|
||||
{
|
||||
template<typename RANGE>
|
||||
instance_from_range(RANGE range):
|
||||
TYPE(boost::begin(range), boost::end(range))
|
||||
{}
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* LLDependencies
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* LLDependencies components that should not be reinstantiated for each KEY,
|
||||
* NODE specialization
|
||||
*/
|
||||
class LL_COMMON_API LLDependenciesBase
|
||||
{
|
||||
public:
|
||||
virtual ~LLDependenciesBase() {}
|
||||
|
||||
/**
|
||||
* Exception thrown by sort() if there's a cycle
|
||||
*/
|
||||
struct Cycle: public std::runtime_error
|
||||
{
|
||||
Cycle(const std::string& what): std::runtime_error(what) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide a short description of this LLDependencies instance on the
|
||||
* specified output stream, assuming that its KEY type has an operator<<()
|
||||
* that works with std::ostream.
|
||||
*
|
||||
* Pass @a full as @c false to omit any keys without dependency constraints.
|
||||
*/
|
||||
virtual std::ostream& describe(std::ostream& out, bool full=true) const;
|
||||
|
||||
/// describe() to a string
|
||||
virtual std::string describe(bool full=true) const;
|
||||
|
||||
protected:
|
||||
typedef std::vector< std::pair<int, int> > EdgeList;
|
||||
typedef std::vector<int> VertexList;
|
||||
VertexList topo_sort(int vertices, const EdgeList& edges) const;
|
||||
|
||||
/**
|
||||
* refpair is specifically intended to capture a pair of references. This
|
||||
* is better than std::pair<T1&, T2&> because some implementations of
|
||||
* std::pair's ctor accept const references to the two types. If the
|
||||
* types are themselves references, this results in an illegal reference-
|
||||
* to-reference.
|
||||
*/
|
||||
template<typename T1, typename T2>
|
||||
struct refpair
|
||||
{
|
||||
refpair(T1 value1, T2 value2):
|
||||
first(value1),
|
||||
second(value2)
|
||||
{}
|
||||
T1 first;
|
||||
T2 second;
|
||||
};
|
||||
};
|
||||
|
||||
/// describe() helper: for most types, report the type as usual
|
||||
template<typename T>
|
||||
inline
|
||||
std::ostream& LLDependencies_describe(std::ostream& out, const T& key)
|
||||
{
|
||||
out << key;
|
||||
return out;
|
||||
}
|
||||
|
||||
/// specialize LLDependencies_describe() for std::string
|
||||
template<>
|
||||
inline
|
||||
std::ostream& LLDependencies_describe(std::ostream& out, const std::string& key)
|
||||
{
|
||||
out << '"' << key << '"';
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's reasonable to use LLDependencies in a keys-only way, more or less like
|
||||
* std::set. For that case, the default NODE type is an empty struct.
|
||||
*/
|
||||
struct LLDependenciesEmpty
|
||||
{
|
||||
LLDependenciesEmpty() {}
|
||||
/**
|
||||
* Give it a constructor accepting void* so caller can pass placeholder
|
||||
* values such as NULL or 0 rather than having to write
|
||||
* LLDependenciesEmpty().
|
||||
*/
|
||||
LLDependenciesEmpty(void*) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class manages abstract dependencies between node types of your
|
||||
* choosing. As with a std::map, nodes are copied when add()ed, so the node
|
||||
* type should be relatively lightweight; to manipulate dependencies between
|
||||
* expensive objects, use a pointer type.
|
||||
*
|
||||
* For a given node, you may state the keys of nodes that must precede it
|
||||
* and/or nodes that must follow it. The sort() method will produce an order
|
||||
* that should work, or throw an exception if the constraints are impossible.
|
||||
* We cache results to minimize the cost of repeated sort() calls.
|
||||
*/
|
||||
template<typename KEY = std::string,
|
||||
typename NODE = LLDependenciesEmpty>
|
||||
class LLDependencies: public LLDependenciesBase
|
||||
{
|
||||
typedef LLDependencies<KEY, NODE> self_type;
|
||||
|
||||
/**
|
||||
* Internally, we bundle the client's NODE with its before/after keys.
|
||||
*/
|
||||
struct DepNode
|
||||
{
|
||||
typedef std::set<KEY> dep_set;
|
||||
DepNode(const NODE& node_, const dep_set& after_, const dep_set& before_):
|
||||
node(node_),
|
||||
after(after_),
|
||||
before(before_)
|
||||
{}
|
||||
NODE node;
|
||||
dep_set after, before;
|
||||
};
|
||||
typedef std::map<KEY, DepNode> DepNodeMap;
|
||||
typedef typename DepNodeMap::value_type DepNodeMapEntry;
|
||||
|
||||
/// We have various ways to get the dependencies for a given DepNode.
|
||||
/// Rather than having to restate each one for 'after' and 'before'
|
||||
/// separately, pass a dep_selector so we can apply each to either.
|
||||
typedef boost::function<const typename DepNode::dep_set&(const DepNode&)> dep_selector;
|
||||
|
||||
public:
|
||||
LLDependencies() {}
|
||||
|
||||
typedef KEY key_type;
|
||||
typedef NODE node_type;
|
||||
|
||||
/// param type used to express lists of other node keys -- note that such
|
||||
/// lists can be initialized with boost::assign::list_of()
|
||||
typedef std::vector<KEY> KeyList;
|
||||
|
||||
/**
|
||||
* Add a new node. State its dependencies on other nodes (which may not
|
||||
* yet have been added) by listing the keys of nodes this new one must
|
||||
* follow, and separately the keys of nodes this new one must precede.
|
||||
*
|
||||
* The node you pass is @em copied into an internal data structure. If you
|
||||
* want to modify the node value after add()ing it, capture the returned
|
||||
* NODE& reference.
|
||||
*
|
||||
* @note
|
||||
* Actual dependency analysis is deferred to the sort() method, so
|
||||
* you can add an arbitrary number of nodes without incurring analysis
|
||||
* overhead for each. The flip side of this is that add()ing nodes that
|
||||
* define a cycle leaves this object in a state in which sort() will
|
||||
* always throw the Cycle exception.
|
||||
*
|
||||
* Two distinct use cases are anticipated:
|
||||
* * The data used to load this object are completely known at compile
|
||||
* time (e.g. LLEventPump listener names). A Cycle exception represents a
|
||||
* bug which can be corrected by the coder. The program need neither catch
|
||||
* Cycle nor attempt to salvage the state of this object.
|
||||
* * The data are loaded at runtime, therefore the universe of
|
||||
* dependencies cannot be known at compile time. The client code should
|
||||
* catch Cycle.
|
||||
* ** If a Cycle exception indicates fatally-flawed input data, this
|
||||
* object can simply be discarded, possibly with the entire program run.
|
||||
* ** If it is essential to restore this object to a working state, the
|
||||
* simplest workaround is to remove() nodes in LIFO order.
|
||||
* *** It may be useful to add functionality to this class to track the
|
||||
* add() chronology, providing a pop() method to remove the most recently
|
||||
* added node.
|
||||
* *** It may further be useful to add a restore() method which would
|
||||
* pop() until sort() no longer throws Cycle. This method would be
|
||||
* expensive -- but it's not clear that client code could resolve the
|
||||
* problem more cheaply.
|
||||
*/
|
||||
NODE& add(const KEY& key, const NODE& node = NODE(),
|
||||
const KeyList& after = KeyList(),
|
||||
const KeyList& before = KeyList())
|
||||
{
|
||||
// Get the passed-in lists as sets for equality comparison
|
||||
typename DepNode::dep_set
|
||||
after_set(after.begin(), after.end()),
|
||||
before_set(before.begin(), before.end());
|
||||
// Try to insert the new node; if it already exists, find the old
|
||||
// node instead.
|
||||
std::pair<typename DepNodeMap::iterator, bool> inserted =
|
||||
mNodes.insert(typename DepNodeMap::value_type(key,
|
||||
DepNode(node, after_set, before_set)));
|
||||
if (! inserted.second) // bool indicating success of insert()
|
||||
{
|
||||
// We already have a node by this name. Have its dependencies
|
||||
// changed? If the existing node's dependencies are identical, the
|
||||
// result will be unchanged, so we can leave the cache intact.
|
||||
// Regardless of inserted.second, inserted.first is the iterator
|
||||
// to the newly-inserted (or existing) map entry. Of course, that
|
||||
// entry's second is the DepNode of interest.
|
||||
if (inserted.first->second.after != after_set ||
|
||||
inserted.first->second.before != before_set)
|
||||
{
|
||||
// Dependencies have changed: clear the cached result.
|
||||
mCache.clear();
|
||||
// save the new dependencies
|
||||
inserted.first->second.after = after_set;
|
||||
inserted.first->second.before = before_set;
|
||||
}
|
||||
}
|
||||
else // this node is new
|
||||
{
|
||||
// This will change results.
|
||||
mCache.clear();
|
||||
}
|
||||
return inserted.first->second.node;
|
||||
}
|
||||
|
||||
/// the value of an iterator, showing both KEY and its NODE
|
||||
typedef refpair<const KEY&, NODE&> value_type;
|
||||
/// the value of a const_iterator
|
||||
typedef refpair<const KEY&, const NODE&> const_value_type;
|
||||
|
||||
private:
|
||||
// Extract functors
|
||||
static value_type value_extract(DepNodeMapEntry& entry)
|
||||
{
|
||||
return value_type(entry.first, entry.second.node);
|
||||
}
|
||||
|
||||
static const_value_type const_value_extract(const DepNodeMapEntry& entry)
|
||||
{
|
||||
return const_value_type(entry.first, entry.second.node);
|
||||
}
|
||||
|
||||
// All the iterator access methods return iterator ranges just to cut down
|
||||
// on the friggin' boilerplate!!
|
||||
|
||||
/// generic mNodes range method
|
||||
template<typename ITERATOR, typename FUNCTION>
|
||||
boost::iterator_range<ITERATOR> generic_range(FUNCTION function)
|
||||
{
|
||||
return make_transform_range(mNodes, function);
|
||||
}
|
||||
|
||||
/// generic mNodes const range method
|
||||
template<typename ITERATOR, typename FUNCTION>
|
||||
boost::iterator_range<ITERATOR> generic_range(FUNCTION function) const
|
||||
{
|
||||
return make_transform_range(mNodes, function);
|
||||
}
|
||||
|
||||
public:
|
||||
/// iterator over value_type entries
|
||||
typedef boost::transform_iterator<boost::function<value_type(DepNodeMapEntry&)>,
|
||||
typename DepNodeMap::iterator> iterator;
|
||||
/// range over value_type entries
|
||||
typedef boost::iterator_range<iterator> range;
|
||||
|
||||
/// iterate over value_type <i>in @c KEY order</i> rather than dependency order
|
||||
range get_range()
|
||||
{
|
||||
return generic_range<iterator>(value_extract);
|
||||
}
|
||||
|
||||
/// iterator over const_value_type entries
|
||||
typedef boost::transform_iterator<boost::function<const_value_type(const DepNodeMapEntry&)>,
|
||||
typename DepNodeMap::const_iterator> const_iterator;
|
||||
/// range over const_value_type entries
|
||||
typedef boost::iterator_range<const_iterator> const_range;
|
||||
|
||||
/// iterate over const_value_type <i>in @c KEY order</i> rather than dependency order
|
||||
const_range get_range() const
|
||||
{
|
||||
return generic_range<const_iterator>(const_value_extract);
|
||||
}
|
||||
|
||||
/// iterator over stored NODEs
|
||||
typedef boost::transform_iterator<boost::function<NODE&(DepNodeMapEntry&)>,
|
||||
typename DepNodeMap::iterator> node_iterator;
|
||||
/// range over stored NODEs
|
||||
typedef boost::iterator_range<node_iterator> node_range;
|
||||
|
||||
/// iterate over NODE <i>in @c KEY order</i> rather than dependency order
|
||||
node_range get_node_range()
|
||||
{
|
||||
// First take a DepNodeMapEntry and extract a reference to its
|
||||
// DepNode, then from that extract a reference to its NODE.
|
||||
return generic_range<node_iterator>(
|
||||
boost::bind<NODE&>(&DepNode::node,
|
||||
boost::bind<DepNode&>(&DepNodeMapEntry::second, _1)));
|
||||
}
|
||||
|
||||
/// const iterator over stored NODEs
|
||||
typedef boost::transform_iterator<boost::function<const NODE&(const DepNodeMapEntry&)>,
|
||||
typename DepNodeMap::const_iterator> const_node_iterator;
|
||||
/// const range over stored NODEs
|
||||
typedef boost::iterator_range<const_node_iterator> const_node_range;
|
||||
|
||||
/// iterate over const NODE <i>in @c KEY order</i> rather than dependency order
|
||||
const_node_range get_node_range() const
|
||||
{
|
||||
// First take a DepNodeMapEntry and extract a reference to its
|
||||
// DepNode, then from that extract a reference to its NODE.
|
||||
return generic_range<const_node_iterator>(
|
||||
boost::bind<const NODE&>(&DepNode::node,
|
||||
boost::bind<const DepNode&>(&DepNodeMapEntry::second, _1)));
|
||||
}
|
||||
|
||||
/// const iterator over stored KEYs
|
||||
typedef boost::transform_iterator<boost::function<const KEY&(const DepNodeMapEntry&)>,
|
||||
typename DepNodeMap::const_iterator> const_key_iterator;
|
||||
/// const range over stored KEYs
|
||||
typedef boost::iterator_range<const_key_iterator> const_key_range;
|
||||
// We don't provide a non-const iterator over KEYs because they should be
|
||||
// immutable, and in fact our underlying std::map won't give us non-const
|
||||
// references.
|
||||
|
||||
/// iterate over const KEY <i>in @c KEY order</i> rather than dependency order
|
||||
const_key_range get_key_range() const
|
||||
{
|
||||
// From a DepNodeMapEntry, extract a reference to its KEY.
|
||||
return generic_range<const_key_iterator>(
|
||||
boost::bind<const KEY&>(&DepNodeMapEntry::first, _1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an existing NODE, or return NULL. We decided to avoid providing a
|
||||
* method analogous to std::map::find(), for a couple of reasons:
|
||||
*
|
||||
* * For a find-by-key, getting back an iterator to the (key, value) pair
|
||||
* is less than useful, since you already have the key in hand.
|
||||
* * For a failed request, comparing to end() is problematic. First, we
|
||||
* provide range accessors, so it's more code to get end(). Second, we
|
||||
* provide a number of different ranges -- quick, to which one's end()
|
||||
* should we compare the iterator returned by find()?
|
||||
*
|
||||
* The returned pointer is solely to allow expressing the not-found
|
||||
* condition. LLDependencies still owns the found NODE.
|
||||
*/
|
||||
const NODE* get(const KEY& key) const
|
||||
{
|
||||
typename DepNodeMap::const_iterator found = mNodes.find(key);
|
||||
if (found != mNodes.end())
|
||||
{
|
||||
return &found->second.node;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* non-const get()
|
||||
*/
|
||||
NODE* get(const KEY& key)
|
||||
{
|
||||
// Use const implementation, then cast away const-ness of return
|
||||
return const_cast<NODE*>(const_cast<const self_type*>(this)->get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node with specified key. This operation is the major reason
|
||||
* we rebuild the graph on the fly instead of storing it.
|
||||
*/
|
||||
bool remove(const KEY& key)
|
||||
{
|
||||
typename DepNodeMap::iterator found = mNodes.find(key);
|
||||
if (found != mNodes.end())
|
||||
{
|
||||
mNodes.erase(found);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
/// cached list of iterators
|
||||
typedef std::vector<iterator> iterator_list;
|
||||
typedef typename iterator_list::iterator iterator_list_iterator;
|
||||
|
||||
public:
|
||||
/**
|
||||
* The return type of the sort() method needs some explanation. Provide a
|
||||
* public typedef to facilitate storing the result.
|
||||
*
|
||||
* * We will prepare mCache by looking up DepNodeMap iterators.
|
||||
* * We want to return a range containing iterators that will walk mCache.
|
||||
* * If we simply stored DepNodeMap iterators and returned
|
||||
* (mCache.begin(), mCache.end()), dereferencing each iterator would
|
||||
* obtain a DepNodeMap iterator.
|
||||
* * We want the caller to loop over @c value_type: pair<KEY, NODE>.
|
||||
* * This requires two transformations:
|
||||
* ** mCache must contain @c LLDependencies::iterator so that
|
||||
* dereferencing each entry will obtain an @c LLDependencies::value_type
|
||||
* rather than a DepNodeMapEntry.
|
||||
* ** We must wrap mCache's iterators in boost::indirect_iterator so that
|
||||
* dereferencing one of our returned iterators will also dereference the
|
||||
* iterator contained in mCache.
|
||||
*/
|
||||
typedef boost::iterator_range<boost::indirect_iterator<iterator_list_iterator> > sorted_range;
|
||||
/// for convenience in looping over a sorted_range
|
||||
typedef typename sorted_range::iterator sorted_iterator;
|
||||
|
||||
/**
|
||||
* Once we've loaded in the dependencies of interest, arrange them into an
|
||||
* order that works -- or throw Cycle exception.
|
||||
*
|
||||
* Return an iterator range over (key, node) pairs that traverses them in
|
||||
* the desired order.
|
||||
*/
|
||||
sorted_range sort() const
|
||||
{
|
||||
// Changes to mNodes cause us to clear our cache, so empty mCache
|
||||
// means it's invalid and should be recomputed. However, if mNodes is
|
||||
// also empty, then an empty mCache represents a valid order, so don't
|
||||
// bother sorting.
|
||||
if (mCache.empty() && ! mNodes.empty())
|
||||
{
|
||||
// Construct a map of node keys to distinct vertex numbers -- even for
|
||||
// nodes mentioned only in before/after constraints, that haven't yet
|
||||
// been explicitly added. Rely on std::map rejecting a second attempt
|
||||
// to insert the same key. Use the map's size() as the vertex number
|
||||
// to get a distinct value for each successful insertion.
|
||||
typedef std::map<KEY, int> VertexMap;
|
||||
VertexMap vmap;
|
||||
// Nest each of these loops because !@#$%? MSVC warns us that its
|
||||
// former broken behavior has finally been fixed -- and our builds
|
||||
// treat warnings as errors.
|
||||
{
|
||||
for (typename DepNodeMap::const_iterator nmi = mNodes.begin(), nmend = mNodes.end();
|
||||
nmi != nmend; ++nmi)
|
||||
{
|
||||
vmap.insert(typename VertexMap::value_type(nmi->first, vmap.size()));
|
||||
for (typename DepNode::dep_set::const_iterator ai = nmi->second.after.begin(),
|
||||
aend = nmi->second.after.end();
|
||||
ai != aend; ++ai)
|
||||
{
|
||||
vmap.insert(typename VertexMap::value_type(*ai, vmap.size()));
|
||||
}
|
||||
for (typename DepNode::dep_set::const_iterator bi = nmi->second.before.begin(),
|
||||
bend = nmi->second.before.end();
|
||||
bi != bend; ++bi)
|
||||
{
|
||||
vmap.insert(typename VertexMap::value_type(*bi, vmap.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Define the edges. For this we must traverse mNodes again, mapping
|
||||
// all the known key dependencies to integer pairs.
|
||||
EdgeList edges;
|
||||
{
|
||||
for (typename DepNodeMap::const_iterator nmi = mNodes.begin(), nmend = mNodes.end();
|
||||
nmi != nmend; ++nmi)
|
||||
{
|
||||
int thisnode = vmap[nmi->first];
|
||||
// after dependencies: build edges from the named node to this one
|
||||
for (typename DepNode::dep_set::const_iterator ai = nmi->second.after.begin(),
|
||||
aend = nmi->second.after.end();
|
||||
ai != aend; ++ai)
|
||||
{
|
||||
edges.push_back(EdgeList::value_type(vmap[*ai], thisnode));
|
||||
}
|
||||
// before dependencies: build edges from this node to the
|
||||
// named one
|
||||
for (typename DepNode::dep_set::const_iterator bi = nmi->second.before.begin(),
|
||||
bend = nmi->second.before.end();
|
||||
bi != bend; ++bi)
|
||||
{
|
||||
edges.push_back(EdgeList::value_type(thisnode, vmap[*bi]));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hide the gory details of our topological sort, since they shouldn't
|
||||
// get reinstantiated for each distinct NODE type.
|
||||
VertexList sorted(topo_sort(vmap.size(), edges));
|
||||
// Build the reverse of vmap to look up the key for each vertex
|
||||
// descriptor. vmap contains exactly one entry for each distinct key,
|
||||
// and we're certain that the associated int values are distinct
|
||||
// indexes. The fact that they're not in order is irrelevant.
|
||||
KeyList vkeys(vmap.size());
|
||||
for (typename VertexMap::const_iterator vmi = vmap.begin(), vmend = vmap.end();
|
||||
vmi != vmend; ++vmi)
|
||||
{
|
||||
vkeys[vmi->second] = vmi->first;
|
||||
}
|
||||
// Walk the sorted output list, building the result into mCache so
|
||||
// we'll have it next time someone asks.
|
||||
mCache.clear();
|
||||
for (VertexList::const_iterator svi = sorted.begin(), svend = sorted.end();
|
||||
svi != svend; ++svi)
|
||||
{
|
||||
// We're certain that vkeys[*svi] exists. However, there might not
|
||||
// yet be a corresponding entry in mNodes.
|
||||
self_type* non_const_this(const_cast<self_type*>(this));
|
||||
typename DepNodeMap::iterator found = non_const_this->mNodes.find(vkeys[*svi]);
|
||||
if (found != non_const_this->mNodes.end())
|
||||
{
|
||||
// Make an iterator of appropriate type.
|
||||
mCache.push_back(iterator(found, value_extract));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Whether or not we've just recomputed mCache, it should now contain
|
||||
// the results we want. Return a range of indirect_iterators over it
|
||||
// so that dereferencing a returned iterator will dereference the
|
||||
// iterator stored in mCache and directly reference the (key, node)
|
||||
// pair.
|
||||
boost::indirect_iterator<iterator_list_iterator>
|
||||
begin(mCache.begin()),
|
||||
end(mCache.end());
|
||||
return sorted_range(begin, end);
|
||||
}
|
||||
|
||||
using LLDependenciesBase::describe; // unhide virtual std::string describe(bool full=true) const;
|
||||
|
||||
/// Override base-class describe() with actual implementation
|
||||
virtual std::ostream& describe(std::ostream& out, bool full=true) const
|
||||
{
|
||||
typename DepNodeMap::const_iterator dmi(mNodes.begin()), dmend(mNodes.end());
|
||||
if (dmi != dmend)
|
||||
{
|
||||
std::string sep;
|
||||
describe(out, sep, *dmi, full);
|
||||
while (++dmi != dmend)
|
||||
{
|
||||
describe(out, sep, *dmi, full);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
/// describe() helper: report a DepNodeEntry
|
||||
static std::ostream& describe(std::ostream& out, std::string& sep,
|
||||
const DepNodeMapEntry& entry, bool full)
|
||||
{
|
||||
// If we were asked for a full report, describe every node regardless
|
||||
// of whether it has dependencies. If we were asked to suppress
|
||||
// independent nodes, describe this one if either after or before is
|
||||
// non-empty.
|
||||
if (full || (! entry.second.after.empty()) || (! entry.second.before.empty()))
|
||||
{
|
||||
out << sep;
|
||||
sep = "\n";
|
||||
if (! entry.second.after.empty())
|
||||
{
|
||||
out << "after ";
|
||||
describe(out, entry.second.after);
|
||||
out << " -> ";
|
||||
}
|
||||
LLDependencies_describe(out, entry.first);
|
||||
if (! entry.second.before.empty())
|
||||
{
|
||||
out << " -> before ";
|
||||
describe(out, entry.second.before);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// describe() helper: report a dep_set
|
||||
static std::ostream& describe(std::ostream& out, const typename DepNode::dep_set& keys)
|
||||
{
|
||||
out << '(';
|
||||
typename DepNode::dep_set::const_iterator ki(keys.begin()), kend(keys.end());
|
||||
if (ki != kend)
|
||||
{
|
||||
LLDependencies_describe(out, *ki);
|
||||
while (++ki != kend)
|
||||
{
|
||||
out << ", ";
|
||||
LLDependencies_describe(out, *ki);
|
||||
}
|
||||
}
|
||||
out << ')';
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Iterator over the before/after KEYs on which a given NODE depends
|
||||
typedef typename DepNode::dep_set::const_iterator dep_iterator;
|
||||
/// range over the before/after KEYs on which a given NODE depends
|
||||
typedef boost::iterator_range<dep_iterator> dep_range;
|
||||
|
||||
/// dependencies access from key
|
||||
dep_range get_dep_range_from_key(const KEY& key, const dep_selector& selector) const
|
||||
{
|
||||
typename DepNodeMap::const_iterator found = mNodes.find(key);
|
||||
if (found != mNodes.end())
|
||||
{
|
||||
return dep_range(selector(found->second));
|
||||
}
|
||||
// We want to return an empty range. On some platforms a default-
|
||||
// constructed range (e.g. dep_range()) does NOT suffice! The client
|
||||
// is likely to try to iterate from boost::begin(range) to
|
||||
// boost::end(range); yet these iterators might not be valid. Instead
|
||||
// return a range over a valid, empty container.
|
||||
static const typename DepNode::dep_set empty_deps;
|
||||
return dep_range(empty_deps.begin(), empty_deps.end());
|
||||
}
|
||||
|
||||
/// dependencies access from any one of our key-order iterators
|
||||
template<typename ITERATOR>
|
||||
dep_range get_dep_range_from_xform(const ITERATOR& iterator, const dep_selector& selector) const
|
||||
{
|
||||
return dep_range(selector(iterator.base()->second));
|
||||
}
|
||||
|
||||
/// dependencies access from sorted_iterator
|
||||
dep_range get_dep_range_from_sorted(const sorted_iterator& sortiter,
|
||||
const dep_selector& selector) const
|
||||
{
|
||||
// sorted_iterator is a boost::indirect_iterator wrapping an mCache
|
||||
// iterator, which we can obtain by sortiter.base(). Deferencing that
|
||||
// gets us an mCache entry, an 'iterator' -- one of our traversal
|
||||
// iterators -- on which we can use get_dep_range_from_xform().
|
||||
return get_dep_range_from_xform(*sortiter.base(), selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a range over the after KEYs stored for the passed KEY or iterator,
|
||||
* in <i>arbitrary order.</i> If you pass a nonexistent KEY, returns empty
|
||||
* range -- same as a KEY with no after KEYs. Detect existence of a KEY
|
||||
* using get() instead.
|
||||
*/
|
||||
template<typename KEY_OR_ITER>
|
||||
dep_range get_after_range(const KEY_OR_ITER& key) const;
|
||||
|
||||
/**
|
||||
* Get a range over the before KEYs stored for the passed KEY or iterator,
|
||||
* in <i>arbitrary order.</i> If you pass a nonexistent KEY, returns empty
|
||||
* range -- same as a KEY with no before KEYs. Detect existence of a KEY
|
||||
* using get() instead.
|
||||
*/
|
||||
template<typename KEY_OR_ITER>
|
||||
dep_range get_before_range(const KEY_OR_ITER& key) const;
|
||||
|
||||
private:
|
||||
DepNodeMap mNodes;
|
||||
mutable iterator_list mCache;
|
||||
};
|
||||
|
||||
/**
|
||||
* Functor to get a dep_range from a KEY or iterator -- generic case. If the
|
||||
* passed value isn't one of our iterator specializations, assume it's
|
||||
* convertible to the KEY type.
|
||||
*/
|
||||
template<typename KEY_ITER>
|
||||
struct LLDependencies_dep_range_from
|
||||
{
|
||||
template<typename KEY, typename NODE, typename SELECTOR>
|
||||
typename LLDependencies<KEY, NODE>::dep_range
|
||||
operator()(const LLDependencies<KEY, NODE>& deps,
|
||||
const KEY_ITER& key,
|
||||
const SELECTOR& selector)
|
||||
{
|
||||
return deps.get_dep_range_from_key(key, selector);
|
||||
}
|
||||
};
|
||||
|
||||
/// Specialize LLDependencies_dep_range_from for our key-order iterators
|
||||
template<typename FUNCTION, typename ITERATOR>
|
||||
struct LLDependencies_dep_range_from< boost::transform_iterator<FUNCTION, ITERATOR> >
|
||||
{
|
||||
template<typename KEY, typename NODE, typename SELECTOR>
|
||||
typename LLDependencies<KEY, NODE>::dep_range
|
||||
operator()(const LLDependencies<KEY, NODE>& deps,
|
||||
const boost::transform_iterator<FUNCTION, ITERATOR>& iter,
|
||||
const SELECTOR& selector)
|
||||
{
|
||||
return deps.get_dep_range_from_xform(iter, selector);
|
||||
}
|
||||
};
|
||||
|
||||
/// Specialize LLDependencies_dep_range_from for sorted_iterator
|
||||
template<typename BASEITER>
|
||||
struct LLDependencies_dep_range_from< boost::indirect_iterator<BASEITER> >
|
||||
{
|
||||
template<typename KEY, typename NODE, typename SELECTOR>
|
||||
typename LLDependencies<KEY, NODE>::dep_range
|
||||
operator()(const LLDependencies<KEY, NODE>& deps,
|
||||
const boost::indirect_iterator<BASEITER>& iter,
|
||||
const SELECTOR& selector)
|
||||
{
|
||||
return deps.get_dep_range_from_sorted(iter, selector);
|
||||
}
|
||||
};
|
||||
|
||||
/// generic get_after_range() implementation
|
||||
template<typename KEY, typename NODE>
|
||||
template<typename KEY_OR_ITER>
|
||||
typename LLDependencies<KEY, NODE>::dep_range
|
||||
LLDependencies<KEY, NODE>::get_after_range(const KEY_OR_ITER& key_iter) const
|
||||
{
|
||||
return LLDependencies_dep_range_from<KEY_OR_ITER>()(
|
||||
*this,
|
||||
key_iter,
|
||||
boost::bind<const typename DepNode::dep_set&>(&DepNode::after, _1));
|
||||
}
|
||||
|
||||
/// generic get_before_range() implementation
|
||||
template<typename KEY, typename NODE>
|
||||
template<typename KEY_OR_ITER>
|
||||
typename LLDependencies<KEY, NODE>::dep_range
|
||||
LLDependencies<KEY, NODE>::get_before_range(const KEY_OR_ITER& key_iter) const
|
||||
{
|
||||
return LLDependencies_dep_range_from<KEY_OR_ITER>()(
|
||||
*this,
|
||||
key_iter,
|
||||
boost::bind<const typename DepNode::dep_set&>(&DepNode::before, _1));
|
||||
}
|
||||
|
||||
#endif /* ! defined(LL_LLDEPENDENCIES_H) */
|
||||
@@ -34,6 +34,8 @@
|
||||
|
||||
#include "llevent.h"
|
||||
|
||||
using namespace LLOldEvents;
|
||||
|
||||
/************************************************
|
||||
Events
|
||||
************************************************/
|
||||
|
||||
@@ -35,9 +35,12 @@
|
||||
#define LL_EVENT_H
|
||||
|
||||
#include "llsd.h"
|
||||
#include "llmemory.h"
|
||||
#include "llpointer.h"
|
||||
#include "llthread.h"
|
||||
|
||||
namespace LLOldEvents
|
||||
{
|
||||
|
||||
class LLEventListener;
|
||||
class LLEvent;
|
||||
class LLEventDispatcher;
|
||||
@@ -194,4 +197,6 @@ public:
|
||||
LLSD mValue;
|
||||
};
|
||||
|
||||
} // LLOldEvents
|
||||
|
||||
#endif // LL_EVENT_H
|
||||
|
||||
77
indra/llcommon/lleventapi.cpp
Normal file
77
indra/llcommon/lleventapi.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @file lleventapi.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-11-10
|
||||
* @brief Implementation for lleventapi.
|
||||
*
|
||||
* $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$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "lleventapi.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llerror.h"
|
||||
|
||||
LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const std::string& field):
|
||||
lbase(name, field),
|
||||
ibase(name),
|
||||
mDesc(desc)
|
||||
{
|
||||
}
|
||||
|
||||
LLEventAPI::~LLEventAPI()
|
||||
{
|
||||
}
|
||||
|
||||
LLEventAPI::Response::Response(const LLSD& seed, const LLSD& request, const LLSD::String& replyKey):
|
||||
mResp(seed),
|
||||
mReq(request),
|
||||
mKey(replyKey)
|
||||
{}
|
||||
|
||||
LLEventAPI::Response::~Response()
|
||||
{
|
||||
// When you instantiate a stack Response object, if the original
|
||||
// request requested a reply, send it when we leave this block, no
|
||||
// matter how.
|
||||
sendReply(mResp, mReq, mKey);
|
||||
}
|
||||
|
||||
void LLEventAPI::Response::warn(const std::string& warning)
|
||||
{
|
||||
LL_WARNS("LLEventAPI::Response") << warning << LL_ENDL;
|
||||
mResp["warnings"].append(warning);
|
||||
}
|
||||
|
||||
void LLEventAPI::Response::error(const std::string& error)
|
||||
{
|
||||
// Use LL_WARNS rather than LL_ERROR: we don't want the viewer to shut
|
||||
// down altogether.
|
||||
LL_WARNS("LLEventAPI::Response") << error << LL_ENDL;
|
||||
|
||||
mResp["error"] = error;
|
||||
}
|
||||
166
indra/llcommon/lleventapi.h
Normal file
166
indra/llcommon/lleventapi.h
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @file lleventapi.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-10-28
|
||||
* @brief LLEventAPI is the base class for every class that wraps a C++ API
|
||||
* in an event API
|
||||
* (see https://wiki.lindenlab.com/wiki/Incremental_Viewer_Automation/Event_API).
|
||||
*
|
||||
* $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_LLEVENTAPI_H)
|
||||
#define LL_LLEVENTAPI_H
|
||||
|
||||
#include "lleventdispatcher.h"
|
||||
#include "llinstancetracker.h"
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* LLEventAPI not only provides operation dispatch functionality, inherited
|
||||
* from LLDispatchListener -- it also gives us event API introspection.
|
||||
* Deriving from LLInstanceTracker lets us enumerate instances.
|
||||
*/
|
||||
class LL_COMMON_API LLEventAPI: public LLDispatchListener,
|
||||
public LLInstanceTracker<LLEventAPI, std::string>
|
||||
{
|
||||
typedef LLDispatchListener lbase;
|
||||
typedef LLInstanceTracker<LLEventAPI, std::string> ibase;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param name LLEventPump name on which this LLEventAPI will listen. This
|
||||
* also serves as the LLInstanceTracker instance key.
|
||||
* @param desc Documentation string shown to a client trying to discover
|
||||
* available event APIs.
|
||||
* @param field LLSD::Map key used by LLDispatchListener to look up the
|
||||
* subclass method to invoke [default "op"].
|
||||
*/
|
||||
LLEventAPI(const std::string& name, const std::string& desc, const std::string& field="op");
|
||||
virtual ~LLEventAPI();
|
||||
|
||||
/// Get the string name of this LLEventAPI
|
||||
std::string getName() const { return ibase::getKey(); }
|
||||
/// Get the documentation string
|
||||
std::string getDesc() const { return mDesc; }
|
||||
|
||||
/**
|
||||
* Publish only selected add() methods from LLEventDispatcher.
|
||||
* Every LLEventAPI add() @em must have a description string.
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
void add(const std::string& name,
|
||||
const std::string& desc,
|
||||
CALLABLE callable,
|
||||
const LLSD& required=LLSD())
|
||||
{
|
||||
LLEventDispatcher::add(name, desc, callable, required);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a Response object in any LLEventAPI subclass method that
|
||||
* wants to guarantee a reply (if requested) will be sent on exit from the
|
||||
* method. The reply will be sent if request.has(@a replyKey), default
|
||||
* "reply". If specified, the value of request[replyKey] is the name of
|
||||
* the LLEventPump on which to send the reply. Conventionally you might
|
||||
* code something like:
|
||||
*
|
||||
* @code
|
||||
* void MyEventAPI::someMethod(const LLSD& request)
|
||||
* {
|
||||
* // Send a reply event as long as request.has("reply")
|
||||
* Response response(LLSD(), request);
|
||||
* // ...
|
||||
* // will be sent in reply event
|
||||
* response["somekey"] = some_data;
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
class LL_COMMON_API Response
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Instantiating a Response object in an LLEventAPI subclass method
|
||||
* ensures that, if desired, a reply event will be sent.
|
||||
*
|
||||
* @a seed is the initial reply LLSD that will be further decorated before
|
||||
* being sent as the reply
|
||||
*
|
||||
* @a request is the incoming request LLSD; we particularly care about
|
||||
* [replyKey] and ["reqid"]
|
||||
*
|
||||
* @a replyKey [default "reply"] is the string name of the LLEventPump
|
||||
* on which the caller wants a reply. If <tt>(!
|
||||
* request.has(replyKey))</tt>, no reply will be sent.
|
||||
*/
|
||||
Response(const LLSD& seed, const LLSD& request, const LLSD::String& replyKey="reply");
|
||||
~Response();
|
||||
|
||||
/**
|
||||
* @code
|
||||
* if (some condition)
|
||||
* {
|
||||
* response.warn("warnings are logged and collected in [\"warnings\"]");
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
void warn(const std::string& warning);
|
||||
/**
|
||||
* @code
|
||||
* if (some condition isn't met)
|
||||
* {
|
||||
* // In a function returning void, you can validly 'return
|
||||
* // expression' if the expression is itself of type void. But
|
||||
* // returning is up to you; response.error() has no effect on
|
||||
* // flow of control.
|
||||
* return response.error("error message, logged and also sent as [\"error\"]");
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
void error(const std::string& error);
|
||||
|
||||
/**
|
||||
* set other keys...
|
||||
*
|
||||
* @code
|
||||
* // set any attributes you want to be sent in the reply
|
||||
* response["info"] = some_value;
|
||||
* // ...
|
||||
* response["ok"] = went_well;
|
||||
* @endcode
|
||||
*/
|
||||
LLSD& operator[](const LLSD::String& key) { return mResp[key]; }
|
||||
|
||||
/**
|
||||
* set the response to the given data
|
||||
*/
|
||||
void setResponse(LLSD const & response){ mResp = response; }
|
||||
|
||||
LLSD mResp, mReq;
|
||||
LLSD::String mKey;
|
||||
};
|
||||
|
||||
private:
|
||||
std::string mDesc;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLEVENTAPI_H) */
|
||||
146
indra/llcommon/lleventcoro.cpp
Normal file
146
indra/llcommon/lleventcoro.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* @file lleventcoro.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-04-29
|
||||
* @brief Implementation for lleventcoro.
|
||||
*
|
||||
* $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$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "lleventcoro.h"
|
||||
// STL headers
|
||||
#include <map>
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llsdserialize.h"
|
||||
#include "llerror.h"
|
||||
#include "llcoros.h"
|
||||
|
||||
std::string LLEventDetail::listenerNameForCoroImpl(const void* self_id)
|
||||
{
|
||||
// First, if this coroutine was launched by LLCoros::launch(), find that name.
|
||||
std::string name(LLCoros::instance().getNameByID(self_id));
|
||||
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 '"
|
||||
<< name << "'" << LL_ENDL;
|
||||
return name;
|
||||
}
|
||||
|
||||
void LLEventDetail::storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value)
|
||||
{
|
||||
if (rawPath.isUndefined())
|
||||
{
|
||||
// no-op case
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrange to treat rawPath uniformly as an array. If it's not already an
|
||||
// array, store it as the only entry in one.
|
||||
LLSD path;
|
||||
if (rawPath.isArray())
|
||||
{
|
||||
path = rawPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
path.append(rawPath);
|
||||
}
|
||||
|
||||
// Need to indicate a current destination -- but that current destination
|
||||
// needs to change as we step through the path array. Where normally we'd
|
||||
// use an LLSD& to capture a subscripted LLSD lvalue, this time we must
|
||||
// instead use a pointer -- since it must be reassigned.
|
||||
LLSD* pdest = &dest;
|
||||
|
||||
// Now loop through that array
|
||||
for (LLSD::Integer i = 0; i < path.size(); ++i)
|
||||
{
|
||||
if (path[i].isString())
|
||||
{
|
||||
// *pdest is an LLSD map
|
||||
pdest = &((*pdest)[path[i].asString()]);
|
||||
}
|
||||
else if (path[i].isInteger())
|
||||
{
|
||||
// *pdest is an LLSD array
|
||||
pdest = &((*pdest)[path[i].asInteger()]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// What do we do with Real or Array or Map or ...?
|
||||
// As it's a coder error -- not a user error -- rub the coder's
|
||||
// face in it so it gets fixed.
|
||||
LL_ERRS("lleventcoro") << "storeToLLSDPath(" << dest << ", " << rawPath << ", " << value
|
||||
<< "): path[" << i << "] bad type " << path[i].type() << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
// Here *pdest is where we should store value.
|
||||
*pdest = value;
|
||||
}
|
||||
|
||||
LLSD errorException(const LLEventWithID& result, const std::string& desc)
|
||||
{
|
||||
// If the result arrived on the error pump (pump 1), instead of
|
||||
// returning it, deliver it via exception.
|
||||
if (result.second)
|
||||
{
|
||||
throw LLErrorEvent(desc, result.first);
|
||||
}
|
||||
// That way, our caller knows a simple return must be from the reply
|
||||
// pump (pump 0).
|
||||
return result.first;
|
||||
}
|
||||
|
||||
LLSD errorLog(const LLEventWithID& result, const std::string& desc)
|
||||
{
|
||||
// If the result arrived on the error pump (pump 1), log it as a fatal
|
||||
// error.
|
||||
if (result.second)
|
||||
{
|
||||
LL_ERRS("errorLog") << desc << ":" << std::endl;
|
||||
LLSDSerialize::toPrettyXML(result.first, LL_CONT);
|
||||
LL_CONT << LL_ENDL;
|
||||
}
|
||||
// A simple return must therefore be from the reply pump (pump 0).
|
||||
return result.first;
|
||||
}
|
||||
569
indra/llcommon/lleventcoro.h
Normal file
569
indra/llcommon/lleventcoro.h
Normal file
@@ -0,0 +1,569 @@
|
||||
/**
|
||||
* @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 <boost/coroutine/coroutine.hpp>
|
||||
#include <boost/coroutine/future.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include "llevents.h"
|
||||
#include "llerror.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 mPump; }
|
||||
bool operator!() const { return ! mPump; }
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
||||
/// Implementation for listenerNameForCoro()
|
||||
LL_COMMON_API std::string listenerNameForCoroImpl(const void* self_id);
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Post specified LLSD event on the specified LLEventPump, then wait 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);
|
||||
* @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
|
||||
* entered. Therefore, the coroutine completely misses an immediate reply
|
||||
* event, making it wait indefinitely.
|
||||
*
|
||||
* By contrast, postAndWait() 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
|
||||
* reply. Pass either the LLEventPump& or its string name. The calling
|
||||
* coroutine will wait 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>.
|
||||
*/
|
||||
template <typename SELF>
|
||||
LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump,
|
||||
const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD())
|
||||
{
|
||||
// declare the future
|
||||
boost::coroutines::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::coroutines::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;
|
||||
}
|
||||
|
||||
/// 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)
|
||||
{
|
||||
// This is now a convenience wrapper for postAndWait().
|
||||
return postAndWait(self, LLSD(), LLEventPumpOrPumpName(), pump);
|
||||
}
|
||||
|
||||
/// return type for two-pump variant of waitForEventOn()
|
||||
typedef std::pair<LLSD, int> LLEventWithID;
|
||||
|
||||
namespace LLEventDetail
|
||||
{
|
||||
/**
|
||||
* 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::coroutines::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
|
||||
* for that function for detailed parameter info.
|
||||
*
|
||||
* While we could have implemented the single-pump variant in terms of this
|
||||
* one, there's enough added complexity here to make it worthwhile to give the
|
||||
* single-pump variant its own straightforward implementation. Conversely,
|
||||
* though we could use preprocessor logic to generate n-pump overloads up to
|
||||
* BOOST_COROUTINE_WAIT_MAX, we don't foresee a use case. This two-pump
|
||||
* overload exists because certain event APIs are defined in terms of a reply
|
||||
* LLEventPump and an error LLEventPump.
|
||||
*
|
||||
* The LLEventWithID return value provides not only the received event, but
|
||||
* 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.
|
||||
* But consider the following ambiguous call:
|
||||
* @code
|
||||
* postAndWait(self, 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
|
||||
* LLSD("someString") or LLEventOrPumpName("someString").
|
||||
*/
|
||||
template <typename SELF>
|
||||
LLEventWithID postAndWait2(SELF& self, const LLSD& event,
|
||||
const LLEventPumpOrPumpName& requestPump,
|
||||
const LLEventPumpOrPumpName& replyPump0,
|
||||
const LLEventPumpOrPumpName& replyPump1,
|
||||
const LLSD& replyPump0NamePath=LLSD(),
|
||||
const LLSD& replyPump1NamePath=LLSD())
|
||||
{
|
||||
// declare the future
|
||||
boost::coroutines::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::coroutines::make_callback(future), 0)));
|
||||
LLTempBoundListener connection1(
|
||||
replyPump1.getPump().listen(name + "b",
|
||||
LLEventDetail::wfeoh(boost::coroutines::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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the next event on either of two specified LLEventPumps.
|
||||
*/
|
||||
template <typename SELF>
|
||||
LLEventWithID
|
||||
waitForEventOn(SELF& self,
|
||||
const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1)
|
||||
{
|
||||
// This is now a convenience wrapper for postAndWait2().
|
||||
return postAndWait2(self, LLSD(), LLEventPumpOrPumpName(), pump0, pump1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for the two-pump variant of waitForEventOn(), e.g.:
|
||||
*
|
||||
* @code
|
||||
* LLSD reply = errorException(waitForEventOn(self, replyPump, errorPump),
|
||||
* "error response from login.cgi");
|
||||
* @endcode
|
||||
*
|
||||
* Examines an LLEventWithID, assuming that the second pump (pump 1) is
|
||||
* listening for an error indication. If the incoming data arrived on pump 1,
|
||||
* throw an LLErrorEvent exception. If the incoming data arrived on pump 0,
|
||||
* just return it. Since a normal return can only be from pump 0, we no longer
|
||||
* need the LLEventWithID's discriminator int; we can just return the LLSD.
|
||||
*
|
||||
* @note I'm not worried about introducing the (fairly generic) name
|
||||
* errorException() into global namespace, because how many other overloads of
|
||||
* the same name are going to accept an LLEventWithID parameter?
|
||||
*/
|
||||
LLSD errorException(const LLEventWithID& result, const std::string& desc);
|
||||
|
||||
/**
|
||||
* Exception thrown by errorException(). We don't call this LLEventError
|
||||
* because it's not an error in event processing: rather, this exception
|
||||
* announces an event that bears error information (for some other API).
|
||||
*/
|
||||
class LL_COMMON_API LLErrorEvent: public std::runtime_error
|
||||
{
|
||||
public:
|
||||
LLErrorEvent(const std::string& what, const LLSD& data):
|
||||
std::runtime_error(what),
|
||||
mData(data)
|
||||
{}
|
||||
virtual ~LLErrorEvent() throw() {}
|
||||
|
||||
LLSD getData() const { return mData; }
|
||||
|
||||
private:
|
||||
LLSD mData;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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 wait() method to wait 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.
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
return waitForEventOn(self, mPump);
|
||||
}
|
||||
|
||||
template <typename SELF>
|
||||
LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump,
|
||||
const LLSD& replyPumpNamePath=LLSD())
|
||||
{
|
||||
return ::postAndWait(self, event, requestPump, mPump, replyPumpNamePath);
|
||||
}
|
||||
|
||||
private:
|
||||
LLEventStream mPump;
|
||||
};
|
||||
|
||||
/**
|
||||
* Other event APIs require the names of two different LLEventPumps: one for
|
||||
* success response, the other for error response. Extend LLCoroEventPump
|
||||
* for the two-pump use case.
|
||||
*/
|
||||
class LL_COMMON_API LLCoroEventPumps
|
||||
{
|
||||
public:
|
||||
LLCoroEventPumps(const std::string& name="coro",
|
||||
const std::string& suff0="Reply",
|
||||
const std::string& suff1="Error"):
|
||||
mPump0(name + suff0, true), // allow tweaking the pump instance name
|
||||
mPump1(name + suff1, true)
|
||||
{}
|
||||
/// request pump 0's name
|
||||
std::string getName0() const { return mPump0.getName(); }
|
||||
/// request pump 1's name
|
||||
std::string getName1() const { return mPump1.getName(); }
|
||||
/// request both names
|
||||
std::pair<std::string, std::string> getNames() const
|
||||
{
|
||||
return std::pair<std::string, std::string>(mPump0.getName(), mPump1.getName());
|
||||
}
|
||||
|
||||
/// request pump 0
|
||||
LLEventPump& getPump0() { return mPump0; }
|
||||
/// request pump 1
|
||||
LLEventPump& getPump1() { return mPump1; }
|
||||
|
||||
/// waitForEventOn(self, either of our two LLEventPumps)
|
||||
template <typename SELF>
|
||||
LLEventWithID wait(SELF& self)
|
||||
{
|
||||
return waitForEventOn(self, mPump0, mPump1);
|
||||
}
|
||||
|
||||
/// errorException(wait(self))
|
||||
template <typename SELF>
|
||||
LLSD waitWithException(SELF& self)
|
||||
{
|
||||
return errorException(wait(self), std::string("Error event on ") + getName1());
|
||||
}
|
||||
|
||||
/// errorLog(wait(self))
|
||||
template <typename SELF>
|
||||
LLSD waitWithLog(SELF& self)
|
||||
{
|
||||
return errorLog(wait(self), std::string("Error event on ") + getName1());
|
||||
}
|
||||
|
||||
template <typename SELF>
|
||||
LLEventWithID postAndWait(SELF& self, const LLSD& event,
|
||||
const LLEventPumpOrPumpName& requestPump,
|
||||
const LLSD& replyPump0NamePath=LLSD(),
|
||||
const LLSD& replyPump1NamePath=LLSD())
|
||||
{
|
||||
return postAndWait2(self, event, requestPump, mPump0, mPump1,
|
||||
replyPump0NamePath, replyPump1NamePath);
|
||||
}
|
||||
|
||||
template <typename SELF>
|
||||
LLSD postAndWaitWithException(SELF& self, 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());
|
||||
}
|
||||
|
||||
template <typename SELF>
|
||||
LLSD postAndWaitWithLog(SELF& self, 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());
|
||||
}
|
||||
|
||||
private:
|
||||
LLEventStream mPump0, mPump1;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLEVENTCORO_H) */
|
||||
672
indra/llcommon/lleventdispatcher.cpp
Normal file
672
indra/llcommon/lleventdispatcher.cpp
Normal file
@@ -0,0 +1,672 @@
|
||||
/**
|
||||
* @file lleventdispatcher.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-06-18
|
||||
* @brief Implementation for lleventdispatcher.
|
||||
*
|
||||
* $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 LL_WINDOWS
|
||||
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
|
||||
#endif
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "lleventdispatcher.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llevents.h"
|
||||
#include "llerror.h"
|
||||
#include "llsdutil.h"
|
||||
#include "stringize.h"
|
||||
#include <memory> // std::auto_ptr
|
||||
|
||||
/*****************************************************************************
|
||||
* LLSDArgsSource
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* Store an LLSD array, producing its elements one at a time. Die with LL_ERRS
|
||||
* if the consumer requests more elements than the array contains.
|
||||
*/
|
||||
class LL_COMMON_API LLSDArgsSource
|
||||
{
|
||||
public:
|
||||
LLSDArgsSource(const std::string function, const LLSD& args);
|
||||
~LLSDArgsSource();
|
||||
|
||||
LLSD next();
|
||||
|
||||
void done() const;
|
||||
|
||||
private:
|
||||
std::string _function;
|
||||
LLSD _args;
|
||||
LLSD::Integer _index;
|
||||
};
|
||||
|
||||
LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args):
|
||||
_function(function),
|
||||
_args(args),
|
||||
_index(0)
|
||||
{
|
||||
if (! (_args.isUndefined() || _args.isArray()))
|
||||
{
|
||||
LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of "
|
||||
<< _args << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
LLSDArgsSource::~LLSDArgsSource()
|
||||
{
|
||||
done();
|
||||
}
|
||||
|
||||
LLSD LLSDArgsSource::next()
|
||||
{
|
||||
if (_index >= _args.size())
|
||||
{
|
||||
LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the "
|
||||
<< _args.size() << " provided: " << _args << LL_ENDL;
|
||||
}
|
||||
return _args[_index++];
|
||||
}
|
||||
|
||||
void LLSDArgsSource::done() const
|
||||
{
|
||||
if (_index < _args.size())
|
||||
{
|
||||
LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index
|
||||
<< " of the " << _args.size() << " arguments provided: "
|
||||
<< _args << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* LLSDArgsMapper
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* From a formal parameters description and a map of arguments, construct an
|
||||
* arguments array.
|
||||
*
|
||||
* That is, given:
|
||||
* - an LLSD array of length n containing parameter-name strings,
|
||||
* corresponding to the arguments of a function of interest
|
||||
* - an LLSD collection specifying default parameter values, either:
|
||||
* - an LLSD array of length m <= n, matching the rightmost m params, or
|
||||
* - an LLSD map explicitly stating default name=value pairs
|
||||
* - an LLSD map of parameter names and actual values for a particular
|
||||
* function call
|
||||
* construct an LLSD array of actual argument values for this function call.
|
||||
*
|
||||
* The parameter-names array and the defaults collection describe the function
|
||||
* being called. The map might vary with every call, providing argument values
|
||||
* for the described parameters.
|
||||
*
|
||||
* The array of parameter names must match the number of parameters expected
|
||||
* by the function of interest.
|
||||
*
|
||||
* If you pass a map of default parameter values, it provides default values
|
||||
* as you might expect. It is an error to specify a default value for a name
|
||||
* not listed in the parameters array.
|
||||
*
|
||||
* If you pass an array of default parameter values, it is mapped to the
|
||||
* rightmost m of the n parameter names. It is an error if the default-values
|
||||
* array is longer than the parameter-names array. Consider the following
|
||||
* parameter names: ["a", "b", "c", "d"].
|
||||
*
|
||||
* - An empty array of default values (or an isUndefined() value) asserts that
|
||||
* every one of the above parameter names is required.
|
||||
* - An array of four default values [1, 2, 3, 4] asserts that every one of
|
||||
* the above parameters is optional. If the current parameter map is empty,
|
||||
* they will be passed to the function as [1, 2, 3, 4].
|
||||
* - An array of two default values [11, 12] asserts that parameters "a" and
|
||||
* "b" are required, while "c" and "d" are optional, having default values
|
||||
* "c"=11 and "d"=12.
|
||||
*
|
||||
* The arguments array is constructed as follows:
|
||||
*
|
||||
* - Arguments-map keys not found in the parameter-names array are ignored.
|
||||
* - Entries from the map provide values for an improper subset of the
|
||||
* parameters named in the parameter-names array. This results in a
|
||||
* tentative values array with "holes." (size of map) + (number of holes) =
|
||||
* (size of names array)
|
||||
* - Holes are filled with the default values.
|
||||
* - Any remaining holes constitute an error.
|
||||
*/
|
||||
class LL_COMMON_API LLSDArgsMapper
|
||||
{
|
||||
public:
|
||||
/// Accept description of function: function name, param names, param
|
||||
/// default values
|
||||
LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults);
|
||||
|
||||
/// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS.
|
||||
LLSD map(const LLSD& argsmap) const;
|
||||
|
||||
private:
|
||||
static std::string formatlist(const LLSD&);
|
||||
|
||||
// The function-name string is purely descriptive. We want error messages
|
||||
// to be able to indicate which function's LLSDArgsMapper has the problem.
|
||||
std::string _function;
|
||||
// Store the names array pretty much as given.
|
||||
LLSD _names;
|
||||
// Though we're handed an array of name strings, it's more useful to us to
|
||||
// store it as a map from name string to position index. Of course that's
|
||||
// easy to generate from the incoming names array, but why do it more than
|
||||
// once?
|
||||
typedef std::map<LLSD::String, LLSD::Integer> IndexMap;
|
||||
IndexMap _indexes;
|
||||
// Generated array of default values, aligned with the array of param names.
|
||||
LLSD _defaults;
|
||||
// Indicate whether we have a default value for each param.
|
||||
typedef std::vector<char> FilledVector;
|
||||
FilledVector _has_dft;
|
||||
};
|
||||
|
||||
LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
|
||||
const LLSD& names, const LLSD& defaults):
|
||||
_function(function),
|
||||
_names(names),
|
||||
_has_dft(names.size())
|
||||
{
|
||||
if (! (_names.isUndefined() || _names.isArray()))
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL;
|
||||
}
|
||||
LLSD::Integer nparams(_names.size());
|
||||
// From _names generate _indexes.
|
||||
for (LLSD::Integer ni = 0, nend = _names.size(); ni < nend; ++ni)
|
||||
{
|
||||
_indexes[_names[ni]] = ni;
|
||||
}
|
||||
|
||||
// Presize _defaults() array so we don't have to resize it more than once.
|
||||
// All entries are initialized to LLSD(); but since _has_dft is still all
|
||||
// 0, they're all "holes" for now.
|
||||
if (nparams)
|
||||
{
|
||||
_defaults[nparams - 1] = LLSD();
|
||||
}
|
||||
|
||||
if (defaults.isUndefined() || defaults.isArray())
|
||||
{
|
||||
LLSD::Integer ndefaults = defaults.size();
|
||||
// defaults is a (possibly empty) array. Right-align it with names.
|
||||
if (ndefaults > nparams)
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << function << " names array " << names
|
||||
<< " shorter than defaults array " << defaults << LL_ENDL;
|
||||
}
|
||||
|
||||
// Offset by which we slide defaults array right to right-align with
|
||||
// _names array
|
||||
LLSD::Integer offset = nparams - ndefaults;
|
||||
// Fill rightmost _defaults entries from defaults, and mark them as
|
||||
// filled
|
||||
for (LLSD::Integer i = 0, iend = ndefaults; i < iend; ++i)
|
||||
{
|
||||
_defaults[i + offset] = defaults[i];
|
||||
_has_dft[i + offset] = 1;
|
||||
}
|
||||
}
|
||||
else if (defaults.isMap())
|
||||
{
|
||||
// defaults is a map. Use it to populate the _defaults array.
|
||||
LLSD bogus;
|
||||
for (LLSD::map_const_iterator mi(defaults.beginMap()), mend(defaults.endMap());
|
||||
mi != mend; ++mi)
|
||||
{
|
||||
IndexMap::const_iterator ixit(_indexes.find(mi->first));
|
||||
if (ixit == _indexes.end())
|
||||
{
|
||||
bogus.append(mi->first);
|
||||
continue;
|
||||
}
|
||||
|
||||
LLSD::Integer pos = ixit->second;
|
||||
// Store default value at that position in the _defaults array.
|
||||
_defaults[pos] = mi->second;
|
||||
// Don't forget to record the fact that we've filled this
|
||||
// position.
|
||||
_has_dft[pos] = 1;
|
||||
}
|
||||
if (bogus.size())
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params "
|
||||
<< formatlist(bogus) << LL_ENDL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not "
|
||||
<< defaults << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
|
||||
{
|
||||
if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray()))
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not "
|
||||
<< argsmap << LL_ENDL;
|
||||
}
|
||||
// Initialize the args array. Indexing a non-const LLSD array grows it
|
||||
// to appropriate size, but we don't want to resize this one on each
|
||||
// new operation. Just make it as big as we need before we start
|
||||
// stuffing values into it.
|
||||
LLSD args(LLSD::emptyArray());
|
||||
if (_defaults.size() == 0)
|
||||
{
|
||||
// If this function requires no arguments, fast exit. (Don't try to
|
||||
// assign to args[-1].)
|
||||
return args;
|
||||
}
|
||||
args[_defaults.size() - 1] = LLSD();
|
||||
|
||||
// Get a vector of chars to indicate holes. It's tempting to just scan
|
||||
// for LLSD::isUndefined() values after filling the args array from
|
||||
// the map, but it's plausible for caller to explicitly pass
|
||||
// isUndefined() as the value of some parameter name. That's legal
|
||||
// since isUndefined() has well-defined conversions (default value)
|
||||
// for LLSD data types. So use a whole separate array for detecting
|
||||
// holes. (Avoid std::vector<bool> which is known to be odd -- can we
|
||||
// iterate?)
|
||||
FilledVector filled(args.size());
|
||||
|
||||
if (argsmap.isArray())
|
||||
{
|
||||
// Fill args from array. If there are too many args in passed array,
|
||||
// ignore the rest.
|
||||
LLSD::Integer size(argsmap.size());
|
||||
if (size > args.size())
|
||||
{
|
||||
// We don't just use std::min() because we want to sneak in this
|
||||
// warning if caller passes too many args.
|
||||
LL_WARNS("LLSDArgsMapper") << _function << " needs " << args.size()
|
||||
<< " params, ignoring last " << (size - args.size())
|
||||
<< " of passed " << size << ": " << argsmap << LL_ENDL;
|
||||
size = args.size();
|
||||
}
|
||||
for (LLSD::Integer i(0); i < size; ++i)
|
||||
{
|
||||
// Copy the actual argument from argsmap
|
||||
args[i] = argsmap[i];
|
||||
// Note that it's been filled
|
||||
filled[i] = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// argsmap is in fact a map. Walk the map.
|
||||
for (LLSD::map_const_iterator mi(argsmap.beginMap()), mend(argsmap.endMap());
|
||||
mi != mend; ++mi)
|
||||
{
|
||||
// mi->first is a parameter-name string, with mi->second its
|
||||
// value. Look up the name's position index in _indexes.
|
||||
IndexMap::const_iterator ixit(_indexes.find(mi->first));
|
||||
if (ixit == _indexes.end())
|
||||
{
|
||||
// Allow for a map containing more params than were passed in
|
||||
// our names array. Caller typically receives a map containing
|
||||
// the function name, cruft such as reqid, etc. Ignore keys
|
||||
// not defined in _indexes.
|
||||
LL_DEBUGS("LLSDArgsMapper") << _function << " ignoring "
|
||||
<< mi->first << "=" << mi->second << LL_ENDL;
|
||||
continue;
|
||||
}
|
||||
LLSD::Integer pos = ixit->second;
|
||||
// Store the value at that position in the args array.
|
||||
args[pos] = mi->second;
|
||||
// Don't forget to record the fact that we've filled this
|
||||
// position.
|
||||
filled[pos] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill any remaining holes from _defaults.
|
||||
LLSD unfilled(LLSD::emptyArray());
|
||||
for (LLSD::Integer i = 0, iend = args.size(); i < iend; ++i)
|
||||
{
|
||||
if (! filled[i])
|
||||
{
|
||||
// If there's no default value for this parameter, that's an
|
||||
// error.
|
||||
if (! _has_dft[i])
|
||||
{
|
||||
unfilled.append(_names[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
args[i] = _defaults[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
// If any required args -- args without defaults -- were left unfilled
|
||||
// by argsmap, that's a problem.
|
||||
if (unfilled.size())
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments "
|
||||
<< formatlist(unfilled) << " from " << argsmap << LL_ENDL;
|
||||
}
|
||||
|
||||
// done
|
||||
return args;
|
||||
}
|
||||
|
||||
std::string LLSDArgsMapper::formatlist(const LLSD& list)
|
||||
{
|
||||
std::ostringstream out;
|
||||
const char* delim = "";
|
||||
for (LLSD::array_const_iterator li(list.beginArray()), lend(list.endArray());
|
||||
li != lend; ++li)
|
||||
{
|
||||
out << delim << li->asString();
|
||||
delim = ", ";
|
||||
}
|
||||
return out.str();
|
||||
}
|
||||
|
||||
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
|
||||
mDesc(desc),
|
||||
mKey(key)
|
||||
{
|
||||
}
|
||||
|
||||
LLEventDispatcher::~LLEventDispatcher()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* DispatchEntry subclass used for callables accepting(const LLSD&)
|
||||
*/
|
||||
struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry
|
||||
{
|
||||
LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required):
|
||||
DispatchEntry(desc),
|
||||
mFunc(func),
|
||||
mRequired(required)
|
||||
{}
|
||||
|
||||
Callable mFunc;
|
||||
LLSD mRequired;
|
||||
|
||||
virtual void call(const std::string& desc, const LLSD& event) const
|
||||
{
|
||||
// Validate the syntax of the event itself.
|
||||
std::string mismatch(llsd_matches(mRequired, event));
|
||||
if (! mismatch.empty())
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL;
|
||||
}
|
||||
// Event syntax looks good, go for it!
|
||||
mFunc(event);
|
||||
}
|
||||
|
||||
virtual LLSD addMetadata(LLSD meta) const
|
||||
{
|
||||
meta["required"] = mRequired;
|
||||
return meta;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DispatchEntry subclass for passing LLSD to functions accepting
|
||||
* arbitrary argument types (convertible via LLSDParam)
|
||||
*/
|
||||
struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry
|
||||
{
|
||||
ParamsDispatchEntry(const std::string& desc, const invoker_function& func):
|
||||
DispatchEntry(desc),
|
||||
mInvoker(func)
|
||||
{}
|
||||
|
||||
invoker_function mInvoker;
|
||||
|
||||
virtual void call(const std::string& desc, const LLSD& event) const
|
||||
{
|
||||
LLSDArgsSource src(desc, event);
|
||||
mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src)));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DispatchEntry subclass for dispatching LLSD::Array to functions accepting
|
||||
* arbitrary argument types (convertible via LLSDParam)
|
||||
*/
|
||||
struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
|
||||
{
|
||||
ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func,
|
||||
LLSD::Integer arity):
|
||||
ParamsDispatchEntry(desc, func),
|
||||
mArity(arity)
|
||||
{}
|
||||
|
||||
LLSD::Integer mArity;
|
||||
|
||||
virtual LLSD addMetadata(LLSD meta) const
|
||||
{
|
||||
LLSD array(LLSD::emptyArray());
|
||||
// Resize to number of arguments required
|
||||
if (mArity)
|
||||
array[mArity - 1] = LLSD();
|
||||
llassert_always(array.size() == mArity);
|
||||
meta["required"] = array;
|
||||
return meta;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DispatchEntry subclass for dispatching LLSD::Map to functions accepting
|
||||
* arbitrary argument types (convertible via LLSDParam)
|
||||
*/
|
||||
struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
|
||||
{
|
||||
MapParamsDispatchEntry(const std::string& name, const std::string& desc,
|
||||
const invoker_function& func,
|
||||
const LLSD& params, const LLSD& defaults):
|
||||
ParamsDispatchEntry(desc, func),
|
||||
mMapper(name, params, defaults),
|
||||
mRequired(LLSD::emptyMap())
|
||||
{
|
||||
// Build the set of all param keys, then delete the ones that are
|
||||
// optional. What's left are the ones that are required.
|
||||
for (LLSD::array_const_iterator pi(params.beginArray()), pend(params.endArray());
|
||||
pi != pend; ++pi)
|
||||
{
|
||||
mRequired[pi->asString()] = LLSD();
|
||||
}
|
||||
|
||||
if (defaults.isArray() || defaults.isUndefined())
|
||||
{
|
||||
// Right-align the params and defaults arrays.
|
||||
LLSD::Integer offset = params.size() - defaults.size();
|
||||
// Now the name of every defaults[i] is at params[i + offset].
|
||||
for (LLSD::Integer i(0), iend(defaults.size()); i < iend; ++i)
|
||||
{
|
||||
// Erase this optional param from mRequired.
|
||||
mRequired.erase(params[i + offset].asString());
|
||||
// Instead, make an entry in mOptional with the default
|
||||
// param's name and value.
|
||||
mOptional[params[i + offset].asString()] = defaults[i];
|
||||
}
|
||||
}
|
||||
else if (defaults.isMap())
|
||||
{
|
||||
// if defaults is already a map, then it's already in the form we
|
||||
// intend to deliver in metadata
|
||||
mOptional = defaults;
|
||||
// Just delete from mRequired every key appearing in mOptional.
|
||||
for (LLSD::map_const_iterator mi(mOptional.beginMap()), mend(mOptional.endMap());
|
||||
mi != mend; ++mi)
|
||||
{
|
||||
mRequired.erase(mi->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LLSDArgsMapper mMapper;
|
||||
LLSD mRequired;
|
||||
LLSD mOptional;
|
||||
|
||||
virtual void call(const std::string& desc, const LLSD& event) const
|
||||
{
|
||||
// Just convert from LLSD::Map to LLSD::Array using mMapper, then pass
|
||||
// to base-class call() method.
|
||||
ParamsDispatchEntry::call(desc, mMapper.map(event));
|
||||
}
|
||||
|
||||
virtual LLSD addMetadata(LLSD meta) const
|
||||
{
|
||||
meta["required"] = mRequired;
|
||||
meta["optional"] = mOptional;
|
||||
return meta;
|
||||
}
|
||||
};
|
||||
|
||||
void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name,
|
||||
const std::string& desc,
|
||||
const invoker_function& invoker,
|
||||
LLSD::Integer arity)
|
||||
{
|
||||
mDispatch.insert(
|
||||
DispatchMap::value_type(name, DispatchMap::mapped_type(
|
||||
new ArrayParamsDispatchEntry(desc, invoker, arity))));
|
||||
}
|
||||
|
||||
void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
|
||||
const std::string& desc,
|
||||
const invoker_function& invoker,
|
||||
const LLSD& params,
|
||||
const LLSD& defaults)
|
||||
{
|
||||
mDispatch.insert(
|
||||
DispatchMap::value_type(name, DispatchMap::mapped_type(
|
||||
new MapParamsDispatchEntry(name, desc, invoker, params, defaults))));
|
||||
}
|
||||
|
||||
/// Register a callable by name
|
||||
void LLEventDispatcher::add(const std::string& name, const std::string& desc,
|
||||
const Callable& callable, const LLSD& required)
|
||||
{
|
||||
mDispatch.insert(
|
||||
DispatchMap::value_type(name, DispatchMap::mapped_type(
|
||||
new LLSDDispatchEntry(desc, callable, required))));
|
||||
}
|
||||
|
||||
void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name
|
||||
<< "): " << classname << " is not a subclass "
|
||||
<< "of LLEventDispatcher" << LL_ENDL;
|
||||
}
|
||||
|
||||
/// Unregister a callable
|
||||
bool LLEventDispatcher::remove(const std::string& name)
|
||||
{
|
||||
DispatchMap::iterator found = mDispatch.find(name);
|
||||
if (found == mDispatch.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
mDispatch.erase(found);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Call a registered callable with an explicitly-specified name. If no
|
||||
/// such callable exists, die with LL_ERRS.
|
||||
void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
|
||||
{
|
||||
if (! try_call(name, event))
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name
|
||||
<< "' not found" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the @a key value from the incoming @a event, and call the
|
||||
/// callable whose name is specified by that map @a key. If no such
|
||||
/// callable exists, die with LL_ERRS.
|
||||
void LLEventDispatcher::operator()(const LLSD& event) const
|
||||
{
|
||||
// This could/should be implemented in terms of the two-arg overload.
|
||||
// However -- we can produce a more informative error message.
|
||||
std::string name(event[mKey]);
|
||||
if (! try_call(name, event))
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey
|
||||
<< " value '" << name << "'" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
bool LLEventDispatcher::try_call(const LLSD& event) const
|
||||
{
|
||||
return try_call(event[mKey], event);
|
||||
}
|
||||
|
||||
bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
|
||||
{
|
||||
DispatchMap::const_iterator found = mDispatch.find(name);
|
||||
if (found == mDispatch.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Found the name, so it's plausible to even attempt the call.
|
||||
found->second->call(STRINGIZE("LLEventDispatcher(" << mDesc << ") calling '" << name << "'"),
|
||||
event);
|
||||
return true; // tell caller we were able to call
|
||||
}
|
||||
|
||||
LLSD LLEventDispatcher::getMetadata(const std::string& name) const
|
||||
{
|
||||
DispatchMap::const_iterator found = mDispatch.find(name);
|
||||
if (found == mDispatch.end())
|
||||
{
|
||||
return LLSD();
|
||||
}
|
||||
LLSD meta;
|
||||
meta["name"] = name;
|
||||
meta["desc"] = found->second->mDesc;
|
||||
return found->second->addMetadata(meta);
|
||||
}
|
||||
|
||||
LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
|
||||
LLEventDispatcher(pumpname, key),
|
||||
mPump(pumpname, true), // allow tweaking for uniqueness
|
||||
mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1)))
|
||||
{
|
||||
}
|
||||
|
||||
bool LLDispatchListener::process(const LLSD& event)
|
||||
{
|
||||
(*this)(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
|
||||
mDesc(desc)
|
||||
{}
|
||||
|
||||
541
indra/llcommon/lleventdispatcher.h
Normal file
541
indra/llcommon/lleventdispatcher.h
Normal file
@@ -0,0 +1,541 @@
|
||||
/**
|
||||
* @file lleventdispatcher.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-06-18
|
||||
* @brief Central mechanism for dispatching events by string name. This is
|
||||
* useful when you have a single LLEventPump listener on which you can
|
||||
* request different operations, vs. instantiating a different
|
||||
* LLEventPump for each such operation.
|
||||
*
|
||||
* $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$
|
||||
*
|
||||
* The invoker machinery that constructs a boost::fusion argument list for use
|
||||
* with boost::fusion::invoke() is derived from
|
||||
* http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp
|
||||
* whose license information is copied below:
|
||||
*
|
||||
* "(C) Copyright Tobias Schwinger
|
||||
*
|
||||
* Use modification and distribution are subject to the boost Software License,
|
||||
* Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt)."
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLEVENTDISPATCHER_H)
|
||||
#define LL_LLEVENTDISPATCHER_H
|
||||
|
||||
// nil is too generic a term to be allowed to be a global macro. In
|
||||
// particular, boost::fusion defines a 'class nil' (properly encapsulated in a
|
||||
// namespace) that a global 'nil' macro breaks badly.
|
||||
#if defined(nil)
|
||||
// Capture the value of the macro 'nil', hoping int is an appropriate type.
|
||||
static const int nil_(nil);
|
||||
// Now forget the macro.
|
||||
#undef nil
|
||||
// Finally, reintroduce 'nil' as a properly-scoped alias for the previously-
|
||||
// defined const 'nil_'. Make it static since otherwise it produces duplicate-
|
||||
// symbol link errors later.
|
||||
static const int& nil(nil_);
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/iterator/transform_iterator.hpp>
|
||||
#include <boost/utility/enable_if.hpp>
|
||||
#include <boost/function_types/is_nonmember_callable_builtin.hpp>
|
||||
#include <boost/function_types/parameter_types.hpp>
|
||||
#include <boost/function_types/function_arity.hpp>
|
||||
#include <boost/type_traits/remove_cv.hpp>
|
||||
#include <boost/type_traits/remove_reference.hpp>
|
||||
#include <boost/fusion/include/push_back.hpp>
|
||||
#include <boost/fusion/include/cons.hpp>
|
||||
#include <boost/fusion/include/invoke.hpp>
|
||||
#include <boost/mpl/begin.hpp>
|
||||
#include <boost/mpl/end.hpp>
|
||||
#include <boost/mpl/next.hpp>
|
||||
#include <boost/mpl/deref.hpp>
|
||||
#include <typeinfo>
|
||||
#include "llevents.h"
|
||||
#include "llsdutil.h"
|
||||
|
||||
class LLSD;
|
||||
|
||||
/**
|
||||
* Given an LLSD map, examine a string-valued key and call a corresponding
|
||||
* callable. This class is designed to be contained by an LLEventPump
|
||||
* listener class that will register some of its own methods, though any
|
||||
* callable can be used.
|
||||
*/
|
||||
class LL_COMMON_API LLEventDispatcher
|
||||
{
|
||||
public:
|
||||
LLEventDispatcher(const std::string& desc, const std::string& key);
|
||||
virtual ~LLEventDispatcher();
|
||||
|
||||
/// @name Register functions accepting(const LLSD&)
|
||||
//@{
|
||||
|
||||
/// Accept any C++ callable with the right signature, typically a
|
||||
/// boost::bind() expression
|
||||
typedef boost::function<void(const LLSD&)> Callable;
|
||||
|
||||
/**
|
||||
* Register a @a callable by @a name. The passed @a callable accepts a
|
||||
* single LLSD value and uses it in any way desired, e.g. extract
|
||||
* parameters and call some other function. The optional @a required
|
||||
* parameter is used to validate the structure of each incoming event (see
|
||||
* llsd_matches()).
|
||||
*/
|
||||
void add(const std::string& name,
|
||||
const std::string& desc,
|
||||
const Callable& callable,
|
||||
const LLSD& required=LLSD());
|
||||
|
||||
/**
|
||||
* The case of a free function (or static method) accepting(const LLSD&)
|
||||
* could also be intercepted by the arbitrary-args overload below. Ensure
|
||||
* that it's directed to the Callable overload above instead.
|
||||
*/
|
||||
void add(const std::string& name,
|
||||
const std::string& desc,
|
||||
void (*f)(const LLSD&),
|
||||
const LLSD& required=LLSD())
|
||||
{
|
||||
add(name, desc, Callable(f), required);
|
||||
}
|
||||
|
||||
/**
|
||||
* Special case: a subclass of this class can pass an unbound member
|
||||
* function pointer (of an LLEventDispatcher subclass) without explicitly
|
||||
* specifying the <tt>boost::bind()</tt> expression. The passed @a method
|
||||
* accepts a single LLSD value, presumably containing other parameters.
|
||||
*/
|
||||
template <class CLASS>
|
||||
void add(const std::string& name,
|
||||
const std::string& desc,
|
||||
void (CLASS::*method)(const LLSD&),
|
||||
const LLSD& required=LLSD())
|
||||
{
|
||||
addMethod<CLASS>(name, desc, method, required);
|
||||
}
|
||||
|
||||
/// Overload for both const and non-const methods. The passed @a method
|
||||
/// accepts a single LLSD value, presumably containing other parameters.
|
||||
template <class CLASS>
|
||||
void add(const std::string& name,
|
||||
const std::string& desc,
|
||||
void (CLASS::*method)(const LLSD&) const,
|
||||
const LLSD& required=LLSD())
|
||||
{
|
||||
addMethod<CLASS>(name, desc, method, required);
|
||||
}
|
||||
|
||||
//@}
|
||||
|
||||
/// @name Register functions with arbitrary param lists
|
||||
//@{
|
||||
|
||||
/**
|
||||
* Register a free function with arbitrary parameters. (This also works
|
||||
* for static class methods.)
|
||||
*
|
||||
* @note This supports functions with up to about 6 parameters -- after
|
||||
* that you start getting dismaying compile errors in which
|
||||
* boost::fusion::joint_view is mentioned a surprising number of times.
|
||||
*
|
||||
* When calling this name, pass an LLSD::Array. Each entry in turn will be
|
||||
* converted to the corresponding parameter type using LLSDParam.
|
||||
*/
|
||||
template<typename Function>
|
||||
typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function>
|
||||
>::type add(const std::string& name,
|
||||
const std::string& desc,
|
||||
Function f);
|
||||
|
||||
/**
|
||||
* Register a nonstatic class method with arbitrary parameters.
|
||||
*
|
||||
* @note This supports functions with up to about 6 parameters -- after
|
||||
* that you start getting dismaying compile errors in which
|
||||
* boost::fusion::joint_view is mentioned a surprising number of times.
|
||||
*
|
||||
* To cover cases such as a method on an LLSingleton we don't yet want to
|
||||
* instantiate, instead of directly storing an instance pointer, accept a
|
||||
* nullary callable returning a pointer/reference to the desired class
|
||||
* instance. If you already have an instance in hand,
|
||||
* boost::lambda::var(instance) or boost::lambda::constant(instance_ptr)
|
||||
* produce suitable callables.
|
||||
*
|
||||
* When calling this name, pass an LLSD::Array. Each entry in turn will be
|
||||
* converted to the corresponding parameter type using LLSDParam.
|
||||
*/
|
||||
template<typename Method, typename InstanceGetter>
|
||||
typename boost::enable_if< boost::function_types::is_member_function_pointer<Method>
|
||||
>::type add(const std::string& name,
|
||||
const std::string& desc,
|
||||
Method f,
|
||||
const InstanceGetter& getter);
|
||||
|
||||
/**
|
||||
* Register a free function with arbitrary parameters. (This also works
|
||||
* for static class methods.)
|
||||
*
|
||||
* @note This supports functions with up to about 6 parameters -- after
|
||||
* that you start getting dismaying compile errors in which
|
||||
* boost::fusion::joint_view is mentioned a surprising number of times.
|
||||
*
|
||||
* Pass an LLSD::Array of parameter names, and optionally another
|
||||
* LLSD::Array of default parameter values, a la LLSDArgsMapper.
|
||||
*
|
||||
* When calling this name, pass an LLSD::Map. We will internally generate
|
||||
* an LLSD::Array using LLSDArgsMapper and then convert each entry in turn
|
||||
* to the corresponding parameter type using LLSDParam.
|
||||
*/
|
||||
template<typename Function>
|
||||
typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function>
|
||||
>::type add(const std::string& name,
|
||||
const std::string& desc,
|
||||
Function f,
|
||||
const LLSD& params,
|
||||
const LLSD& defaults=LLSD());
|
||||
|
||||
/**
|
||||
* Register a nonstatic class method with arbitrary parameters.
|
||||
*
|
||||
* @note This supports functions with up to about 6 parameters -- after
|
||||
* that you start getting dismaying compile errors in which
|
||||
* boost::fusion::joint_view is mentioned a surprising number of times.
|
||||
*
|
||||
* To cover cases such as a method on an LLSingleton we don't yet want to
|
||||
* instantiate, instead of directly storing an instance pointer, accept a
|
||||
* nullary callable returning a pointer/reference to the desired class
|
||||
* instance. If you already have an instance in hand,
|
||||
* boost::lambda::var(instance) or boost::lambda::constant(instance_ptr)
|
||||
* produce suitable callables.
|
||||
*
|
||||
* Pass an LLSD::Array of parameter names, and optionally another
|
||||
* LLSD::Array of default parameter values, a la LLSDArgsMapper.
|
||||
*
|
||||
* When calling this name, pass an LLSD::Map. We will internally generate
|
||||
* an LLSD::Array using LLSDArgsMapper and then convert each entry in turn
|
||||
* to the corresponding parameter type using LLSDParam.
|
||||
*/
|
||||
template<typename Method, typename InstanceGetter>
|
||||
typename boost::enable_if< boost::function_types::is_member_function_pointer<Method>
|
||||
>::type add(const std::string& name,
|
||||
const std::string& desc,
|
||||
Method f,
|
||||
const InstanceGetter& getter,
|
||||
const LLSD& params,
|
||||
const LLSD& defaults=LLSD());
|
||||
|
||||
//@}
|
||||
|
||||
/// Unregister a callable
|
||||
bool remove(const std::string& name);
|
||||
|
||||
/// Call a registered callable with an explicitly-specified name. If no
|
||||
/// such callable exists, die with LL_ERRS. If the @a event fails to match
|
||||
/// the @a required prototype specified at add() time, die with LL_ERRS.
|
||||
void operator()(const std::string& name, const LLSD& event) const;
|
||||
|
||||
/// Call a registered callable with an explicitly-specified name and
|
||||
/// return <tt>true</tt>. If no such callable exists, return
|
||||
/// <tt>false</tt>. If the @a event fails to match the @a required
|
||||
/// prototype specified at add() time, die with LL_ERRS.
|
||||
bool try_call(const std::string& name, const LLSD& event) const;
|
||||
|
||||
/// Extract the @a key value from the incoming @a event, and call the
|
||||
/// callable whose name is specified by that map @a key. If no such
|
||||
/// callable exists, die with LL_ERRS. If the @a event fails to match the
|
||||
/// @a required prototype specified at add() time, die with LL_ERRS.
|
||||
void operator()(const LLSD& event) const;
|
||||
|
||||
/// Extract the @a key value from the incoming @a event, call the callable
|
||||
/// whose name is specified by that map @a key and return <tt>true</tt>.
|
||||
/// If no such callable exists, return <tt>false</tt>. If the @a event
|
||||
/// fails to match the @a required prototype specified at add() time, die
|
||||
/// with LL_ERRS.
|
||||
bool try_call(const LLSD& event) const;
|
||||
|
||||
/// @name Iterate over defined names
|
||||
//@{
|
||||
typedef std::pair<std::string, std::string> NameDesc;
|
||||
|
||||
private:
|
||||
struct DispatchEntry
|
||||
{
|
||||
DispatchEntry(const std::string& desc);
|
||||
virtual ~DispatchEntry() {} // suppress MSVC warning, sigh
|
||||
|
||||
std::string mDesc;
|
||||
|
||||
virtual void call(const std::string& desc, const LLSD& event) const = 0;
|
||||
virtual LLSD addMetadata(LLSD) const = 0;
|
||||
};
|
||||
// Tried using boost::ptr_map<std::string, DispatchEntry>, but ptr_map<>
|
||||
// wants its value type to be "clonable," even just to dereference an
|
||||
// iterator. I don't want to clone entries -- if I have to copy an entry
|
||||
// around, I want it to continue pointing to the same DispatchEntry
|
||||
// subclass object. However, I definitely want DispatchMap to destroy
|
||||
// DispatchEntry if no references are outstanding at the time an entry is
|
||||
// removed. This looks like a job for boost::shared_ptr.
|
||||
typedef std::map<std::string, boost::shared_ptr<DispatchEntry> > DispatchMap;
|
||||
|
||||
public:
|
||||
/// We want the flexibility to redefine what data we store per name,
|
||||
/// therefore our public interface doesn't expose DispatchMap iterators,
|
||||
/// or DispatchMap itself, or DispatchEntry. Instead we explicitly
|
||||
/// transform each DispatchMap item to NameDesc on dereferencing.
|
||||
typedef boost::transform_iterator<NameDesc(*)(const DispatchMap::value_type&), DispatchMap::const_iterator> const_iterator;
|
||||
const_iterator begin() const
|
||||
{
|
||||
return boost::make_transform_iterator(mDispatch.begin(), makeNameDesc);
|
||||
}
|
||||
const_iterator end() const
|
||||
{
|
||||
return boost::make_transform_iterator(mDispatch.end(), makeNameDesc);
|
||||
}
|
||||
//@}
|
||||
|
||||
/// Get information about a specific Callable
|
||||
LLSD getMetadata(const std::string& name) const;
|
||||
|
||||
/// Retrieve the LLSD key we use for one-arg <tt>operator()</tt> method
|
||||
std::string getDispatchKey() const { return mKey; }
|
||||
|
||||
private:
|
||||
template <class CLASS, typename METHOD>
|
||||
void addMethod(const std::string& name, const std::string& desc,
|
||||
const METHOD& method, const LLSD& required)
|
||||
{
|
||||
CLASS* downcast = dynamic_cast<CLASS*>(this);
|
||||
if (! downcast)
|
||||
{
|
||||
addFail(name, typeid(CLASS).name());
|
||||
}
|
||||
else
|
||||
{
|
||||
add(name, desc, boost::bind(method, downcast, _1), required);
|
||||
}
|
||||
}
|
||||
void addFail(const std::string& name, const std::string& classname) const;
|
||||
|
||||
std::string mDesc, mKey;
|
||||
DispatchMap mDispatch;
|
||||
|
||||
static NameDesc makeNameDesc(const DispatchMap::value_type& item)
|
||||
{
|
||||
return NameDesc(item.first, item.second->mDesc);
|
||||
}
|
||||
|
||||
struct LLSDDispatchEntry;
|
||||
struct ParamsDispatchEntry;
|
||||
struct ArrayParamsDispatchEntry;
|
||||
struct MapParamsDispatchEntry;
|
||||
|
||||
// Step 2 of parameter analysis. Instantiating invoker<some_function_type>
|
||||
// implicitly sets its From and To parameters to the (compile time) begin
|
||||
// and end iterators over that function's parameter types.
|
||||
template< typename Function
|
||||
, class From = typename boost::mpl::begin< boost::function_types::parameter_types<Function> >::type
|
||||
, class To = typename boost::mpl::end< boost::function_types::parameter_types<Function> >::type
|
||||
>
|
||||
struct invoker;
|
||||
|
||||
// deliver LLSD arguments one at a time
|
||||
typedef boost::function<LLSD()> args_source;
|
||||
// obtain args from an args_source to build param list and call target
|
||||
// function
|
||||
typedef boost::function<void(const args_source&)> invoker_function;
|
||||
|
||||
template <typename Function>
|
||||
invoker_function make_invoker(Function f);
|
||||
template <typename Method, typename InstanceGetter>
|
||||
invoker_function make_invoker(Method f, const InstanceGetter& getter);
|
||||
void addArrayParamsDispatchEntry(const std::string& name,
|
||||
const std::string& desc,
|
||||
const invoker_function& invoker,
|
||||
LLSD::Integer arity);
|
||||
void addMapParamsDispatchEntry(const std::string& name,
|
||||
const std::string& desc,
|
||||
const invoker_function& invoker,
|
||||
const LLSD& params,
|
||||
const LLSD& defaults);
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* LLEventDispatcher template implementation details
|
||||
*****************************************************************************/
|
||||
// Step 3 of parameter analysis, the recursive case.
|
||||
template<typename Function, class From, class To>
|
||||
struct LLEventDispatcher::invoker
|
||||
{
|
||||
template<typename T>
|
||||
struct remove_cv_ref
|
||||
: boost::remove_cv< typename boost::remove_reference<T>::type >
|
||||
{ };
|
||||
|
||||
// apply() accepts an arbitrary boost::fusion sequence as args. It
|
||||
// examines the next parameter type in the parameter-types sequence
|
||||
// bounded by From and To, obtains the next LLSD object from the passed
|
||||
// args_source and constructs an LLSDParam of appropriate type to try
|
||||
// to convert the value. It then recurs with the next parameter-types
|
||||
// iterator, passing the args sequence thus far.
|
||||
template<typename Args>
|
||||
static inline
|
||||
void apply(Function func, const args_source& argsrc, Args const & args)
|
||||
{
|
||||
typedef typename boost::mpl::deref<From>::type arg_type;
|
||||
typedef typename boost::mpl::next<From>::type next_iter_type;
|
||||
typedef typename remove_cv_ref<arg_type>::type plain_arg_type;
|
||||
|
||||
invoker<Function, next_iter_type, To>::apply
|
||||
( func, argsrc, boost::fusion::push_back(args, LLSDParam<plain_arg_type>(argsrc())));
|
||||
}
|
||||
|
||||
// Special treatment for instance (first) parameter of a non-static member
|
||||
// function. Accept the instance-getter callable, calling that to produce
|
||||
// the first args value. Since we know we're at the top of the recursion
|
||||
// chain, we need not also require a partial args sequence from our caller.
|
||||
template <typename InstanceGetter>
|
||||
static inline
|
||||
void method_apply(Function func, const args_source& argsrc, const InstanceGetter& getter)
|
||||
{
|
||||
typedef typename boost::mpl::next<From>::type next_iter_type;
|
||||
|
||||
// Instead of grabbing the first item from argsrc and making an
|
||||
// LLSDParam of it, call getter() and pass that as the instance param.
|
||||
invoker<Function, next_iter_type, To>::apply
|
||||
( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), boost::ref(getter())));
|
||||
}
|
||||
};
|
||||
|
||||
// Step 4 of parameter analysis, the leaf case. When the general
|
||||
// invoker<Function, From, To> logic has advanced From until it matches To,
|
||||
// the compiler will pick this template specialization.
|
||||
template<typename Function, class To>
|
||||
struct LLEventDispatcher::invoker<Function,To,To>
|
||||
{
|
||||
// the argument list is complete, now call the function
|
||||
template<typename Args>
|
||||
static inline
|
||||
void apply(Function func, const args_source&, Args const & args)
|
||||
{
|
||||
boost::fusion::invoke(func, args);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Function>
|
||||
typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> >::type
|
||||
LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f)
|
||||
{
|
||||
// Construct an invoker_function, a callable accepting const args_source&.
|
||||
// Add to DispatchMap an ArrayParamsDispatchEntry that will handle the
|
||||
// caller's LLSD::Array.
|
||||
addArrayParamsDispatchEntry(name, desc, make_invoker(f),
|
||||
boost::function_types::function_arity<Function>::value);
|
||||
}
|
||||
|
||||
template<typename Method, typename InstanceGetter>
|
||||
typename boost::enable_if< boost::function_types::is_member_function_pointer<Method> >::type
|
||||
LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f,
|
||||
const InstanceGetter& getter)
|
||||
{
|
||||
// Subtract 1 from the compile-time arity because the getter takes care of
|
||||
// the first parameter. We only need (arity - 1) additional arguments.
|
||||
addArrayParamsDispatchEntry(name, desc, make_invoker(f, getter),
|
||||
boost::function_types::function_arity<Method>::value - 1);
|
||||
}
|
||||
|
||||
template<typename Function>
|
||||
typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> >::type
|
||||
LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f,
|
||||
const LLSD& params, const LLSD& defaults)
|
||||
{
|
||||
// See comments for previous is_nonmember_callable_builtin add().
|
||||
addMapParamsDispatchEntry(name, desc, make_invoker(f), params, defaults);
|
||||
}
|
||||
|
||||
template<typename Method, typename InstanceGetter>
|
||||
typename boost::enable_if< boost::function_types::is_member_function_pointer<Method> >::type
|
||||
LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f,
|
||||
const InstanceGetter& getter,
|
||||
const LLSD& params, const LLSD& defaults)
|
||||
{
|
||||
addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
LLEventDispatcher::invoker_function
|
||||
LLEventDispatcher::make_invoker(Function f)
|
||||
{
|
||||
// Step 1 of parameter analysis, the top of the recursion. Passing a
|
||||
// suitable f (see add()'s enable_if condition) to this method causes it
|
||||
// to infer the function type; specifying that function type to invoker<>
|
||||
// causes it to fill in the begin/end MPL iterators over the function's
|
||||
// list of parameter types.
|
||||
// While normally invoker::apply() could infer its template type from the
|
||||
// boost::fusion::nil parameter value, here we must be explicit since
|
||||
// we're boost::bind()ing it rather than calling it directly.
|
||||
return boost::bind(&invoker<Function>::template apply<boost::fusion::nil>,
|
||||
f,
|
||||
_1,
|
||||
boost::fusion::nil());
|
||||
}
|
||||
|
||||
template <typename Method, typename InstanceGetter>
|
||||
LLEventDispatcher::invoker_function
|
||||
LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter)
|
||||
{
|
||||
// Use invoker::method_apply() to treat the instance (first) arg specially.
|
||||
return boost::bind(&invoker<Method>::template method_apply<InstanceGetter>,
|
||||
f,
|
||||
_1,
|
||||
getter);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* LLDispatchListener
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* Bundle an LLEventPump and a listener with an LLEventDispatcher. A class
|
||||
* that contains (or derives from) LLDispatchListener need only specify the
|
||||
* LLEventPump name and dispatch key, and add() its methods. Incoming events
|
||||
* will automatically be dispatched.
|
||||
*/
|
||||
class LL_COMMON_API LLDispatchListener: public LLEventDispatcher
|
||||
{
|
||||
public:
|
||||
LLDispatchListener(const std::string& pumpname, const std::string& key);
|
||||
|
||||
std::string getPumpName() const { return mPump.getName(); }
|
||||
|
||||
private:
|
||||
bool process(const LLSD& event);
|
||||
|
||||
LLEventStream mPump;
|
||||
LLTempBoundListener mBoundListener;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLEVENTDISPATCHER_H) */
|
||||
166
indra/llcommon/lleventfilter.cpp
Normal file
166
indra/llcommon/lleventfilter.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @file lleventfilter.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-03-05
|
||||
* @brief Implementation for lleventfilter.
|
||||
*
|
||||
* $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$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "lleventfilter.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/bind.hpp>
|
||||
// other Linden headers
|
||||
#include "llerror.h" // LL_ERRS
|
||||
#include "llsdutil.h" // llsd_matches()
|
||||
|
||||
LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak):
|
||||
LLEventStream(name, tweak)
|
||||
{
|
||||
source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1));
|
||||
}
|
||||
|
||||
LLEventMatching::LLEventMatching(const LLSD& pattern):
|
||||
LLEventFilter("matching"),
|
||||
mPattern(pattern)
|
||||
{
|
||||
}
|
||||
|
||||
LLEventMatching::LLEventMatching(LLEventPump& source, const LLSD& pattern):
|
||||
LLEventFilter(source, "matching"),
|
||||
mPattern(pattern)
|
||||
{
|
||||
}
|
||||
|
||||
bool LLEventMatching::post(const LLSD& event)
|
||||
{
|
||||
if (! llsd_matches(mPattern, event).empty())
|
||||
return false;
|
||||
|
||||
return LLEventStream::post(event);
|
||||
}
|
||||
|
||||
LLEventTimeoutBase::LLEventTimeoutBase():
|
||||
LLEventFilter("timeout")
|
||||
{
|
||||
}
|
||||
|
||||
LLEventTimeoutBase::LLEventTimeoutBase(LLEventPump& source):
|
||||
LLEventFilter(source, "timeout")
|
||||
{
|
||||
}
|
||||
|
||||
void LLEventTimeoutBase::actionAfter(F32 seconds, const Action& action)
|
||||
{
|
||||
setCountdown(seconds);
|
||||
mAction = action;
|
||||
if (! mMainloop.connected())
|
||||
{
|
||||
LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop"));
|
||||
mMainloop = mainloop.listen(getName(), boost::bind(&LLEventTimeoutBase::tick, this, _1));
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorAfter
|
||||
{
|
||||
public:
|
||||
ErrorAfter(const std::string& message): mMessage(message) {}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
LL_ERRS("LLEventTimeout") << mMessage << LL_ENDL;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string mMessage;
|
||||
};
|
||||
|
||||
void LLEventTimeoutBase::errorAfter(F32 seconds, const std::string& message)
|
||||
{
|
||||
actionAfter(seconds, ErrorAfter(message));
|
||||
}
|
||||
|
||||
class EventAfter
|
||||
{
|
||||
public:
|
||||
EventAfter(LLEventPump& pump, const LLSD& event):
|
||||
mPump(pump),
|
||||
mEvent(event)
|
||||
{}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
mPump.post(mEvent);
|
||||
}
|
||||
|
||||
private:
|
||||
LLEventPump& mPump;
|
||||
LLSD mEvent;
|
||||
};
|
||||
|
||||
void LLEventTimeoutBase::eventAfter(F32 seconds, const LLSD& event)
|
||||
{
|
||||
actionAfter(seconds, EventAfter(*this, event));
|
||||
}
|
||||
|
||||
bool LLEventTimeoutBase::post(const LLSD& event)
|
||||
{
|
||||
cancel();
|
||||
return LLEventStream::post(event);
|
||||
}
|
||||
|
||||
void LLEventTimeoutBase::cancel()
|
||||
{
|
||||
mMainloop.disconnect();
|
||||
}
|
||||
|
||||
bool LLEventTimeoutBase::tick(const LLSD&)
|
||||
{
|
||||
if (countdownElapsed())
|
||||
{
|
||||
cancel();
|
||||
mAction();
|
||||
}
|
||||
return false; // show event to other listeners
|
||||
}
|
||||
|
||||
LLEventTimeout::LLEventTimeout() {}
|
||||
|
||||
LLEventTimeout::LLEventTimeout(LLEventPump& source):
|
||||
LLEventTimeoutBase(source)
|
||||
{
|
||||
}
|
||||
|
||||
void LLEventTimeout::setCountdown(F32 seconds)
|
||||
{
|
||||
mTimer.setTimerExpirySec(seconds);
|
||||
}
|
||||
|
||||
bool LLEventTimeout::countdownElapsed() const
|
||||
{
|
||||
return mTimer.hasExpired();
|
||||
}
|
||||
203
indra/llcommon/lleventfilter.h
Normal file
203
indra/llcommon/lleventfilter.h
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* @file lleventfilter.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-03-05
|
||||
* @brief Define LLEventFilter: LLEventStream subclass with conditions
|
||||
*
|
||||
* $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_LLEVENTFILTER_H)
|
||||
#define LL_LLEVENTFILTER_H
|
||||
|
||||
#include "llevents.h"
|
||||
#include "stdtypes.h"
|
||||
#include "lltimer.h"
|
||||
#include <boost/function.hpp>
|
||||
|
||||
/**
|
||||
* Generic base class
|
||||
*/
|
||||
class LL_COMMON_API LLEventFilter: public LLEventStream
|
||||
{
|
||||
public:
|
||||
/// construct a standalone LLEventFilter
|
||||
LLEventFilter(const std::string& name="filter", bool tweak=true):
|
||||
LLEventStream(name, tweak)
|
||||
{}
|
||||
/// construct LLEventFilter and connect it to the specified LLEventPump
|
||||
LLEventFilter(LLEventPump& source, const std::string& name="filter", bool tweak=true);
|
||||
|
||||
/// Post an event to all listeners
|
||||
virtual bool post(const LLSD& event) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pass through only events matching a specified pattern
|
||||
*/
|
||||
class LLEventMatching: public LLEventFilter
|
||||
{
|
||||
public:
|
||||
/// Pass an LLSD map with keys and values the incoming event must match
|
||||
LLEventMatching(const LLSD& pattern);
|
||||
/// instantiate and connect
|
||||
LLEventMatching(LLEventPump& source, const LLSD& pattern);
|
||||
|
||||
/// Only pass through events matching the pattern
|
||||
virtual bool post(const LLSD& event);
|
||||
|
||||
private:
|
||||
LLSD mPattern;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for an event to be posted. If no such event arrives within a specified
|
||||
* time, take a specified action. See LLEventTimeout for production
|
||||
* implementation.
|
||||
*
|
||||
* @NOTE This is an abstract base class so that, for testing, we can use an
|
||||
* alternate "timer" that doesn't actually consume real time.
|
||||
*/
|
||||
class LL_COMMON_API LLEventTimeoutBase: public LLEventFilter
|
||||
{
|
||||
public:
|
||||
/// construct standalone
|
||||
LLEventTimeoutBase();
|
||||
/// construct and connect
|
||||
LLEventTimeoutBase(LLEventPump& source);
|
||||
|
||||
/// Callable, can be constructed with boost::bind()
|
||||
typedef boost::function<void()> Action;
|
||||
|
||||
/**
|
||||
* Start countdown timer for the specified number of @a seconds. Forward
|
||||
* all events. If any event arrives before timer expires, cancel timer. If
|
||||
* no event arrives before timer expires, take specified @a action.
|
||||
*
|
||||
* This is a one-shot timer. Once it has either expired or been canceled,
|
||||
* it is inert until another call to actionAfter().
|
||||
*
|
||||
* Calling actionAfter() while an existing timer is running cheaply
|
||||
* replaces that original timer. Thus, a valid use case is to detect
|
||||
* idleness of some event source by calling actionAfter() on each new
|
||||
* event. A rapid sequence of events will keep the timer from expiring;
|
||||
* the first gap in events longer than the specified timer will fire the
|
||||
* specified Action.
|
||||
*
|
||||
* Any post() call cancels the timer. To be satisfied with only a
|
||||
* particular event, chain on an LLEventMatching that only passes such
|
||||
* events:
|
||||
*
|
||||
* @code
|
||||
* event ultimate
|
||||
* source ---> LLEventMatching ---> LLEventTimeout ---> listener
|
||||
* @endcode
|
||||
*
|
||||
* @NOTE
|
||||
* The implementation relies on frequent events on the LLEventPump named
|
||||
* "mainloop".
|
||||
*/
|
||||
void actionAfter(F32 seconds, const Action& action);
|
||||
|
||||
/**
|
||||
* Like actionAfter(), but where the desired Action is LL_ERRS
|
||||
* termination. Pass the timeout time and the desired LL_ERRS @a message.
|
||||
*
|
||||
* This method is useful when, for instance, some async API guarantees an
|
||||
* event, whether success or failure, within a stated time window.
|
||||
* Instantiate an LLEventTimeout listening to that API and call
|
||||
* errorAfter() on each async request with a timeout comfortably longer
|
||||
* than the API's time guarantee (much longer than the anticipated
|
||||
* "mainloop" granularity).
|
||||
*
|
||||
* Then if the async API breaks its promise, the program terminates with
|
||||
* the specified LL_ERRS @a message. The client of the async API can
|
||||
* therefore assume the guarantee is upheld.
|
||||
*
|
||||
* @NOTE
|
||||
* errorAfter() is implemented in terms of actionAfter(), so all remarks
|
||||
* about calling actionAfter() also apply to errorAfter().
|
||||
*/
|
||||
void errorAfter(F32 seconds, const std::string& message);
|
||||
|
||||
/**
|
||||
* Like actionAfter(), but where the desired Action is a particular event
|
||||
* for all listeners. Pass the timeout time and the desired @a event data.
|
||||
*
|
||||
* Suppose the timeout should only be satisfied by a particular event, but
|
||||
* the ultimate listener must see all other incoming events as well, plus
|
||||
* the timeout @a event if any:
|
||||
*
|
||||
* @code
|
||||
* some LLEventMatching LLEventMatching
|
||||
* event ---> for particular ---> LLEventTimeout ---> for timeout
|
||||
* source event event \
|
||||
* \ \ ultimate
|
||||
* `-----------------------------------------------------> listener
|
||||
* @endcode
|
||||
*
|
||||
* Since a given listener can listen on more than one LLEventPump, we can
|
||||
* set things up so it sees the set union of events from LLEventTimeout
|
||||
* and the original event source. However, as LLEventTimeout passes
|
||||
* through all incoming events, the "particular event" that satisfies the
|
||||
* left LLEventMatching would reach the ultimate listener twice. So we add
|
||||
* an LLEventMatching that only passes timeout events.
|
||||
*
|
||||
* @NOTE
|
||||
* eventAfter() is implemented in terms of actionAfter(), so all remarks
|
||||
* about calling actionAfter() also apply to eventAfter().
|
||||
*/
|
||||
void eventAfter(F32 seconds, const LLSD& event);
|
||||
|
||||
/// Pass event through, canceling the countdown timer
|
||||
virtual bool post(const LLSD& event);
|
||||
|
||||
/// Cancel timer without event
|
||||
void cancel();
|
||||
|
||||
protected:
|
||||
virtual void setCountdown(F32 seconds) = 0;
|
||||
virtual bool countdownElapsed() const = 0;
|
||||
|
||||
private:
|
||||
bool tick(const LLSD&);
|
||||
|
||||
LLBoundListener mMainloop;
|
||||
Action mAction;
|
||||
};
|
||||
|
||||
/// Production implementation of LLEventTimoutBase
|
||||
class LL_COMMON_API LLEventTimeout: public LLEventTimeoutBase
|
||||
{
|
||||
public:
|
||||
LLEventTimeout();
|
||||
LLEventTimeout(LLEventPump& source);
|
||||
|
||||
protected:
|
||||
virtual void setCountdown(F32 seconds);
|
||||
virtual bool countdownElapsed() const;
|
||||
|
||||
private:
|
||||
LLTimer mTimer;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLEVENTFILTER_H) */
|
||||
614
indra/llcommon/llevents.cpp
Normal file
614
indra/llcommon/llevents.cpp
Normal file
@@ -0,0 +1,614 @@
|
||||
/**
|
||||
* @file llevents.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2008-09-12
|
||||
* @brief Implementation for llevents.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&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$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want!
|
||||
#endif
|
||||
|
||||
// associated header
|
||||
#include "llevents.h"
|
||||
// STL headers
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
// std headers
|
||||
#include <typeinfo>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
// external library headers
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable : 4701) // compiler thinks might use uninitialized var, but no
|
||||
#endif
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
// other Linden headers
|
||||
#include "stringize.h"
|
||||
#include "llerror.h"
|
||||
#include "llsdutil.h"
|
||||
#if LL_MSVC
|
||||
#pragma warning (disable : 4702)
|
||||
#endif
|
||||
|
||||
/*****************************************************************************
|
||||
* queue_names: specify LLEventPump names that should be instantiated as
|
||||
* LLEventQueue
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* At present, we recognize particular requested LLEventPump names as needing
|
||||
* LLEventQueues. Later on we'll migrate this information to an external
|
||||
* configuration file.
|
||||
*/
|
||||
const char* queue_names[] =
|
||||
{
|
||||
"placeholder - replace with first real name string"
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* If there's a "mainloop" pump, listen on that to flush all LLEventQueues
|
||||
*****************************************************************************/
|
||||
struct RegisterFlush : public LLEventTrackable
|
||||
{
|
||||
RegisterFlush():
|
||||
pumps(LLEventPumps::instance())
|
||||
{
|
||||
pumps.obtain("mainloop").listen("flushLLEventQueues", boost::bind(&RegisterFlush::flush, this, _1));
|
||||
}
|
||||
bool flush(const LLSD&)
|
||||
{
|
||||
pumps.flush();
|
||||
return false;
|
||||
}
|
||||
~RegisterFlush()
|
||||
{
|
||||
// LLEventTrackable handles stopListening for us.
|
||||
}
|
||||
LLEventPumps& pumps;
|
||||
};
|
||||
static RegisterFlush registerFlush;
|
||||
|
||||
/*****************************************************************************
|
||||
* LLEventPumps
|
||||
*****************************************************************************/
|
||||
LLEventPumps::LLEventPumps():
|
||||
// Until we migrate this information to an external config file,
|
||||
// initialize mQueueNames from the static queue_names array.
|
||||
mQueueNames(boost::begin(queue_names), boost::end(queue_names))
|
||||
{
|
||||
}
|
||||
|
||||
LLEventPump& LLEventPumps::obtain(const std::string& name)
|
||||
{
|
||||
PumpMap::iterator found = mPumpMap.find(name);
|
||||
if (found != mPumpMap.end())
|
||||
{
|
||||
// Here we already have an LLEventPump instance with the requested
|
||||
// name.
|
||||
return *found->second;
|
||||
}
|
||||
// Here we must instantiate an LLEventPump subclass.
|
||||
LLEventPump* newInstance;
|
||||
// Should this name be an LLEventQueue?
|
||||
PumpNames::const_iterator nfound = mQueueNames.find(name);
|
||||
if (nfound != mQueueNames.end())
|
||||
newInstance = new LLEventQueue(name);
|
||||
else
|
||||
newInstance = new LLEventStream(name);
|
||||
// LLEventPump's constructor implicitly registers each new instance in
|
||||
// mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll
|
||||
// delete it later.
|
||||
mOurPumps.insert(newInstance);
|
||||
return *newInstance;
|
||||
}
|
||||
|
||||
void LLEventPumps::flush()
|
||||
{
|
||||
// Flush every known LLEventPump instance. Leave it up to each instance to
|
||||
// decide what to do with the flush() call.
|
||||
for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi)
|
||||
{
|
||||
pmi->second->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void LLEventPumps::reset()
|
||||
{
|
||||
// Reset every known LLEventPump instance. Leave it up to each instance to
|
||||
// decide what to do with the reset() call.
|
||||
for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi)
|
||||
{
|
||||
pmi->second->reset();
|
||||
}
|
||||
}
|
||||
|
||||
std::string LLEventPumps::registerNew(const LLEventPump& pump, const std::string& name, bool tweak)
|
||||
{
|
||||
std::pair<PumpMap::iterator, bool> inserted =
|
||||
mPumpMap.insert(PumpMap::value_type(name, const_cast<LLEventPump*>(&pump)));
|
||||
// If the insert worked, then the name is unique; return that.
|
||||
if (inserted.second)
|
||||
return name;
|
||||
// Here the new entry was NOT inserted, and therefore name isn't unique.
|
||||
// Unless we're permitted to tweak it, that's Bad.
|
||||
if (! tweak)
|
||||
{
|
||||
throw LLEventPump::DupPumpName(std::string("Duplicate LLEventPump name '") + name + "'");
|
||||
}
|
||||
// The passed name isn't unique, but we're permitted to tweak it. Find the
|
||||
// first decimal-integer suffix not already taken. The insert() attempt
|
||||
// above will have set inserted.first to the iterator of the existing
|
||||
// entry by that name. Starting there, walk forward until we reach an
|
||||
// entry that doesn't start with 'name'. For each entry consisting of name
|
||||
// + integer suffix, capture the integer suffix in a set. Use a set
|
||||
// because we're going to encounter string suffixes in the order: name1,
|
||||
// name10, name11, name2, ... Walking those possibilities in that order
|
||||
// isn't convenient to detect the first available "hole."
|
||||
std::set<int> suffixes;
|
||||
PumpMap::iterator pmi(inserted.first), pmend(mPumpMap.end());
|
||||
// We already know inserted.first references the existing entry with
|
||||
// 'name' as the key; skip that one and start with the next.
|
||||
while (++pmi != pmend)
|
||||
{
|
||||
if (pmi->first.substr(0, name.length()) != name)
|
||||
{
|
||||
// Found the first entry beyond the entries starting with 'name':
|
||||
// stop looping.
|
||||
break;
|
||||
}
|
||||
// Here we're looking at an entry that starts with 'name'. Is the rest
|
||||
// of it an integer?
|
||||
// Dubious (?) assumption: in the local character set, decimal digits
|
||||
// are in increasing order such that '9' is the last of them. This
|
||||
// test deals with 'name' values such as 'a', where there might be a
|
||||
// very large number of entries starting with 'a' whose suffixes
|
||||
// aren't integers. A secondary assumption is that digit characters
|
||||
// precede most common name characters (true in ASCII, false in
|
||||
// EBCDIC). The test below is correct either way, but it's worth more
|
||||
// if the assumption holds.
|
||||
if (pmi->first[name.length()] > '9')
|
||||
break;
|
||||
// It should be cheaper to detect that we're not looking at a digit
|
||||
// character -- and therefore the suffix can't possibly be an integer
|
||||
// -- than to attempt the lexical_cast and catch the exception.
|
||||
if (! std::isdigit(pmi->first[name.length()]))
|
||||
continue;
|
||||
// Okay, the first character of the suffix is a digit, it's worth at
|
||||
// least attempting to convert to int.
|
||||
try
|
||||
{
|
||||
suffixes.insert(boost::lexical_cast<int>(pmi->first.substr(name.length())));
|
||||
}
|
||||
catch (const boost::bad_lexical_cast&)
|
||||
{
|
||||
// If the rest of pmi->first isn't an int, just ignore it.
|
||||
}
|
||||
}
|
||||
// Here we've accumulated in 'suffixes' all existing int suffixes of the
|
||||
// entries starting with 'name'. Find the first unused one.
|
||||
int suffix = 1;
|
||||
for ( ; suffixes.find(suffix) != suffixes.end(); ++suffix)
|
||||
;
|
||||
// Here 'suffix' is not in 'suffixes'. Construct a new name based on that
|
||||
// suffix, insert it and return it.
|
||||
std::ostringstream out;
|
||||
out << name << suffix;
|
||||
return registerNew(pump, out.str(), tweak);
|
||||
}
|
||||
|
||||
void LLEventPumps::unregister(const LLEventPump& pump)
|
||||
{
|
||||
// Remove this instance from mPumpMap
|
||||
PumpMap::iterator found = mPumpMap.find(pump.getName());
|
||||
if (found != mPumpMap.end())
|
||||
{
|
||||
mPumpMap.erase(found);
|
||||
}
|
||||
// If this instance is one we created, also remove it from mOurPumps so we
|
||||
// won't try again to delete it later!
|
||||
PumpSet::iterator psfound = mOurPumps.find(const_cast<LLEventPump*>(&pump));
|
||||
if (psfound != mOurPumps.end())
|
||||
{
|
||||
mOurPumps.erase(psfound);
|
||||
}
|
||||
}
|
||||
|
||||
LLEventPumps::~LLEventPumps()
|
||||
{
|
||||
// On destruction, delete every LLEventPump we instantiated (via
|
||||
// obtain()). CAREFUL: deleting an LLEventPump calls its destructor, which
|
||||
// calls unregister(), which removes that LLEventPump instance from
|
||||
// mOurPumps. So an iterator loop over mOurPumps to delete contained
|
||||
// LLEventPump instances is dangerous! Instead, delete them one at a time
|
||||
// until mOurPumps is empty.
|
||||
while (! mOurPumps.empty())
|
||||
{
|
||||
delete *mOurPumps.begin();
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* LLEventPump
|
||||
*****************************************************************************/
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
|
||||
#endif
|
||||
|
||||
LLEventPump::LLEventPump(const std::string& name, bool tweak):
|
||||
// Register every new instance with LLEventPumps
|
||||
mName(LLEventPumps::instance().registerNew(*this, name, tweak)),
|
||||
mSignal(new LLStandardSignal()),
|
||||
mEnabled(true)
|
||||
{}
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
|
||||
LLEventPump::~LLEventPump()
|
||||
{
|
||||
// Unregister this doomed instance from LLEventPumps
|
||||
LLEventPumps::instance().unregister(*this);
|
||||
}
|
||||
|
||||
// static data member
|
||||
const LLEventPump::NameList LLEventPump::empty;
|
||||
|
||||
std::string LLEventPump::inventName(const std::string& pfx)
|
||||
{
|
||||
static long suffix = 0;
|
||||
return STRINGIZE(pfx << suffix++);
|
||||
}
|
||||
|
||||
void LLEventPump::reset()
|
||||
{
|
||||
mSignal.reset();
|
||||
mConnections.clear();
|
||||
//mDeps.clear();
|
||||
}
|
||||
|
||||
LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventListener& listener,
|
||||
const NameList& after,
|
||||
const NameList& before)
|
||||
{
|
||||
// Check for duplicate name before connecting listener to mSignal
|
||||
ConnectionMap::const_iterator found = mConnections.find(name);
|
||||
// In some cases the user might disconnect a connection explicitly -- or
|
||||
// might use LLEventTrackable to disconnect implicitly. Either way, we can
|
||||
// end up retaining in mConnections a zombie connection object that's
|
||||
// already been disconnected. Such a connection object can't be
|
||||
// reconnected -- nor, in the case of LLEventTrackable, would we want to
|
||||
// try, since disconnection happens with the destruction of the listener
|
||||
// object. That means it's safe to overwrite a disconnected connection
|
||||
// object with the new one we're attempting. The case we want to prevent
|
||||
// is only when the existing connection object is still connected.
|
||||
if (found != mConnections.end() && found->second.connected())
|
||||
{
|
||||
throw DupListenerName(std::string("Attempt to register duplicate listener name '") + name +
|
||||
"' on " + typeid(*this).name() + " '" + getName() + "'");
|
||||
}
|
||||
// Okay, name is unique, try to reconcile its dependencies. Specify a new
|
||||
// "node" value that we never use for an mSignal placement; we'll fix it
|
||||
// later.
|
||||
DependencyMap::node_type& newNode = mDeps.add(name, -1.0, after, before);
|
||||
// What if this listener has been added, removed and re-added? In that
|
||||
// case newNode already has a non-negative value because we never remove a
|
||||
// listener from mDeps. But keep processing uniformly anyway in case the
|
||||
// listener was added back with different dependencies. Then mDeps.sort()
|
||||
// would put it in a different position, and the old newNode placement
|
||||
// value would be wrong, so we'd have to reassign it anyway. Trust that
|
||||
// re-adding a listener with the same dependencies is the trivial case for
|
||||
// mDeps.sort(): it can just replay its cache.
|
||||
DependencyMap::sorted_range sorted_range;
|
||||
try
|
||||
{
|
||||
// Can we pick an order that works including this new entry?
|
||||
sorted_range = mDeps.sort();
|
||||
}
|
||||
catch (const DependencyMap::Cycle& e)
|
||||
{
|
||||
// No: the new node's after/before dependencies have made mDeps
|
||||
// unsortable. If we leave the new node in mDeps, it will continue
|
||||
// to screw up all future attempts to sort()! Pull it out.
|
||||
mDeps.remove(name);
|
||||
throw Cycle(std::string("New listener '") + name + "' on " + typeid(*this).name() +
|
||||
" '" + getName() + "' would cause cycle: " + e.what());
|
||||
}
|
||||
// Walk the list to verify that we haven't changed the order.
|
||||
float previous = 0.0, myprev = 0.0;
|
||||
DependencyMap::sorted_iterator mydmi = sorted_range.end(); // need this visible after loop
|
||||
for (DependencyMap::sorted_iterator dmi = sorted_range.begin();
|
||||
dmi != sorted_range.end(); ++dmi)
|
||||
{
|
||||
// Since we've added the new entry with an invalid placement,
|
||||
// recognize it and skip it.
|
||||
if (dmi->first == name)
|
||||
{
|
||||
// Remember the iterator belonging to our new node, and which
|
||||
// placement value was 'previous' at that point.
|
||||
mydmi = dmi;
|
||||
myprev = previous;
|
||||
continue;
|
||||
}
|
||||
// If the new node has rearranged the existing nodes, we'll find
|
||||
// that their placement values are no longer in increasing order.
|
||||
if (dmi->second < previous)
|
||||
{
|
||||
// This is another scenario in which we'd better back out the
|
||||
// newly-added node from mDeps -- but don't do it yet, we want to
|
||||
// traverse the existing mDeps to report on it!
|
||||
// Describe the change to the order of our listeners. Copy
|
||||
// everything but the newest listener to a vector we can sort to
|
||||
// obtain the old order.
|
||||
typedef std::vector< std::pair<float, std::string> > SortNameList;
|
||||
SortNameList sortnames;
|
||||
for (DependencyMap::sorted_iterator cdmi(sorted_range.begin()), cdmend(sorted_range.end());
|
||||
cdmi != cdmend; ++cdmi)
|
||||
{
|
||||
if (cdmi->first != name)
|
||||
{
|
||||
sortnames.push_back(SortNameList::value_type(cdmi->second, cdmi->first));
|
||||
}
|
||||
}
|
||||
std::sort(sortnames.begin(), sortnames.end());
|
||||
std::ostringstream out;
|
||||
out << "New listener '" << name << "' on " << typeid(*this).name() << " '" << getName()
|
||||
<< "' would move previous listener '" << dmi->first << "'\nwas: ";
|
||||
SortNameList::const_iterator sni(sortnames.begin()), snend(sortnames.end());
|
||||
if (sni != snend)
|
||||
{
|
||||
out << sni->second;
|
||||
while (++sni != snend)
|
||||
{
|
||||
out << ", " << sni->second;
|
||||
}
|
||||
}
|
||||
out << "\nnow: ";
|
||||
DependencyMap::sorted_iterator ddmi(sorted_range.begin()), ddmend(sorted_range.end());
|
||||
if (ddmi != ddmend)
|
||||
{
|
||||
out << ddmi->first;
|
||||
while (++ddmi != ddmend)
|
||||
{
|
||||
out << ", " << ddmi->first;
|
||||
}
|
||||
}
|
||||
// NOW remove the offending listener node.
|
||||
mDeps.remove(name);
|
||||
// Having constructed a description of the order change, inform caller.
|
||||
throw OrderChange(out.str());
|
||||
}
|
||||
// This node becomes the previous one.
|
||||
previous = dmi->second;
|
||||
}
|
||||
// We just got done with a successful mDeps.add(name, ...) call. We'd
|
||||
// better have found 'name' somewhere in that sorted list!
|
||||
assert(mydmi != sorted_range.end());
|
||||
// Four cases:
|
||||
// 0. name is the only entry: placement 1.0
|
||||
// 1. name is the first of several entries: placement (next placement)/2
|
||||
// 2. name is between two other entries: placement (myprev + (next placement))/2
|
||||
// 3. name is the last entry: placement ceil(myprev) + 1.0
|
||||
// Since we've cleverly arranged for myprev to be 0.0 if name is the
|
||||
// first entry, this folds down to two cases. Case 1 is subsumed by
|
||||
// case 2, and case 0 is subsumed by case 3. So we need only handle
|
||||
// cases 2 and 3, which means we need only detect whether name is the
|
||||
// last entry. Increment mydmi to see if there's anything beyond.
|
||||
if (++mydmi != sorted_range.end())
|
||||
{
|
||||
// The new node isn't last. Place it between the previous node and
|
||||
// the successor.
|
||||
newNode = (myprev + mydmi->second)/2.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The new node is last. Bump myprev up to the next integer, add
|
||||
// 1.0 and use that.
|
||||
newNode = std::ceil(myprev) + 1.0;
|
||||
}
|
||||
// Now that newNode has a value that places it appropriately in mSignal,
|
||||
// connect it.
|
||||
LLBoundListener bound = mSignal->connect(newNode, listener);
|
||||
mConnections[name] = bound;
|
||||
return bound;
|
||||
}
|
||||
|
||||
LLBoundListener LLEventPump::getListener(const std::string& name) const
|
||||
{
|
||||
ConnectionMap::const_iterator found = mConnections.find(name);
|
||||
if (found != mConnections.end())
|
||||
{
|
||||
return found->second;
|
||||
}
|
||||
// not found, return dummy LLBoundListener
|
||||
return LLBoundListener();
|
||||
}
|
||||
|
||||
void LLEventPump::stopListening(const std::string& name)
|
||||
{
|
||||
ConnectionMap::iterator found = mConnections.find(name);
|
||||
if (found != mConnections.end())
|
||||
{
|
||||
found->second.disconnect();
|
||||
mConnections.erase(found);
|
||||
}
|
||||
// We intentionally do NOT remove this name from mDeps. It may happen that
|
||||
// the same listener with the same name and dependencies will jump on and
|
||||
// off this LLEventPump repeatedly. Keeping a cache of dependencies will
|
||||
// avoid a new dependency sort in such cases.
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* LLEventStream
|
||||
*****************************************************************************/
|
||||
bool LLEventStream::post(const LLSD& event)
|
||||
{
|
||||
if (! mEnabled || !mSignal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// NOTE NOTE NOTE: Any new access to member data beyond this point should
|
||||
// cause us to move our LLStandardSignal object to a pimpl class along
|
||||
// with said member data. Then the local shared_ptr will preserve both.
|
||||
|
||||
// DEV-43463: capture a local copy of mSignal. We've turned up a
|
||||
// cross-coroutine scenario (described in the Jira) in which this post()
|
||||
// call could end up destroying 'this', the LLEventPump subclass instance
|
||||
// containing mSignal, during the call through *mSignal. So -- capture a
|
||||
// *stack* instance of the shared_ptr, ensuring that our heap
|
||||
// LLStandardSignal object will live at least until post() returns, even
|
||||
// if 'this' gets destroyed during the call.
|
||||
boost::shared_ptr<LLStandardSignal> signal(mSignal);
|
||||
// Let caller know if any one listener handled the event. This is mostly
|
||||
// useful when using LLEventStream as a listener for an upstream
|
||||
// LLEventPump.
|
||||
return (*signal)(event);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* LLEventQueue
|
||||
*****************************************************************************/
|
||||
bool LLEventQueue::post(const LLSD& event)
|
||||
{
|
||||
if (mEnabled)
|
||||
{
|
||||
// Defer sending this event by queueing it until flush()
|
||||
mEventQueue.push_back(event);
|
||||
}
|
||||
// Unconditionally return false. We won't know until flush() whether a
|
||||
// listener claims to have handled the event -- meanwhile, don't block
|
||||
// other listeners.
|
||||
return false;
|
||||
}
|
||||
|
||||
void LLEventQueue::flush()
|
||||
{
|
||||
if(!mSignal) return;
|
||||
|
||||
// Consider the case when a given listener on this LLEventQueue posts yet
|
||||
// another event on the same queue. If we loop over mEventQueue directly,
|
||||
// we'll end up processing all those events during the same flush() call
|
||||
// -- rather like an EventStream. Instead, copy mEventQueue and clear it,
|
||||
// so that any new events posted to this LLEventQueue during flush() will
|
||||
// be processed in the *next* flush() call.
|
||||
EventQueue queue(mEventQueue);
|
||||
mEventQueue.clear();
|
||||
// NOTE NOTE NOTE: Any new access to member data beyond this point should
|
||||
// cause us to move our LLStandardSignal object to a pimpl class along
|
||||
// with said member data. Then the local shared_ptr will preserve both.
|
||||
|
||||
// DEV-43463: capture a local copy of mSignal. See LLEventStream::post()
|
||||
// for detailed comments.
|
||||
boost::shared_ptr<LLStandardSignal> signal(mSignal);
|
||||
for ( ; ! queue.empty(); queue.pop_front())
|
||||
{
|
||||
(*signal)(queue.front());
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* LLListenerOrPumpName
|
||||
*****************************************************************************/
|
||||
LLListenerOrPumpName::LLListenerOrPumpName(const std::string& pumpname):
|
||||
// Look up the specified pumpname, and bind its post() method as our listener
|
||||
mListener(boost::bind(&LLEventPump::post,
|
||||
boost::ref(LLEventPumps::instance().obtain(pumpname)),
|
||||
_1))
|
||||
{
|
||||
}
|
||||
|
||||
LLListenerOrPumpName::LLListenerOrPumpName(const char* pumpname):
|
||||
// Look up the specified pumpname, and bind its post() method as our listener
|
||||
mListener(boost::bind(&LLEventPump::post,
|
||||
boost::ref(LLEventPumps::instance().obtain(pumpname)),
|
||||
_1))
|
||||
{
|
||||
}
|
||||
|
||||
bool LLListenerOrPumpName::operator()(const LLSD& event) const
|
||||
{
|
||||
if (! mListener)
|
||||
{
|
||||
throw Empty("attempting to call uninitialized");
|
||||
}
|
||||
return (*mListener)(event);
|
||||
}
|
||||
|
||||
void LLReqID::stamp(LLSD& response) const
|
||||
{
|
||||
if (! (response.isUndefined() || response.isMap()))
|
||||
{
|
||||
// If 'response' was previously completely empty, it's okay to
|
||||
// turn it into a map. If it was already a map, then it should be
|
||||
// okay to add a key. But if it was anything else (e.g. a scalar),
|
||||
// assigning a ["reqid"] key will DISCARD the previous value,
|
||||
// replacing it with a map. That would be Bad.
|
||||
LL_INFOS("LLReqID") << "stamp(" << mReqid << ") leaving non-map response unmodified: "
|
||||
<< response << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
LLSD oldReqid(response["reqid"]);
|
||||
if (! (oldReqid.isUndefined() || llsd_equals(oldReqid, mReqid)))
|
||||
{
|
||||
LL_INFOS("LLReqID") << "stamp(" << mReqid << ") preserving existing [\"reqid\"] value "
|
||||
<< oldReqid << " in response: " << response << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
response["reqid"] = mReqid;
|
||||
}
|
||||
|
||||
bool sendReply(const LLSD& reply, const LLSD& request, const std::string& replyKey)
|
||||
{
|
||||
// If the original request has no value for replyKey, it's pointless to
|
||||
// construct or send a reply event: on which LLEventPump should we send
|
||||
// it? Allow that to be optional: if the caller wants to require replyKey,
|
||||
// it can so specify when registering the operation method.
|
||||
if (! request.has(replyKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Here the request definitely contains replyKey; reasonable to proceed.
|
||||
|
||||
// Copy 'reply' to modify it.
|
||||
LLSD newreply(reply);
|
||||
// Get the ["reqid"] element from request
|
||||
LLReqID reqID(request);
|
||||
// and copy it to 'newreply'.
|
||||
reqID.stamp(newreply);
|
||||
// Send reply on LLEventPump named in request[replyKey]. Don't forget to
|
||||
// send the modified 'newreply' instead of the original 'reply'.
|
||||
return LLEventPumps::instance().obtain(request[replyKey]).post(newreply);
|
||||
}
|
||||
1049
indra/llcommon/llevents.h
Normal file
1049
indra/llcommon/llevents.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -65,6 +65,7 @@ set(llmessage_SOURCE_FILES
|
||||
llregionpresenceverifier.cpp
|
||||
llsdappservices.cpp
|
||||
llsdhttpserver.cpp
|
||||
llsdmessage.cpp
|
||||
llsdmessagebuilder.cpp
|
||||
llsdmessagereader.cpp
|
||||
llsdrpcclient.cpp
|
||||
@@ -163,6 +164,7 @@ set(llmessage_HEADER_FILES
|
||||
llregionpresenceverifier.h
|
||||
llsdappservices.h
|
||||
llsdhttpserver.h
|
||||
llsdmessage.h
|
||||
llsdmessagebuilder.h
|
||||
llsdmessagereader.h
|
||||
llsdrpcclient.h
|
||||
|
||||
170
indra/llmessage/llsdmessage.cpp
Normal file
170
indra/llmessage/llsdmessage.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* @file llsdmessage.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2008-10-31
|
||||
* @brief Implementation for llsdmessage.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&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 LL_WINDOWS
|
||||
#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want!
|
||||
#endif
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llsdmessage.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llevents.h"
|
||||
#include "llsdserialize.h"
|
||||
#include "llhttpclient.h"
|
||||
#include "llmessageconfig.h"
|
||||
#include "llhost.h"
|
||||
#include "message.h"
|
||||
#include "llsdutil.h"
|
||||
|
||||
// Declare a static LLSDMessage instance to ensure that we have a listener as
|
||||
// soon as someone tries to post on our canonical LLEventPump name.
|
||||
static LLSDMessage httpListener;
|
||||
|
||||
LLSDMessage::LLSDMessage():
|
||||
// Instantiating our own local LLEventPump with a string name the
|
||||
// constructor is NOT allowed to tweak is a way of ensuring Singleton
|
||||
// semantics: attempting to instantiate a second LLSDMessage object would
|
||||
// throw LLEventPump::DupPumpName.
|
||||
mEventPump("LLHTTPClient")
|
||||
{
|
||||
mEventPump.listen("self", boost::bind(&LLSDMessage::httpListener, this, _1));
|
||||
}
|
||||
|
||||
bool LLSDMessage::httpListener(const LLSD& request)
|
||||
{
|
||||
// Extract what we want from the request object. We do it all up front
|
||||
// partly to document what we expect.
|
||||
LLSD::String url(request["url"]);
|
||||
LLSD payload(request["payload"]);
|
||||
LLSD::String reply(request["reply"]);
|
||||
LLSD::String error(request["error"]);
|
||||
LLSD::Real timeout(request["timeout"]);
|
||||
// If the LLSD doesn't even have a "url" key, we doubt it was intended for
|
||||
// this listener.
|
||||
if (url.empty())
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "request event without 'url' key to '" << mEventPump.getName() << "'";
|
||||
throw ArgError(out.str());
|
||||
}
|
||||
// Establish default timeout. This test relies on LLSD::asReal() returning
|
||||
// exactly 0.0 for an undef value.
|
||||
if (! timeout)
|
||||
{
|
||||
timeout = HTTP_REQUEST_EXPIRY_SECS;
|
||||
}
|
||||
LLHTTPClient::post(url, payload,
|
||||
new LLSDMessage::EventResponder(LLEventPumps::instance(),
|
||||
request,
|
||||
url, "POST", reply, error),
|
||||
LLSD(), // headers
|
||||
timeout);
|
||||
return false;
|
||||
}
|
||||
|
||||
void LLSDMessage::EventResponder::result(const LLSD& data)
|
||||
{
|
||||
// If our caller passed an empty replyPump name, they're not
|
||||
// listening: this is a fire-and-forget message. Don't bother posting
|
||||
// to the pump whose name is "".
|
||||
if (! mReplyPump.empty())
|
||||
{
|
||||
LLSD response(data);
|
||||
mReqID.stamp(response);
|
||||
mPumps.obtain(mReplyPump).post(response);
|
||||
}
|
||||
else // default success handling
|
||||
{
|
||||
LL_INFOS("LLSDMessage::EventResponder")
|
||||
<< "'" << mMessage << "' to '" << mTarget << "' succeeded"
|
||||
<< LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
void LLSDMessage::EventResponder::errorWithContent(U32 status, const std::string& reason, const LLSD& content)
|
||||
{
|
||||
// If our caller passed an empty errorPump name, they're not
|
||||
// listening: "default error handling is acceptable." Only post to an
|
||||
// explicit pump name.
|
||||
if (! mErrorPump.empty())
|
||||
{
|
||||
LLSD info(mReqID.makeResponse());
|
||||
info["target"] = mTarget;
|
||||
info["message"] = mMessage;
|
||||
info["status"] = LLSD::Integer(status);
|
||||
info["reason"] = reason;
|
||||
info["content"] = content;
|
||||
mPumps.obtain(mErrorPump).post(info);
|
||||
}
|
||||
else // default error handling
|
||||
{
|
||||
// convention seems to be to use llinfos, but that seems a bit casual?
|
||||
LL_WARNS("LLSDMessage::EventResponder")
|
||||
<< "'" << mMessage << "' to '" << mTarget
|
||||
<< "' failed with code " << status << ": " << reason << '\n'
|
||||
<< ll_pretty_print_sd(content)
|
||||
<< LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
LLSDMessage::ResponderAdapter::ResponderAdapter(LLHTTPClient::ResponderPtr responder,
|
||||
const std::string& name):
|
||||
mResponder(responder),
|
||||
mReplyPump(name + ".reply", true), // tweak name for uniqueness
|
||||
mErrorPump(name + ".error", true)
|
||||
{
|
||||
mReplyPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, true));
|
||||
mErrorPump.listen("self", boost::bind(&ResponderAdapter::listener, this, _1, false));
|
||||
}
|
||||
|
||||
bool LLSDMessage::ResponderAdapter::listener(const LLSD& payload, bool success)
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
mResponder->result(payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
mResponder->errorWithContent(payload["status"].asInteger(), payload["reason"], payload["content"]);
|
||||
}
|
||||
|
||||
/*---------------- MUST BE LAST STATEMENT BEFORE RETURN ----------------*/
|
||||
delete this;
|
||||
// Destruction of mResponder will usually implicitly free its referent as well
|
||||
/*------------------------- NOTHING AFTER THIS -------------------------*/
|
||||
return false;
|
||||
}
|
||||
|
||||
void LLSDMessage::link()
|
||||
{
|
||||
}
|
||||
166
indra/llmessage/llsdmessage.h
Normal file
166
indra/llmessage/llsdmessage.h
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @file llsdmessage.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2008-10-30
|
||||
* @brief API intended to unify sending capability, UDP and TCP messages:
|
||||
* https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&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_LLSDMESSAGE_H)
|
||||
#define LL_LLSDMESSAGE_H
|
||||
|
||||
#include "llerror.h" // LOG_CLASS()
|
||||
#include "llevents.h" // LLEventPumps
|
||||
#include "llhttpclient.h"
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
class LLSD;
|
||||
|
||||
/**
|
||||
* Class managing the messaging API described in
|
||||
* https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes
|
||||
*/
|
||||
class LLSDMessage
|
||||
{
|
||||
LOG_CLASS(LLSDMessage);
|
||||
|
||||
public:
|
||||
LLSDMessage();
|
||||
|
||||
/// Exception if you specify arguments badly
|
||||
struct ArgError: public std::runtime_error
|
||||
{
|
||||
ArgError(const std::string& what):
|
||||
std::runtime_error(std::string("ArgError: ") + what) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* The response idiom used by LLSDMessage -- LLEventPump names on which to
|
||||
* post reply or error -- is designed for the case in which your
|
||||
* reply/error handlers are methods on the same class as the method
|
||||
* sending the message. Any state available to the sending method that
|
||||
* must be visible to the reply/error methods can conveniently be stored
|
||||
* on that class itself, if it's not already.
|
||||
*
|
||||
* The LLHTTPClient::Responder idiom requires a separate instance of a
|
||||
* separate class so that it can dispatch to the code of interest by
|
||||
* calling canonical virtual methods. Interesting state must be copied
|
||||
* into that new object.
|
||||
*
|
||||
* With some trepidation, because existing response code is packaged in
|
||||
* LLHTTPClient::Responder subclasses, we provide this adapter class
|
||||
* <i>for transitional purposes only.</i> Instantiate a new heap
|
||||
* ResponderAdapter with your new LLHTTPClient::ResponderPtr. Pass
|
||||
* ResponderAdapter::getReplyName() and/or getErrorName() in your
|
||||
* LLSDMessage (or LLViewerRegion::getCapAPI()) request event. The
|
||||
* ResponderAdapter will call the appropriate Responder method, then
|
||||
* @c delete itself.
|
||||
*/
|
||||
class ResponderAdapter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Bind the new LLHTTPClient::Responder subclass instance.
|
||||
*
|
||||
* Passing the constructor a name other than the default is only
|
||||
* interesting if you suspect some usage will lead to an exception or
|
||||
* log message.
|
||||
*/
|
||||
ResponderAdapter(LLHTTPClient::ResponderPtr responder,
|
||||
const std::string& name="ResponderAdapter");
|
||||
|
||||
/// EventPump name on which LLSDMessage should post reply event
|
||||
std::string getReplyName() const { return mReplyPump.getName(); }
|
||||
/// EventPump name on which LLSDMessage should post error event
|
||||
std::string getErrorName() const { return mErrorPump.getName(); }
|
||||
|
||||
private:
|
||||
// We have two different LLEventStreams, though we route them both to
|
||||
// the same listener, so that we can bind an extra flag identifying
|
||||
// which case (reply or error) reached that listener.
|
||||
bool listener(const LLSD&, bool success);
|
||||
|
||||
LLHTTPClient::ResponderPtr mResponder;
|
||||
LLEventStream mReplyPump, mErrorPump;
|
||||
};
|
||||
|
||||
/**
|
||||
* Force our implementation file to be linked with caller. The .cpp file
|
||||
* contains a static instance of this class, which must be linked into the
|
||||
* executable to support the canonical listener. But since the primary
|
||||
* interface to that static instance is via a named LLEventPump rather
|
||||
* than by direct reference, the linker doesn't necessarily perceive the
|
||||
* necessity to bring in the translation unit. Referencing this dummy
|
||||
* method forces the issue.
|
||||
*/
|
||||
static void link();
|
||||
|
||||
private:
|
||||
friend class LLCapabilityListener;
|
||||
/// Responder used for internal purposes by LLSDMessage and
|
||||
/// LLCapabilityListener. Others should use higher-level APIs.
|
||||
class EventResponder: public LLHTTPClient::Responder
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* LLHTTPClient::Responder that dispatches via named LLEventPump instances.
|
||||
* We bind LLEventPumps, even though it's an LLSingleton, for testability.
|
||||
* We bind the string names of the desired LLEventPump instances rather
|
||||
* than actually obtain()ing them so we only obtain() the one we're going
|
||||
* to use. If the caller doesn't bother to listen() on it, the other pump
|
||||
* may never materialize at all.
|
||||
* @a target and @a message are only to clarify error processing.
|
||||
* For a capability message, @a target should be the region description,
|
||||
* @a message should be the capability name.
|
||||
* For a service with a visible URL, pass the URL as @a target and the HTTP verb
|
||||
* (e.g. "POST") as @a message.
|
||||
*/
|
||||
EventResponder(LLEventPumps& pumps,
|
||||
const LLSD& request,
|
||||
const std::string& target, const std::string& message,
|
||||
const std::string& replyPump, const std::string& errorPump):
|
||||
mPumps(pumps),
|
||||
mReqID(request),
|
||||
mTarget(target),
|
||||
mMessage(message),
|
||||
mReplyPump(replyPump),
|
||||
mErrorPump(errorPump)
|
||||
{}
|
||||
|
||||
virtual void result(const LLSD& data);
|
||||
virtual void errorWithContent(U32 status, const std::string& reason, const LLSD& content);
|
||||
|
||||
private:
|
||||
LLEventPumps& mPumps;
|
||||
LLReqID mReqID;
|
||||
const std::string mTarget, mMessage, mReplyPump, mErrorPump;
|
||||
};
|
||||
|
||||
private:
|
||||
bool httpListener(const LLSD&);
|
||||
LLEventStream mEventPump;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLSDMESSAGE_H) */
|
||||
@@ -62,7 +62,7 @@
|
||||
#include "llevent.h"
|
||||
|
||||
template <class T>
|
||||
class LLMemberListener : public LLSimpleListener
|
||||
class LLMemberListener : public LLOldEvents::LLSimpleListener
|
||||
{
|
||||
public:
|
||||
LLMemberListener() : mPtr(NULL), mRegisteredName() { }
|
||||
@@ -75,7 +75,7 @@ public:
|
||||
}
|
||||
|
||||
// This is what you have to override to handle this event
|
||||
virtual bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) = 0;
|
||||
virtual bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata) = 0;
|
||||
|
||||
protected:
|
||||
T *mPtr; // The object that this listener manipulates
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
#include <set>
|
||||
#include <boost/tokenizer.hpp>
|
||||
|
||||
using namespace LLOldEvents;
|
||||
|
||||
// static
|
||||
LLMenuHolderGL *LLMenuGL::sMenuContainer = NULL;
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ private:
|
||||
// calls a user defined callback.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
class LLMenuItemCallGL : public LLMenuItemGL, public LLObservable
|
||||
class LLMenuItemCallGL : public LLMenuItemGL, public LLOldEvents::LLObservable
|
||||
{
|
||||
public:
|
||||
// normal constructor
|
||||
|
||||
@@ -100,43 +100,12 @@
|
||||
#include "llinstancetracker.h"
|
||||
|
||||
// and we need this to manage the notification callbacks
|
||||
#include "llevents.h"
|
||||
#include "llfunctorregistry.h"
|
||||
#include "llui.h"
|
||||
#include "llxmlnode.h"
|
||||
#include "llnotificationptr.h"
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* Signal and handler declarations
|
||||
* Using a single handler signature means that we can have a common handler
|
||||
* type, rather than needing a distinct one for each different handler.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* A boost::signals2 Combiner that stops the first time a handler returns true
|
||||
* We need this because we want to have our handlers return bool, so that
|
||||
* we have the option to cause a handler to stop further processing. The
|
||||
* default handler fails when the signal returns a value but has no slots.
|
||||
*/
|
||||
struct LLStopWhenHandled
|
||||
{
|
||||
typedef bool result_type;
|
||||
|
||||
template<typename InputIterator>
|
||||
result_type operator()(InputIterator first, InputIterator last) const
|
||||
{
|
||||
for (InputIterator si = first; si != last; ++si)
|
||||
{
|
||||
if (*si)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
typedef enum e_notification_priority
|
||||
{
|
||||
NOTIFICATION_PRIORITY_UNSPECIFIED,
|
||||
@@ -146,27 +115,11 @@ typedef enum e_notification_priority
|
||||
NOTIFICATION_PRIORITY_CRITICAL
|
||||
} ENotificationPriority;
|
||||
|
||||
/**
|
||||
* We want to have a standard signature for all signals; this way,
|
||||
* we can easily document a protocol for communicating across
|
||||
* dlls and into scripting languages someday.
|
||||
* we want to return a bool to indicate whether the signal has been
|
||||
* handled and should NOT be passed on to other listeners.
|
||||
* Return true to stop further handling of the signal, and false
|
||||
* to continue.
|
||||
* We take an LLSD because this way the contents of the signal
|
||||
* are independent of the API used to communicate it.
|
||||
* It is const ref because then there's low cost to pass it;
|
||||
* if you only need to inspect it, it's very cheap.
|
||||
*/
|
||||
|
||||
typedef boost::function<void (const LLSD&, const LLSD&)> LLNotificationResponder;
|
||||
|
||||
typedef LLFunctorRegistry<LLNotificationResponder> LLNotificationFunctorRegistry;
|
||||
typedef LLFunctorRegistration<LLNotificationResponder> LLNotificationFunctorRegistration;
|
||||
|
||||
typedef boost::signals2::signal<bool(const LLSD&), LLStopWhenHandled> LLStandardSignal;
|
||||
|
||||
// context data that can be looked up via a notification's payload by the display logic
|
||||
// derive from this class to implement specific contexts
|
||||
class LLNotificationContext : public LLInstanceTracker<LLNotificationContext, LLUUID>
|
||||
|
||||
@@ -63,6 +63,8 @@
|
||||
#include "lldelayeduidelete.h"
|
||||
// </edit>
|
||||
|
||||
using namespace LLOldEvents;
|
||||
|
||||
//HACK: this allows you to instantiate LLView from xml with "<view/>" which we don't want
|
||||
static LLRegisterWidget<LLView> r("view");
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ virtual void setControlName(const std::string& control, LLView *context);
|
||||
LLSliderCtrl, LLCheckBoxCtrl
|
||||
virtual std::string getControlName() const { return mControlName; }
|
||||
LLSliderCtrl, LLCheckBoxCtrl
|
||||
virtual bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
virtual bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
LLMenuItem
|
||||
virtual void setValue(const LLSD& value);
|
||||
*
|
||||
@@ -443,15 +443,15 @@ public:
|
||||
void localRectToScreen( const LLRect& local, LLRect* screen ) const;
|
||||
|
||||
// Listener dispatching functions (Dispatcher deletes pointers to listeners on deregistration or destruction)
|
||||
LLSimpleListener* getListenerByName(const std::string& callback_name);
|
||||
void registerEventListener(std::string name, LLSimpleListener* function);
|
||||
LLOldEvents::LLSimpleListener* getListenerByName(const std::string& callback_name);
|
||||
void registerEventListener(std::string name, LLOldEvents::LLSimpleListener* function);
|
||||
void deregisterEventListener(std::string name);
|
||||
|
||||
typedef boost::signals2::signal<void (LLPointer<LLEvent> event, const LLSD& userdata)> event_signal_t;
|
||||
typedef boost::signals2::signal<void (LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata)> event_signal_t;
|
||||
void registerEventListener(std::string name, event_signal_t::slot_type &cb);
|
||||
|
||||
std::string findEventListener(LLSimpleListener *listener) const;
|
||||
void addListenerToControl(LLEventDispatcher *observer, const std::string& name, LLSD filter, LLSD userdata);
|
||||
std::string findEventListener(LLOldEvents::LLSimpleListener *listener) const;
|
||||
void addListenerToControl(LLOldEvents::LLEventDispatcher *observer, const std::string& name, LLSD filter, LLSD userdata);
|
||||
|
||||
void addBoolControl(const std::string& name, bool initial_value);
|
||||
LLControlVariable *getControl(const std::string& name);
|
||||
@@ -460,7 +460,7 @@ public:
|
||||
bool setControlValue(const LLSD& value);
|
||||
virtual void setControlName(const std::string& control, LLView *context);
|
||||
virtual std::string getControlName() const { return mControlName; }
|
||||
// virtual bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
// virtual bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
virtual void setValue(const LLSD& value);
|
||||
virtual LLSD getValue() const;
|
||||
|
||||
@@ -663,7 +663,7 @@ private:
|
||||
|
||||
static LLWindow* sWindow; // All root views must know about their window.
|
||||
|
||||
typedef std::map<std::string, LLPointer<LLSimpleListener> > dispatch_list_t;
|
||||
typedef std::map<std::string, LLPointer<LLOldEvents::LLSimpleListener> > dispatch_list_t;
|
||||
dispatch_list_t mDispatchList;
|
||||
|
||||
std::string mControlName;
|
||||
|
||||
@@ -134,6 +134,7 @@ set(viewer_SOURCE_FILES
|
||||
llbuildnewviewsscheduler.cpp
|
||||
llcallbacklist.cpp
|
||||
llcallingcard.cpp
|
||||
llcapabilitylistener.cpp
|
||||
llcaphttpsender.cpp
|
||||
llchatbar.cpp
|
||||
llclassifiedinfo.cpp
|
||||
@@ -492,6 +493,7 @@ set(viewer_SOURCE_FILES
|
||||
llviewerregion.cpp
|
||||
llviewershadermgr.cpp
|
||||
llviewerstats.cpp
|
||||
llviewerstatsrecorder.cpp
|
||||
llviewertexteditor.cpp
|
||||
llviewertexture.cpp
|
||||
llviewertextureanim.cpp
|
||||
@@ -620,6 +622,7 @@ set(viewer_HEADER_FILES
|
||||
llbuildnewviewsscheduler.h
|
||||
llcallbacklist.h
|
||||
llcallingcard.h
|
||||
llcapabilitylistener.h
|
||||
llcaphttpsender.h
|
||||
llchatbar.h
|
||||
llclassifiedinfo.h
|
||||
@@ -983,6 +986,7 @@ set(viewer_HEADER_FILES
|
||||
llviewerregion.h
|
||||
llviewershadermgr.h
|
||||
llviewerstats.h
|
||||
llviewerstatsrecorder.h
|
||||
llviewertexteditor.h
|
||||
llviewertexture.h
|
||||
llviewertextureanim.h
|
||||
|
||||
@@ -2818,7 +2818,7 @@ void update_group_floaters(const LLUUID& group_id)
|
||||
gIMMgr->refresh();
|
||||
}
|
||||
|
||||
gAgent.fireEvent(new LLEvent(&gAgent, "new group"), "");
|
||||
gAgent.fireEvent(new LLOldEvents::LLEvent(&gAgent, "new group"), "");
|
||||
}
|
||||
|
||||
// static
|
||||
|
||||
@@ -92,7 +92,7 @@ struct LLGroupData
|
||||
|
||||
//
|
||||
|
||||
class LLAgent : public LLObservable
|
||||
class LLAgent : public LLOldEvents::LLObservable
|
||||
{
|
||||
LOG_CLASS(LLAgent);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
#include "llmemory.h" // LLSingleton<>
|
||||
#include "llevent.h"
|
||||
|
||||
class LLAgentLanguage: public LLSingleton<LLAgentLanguage>, public LLSimpleListener
|
||||
class LLAgentLanguage: public LLSingleton<LLAgentLanguage>, public LLOldEvents::LLSimpleListener
|
||||
{
|
||||
public:
|
||||
LLAgentLanguage();
|
||||
|
||||
202
indra/newview/llcapabilitylistener.cpp
Normal file
202
indra/newview/llcapabilitylistener.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* @file llcapabilitylistener.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-01-07
|
||||
* @brief Implementation for llcapabilitylistener.
|
||||
*
|
||||
* $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$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
// associated header
|
||||
#include "llcapabilitylistener.h"
|
||||
// STL headers
|
||||
#include <map>
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/bind.hpp>
|
||||
// other Linden headers
|
||||
#include "stringize.h"
|
||||
#include "llcapabilityprovider.h"
|
||||
#include "message.h"
|
||||
|
||||
class LLCapabilityListener::CapabilityMappers: public LLSingleton<LLCapabilityListener::CapabilityMappers>
|
||||
{
|
||||
public:
|
||||
void registerMapper(const LLCapabilityListener::CapabilityMapper*);
|
||||
void unregisterMapper(const LLCapabilityListener::CapabilityMapper*);
|
||||
const LLCapabilityListener::CapabilityMapper* find(const std::string& cap) const;
|
||||
|
||||
struct DupCapMapper: public std::runtime_error
|
||||
{
|
||||
DupCapMapper(const std::string& what):
|
||||
std::runtime_error(std::string("DupCapMapper: ") + what)
|
||||
{}
|
||||
};
|
||||
|
||||
private:
|
||||
friend class LLSingleton<LLCapabilityListener::CapabilityMappers>;
|
||||
CapabilityMappers();
|
||||
|
||||
typedef std::map<std::string, const LLCapabilityListener::CapabilityMapper*> CapabilityMap;
|
||||
CapabilityMap mMap;
|
||||
};
|
||||
|
||||
LLCapabilityListener::LLCapabilityListener(const std::string& name,
|
||||
LLMessageSystem* messageSystem,
|
||||
const LLCapabilityProvider& provider,
|
||||
const LLUUID& agentID,
|
||||
const LLUUID& sessionID):
|
||||
mEventPump(name),
|
||||
mMessageSystem(messageSystem),
|
||||
mProvider(provider),
|
||||
mAgentID(agentID),
|
||||
mSessionID(sessionID)
|
||||
{
|
||||
mEventPump.listen("self", boost::bind(&LLCapabilityListener::capListener, this, _1));
|
||||
}
|
||||
|
||||
bool LLCapabilityListener::capListener(const LLSD& request)
|
||||
{
|
||||
// Extract what we want from the request object. We do it all up front
|
||||
// partly to document what we expect.
|
||||
LLSD::String cap(request["message"]);
|
||||
LLSD payload(request["payload"]);
|
||||
LLSD::String reply(request["reply"]);
|
||||
LLSD::String error(request["error"]);
|
||||
LLSD::Real timeout(request["timeout"]);
|
||||
// If the LLSD doesn't even have a "message" key, we doubt it was intended
|
||||
// for this listener.
|
||||
if (cap.empty())
|
||||
{
|
||||
LL_ERRS("capListener") << "capability request event without 'message' key to '"
|
||||
<< getCapAPI().getName()
|
||||
<< "' on region\n" << mProvider.getDescription()
|
||||
<< LL_ENDL;
|
||||
return false; // in case fatal-error function isn't
|
||||
}
|
||||
// Establish default timeout. This test relies on LLSD::asReal() returning
|
||||
// exactly 0.0 for an undef value.
|
||||
if (! timeout)
|
||||
{
|
||||
timeout = HTTP_REQUEST_EXPIRY_SECS;
|
||||
}
|
||||
// Look up the url for the requested capability name.
|
||||
std::string url = mProvider.getCapability(cap);
|
||||
if (! url.empty())
|
||||
{
|
||||
// This capability is supported by the region to which we're talking.
|
||||
LLHTTPClient::post(url, payload,
|
||||
new LLSDMessage::EventResponder(LLEventPumps::instance(),
|
||||
request,
|
||||
mProvider.getDescription(),
|
||||
cap, reply, error),
|
||||
LLSD(), // headers
|
||||
timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Capability not supported -- do we have a registered mapper?
|
||||
const CapabilityMapper* mapper = CapabilityMappers::instance().find(cap);
|
||||
if (! mapper) // capability neither supported nor mapped
|
||||
{
|
||||
LL_ERRS("capListener") << "unsupported capability '" << cap << "' request to '"
|
||||
<< getCapAPI().getName() << "' on region\n"
|
||||
<< mProvider.getDescription()
|
||||
<< LL_ENDL;
|
||||
}
|
||||
else if (! mapper->getReplyName().empty()) // mapper expects reply support
|
||||
{
|
||||
LL_ERRS("capListener") << "Mapper for capability '" << cap
|
||||
<< "' requires unimplemented support for reply message '"
|
||||
<< mapper->getReplyName()
|
||||
<< "' on '" << getCapAPI().getName() << "' on region\n"
|
||||
<< mProvider.getDescription()
|
||||
<< LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_INFOS("capListener") << "fallback invoked for capability '" << cap
|
||||
<< "' request to '" << getCapAPI().getName()
|
||||
<< "' on region\n" << mProvider.getDescription()
|
||||
<< LL_ENDL;
|
||||
mapper->buildMessage(mMessageSystem, mAgentID, mSessionID, cap, payload);
|
||||
mMessageSystem->sendReliable(mProvider.getHost());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LLCapabilityListener::CapabilityMapper::CapabilityMapper(const std::string& cap, const std::string& reply):
|
||||
mCapName(cap),
|
||||
mReplyName(reply)
|
||||
{
|
||||
LLCapabilityListener::CapabilityMappers::instance().registerMapper(this);
|
||||
}
|
||||
|
||||
LLCapabilityListener::CapabilityMapper::~CapabilityMapper()
|
||||
{
|
||||
LLCapabilityListener::CapabilityMappers::instance().unregisterMapper(this);
|
||||
}
|
||||
|
||||
LLSD LLCapabilityListener::CapabilityMapper::readResponse(LLMessageSystem* messageSystem) const
|
||||
{
|
||||
return LLSD();
|
||||
}
|
||||
|
||||
LLCapabilityListener::CapabilityMappers::CapabilityMappers() {}
|
||||
|
||||
void LLCapabilityListener::CapabilityMappers::registerMapper(const LLCapabilityListener::CapabilityMapper* mapper)
|
||||
{
|
||||
// Try to insert a new map entry by which we can look up the passed mapper
|
||||
// instance.
|
||||
std::pair<CapabilityMap::iterator, bool> inserted =
|
||||
mMap.insert(CapabilityMap::value_type(mapper->getCapName(), mapper));
|
||||
// If we already have a mapper for that name, insert() merely located the
|
||||
// existing iterator and returned false. It is a coding error to try to
|
||||
// register more than one mapper for the same capability name.
|
||||
if (! inserted.second)
|
||||
{
|
||||
throw DupCapMapper(std::string("Duplicate capability name ") + mapper->getCapName());
|
||||
}
|
||||
}
|
||||
|
||||
void LLCapabilityListener::CapabilityMappers::unregisterMapper(const LLCapabilityListener::CapabilityMapper* mapper)
|
||||
{
|
||||
CapabilityMap::iterator found = mMap.find(mapper->getCapName());
|
||||
if (found != mMap.end())
|
||||
{
|
||||
mMap.erase(found);
|
||||
}
|
||||
}
|
||||
|
||||
const LLCapabilityListener::CapabilityMapper*
|
||||
LLCapabilityListener::CapabilityMappers::find(const std::string& cap) const
|
||||
{
|
||||
CapabilityMap::const_iterator found = mMap.find(cap);
|
||||
if (found != mMap.end())
|
||||
{
|
||||
return found->second;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
131
indra/newview/llcapabilitylistener.h
Normal file
131
indra/newview/llcapabilitylistener.h
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* @file llcapabilitylistener.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2009-01-07
|
||||
* @brief Provide an event-based API for capability requests
|
||||
*
|
||||
* $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_LLCAPABILITYLISTENER_H)
|
||||
#define LL_LLCAPABILITYLISTENER_H
|
||||
|
||||
#include "llevents.h" // LLEventPump
|
||||
#include "llsdmessage.h" // LLSDMessage::ArgError
|
||||
#include "llerror.h" // LOG_CLASS()
|
||||
|
||||
class LLCapabilityProvider;
|
||||
class LLMessageSystem;
|
||||
class LLSD;
|
||||
|
||||
class LLCapabilityListener
|
||||
{
|
||||
LOG_CLASS(LLCapabilityListener);
|
||||
public:
|
||||
LLCapabilityListener(const std::string& name, LLMessageSystem* messageSystem,
|
||||
const LLCapabilityProvider& provider,
|
||||
const LLUUID& agentID, const LLUUID& sessionID);
|
||||
|
||||
/// Capability-request exception
|
||||
typedef LLSDMessage::ArgError ArgError;
|
||||
/// Get LLEventPump on which we listen for capability requests
|
||||
/// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities)
|
||||
LLEventPump& getCapAPI() { return mEventPump; }
|
||||
|
||||
/**
|
||||
* Base class for mapping an as-yet-undeployed capability name to a (pair
|
||||
* of) LLMessageSystem message(s). To map a capability name to such
|
||||
* messages, derive a subclass of CapabilityMapper and declare a static
|
||||
* instance in a translation unit known to be loaded. The mapping is not
|
||||
* region-specific. If an LLViewerRegion's capListener() receives a
|
||||
* request for a supported capability, it will use the capability's URL.
|
||||
* If not, it will look for an applicable CapabilityMapper subclass
|
||||
* instance.
|
||||
*/
|
||||
class CapabilityMapper
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Base-class constructor. Typically your subclass constructor will
|
||||
* pass these parameters as literals.
|
||||
* @param cap the capability name handled by this (subclass) instance
|
||||
* @param reply the name of the response LLMessageSystem message. Omit
|
||||
* if the LLMessageSystem message you intend to send doesn't prompt a
|
||||
* reply message, or if you already handle that message in some other
|
||||
* way.
|
||||
*/
|
||||
CapabilityMapper(const std::string& cap, const std::string& reply = "");
|
||||
virtual ~CapabilityMapper();
|
||||
/// query the capability name
|
||||
std::string getCapName() const { return mCapName; }
|
||||
/// query the reply message name
|
||||
std::string getReplyName() const { return mReplyName; }
|
||||
/**
|
||||
* Override this method to build the LLMessageSystem message we should
|
||||
* send instead of the requested capability message. DO NOT send that
|
||||
* message: that will be handled by the caller.
|
||||
*/
|
||||
virtual void buildMessage(LLMessageSystem* messageSystem,
|
||||
const LLUUID& agentID,
|
||||
const LLUUID& sessionID,
|
||||
const std::string& capabilityName,
|
||||
const LLSD& payload) const = 0;
|
||||
/**
|
||||
* Override this method if you pass a non-empty @a reply
|
||||
* LLMessageSystem message name to the constructor: that is, if you
|
||||
* expect to receive an LLMessageSystem message in response to the
|
||||
* message you constructed in buildMessage(). If you don't pass a @a
|
||||
* reply message name, you need not override this method as it won't
|
||||
* be called.
|
||||
*
|
||||
* Using LLMessageSystem message-reading operations, your
|
||||
* readResponse() override should construct and return an LLSD object
|
||||
* of the form you expect to receive from the real implementation of
|
||||
* the capability you intend to invoke, when it finally goes live.
|
||||
*/
|
||||
virtual LLSD readResponse(LLMessageSystem* messageSystem) const;
|
||||
|
||||
private:
|
||||
const std::string mCapName;
|
||||
const std::string mReplyName;
|
||||
};
|
||||
|
||||
private:
|
||||
/// Bind the LLCapabilityProvider passed to our ctor
|
||||
const LLCapabilityProvider& mProvider;
|
||||
|
||||
/// Post an event to this LLEventPump to invoke a capability message on
|
||||
/// the bound LLCapabilityProvider's server
|
||||
/// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities)
|
||||
LLEventStream mEventPump;
|
||||
|
||||
LLMessageSystem* mMessageSystem;
|
||||
LLUUID mAgentID, mSessionID;
|
||||
|
||||
/// listener to process capability requests
|
||||
bool capListener(const LLSD&);
|
||||
|
||||
/// helper class for capListener()
|
||||
class CapabilityMappers;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLCAPABILITYLISTENER_H) */
|
||||
@@ -57,6 +57,8 @@
|
||||
|
||||
#include "llavatarname.h"
|
||||
|
||||
using namespace LLOldEvents;
|
||||
|
||||
const F32 SPEAKER_TIMEOUT = 10.f; // seconds of not being on voice channel before removed from list of active speakers
|
||||
const F32 RESORT_TIMEOUT = 5.f; // seconds of mouse inactivity before it's ok to sort regardless of mouse-in-view.
|
||||
const LLColor4 INACTIVE_COLOR(0.3f, 0.3f, 0.3f, 0.5f);
|
||||
|
||||
@@ -49,7 +49,7 @@ class LLVoiceChannel;
|
||||
|
||||
|
||||
// data for a given participant in a voice channel
|
||||
class LLSpeaker : public LLRefCount, public LLObservable, public LLHandleProvider<LLSpeaker>
|
||||
class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider<LLSpeaker>
|
||||
{
|
||||
public:
|
||||
typedef enum e_speaker_type
|
||||
@@ -96,21 +96,21 @@ public:
|
||||
std::string mLegacyName;
|
||||
};
|
||||
|
||||
class LLSpeakerTextModerationEvent : public LLEvent
|
||||
class LLSpeakerTextModerationEvent : public LLOldEvents::LLEvent
|
||||
{
|
||||
public:
|
||||
LLSpeakerTextModerationEvent(LLSpeaker* source);
|
||||
/*virtual*/ LLSD getValue();
|
||||
};
|
||||
|
||||
class LLSpeakerVoiceModerationEvent : public LLEvent
|
||||
class LLSpeakerVoiceModerationEvent : public LLOldEvents::LLEvent
|
||||
{
|
||||
public:
|
||||
LLSpeakerVoiceModerationEvent(LLSpeaker* source);
|
||||
/*virtual*/ LLSD getValue();
|
||||
};
|
||||
|
||||
class LLSpeakerListChangeEvent : public LLEvent
|
||||
class LLSpeakerListChangeEvent : public LLOldEvents::LLEvent
|
||||
{
|
||||
public:
|
||||
LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id);
|
||||
@@ -120,7 +120,7 @@ private:
|
||||
const LLUUID& mSpeakerID;
|
||||
};
|
||||
|
||||
class LLSpeakerMgr : public LLObservable
|
||||
class LLSpeakerMgr : public LLOldEvents::LLObservable
|
||||
{
|
||||
public:
|
||||
LLSpeakerMgr(LLVoiceChannel* channelp);
|
||||
@@ -237,46 +237,46 @@ public:
|
||||
static void onChangeModerationMode(LLUICtrl* ctrl, void* user_data);
|
||||
|
||||
protected:
|
||||
class SpeakerMuteListener : public LLSimpleListener
|
||||
class SpeakerMuteListener : public LLOldEvents::LLSimpleListener
|
||||
{
|
||||
public:
|
||||
SpeakerMuteListener(LLPanelActiveSpeakers* panel) : mPanel(panel) {}
|
||||
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
|
||||
LLPanelActiveSpeakers* mPanel;
|
||||
};
|
||||
|
||||
friend class SpeakerAddListener;
|
||||
class SpeakerAddListener : public LLSimpleListener
|
||||
class SpeakerAddListener : public LLOldEvents::LLSimpleListener
|
||||
{
|
||||
public:
|
||||
SpeakerAddListener(LLPanelActiveSpeakers* panel) : mPanel(panel) {}
|
||||
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
|
||||
LLPanelActiveSpeakers* mPanel;
|
||||
};
|
||||
|
||||
friend class SpeakerRemoveListener;
|
||||
class SpeakerRemoveListener : public LLSimpleListener
|
||||
class SpeakerRemoveListener : public LLOldEvents::LLSimpleListener
|
||||
{
|
||||
public:
|
||||
SpeakerRemoveListener(LLPanelActiveSpeakers* panel) : mPanel(panel) {}
|
||||
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
|
||||
LLPanelActiveSpeakers* mPanel;
|
||||
};
|
||||
|
||||
|
||||
friend class SpeakerClearListener;
|
||||
class SpeakerClearListener : public LLSimpleListener
|
||||
class SpeakerClearListener : public LLOldEvents::LLSimpleListener
|
||||
{
|
||||
public:
|
||||
SpeakerClearListener(LLPanelActiveSpeakers* panel) : mPanel(panel) {}
|
||||
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
|
||||
LLPanelActiveSpeakers* mPanel;
|
||||
};
|
||||
|
||||
@@ -62,6 +62,8 @@
|
||||
|
||||
#include "hippolimits.h"
|
||||
|
||||
using namespace LLOldEvents;
|
||||
|
||||
// static
|
||||
std::map<const LLUUID, LLFloaterGroupPicker*> LLFloaterGroupPicker::sInstances;
|
||||
|
||||
|
||||
@@ -84,14 +84,14 @@ protected:
|
||||
static instance_map_t sInstances;
|
||||
};
|
||||
|
||||
class LLPanelGroups : public LLPanel, public LLSimpleListener
|
||||
class LLPanelGroups : public LLPanel, public LLOldEvents::LLSimpleListener
|
||||
{
|
||||
public:
|
||||
LLPanelGroups();
|
||||
virtual ~LLPanelGroups();
|
||||
|
||||
//LLEventListener
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
|
||||
// clear the group list, and get a fresh set of info.
|
||||
void reset();
|
||||
|
||||
@@ -99,6 +99,8 @@
|
||||
#include "statemachine/aifilepicker.h"
|
||||
// </edit>
|
||||
|
||||
using namespace LLOldEvents;
|
||||
|
||||
const std::string NEW_LSL_NAME = "New Script"; // *TODO:Translate? (probably not)
|
||||
const std::string NEW_NOTECARD_NAME = "New Note"; // *TODO:Translate? (probably not)
|
||||
const std::string NEW_GESTURE_NAME = "New Gesture"; // *TODO:Translate? (probably not)
|
||||
|
||||
@@ -140,6 +140,8 @@ struct LLMoveInv
|
||||
void* mUserData;
|
||||
};
|
||||
|
||||
using namespace LLOldEvents;
|
||||
|
||||
// Helpers
|
||||
// bug in busy count inc/dec right now, logic is complex... do we really need it?
|
||||
void inc_busy_count()
|
||||
|
||||
@@ -80,6 +80,8 @@
|
||||
#include "rlvhandler.h"
|
||||
// [/RLVa:KB]
|
||||
|
||||
using namespace LLOldEvents;
|
||||
|
||||
const F32 MAP_SCALE_MIN = 32;
|
||||
const F32 MAP_SCALE_MID = 256;
|
||||
const F32 MAP_SCALE_MAX = 4096;
|
||||
|
||||
@@ -130,55 +130,55 @@ private:
|
||||
class LLScaleMap : public LLMemberListener<LLNetMap>
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
|
||||
class LLCenterMap : public LLMemberListener<LLNetMap>
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
|
||||
class LLCheckCenterMap : public LLMemberListener<LLNetMap>
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
|
||||
class LLRotateMap : public LLMemberListener<LLNetMap>
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
|
||||
class LLCheckRotateMap : public LLMemberListener<LLNetMap>
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
|
||||
class LLStopTracking : public LLMemberListener<LLNetMap>
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
|
||||
class LLEnableTracking : public LLMemberListener<LLNetMap>
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
|
||||
class LLShowAgentProfile : public LLMemberListener<LLNetMap>
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
|
||||
class LLCamFollow : public LLMemberListener<LLNetMap> //moymod
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
|
||||
|
||||
@@ -188,37 +188,37 @@ private:
|
||||
class mmsetred : public LLMemberListener<LLNetMap> //moymod
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
class mmsetgreen : public LLMemberListener<LLNetMap> //moymod
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
class mmsetblue : public LLMemberListener<LLNetMap> //moymod
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
class mmsetyellow : public LLMemberListener<LLNetMap> //moymod
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
class mmsetcustom : public LLMemberListener<LLNetMap> //moymod
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
class mmsetunmark : public LLMemberListener<LLNetMap> //moymod
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
class mmenableunmark : public LLMemberListener<LLNetMap> //moymod
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ private:
|
||||
class LLEnableProfile : public LLMemberListener<LLNetMap>
|
||||
{
|
||||
public:
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
|
||||
/*virtual*/ bool handleEvent(LLPointer<LLOldEvents::LLEvent> event, const LLSD& userdata);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -261,6 +261,7 @@
|
||||
|
||||
#include "hippogridmanager.h"
|
||||
|
||||
using namespace LLOldEvents;
|
||||
using namespace LLVOAvatarDefines;
|
||||
void init_client_menu(LLMenuGL* menu);
|
||||
void init_server_menu(LLMenuGL* menu);
|
||||
|
||||
@@ -96,6 +96,8 @@
|
||||
|
||||
#include "importtracker.h"
|
||||
|
||||
using namespace LLOldEvents;
|
||||
|
||||
std::deque<std::string> gUploadQueue;
|
||||
|
||||
typedef LLMemberListener<LLView> view_listener_t;
|
||||
|
||||
258
indra/newview/llviewerstatsrecorder.cpp
Normal file
258
indra/newview/llviewerstatsrecorder.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* @file llviewerstatsrecorder.cpp
|
||||
* @brief record info about viewer events to a metrics log file
|
||||
*
|
||||
* $LicenseInfo:firstyear=2010&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$
|
||||
*/
|
||||
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
#include "llviewerstatsrecorder.h"
|
||||
|
||||
#if LL_RECORD_VIEWER_STATS
|
||||
|
||||
#include "llfile.h"
|
||||
#include "llviewerregion.h"
|
||||
#include "llviewerobject.h"
|
||||
|
||||
|
||||
// To do - something using region name or global position
|
||||
#if LL_WINDOWS
|
||||
static const std::string STATS_FILE_NAME("C:\\ViewerObjectCacheStats.csv");
|
||||
#else
|
||||
static const std::string STATS_FILE_NAME("/tmp/viewerstats.csv");
|
||||
#endif
|
||||
|
||||
LLViewerStatsRecorder* LLViewerStatsRecorder::sInstance = NULL;
|
||||
LLViewerStatsRecorder::LLViewerStatsRecorder() :
|
||||
mObjectCacheFile(NULL),
|
||||
mTimer(),
|
||||
mRegionp(NULL),
|
||||
mStartTime(0.f),
|
||||
mProcessingTime(0.f)
|
||||
{
|
||||
if (NULL != sInstance)
|
||||
{
|
||||
llerrs << "Attempted to create multiple instances of LLViewerStatsRecorder!" << llendl;
|
||||
}
|
||||
sInstance = this;
|
||||
clearStats();
|
||||
}
|
||||
|
||||
LLViewerStatsRecorder::~LLViewerStatsRecorder()
|
||||
{
|
||||
if (mObjectCacheFile != NULL)
|
||||
{
|
||||
LLFile::close(mObjectCacheFile);
|
||||
mObjectCacheFile = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void LLViewerStatsRecorder::initClass()
|
||||
{
|
||||
sInstance = new LLViewerStatsRecorder();
|
||||
}
|
||||
|
||||
// static
|
||||
void LLViewerStatsRecorder::cleanupClass()
|
||||
{
|
||||
delete sInstance;
|
||||
sInstance = NULL;
|
||||
}
|
||||
|
||||
|
||||
void LLViewerStatsRecorder::initStatsRecorder(LLViewerRegion *regionp)
|
||||
{
|
||||
if (mObjectCacheFile == NULL)
|
||||
{
|
||||
mStartTime = LLTimer::getTotalTime();
|
||||
mObjectCacheFile = LLFile::fopen(STATS_FILE_NAME, "wb");
|
||||
if (mObjectCacheFile)
|
||||
{ // Write column headers
|
||||
std::ostringstream data_msg;
|
||||
data_msg << "EventTime, "
|
||||
<< "ProcessingTime, "
|
||||
<< "CacheHits, "
|
||||
<< "CacheFullMisses, "
|
||||
<< "CacheCrcMisses, "
|
||||
<< "FullUpdates, "
|
||||
<< "TerseUpdates, "
|
||||
<< "CacheMissRequests, "
|
||||
<< "CacheMissResponses, "
|
||||
<< "CacheUpdateDupes, "
|
||||
<< "CacheUpdateChanges, "
|
||||
<< "CacheUpdateAdds, "
|
||||
<< "CacheUpdateReplacements, "
|
||||
<< "UpdateFailures"
|
||||
<< "\n";
|
||||
|
||||
fwrite(data_msg.str().c_str(), 1, data_msg.str().size(), mObjectCacheFile );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLViewerStatsRecorder::beginObjectUpdateEvents(LLViewerRegion *regionp)
|
||||
{
|
||||
initStatsRecorder(regionp);
|
||||
mRegionp = regionp;
|
||||
mProcessingTime = LLTimer::getTotalTime();
|
||||
clearStats();
|
||||
}
|
||||
|
||||
void LLViewerStatsRecorder::clearStats()
|
||||
{
|
||||
mObjectCacheHitCount = 0;
|
||||
mObjectCacheMissFullCount = 0;
|
||||
mObjectCacheMissCrcCount = 0;
|
||||
mObjectFullUpdates = 0;
|
||||
mObjectTerseUpdates = 0;
|
||||
mObjectCacheMissRequests = 0;
|
||||
mObjectCacheMissResponses = 0;
|
||||
mObjectCacheUpdateDupes = 0;
|
||||
mObjectCacheUpdateChanges = 0;
|
||||
mObjectCacheUpdateAdds = 0;
|
||||
mObjectCacheUpdateReplacements = 0;
|
||||
mObjectUpdateFailures = 0;
|
||||
}
|
||||
|
||||
|
||||
void LLViewerStatsRecorder::recordObjectUpdateFailure(U32 local_id, const EObjectUpdateType update_type)
|
||||
{
|
||||
mObjectUpdateFailures++;
|
||||
}
|
||||
|
||||
void LLViewerStatsRecorder::recordCacheMissEvent(U32 local_id, const EObjectUpdateType update_type, U8 cache_miss_type)
|
||||
{
|
||||
if (LLViewerRegion::CACHE_MISS_TYPE_FULL == cache_miss_type)
|
||||
{
|
||||
mObjectCacheMissFullCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
mObjectCacheMissCrcCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void LLViewerStatsRecorder::recordObjectUpdateEvent(U32 local_id, const EObjectUpdateType update_type, LLViewerObject * objectp)
|
||||
{
|
||||
switch (update_type)
|
||||
{
|
||||
case OUT_FULL:
|
||||
mObjectFullUpdates++;
|
||||
break;
|
||||
case OUT_TERSE_IMPROVED:
|
||||
mObjectTerseUpdates++;
|
||||
break;
|
||||
case OUT_FULL_COMPRESSED:
|
||||
mObjectCacheMissResponses++;
|
||||
break;
|
||||
case OUT_FULL_CACHED:
|
||||
mObjectCacheHitCount++;
|
||||
break;
|
||||
default:
|
||||
llwarns << "Unknown update_type" << llendl;
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void LLViewerStatsRecorder::recordCacheFullUpdate(U32 local_id, const EObjectUpdateType update_type, LLViewerRegion::eCacheUpdateResult update_result, LLViewerObject* objectp)
|
||||
{
|
||||
switch (update_result)
|
||||
{
|
||||
case LLViewerRegion::CACHE_UPDATE_DUPE:
|
||||
mObjectCacheUpdateDupes++;
|
||||
break;
|
||||
case LLViewerRegion::CACHE_UPDATE_CHANGED:
|
||||
mObjectCacheUpdateChanges++;
|
||||
break;
|
||||
case LLViewerRegion::CACHE_UPDATE_ADDED:
|
||||
mObjectCacheUpdateAdds++;
|
||||
break;
|
||||
case LLViewerRegion::CACHE_UPDATE_REPLACED:
|
||||
mObjectCacheUpdateReplacements++;
|
||||
break;
|
||||
default:
|
||||
llwarns << "Unknown update_result type" << llendl;
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void LLViewerStatsRecorder::recordRequestCacheMissesEvent(S32 count)
|
||||
{
|
||||
mObjectCacheMissRequests += count;
|
||||
}
|
||||
|
||||
void LLViewerStatsRecorder::endObjectUpdateEvents()
|
||||
{
|
||||
llinfos << "ILX: "
|
||||
<< mObjectCacheHitCount << " hits, "
|
||||
<< mObjectCacheMissFullCount << " full misses, "
|
||||
<< mObjectCacheMissCrcCount << " crc misses, "
|
||||
<< mObjectFullUpdates << " full updates, "
|
||||
<< mObjectTerseUpdates << " terse updates, "
|
||||
<< mObjectCacheMissRequests << " cache miss requests, "
|
||||
<< mObjectCacheMissResponses << " cache miss responses, "
|
||||
<< mObjectCacheUpdateDupes << " cache update dupes, "
|
||||
<< mObjectCacheUpdateChanges << " cache update changes, "
|
||||
<< mObjectCacheUpdateAdds << " cache update adds, "
|
||||
<< mObjectCacheUpdateReplacements << " cache update replacements, "
|
||||
<< mObjectUpdateFailures << " update failures"
|
||||
<< llendl;
|
||||
|
||||
S32 total_objects = mObjectCacheHitCount + mObjectCacheMissCrcCount + mObjectCacheMissFullCount + mObjectFullUpdates + mObjectTerseUpdates + mObjectCacheMissRequests + mObjectCacheMissResponses + mObjectCacheUpdateDupes + mObjectCacheUpdateChanges + mObjectCacheUpdateAdds + mObjectCacheUpdateReplacements + mObjectUpdateFailures;
|
||||
if (mObjectCacheFile != NULL &&
|
||||
total_objects > 0)
|
||||
{
|
||||
std::ostringstream data_msg;
|
||||
F32 processing32 = (F32) ((LLTimer::getTotalTime() - mProcessingTime) / 1000.0);
|
||||
|
||||
data_msg << getTimeSinceStart()
|
||||
<< ", " << processing32
|
||||
<< ", " << mObjectCacheHitCount
|
||||
<< ", " << mObjectCacheMissFullCount
|
||||
<< ", " << mObjectCacheMissCrcCount
|
||||
<< ", " << mObjectFullUpdates
|
||||
<< ", " << mObjectTerseUpdates
|
||||
<< ", " << mObjectCacheMissRequests
|
||||
<< ", " << mObjectCacheMissResponses
|
||||
<< ", " << mObjectCacheUpdateDupes
|
||||
<< ", " << mObjectCacheUpdateChanges
|
||||
<< ", " << mObjectCacheUpdateAdds
|
||||
<< ", " << mObjectCacheUpdateReplacements
|
||||
<< ", " << mObjectUpdateFailures
|
||||
<< "\n";
|
||||
|
||||
fwrite(data_msg.str().c_str(), 1, data_msg.str().size(), mObjectCacheFile );
|
||||
}
|
||||
|
||||
clearStats();
|
||||
}
|
||||
|
||||
F32 LLViewerStatsRecorder::getTimeSinceStart()
|
||||
{
|
||||
return (F32) ((LLTimer::getTotalTime() - mStartTime) / 1000.0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
97
indra/newview/llviewerstatsrecorder.h
Normal file
97
indra/newview/llviewerstatsrecorder.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @file llviewerstatsrecorder.h
|
||||
* @brief record info about viewer events to a metrics log file
|
||||
*
|
||||
* $LicenseInfo:firstyear=2010&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$
|
||||
*/
|
||||
|
||||
#ifndef LLVIEWERSTATSRECORDER_H
|
||||
#define LLVIEWERSTATSRECORDER_H
|
||||
|
||||
|
||||
// This is a diagnostic class used to record information from the viewer
|
||||
// for analysis.
|
||||
|
||||
// This is normally 0. Set to 1 to enable viewer stats recording
|
||||
#define LL_RECORD_VIEWER_STATS 0
|
||||
|
||||
|
||||
#if LL_RECORD_VIEWER_STATS
|
||||
#include "llframetimer.h"
|
||||
#include "llviewerobject.h"
|
||||
#include "llviewerregion.h"
|
||||
|
||||
class LLMutex;
|
||||
class LLViewerRegion;
|
||||
class LLViewerObject;
|
||||
|
||||
class LLViewerStatsRecorder
|
||||
{
|
||||
public:
|
||||
LLViewerStatsRecorder();
|
||||
~LLViewerStatsRecorder();
|
||||
|
||||
static void initClass();
|
||||
static void cleanupClass();
|
||||
static LLViewerStatsRecorder* instance() {return sInstance; }
|
||||
|
||||
void initStatsRecorder(LLViewerRegion *regionp);
|
||||
|
||||
void beginObjectUpdateEvents(LLViewerRegion *regionp);
|
||||
void recordObjectUpdateFailure(U32 local_id, const EObjectUpdateType update_type);
|
||||
void recordCacheMissEvent(U32 local_id, const EObjectUpdateType update_type, U8 cache_miss_type);
|
||||
void recordObjectUpdateEvent(U32 local_id, const EObjectUpdateType update_type, LLViewerObject * objectp);
|
||||
void recordCacheFullUpdate(U32 local_id, const EObjectUpdateType update_type, LLViewerRegion::eCacheUpdateResult update_result, LLViewerObject* objectp);
|
||||
void recordRequestCacheMissesEvent(S32 count);
|
||||
void endObjectUpdateEvents();
|
||||
|
||||
F32 getTimeSinceStart();
|
||||
|
||||
private:
|
||||
static LLViewerStatsRecorder* sInstance;
|
||||
|
||||
LLFILE * mObjectCacheFile; // File to write data into
|
||||
LLFrameTimer mTimer;
|
||||
LLViewerRegion* mRegionp;
|
||||
F64 mStartTime;
|
||||
F64 mProcessingTime;
|
||||
|
||||
S32 mObjectCacheHitCount;
|
||||
S32 mObjectCacheMissFullCount;
|
||||
S32 mObjectCacheMissCrcCount;
|
||||
S32 mObjectFullUpdates;
|
||||
S32 mObjectTerseUpdates;
|
||||
S32 mObjectCacheMissRequests;
|
||||
S32 mObjectCacheMissResponses;
|
||||
S32 mObjectCacheUpdateDupes;
|
||||
S32 mObjectCacheUpdateChanges;
|
||||
S32 mObjectCacheUpdateAdds;
|
||||
S32 mObjectCacheUpdateReplacements;
|
||||
S32 mObjectUpdateFailures;
|
||||
|
||||
|
||||
void clearStats();
|
||||
};
|
||||
#endif // LL_RECORD_VIEWER_STATS
|
||||
|
||||
#endif // LLVIEWERSTATSRECORDER_H
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
#include "llviewerstats.h"
|
||||
#include "lldatapacker.h"
|
||||
|
||||
using namespace LLOldEvents;
|
||||
|
||||
// consts
|
||||
|
||||
// The viewer is allowed to set the under-the-hood bandwidth to 50%
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#include "rlvcommon.h"
|
||||
#include "rlvhandler.h"
|
||||
|
||||
using namespace LLOldEvents;
|
||||
|
||||
// ============================================================================
|
||||
// RlvNotifications
|
||||
//
|
||||
|
||||
@@ -195,7 +195,7 @@ typedef bool (RlvCommandHandler::*rlvCommandHandler)(const RlvCommand& rlvCmd, E
|
||||
|
||||
class RlvEnableIfNot : public LLMemberListener<LLView>
|
||||
{
|
||||
bool handleEvent(LLPointer<LLEvent>, const LLSD&);
|
||||
bool handleEvent(LLPointer<LLOldEvents::LLEvent>, const LLSD&);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user