diff --git a/indra/cmake/FindJsonCpp.cmake b/indra/cmake/FindJsonCpp.cmake index cf84b309c..7dfc2b097 100644 --- a/indra/cmake/FindJsonCpp.cmake +++ b/indra/cmake/FindJsonCpp.cmake @@ -9,7 +9,7 @@ # also defined, but not for general use are # JSONCPP_LIBRARY, where to find the jsoncpp library. -FIND_PATH(JSONCPP_INCLUDE_DIR jsoncpp/json.h +FIND_PATH(JSONCPP_INCLUDE_DIR json/json.h /usr/local/include /usr/include ) diff --git a/indra/cmake/WebKitLibPlugin.cmake b/indra/cmake/WebKitLibPlugin.cmake index abae1762d..7ed824aea 100644 --- a/indra/cmake/WebKitLibPlugin.cmake +++ b/indra/cmake/WebKitLibPlugin.cmake @@ -76,6 +76,7 @@ elseif (LINUX) fontconfig X11 Xrender + Xext GL ) endif (STANDALONE) diff --git a/indra/cwdebug/debug.cc b/indra/cwdebug/debug.cc index 48392beae..2a4f1c4fa 100644 --- a/indra/cwdebug/debug.cc +++ b/indra/cwdebug/debug.cc @@ -171,7 +171,8 @@ void stop_recording_backtraces(void) channel_ct gtk DDCN("GTK"); //!< This debug channel is used for output related to gtk. channel_ct sdl DDCN("SDL"); //!< This debug channel is used for output related to sdl locking. channel_ct backtrace DDCN("BACKTRACE"); //!< This debug channel is used for backtraces. - channel_ct statemachine DDCN("STATEMACHINE"); //!< This debug channel is used class AIStateMachine. + channel_ct statemachine DDCN("STATEMACHINE"); //!< This debug channel is used for output related to class AIStateMachine. + channel_ct caps DDCN("CAPS"); //!< This debug channel is used for output related to Capabilities. } // namespace dc } // namespace DEBUGCHANNELS diff --git a/indra/cwdebug/debug.h b/indra/cwdebug/debug.h index e91eab92e..786b0c52a 100644 --- a/indra/cwdebug/debug.h +++ b/indra/cwdebug/debug.h @@ -117,6 +117,7 @@ extern CWD_API channel_ct gtk; extern CWD_API channel_ct sdl; extern CWD_API channel_ct backtrace; extern CWD_API channel_ct statemachine; +extern CWD_API channel_ct caps; #endif diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index e5801f6cb..2ff8856ec 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -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 diff --git a/indra/llcommon/ll_template_cast.h b/indra/llcommon/ll_template_cast.h new file mode 100644 index 000000000..c8f9a2f7e --- /dev/null +++ b/indra/llcommon/ll_template_cast.h @@ -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 +struct ll_template_cast_impl +{ + T operator()(U) + { + return 0; + } +}; + +/** + * ll_template_cast(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 + * void somefunc(const REALCLASS& instance) + * { + * const SpecialClass* ptr = ll_template_cast(&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(&other); + * @endcode + * to say nothing of this: + * @code + * void function(int); + * SpecialClass* ptr = dynamic_cast(&function); + * @endcode + * ll_template_cast handles these kinds of cases by returning 0. + */ +template +T ll_template_cast(U value) +{ + return ll_template_cast_impl()(value); +} + +/** + * Implementation for ll_template_cast() (q.v.). + * + * Implementation for identical types: return same value. + */ +template +struct ll_template_cast_impl +{ + T operator()(T value) + { + return value; + } +}; + +/** + * LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of + * type @c source, ll_template_cast(s) will return @c s -- + * presuming that @c source can be converted to @c dest by the normal rules of + * C++. + * + * By default, ll_template_cast(s) 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(&myFoo); + * @endcode + * + * Here @c fooptr will be 0 because &myFoo is of type Foo* + * -- @em not const Foo*. (Declaring const Foo myFoo; 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(&object); + * @endcode + * + * Here @c ptr will be 0 because &object is of type + * Subclass* rather than Base*. 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(&object); + * @endcode + * + * However, as noted earlier, this is easily fooled: + * @code + * const Base* ptr = ll_template_cast(&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 LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*) + * because that's not permitted by normal C++ assignment anyway.) + */ +#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \ +template <> \ +struct ll_template_cast_impl \ +{ \ + DEST operator()(SOURCE wrapper) \ + { \ + return wrapper; \ + } \ +} + +#endif /* ! defined(LL_LL_TEMPLATE_CAST_H) */ diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp new file mode 100644 index 000000000..0b5829eb7 --- /dev/null +++ b/indra/llcommon/llcoros.cpp @@ -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 +// 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(coro_private::coroutine_accessor::get_impl(const_cast(*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 diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h new file mode 100644 index 000000000..d75f28ec1 --- /dev/null +++ b/indra/llcommon/llcoros.h @@ -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 +#include "llsingleton.h" +#include +#include +#include +#include +#include +#include + +/** + * 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 +{ +public: + /// Canonical boost::coroutines::coroutine signature we use + typedef boost::coroutines::coroutine 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 + 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 + 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(); + 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 CoroMap; + CoroMap mCoros; +}; + +#endif /* ! defined(LL_LLCOROS_H) */ diff --git a/indra/llcommon/lldependencies.cpp b/indra/llcommon/lldependencies.cpp new file mode 100644 index 000000000..0e72c175c --- /dev/null +++ b/indra/llcommon/lldependencies.cpp @@ -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 +#include +// std headers +// external library headers +#include // for boost::graph_traits +#include +#include +#include +// 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 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::vertex_descriptor VertexDesc; + typedef std::vector 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 << ""; + 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 + // in the header file. + std::ostringstream out; + describe(out, full); + return out.str(); +} diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h new file mode 100644 index 000000000..e0294e271 --- /dev/null +++ b/indra/llcommon/lldependencies.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/***************************************************************************** +* 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 +inline +boost::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::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 +inline +boost::iterator_range::type> > +make_transform_range(RANGE& range, FUNCTION function) +{ + // shorthand for the iterator type embedded in our return type + typedef boost::transform_iterator::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 +struct instance_from_range: public TYPE +{ + template + 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 > EdgeList; + typedef std::vector 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 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 + 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 +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 +class LLDependencies: public LLDependenciesBase +{ + typedef LLDependencies self_type; + + /** + * Internally, we bundle the client's NODE with its before/after keys. + */ + struct DepNode + { + typedef std::set 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 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 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 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 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 value_type; + /// the value of a const_iterator + typedef refpair 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 + boost::iterator_range generic_range(FUNCTION function) + { + return make_transform_range(mNodes, function); + } + + /// generic mNodes const range method + template + boost::iterator_range generic_range(FUNCTION function) const + { + return make_transform_range(mNodes, function); + } + +public: + /// iterator over value_type entries + typedef boost::transform_iterator, + typename DepNodeMap::iterator> iterator; + /// range over value_type entries + typedef boost::iterator_range range; + + /// iterate over value_type in @c KEY order rather than dependency order + range get_range() + { + return generic_range(value_extract); + } + + /// iterator over const_value_type entries + typedef boost::transform_iterator, + typename DepNodeMap::const_iterator> const_iterator; + /// range over const_value_type entries + typedef boost::iterator_range const_range; + + /// iterate over const_value_type in @c KEY order rather than dependency order + const_range get_range() const + { + return generic_range(const_value_extract); + } + + /// iterator over stored NODEs + typedef boost::transform_iterator, + typename DepNodeMap::iterator> node_iterator; + /// range over stored NODEs + typedef boost::iterator_range node_range; + + /// iterate over NODE in @c KEY order 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( + boost::bind(&DepNode::node, + boost::bind(&DepNodeMapEntry::second, _1))); + } + + /// const iterator over stored NODEs + typedef boost::transform_iterator, + typename DepNodeMap::const_iterator> const_node_iterator; + /// const range over stored NODEs + typedef boost::iterator_range const_node_range; + + /// iterate over const NODE in @c KEY order 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( + boost::bind(&DepNode::node, + boost::bind(&DepNodeMapEntry::second, _1))); + } + + /// const iterator over stored KEYs + typedef boost::transform_iterator, + typename DepNodeMap::const_iterator> const_key_iterator; + /// const range over stored KEYs + typedef boost::iterator_range 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 in @c KEY order rather than dependency order + const_key_range get_key_range() const + { + // From a DepNodeMapEntry, extract a reference to its KEY. + return generic_range( + boost::bind(&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(const_cast(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_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. + * * 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 > 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 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(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 + 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_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 + 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 arbitrary order. 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 + 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 arbitrary order. 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 + 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 +struct LLDependencies_dep_range_from +{ + template + typename LLDependencies::dep_range + operator()(const LLDependencies& 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 +struct LLDependencies_dep_range_from< boost::transform_iterator > +{ + template + typename LLDependencies::dep_range + operator()(const LLDependencies& deps, + const boost::transform_iterator& iter, + const SELECTOR& selector) + { + return deps.get_dep_range_from_xform(iter, selector); + } +}; + +/// Specialize LLDependencies_dep_range_from for sorted_iterator +template +struct LLDependencies_dep_range_from< boost::indirect_iterator > +{ + template + typename LLDependencies::dep_range + operator()(const LLDependencies& deps, + const boost::indirect_iterator& iter, + const SELECTOR& selector) + { + return deps.get_dep_range_from_sorted(iter, selector); + } +}; + +/// generic get_after_range() implementation +template +template +typename LLDependencies::dep_range +LLDependencies::get_after_range(const KEY_OR_ITER& key_iter) const +{ + return LLDependencies_dep_range_from()( + *this, + key_iter, + boost::bind(&DepNode::after, _1)); +} + +/// generic get_before_range() implementation +template +template +typename LLDependencies::dep_range +LLDependencies::get_before_range(const KEY_OR_ITER& key_iter) const +{ + return LLDependencies_dep_range_from()( + *this, + key_iter, + boost::bind(&DepNode::before, _1)); +} + +#endif /* ! defined(LL_LLDEPENDENCIES_H) */ diff --git a/indra/llcommon/llevent.cpp b/indra/llcommon/llevent.cpp index 24be6e8b3..f669d0e13 100644 --- a/indra/llcommon/llevent.cpp +++ b/indra/llcommon/llevent.cpp @@ -34,6 +34,8 @@ #include "llevent.h" +using namespace LLOldEvents; + /************************************************ Events ************************************************/ diff --git a/indra/llcommon/llevent.h b/indra/llcommon/llevent.h index 6b223a8ee..0ea7cf4ae 100644 --- a/indra/llcommon/llevent.h +++ b/indra/llcommon/llevent.h @@ -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 diff --git a/indra/llcommon/lleventapi.cpp b/indra/llcommon/lleventapi.cpp new file mode 100644 index 000000000..ff5459c1e --- /dev/null +++ b/indra/llcommon/lleventapi.cpp @@ -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; +} diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h new file mode 100644 index 000000000..1a37d780b --- /dev/null +++ b/indra/llcommon/lleventapi.h @@ -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 + +/** + * 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 +{ + typedef LLDispatchListener lbase; + typedef LLInstanceTracker 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 + 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 (! + * request.has(replyKey)), 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) */ diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp new file mode 100644 index 000000000..81cc33fbb --- /dev/null +++ b/indra/llcommon/lleventcoro.cpp @@ -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 +// 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 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; +} diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h new file mode 100644 index 000000000..88a5e6ec7 --- /dev/null +++ b/indra/llcommon/lleventcoro.h @@ -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 +#include +#include +#include +#include +#include "llevents.h" +#include "llerror.h" + +/** + * Like LLListenerOrPumpName, this is a class intended for parameter lists: + * accept a const LLEventPumpOrPumpName& and you can accept either an + * LLEventPump& 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 const char* to a function + /// accepting const LLEventPumpOrPumpName& would require two + /// different implicit conversions: const char* -> const + /// std::string& -> const LLEventPumpOrPumpName&. + 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 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 +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 +LLVoidListener voidlistener(const LISTENER& listener) +{ + return LLVoidListener(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 + 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 path.isUndefined(), do nothing. + * * If path.isString(), @a dest is an LLSD map: store @a value + * into dest[path.asString()]. + * * If path.isInteger(), @a dest is an LLSD array: store @a + * value into dest[path.asInteger()]. + * * If path.isArray(), 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 requestPump.post() returns. In the + * sequence above, the running coroutine isn't even listening on @a replyPump + * until requestPump.post() 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 replyPump.getName(). 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 replyPump.getName() + * in event[replyPumpNamePath.asInteger()]. + * * @c isString(): @a event is a map. Store replyPump.getName() in + * event[replyPumpNamePath.asString()]. + * * @c isArray(): @a event has several levels of structure, e.g. map of + * maps, array of arrays, array of maps, map of arrays, ... Store + * replyPump.getName() in + * event[replyPumpNamePath[0]][replyPumpNamePath[1]]... In other + * words, examine each array entry in @a replyPumpNamePath in turn. If it's an + * LLSD::String, the current level of @a event is a map; step down to + * that map entry. If it's an LLSD::Integer, 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 replyPump.getName(). + */ +template +LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD()) +{ + // declare the future + boost::coroutines::future 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 +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 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 + 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 + WaitForEventOnHelper wfeoh(const LISTENER& listener, int discriminator) + { + return WaitForEventOnHelper(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 +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 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 +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 + LLSD wait(SELF& self) + { + return waitForEventOn(self, mPump); + } + + template + 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 getNames() const + { + return std::pair(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 + LLEventWithID wait(SELF& self) + { + return waitForEventOn(self, mPump0, mPump1); + } + + /// errorException(wait(self)) + template + LLSD waitWithException(SELF& self) + { + return errorException(wait(self), std::string("Error event on ") + getName1()); + } + + /// errorLog(wait(self)) + template + LLSD waitWithLog(SELF& self) + { + return errorLog(wait(self), std::string("Error event on ") + getName1()); + } + + template + 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 + 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 + 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) */ diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp new file mode 100644 index 000000000..5b6d4efbe --- /dev/null +++ b/indra/llcommon/lleventdispatcher.cpp @@ -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 // 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 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 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 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) +{} + diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h new file mode 100644 index 000000000..7acc61de4 --- /dev/null +++ b/indra/llcommon/lleventdispatcher.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 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 boost::bind() expression. The passed @a method + * accepts a single LLSD value, presumably containing other parameters. + */ + template + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(const LLSD&), + const LLSD& required=LLSD()) + { + addMethod(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 + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(const LLSD&) const, + const LLSD& required=LLSD()) + { + addMethod(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 boost::enable_if< boost::function_types::is_nonmember_callable_builtin + >::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 boost::enable_if< boost::function_types::is_member_function_pointer + >::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 boost::enable_if< boost::function_types::is_nonmember_callable_builtin + >::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 boost::enable_if< boost::function_types::is_member_function_pointer + >::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 true. If no such callable exists, return + /// false. 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 true. + /// If no such callable exists, return false. 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 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, 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 > 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 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 operator() method + std::string getDispatchKey() const { return mKey; } + +private: + template + void addMethod(const std::string& name, const std::string& desc, + const METHOD& method, const LLSD& required) + { + CLASS* downcast = dynamic_cast(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 + // 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 >::type + , class To = typename boost::mpl::end< boost::function_types::parameter_types >::type + > + struct invoker; + + // deliver LLSD arguments one at a time + typedef boost::function args_source; + // obtain args from an args_source to build param list and call target + // function + typedef boost::function invoker_function; + + template + invoker_function make_invoker(Function f); + template + 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 +struct LLEventDispatcher::invoker +{ + template + struct remove_cv_ref + : boost::remove_cv< typename boost::remove_reference::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 + static inline + void apply(Function func, const args_source& argsrc, Args const & args) + { + typedef typename boost::mpl::deref::type arg_type; + typedef typename boost::mpl::next::type next_iter_type; + typedef typename remove_cv_ref::type plain_arg_type; + + invoker::apply + ( func, argsrc, boost::fusion::push_back(args, LLSDParam(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 + static inline + void method_apply(Function func, const args_source& argsrc, const InstanceGetter& getter) + { + typedef typename boost::mpl::next::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::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 logic has advanced From until it matches To, +// the compiler will pick this template specialization. +template +struct LLEventDispatcher::invoker +{ + // the argument list is complete, now call the function + template + static inline + void apply(Function func, const args_source&, Args const & args) + { + boost::fusion::invoke(func, args); + } +}; + +template +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::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::value); +} + +template +typename boost::enable_if< boost::function_types::is_member_function_pointer >::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::value - 1); +} + +template +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::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 boost::enable_if< boost::function_types::is_member_function_pointer >::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 +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::template apply, + f, + _1, + boost::fusion::nil()); +} + +template +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::template method_apply, + 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) */ diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp new file mode 100644 index 000000000..d36a10725 --- /dev/null +++ b/indra/llcommon/lleventfilter.cpp @@ -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 +// 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(); +} diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h new file mode 100644 index 000000000..e822a664f --- /dev/null +++ b/indra/llcommon/lleventfilter.h @@ -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 + +/** + * 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 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) */ diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp new file mode 100644 index 000000000..db1ea4792 --- /dev/null +++ b/indra/llcommon/llevents.cpp @@ -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 +#include +#include +// std headers +#include +#include +#include +#include +// external library headers +#include +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable : 4701) // compiler thinks might use uninitialized var, but no +#endif +#include +#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 inserted = + mPumpMap.insert(PumpMap::value_type(name, const_cast(&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 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(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(&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 > 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 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 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); +} diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h new file mode 100644 index 000000000..3ada48eb6 --- /dev/null +++ b/indra/llcommon/llevents.h @@ -0,0 +1,1049 @@ +/** + * @file llevents.h + * @author Kent Quirk, Nat Goodspeed + * @date 2008-09-11 + * @brief This is an implementation of the event system described at + * https://wiki.lindenlab.com/wiki/Viewer:Messaging/Event_System, + * originally introduced in llnotifications.h. It has nothing + * whatsoever to do with the older system in llevent.h. + * + * $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_LLEVENTS_H) +#define LL_LLEVENTS_H + +#include +#include +#include +#include +#include +#include +#if LL_WINDOWS + #pragma warning (push) + #pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch + #pragma warning (disable : 4264) +#endif +#include +#if LL_WINDOWS + #pragma warning (pop) +#endif + +#include +#include +#include +#include // noncopyable +#include +#include +#include // reference_wrapper +#include +#include +#include +#include "llsd.h" +#include "llsingleton.h" +#include "lldependencies.h" +#include "ll_template_cast.h" + +/*==========================================================================*| +// override this to allow binding free functions with more parameters +#ifndef LLEVENTS_LISTENER_ARITY +#define LLEVENTS_LISTENER_ARITY 10 +#endif +|*==========================================================================*/ + +// hack for testing +#ifndef testable +#define testable private +#endif + +/***************************************************************************** +* 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 + result_type operator()(InputIterator first, InputIterator last) const + { + for (InputIterator si = first; si != last; ++si) + { + if (*si) + { + return true; + } + } + return false; + } +}; + +/** + * 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. + * + * @internal + * The @c float template parameter indicates that we will internally use @c + * float to indicate relative listener order on a given LLStandardSignal. + * Don't worry, the @c float values are strictly internal! They are not part + * of the interface, for the excellent reason that requiring the caller to + * specify a numeric key to establish order means that the caller must know + * the universe of possible values. We use LLDependencies for that instead. + */ +typedef boost::signals2::signal LLStandardSignal; +/// Methods that forward listeners (e.g. constructed with +/// boost::bind()) should accept (const LLEventListener&) +typedef LLStandardSignal::slot_type LLEventListener; +/// Result of registering a listener, supports connected(), +/// disconnect() and blocked() +typedef boost::signals2::connection LLBoundListener; +/// Storing an LLBoundListener in LLTempBoundListener will disconnect the +/// referenced listener when the LLTempBoundListener instance is destroyed. +typedef boost::signals2::scoped_connection LLTempBoundListener; + +/** + * A common idiom for event-based code is to accept either a callable -- + * directly called on completion -- or the string name of an LLEventPump on + * which to post the completion event. Specifying a parameter as const + * LLListenerOrPumpName& allows either. + * + * Calling a validly-constructed LLListenerOrPumpName, passing the LLSD + * 'event' object, either calls the callable or posts the event to the named + * LLEventPump. + * + * A default-constructed LLListenerOrPumpName is 'empty'. (This is useful as + * the default value of an optional method parameter.) Calling it throws + * LLListenerOrPumpName::Empty. Test for this condition beforehand using + * either if (param) or if (! param). + */ +class LL_COMMON_API LLListenerOrPumpName +{ +public: + /// passing string name of LLEventPump + LLListenerOrPumpName(const std::string& pumpname); + /// passing string literal (overload so compiler isn't forced to infer + /// double conversion) + LLListenerOrPumpName(const char* pumpname); + /// passing listener -- the "anything else" catch-all case. The type of an + /// object constructed by boost::bind() isn't intended to be written out. + /// Normally we'd just accept 'const LLEventListener&', but that would + /// require double implicit conversion: boost::bind() object to + /// LLEventListener, LLEventListener to LLListenerOrPumpName. So use a + /// template to forward anything. + template + LLListenerOrPumpName(const T& listener): mListener(listener) {} + + /// for omitted method parameter: uninitialized mListener + LLListenerOrPumpName() {} + + /// test for validity + operator bool() const { return bool(mListener); } + bool operator! () const { return ! mListener; } + + /// explicit accessor + const LLEventListener& getListener() const { return *mListener; } + + /// implicit conversion to LLEventListener + operator LLEventListener() const { return *mListener; } + + /// allow calling directly + bool operator()(const LLSD& event) const; + + /// exception if you try to call when empty + struct Empty: public std::runtime_error + { + Empty(const std::string& what): + std::runtime_error(std::string("LLListenerOrPumpName::Empty: ") + what) {} + }; + +private: + boost::optional mListener; +}; + +/***************************************************************************** +* LLEventPumps +*****************************************************************************/ +class LLEventPump; + +/** + * LLEventPumps is a Singleton manager through which one typically accesses + * this subsystem. + */ +class LL_COMMON_API LLEventPumps: public LLSingleton +{ + friend class LLSingleton; +public: + /** + * Find or create an LLEventPump instance with a specific name. We return + * a reference so there's no question about ownership. obtain() @em finds + * an instance without conferring @em ownership. + */ + LLEventPump& obtain(const std::string& name); + /** + * Flush all known LLEventPump instances + */ + void flush(); + + /** + * Reset all known LLEventPump instances + * workaround for DEV-35406 crash on shutdown + */ + void reset(); + +private: + friend class LLEventPump; + /** + * Register a new LLEventPump instance (internal) + */ + std::string registerNew(const LLEventPump&, const std::string& name, bool tweak); + /** + * Unregister a doomed LLEventPump instance (internal) + */ + void unregister(const LLEventPump&); + +private: + LLEventPumps(); + ~LLEventPumps(); + +testable: + // Map of all known LLEventPump instances, whether or not we instantiated + // them. We store a plain old LLEventPump* because this map doesn't claim + // ownership of the instances. Though the common usage pattern is to + // request an instance using obtain(), it's fair to instantiate an + // LLEventPump subclass statically, as a class member, on the stack or on + // the heap. In such cases, the instantiating party is responsible for its + // lifespan. + typedef std::map PumpMap; + PumpMap mPumpMap; + // Set of all LLEventPumps we instantiated. Membership in this set means + // we claim ownership, and will delete them when this LLEventPumps is + // destroyed. + typedef std::set PumpSet; + PumpSet mOurPumps; + // LLEventPump names that should be instantiated as LLEventQueue rather + // than as LLEventStream + typedef std::set PumpNames; + PumpNames mQueueNames; +}; + +/***************************************************************************** +* details +*****************************************************************************/ +namespace LLEventDetail +{ + /// Any callable capable of connecting an LLEventListener to an + /// LLStandardSignal to produce an LLBoundListener can be mapped to this + /// signature. + typedef boost::function ConnectFunc; + + /// overload of visit_and_connect() when we have a string identifier available + template + LLBoundListener visit_and_connect(const std::string& name, + const LISTENER& listener, + const ConnectFunc& connect_func); + /** + * Utility template function to use Visitor appropriately + * + * @param listener Callable to connect, typically a boost::bind() + * expression. This will be visited by Visitor using boost::visit_each(). + * @param connect_func Callable that will connect() @a listener to an + * LLStandardSignal, returning LLBoundListener. + */ + template + LLBoundListener visit_and_connect(const LISTENER& listener, + const ConnectFunc& connect_func) + { + return visit_and_connect("", listener, connect_func); + } +} // namespace LLEventDetail + +/***************************************************************************** +* LLEventTrackable +*****************************************************************************/ +/** + * LLEventTrackable wraps boost::signals2::trackable, which resembles + * boost::trackable. Derive your listener class from LLEventTrackable instead, + * and use something like + * LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, + * instance, _1)). This will implicitly disconnect when the object + * referenced by @c instance is destroyed. + * + * @note + * LLEventTrackable doesn't address a couple of cases: + * * Object destroyed during call + * - You enter a slot call in thread A. + * - Thread B destroys the object, which of course disconnects it from any + * future slot calls. + * - Thread A's call uses 'this', which now refers to a defunct object. + * Undefined behavior results. + * * Call during destruction + * - @c MySubclass is derived from LLEventTrackable. + * - @c MySubclass registers one of its own methods using + * LLEventPump::listen(). + * - The @c MySubclass object begins destruction. ~MySubclass() + * runs, destroying state specific to the subclass. (For instance, a + * Foo* data member is deleted but not zeroed.) + * - The listening method will not be disconnected until + * ~LLEventTrackable() runs. + * - Before we get there, another thread posts data to the @c LLEventPump + * instance, calling the @c MySubclass method. + * - The method in question relies on valid @c MySubclass state. (For + * instance, it attempts to dereference the Foo* pointer that was + * deleted but not zeroed.) + * - Undefined behavior results. + * If you suspect you may encounter any such scenario, you're better off + * managing the lifespan of your object with boost::shared_ptr. + * Passing LLEventPump::listen() a boost::bind() expression + * involving a boost::weak_ptr is recognized specially, engaging + * thread-safe Boost.Signals2 machinery. + */ +typedef boost::signals2::trackable LLEventTrackable; + +/***************************************************************************** +* LLEventPump +*****************************************************************************/ +/** + * LLEventPump is the base class interface through which we access the + * concrete subclasses LLEventStream and LLEventQueue. + * + * @NOTE + * LLEventPump derives from LLEventTrackable so that when you "chain" + * LLEventPump instances together, they will automatically disconnect on + * destruction. Please see LLEventTrackable documentation for situations in + * which this may be perilous across threads. + */ +class LL_COMMON_API LLEventPump: public LLEventTrackable +{ +public: + /** + * Exception thrown by LLEventPump(). You are trying to instantiate an + * LLEventPump (subclass) using the same name as some other instance, and + * you didn't pass tweak=true to permit it to generate a unique + * variant. + */ + struct DupPumpName: public std::runtime_error + { + DupPumpName(const std::string& what): + std::runtime_error(std::string("DupPumpName: ") + what) {} + }; + + /** + * Instantiate an LLEventPump (subclass) with the string name by which it + * can be found using LLEventPumps::obtain(). + * + * If you pass (or default) @a tweak to @c false, then a duplicate name + * will throw DupPumpName. This won't happen if LLEventPumps::obtain() + * instantiates the LLEventPump, because obtain() uses find-or-create + * logic. It can only happen if you instantiate an LLEventPump in your own + * code -- and a collision with the name of some other LLEventPump is + * likely to cause much more subtle problems! + * + * When you hand-instantiate an LLEventPump, consider passing @a tweak as + * @c true. This directs LLEventPump() to append a suffix to the passed @a + * name to make it unique. You can retrieve the adjusted name by calling + * getName() on your new instance. + */ + LLEventPump(const std::string& name, bool tweak=false); + virtual ~LLEventPump(); + + /// group exceptions thrown by listen(). We use exceptions because these + /// particular errors are likely to be coding errors, found and fixed by + /// the developer even before preliminary checkin. + struct ListenError: public std::runtime_error + { + ListenError(const std::string& what): std::runtime_error(what) {} + }; + /** + * exception thrown by listen(). You are attempting to register a + * listener on this LLEventPump using the same listener name as an + * already-registered listener. + */ + struct DupListenerName: public ListenError + { + DupListenerName(const std::string& what): + ListenError(std::string("DupListenerName: ") + what) + {} + }; + /** + * exception thrown by listen(). The order dependencies specified for your + * listener are incompatible with existing listeners. + * + * Consider listener "a" which specifies before "b" and "b" which + * specifies before "c". You are now attempting to register "c" before + * "a". There is no order that can satisfy all constraints. + */ + struct Cycle: public ListenError + { + Cycle(const std::string& what): ListenError(std::string("Cycle: ") + what) {} + }; + /** + * exception thrown by listen(). This one means that your new listener + * would force a change to the order of previously-registered listeners, + * and we don't have a good way to implement that. + * + * Consider listeners "some", "other" and "third". "some" and "other" are + * registered earlier without specifying relative order, so "other" + * happens to be first. Now you attempt to register "third" after "some" + * and before "other". Whoops, that would require swapping "some" and + * "other", which we can't do. Instead we throw this exception. + * + * It may not be possible to change the registration order so we already + * know "third"s order requirement by the time we register the second of + * "some" and "other". A solution would be to specify that "some" must + * come before "other", or equivalently that "other" must come after + * "some". + */ + struct OrderChange: public ListenError + { + OrderChange(const std::string& what): ListenError(std::string("OrderChange: ") + what) {} + }; + + /// used by listen() + typedef std::vector NameList; + /// convenience placeholder for when you explicitly want to pass an empty + /// NameList + const static NameList empty; + + /// Get this LLEventPump's name + std::string getName() const { return mName; } + + /** + * Register a new listener with a unique name. Specify an optional list + * of other listener names after which this one must be called, likewise + * an optional list of other listener names before which this one must be + * called. The other listeners mentioned need not yet be registered + * themselves. listen() can throw any ListenError; see ListenError + * subclasses. + * + * The listener name must be unique among active listeners for this + * LLEventPump, else you get DupListenerName. If you don't care to invent + * a name yourself, use inventName(). (I was tempted to recognize e.g. "" + * and internally generate a distinct name for that case. But that would + * handle badly the scenario in which you want to add, remove, re-add, + * etc. the same listener: each new listen() call would necessarily + * perform a new dependency sort. Assuming you specify the same + * after/before lists each time, using inventName() when you first + * instantiate your listener, then passing the same name on each listen() + * call, allows us to optimize away the second and subsequent dependency + * sorts. + * + * If (as is typical) you pass a boost::bind() expression as @a + * listener, listen() will inspect the components of that expression. If a + * bound object matches any of several cases, the connection will + * automatically be disconnected when that object is destroyed. + * + * * You bind a boost::weak_ptr. + * * Binding a boost::shared_ptr that way would ensure that the + * referenced object would @em never be destroyed, since the @c + * shared_ptr stored in the LLEventPump would remain an outstanding + * reference. Use the weaken() function to convert your @c shared_ptr to + * @c weak_ptr. Because this is easy to forget, binding a @c shared_ptr + * will produce a compile error (@c BOOST_STATIC_ASSERT failure). + * * You bind a simple pointer or reference to an object derived from + * boost::enable_shared_from_this. (UNDER CONSTRUCTION) + * * You bind a simple pointer or reference to an object derived from + * LLEventTrackable. Unlike the cases described above, though, this is + * vulnerable to a couple of cross-thread race conditions, as described + * in the LLEventTrackable documentation. + */ + template + LLBoundListener listen(const std::string& name, const LISTENER& listener, + const NameList& after=NameList(), + const NameList& before=NameList()) + { + // Examine listener, using our listen_impl() method to make the + // actual connection. + // This is why listen() is a template. Conversion from boost::bind() + // to LLEventListener performs type erasure, so it's important to look + // at the boost::bind object itself before that happens. + return LLEventDetail::visit_and_connect(name, + listener, + boost::bind(&LLEventPump::listen_impl, + this, + name, + _1, + after, + before)); + } + + /// Get the LLBoundListener associated with the passed name (dummy + /// LLBoundListener if not found) + virtual LLBoundListener getListener(const std::string& name) const; + /** + * Instantiate one of these to block an existing connection: + * @code + * { // in some local scope + * LLEventPump::Blocker block(someLLBoundListener); + * // code that needs the connection blocked + * } // unblock the connection again + * @endcode + */ + typedef boost::signals2::shared_connection_block Blocker; + /// Unregister a listener by name. Prefer this to + /// getListener(name).disconnect() because stopListening() also + /// forgets this name. + virtual void stopListening(const std::string& name); + /// Post an event to all listeners. The @c bool return is only meaningful + /// if the underlying leaf class is LLEventStream -- beware of relying on + /// it too much! Truthfully, we return @c bool mostly to permit chaining + /// one LLEventPump as a listener on another. + virtual bool post(const LLSD&) = 0; + /// Enable/disable: while disabled, silently ignore all post() calls + virtual void enable(bool enabled=true) { mEnabled = enabled; } + /// query + virtual bool enabled() const { return mEnabled; } + + /// Generate a distinct name for a listener -- see listen() + static std::string inventName(const std::string& pfx="listener"); + +private: + friend class LLEventPumps; + /// flush queued events + virtual void flush() {} + + virtual void reset(); + +private: + virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + const NameList& after, + const NameList& before); + std::string mName; + +protected: + /// implement the dispatching + boost::shared_ptr mSignal; + + /// valve open? + bool mEnabled; + /// Map of named listeners. This tracks the listeners that actually exist + /// at this moment. When we stopListening(), we discard the entry from + /// this map. + typedef std::map ConnectionMap; + ConnectionMap mConnections; + typedef LLDependencies DependencyMap; + /// Dependencies between listeners. For each listener, track the float + /// used to establish its place in mSignal's order. This caches all the + /// listeners that have ever registered; stopListening() does not discard + /// the entry from this map. This is to avoid a new dependency sort if the + /// same listener with the same dependencies keeps hopping on and off this + /// LLEventPump. + DependencyMap mDeps; +}; + +/***************************************************************************** +* LLEventStream +*****************************************************************************/ +/** + * LLEventStream is a thin wrapper around LLStandardSignal. Posting an + * event immediately calls all registered listeners. + */ +class LL_COMMON_API LLEventStream: public LLEventPump +{ +public: + LLEventStream(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} + virtual ~LLEventStream() {} + + /// Post an event to all listeners + virtual bool post(const LLSD& event); +}; + +/***************************************************************************** +* LLEventQueue +*****************************************************************************/ +/** + * LLEventQueue isa LLEventPump whose post() method defers calling registered + * listeners until flush() is called. + */ +class LL_COMMON_API LLEventQueue: public LLEventPump +{ +public: + LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} + virtual ~LLEventQueue() {} + + /// Post an event to all listeners + virtual bool post(const LLSD& event); + +private: + /// flush queued events + virtual void flush(); + +private: + typedef std::deque EventQueue; + EventQueue mEventQueue; +}; + +/***************************************************************************** +* LLReqID +*****************************************************************************/ +/** + * This class helps the implementer of a given event API to honor the + * ["reqid"] convention. By this convention, each event API stamps into its + * response LLSD a ["reqid"] key whose value echoes the ["reqid"] value, if + * any, from the corresponding request. + * + * This supports an (atypical, but occasionally necessary) use case in which + * two or more asynchronous requests are multiplexed onto the same ["reply"] + * LLEventPump. Since the response events could arrive in arbitrary order, the + * caller must be able to demux them. It does so by matching the ["reqid"] + * value in each response with the ["reqid"] value in the corresponding + * request. + * + * It is the caller's responsibility to ensure distinct ["reqid"] values for + * that case. Though LLSD::UUID is guaranteed to work, it might be overkill: + * the "namespace" of unique ["reqid"] values is simply the set of requests + * specifying the same ["reply"] LLEventPump name. + * + * Making a given event API echo the request's ["reqid"] into the response is + * nearly trivial. This helper is mostly for mnemonic purposes, to serve as a + * place to put these comments. We hope that each time a coder implements a + * new event API based on some existing one, s/he will say, "Huh, what's an + * LLReqID?" and look up this material. + * + * The hardest part about the convention is deciding where to store the + * ["reqid"] value. Ironically, LLReqID can't help with that: you must store + * an LLReqID instance in whatever storage will persist until the reply is + * sent. For example, if the request ultimately ends up using a Responder + * subclass, storing an LLReqID instance in the Responder works. + * + * @note + * The @em implementer of an event API must honor the ["reqid"] convention. + * However, the @em caller of an event API need only use it if s/he is sharing + * the same ["reply"] LLEventPump for two or more asynchronous event API + * requests. + * + * In most cases, it's far easier for the caller to instantiate a local + * LLEventStream and pass its name to the event API in question. Then it's + * perfectly reasonable not to set a ["reqid"] key in the request, ignoring + * the @c isUndefined() ["reqid"] value in the response. + */ +class LL_COMMON_API LLReqID +{ +public: + /** + * If you have the request in hand at the time you instantiate the + * LLReqID, pass that request to extract its ["reqid"]. + */ + LLReqID(const LLSD& request): + mReqid(request["reqid"]) + {} + /// If you don't yet have the request, use setFrom() later. + LLReqID() {} + + /// Extract and store the ["reqid"] value from an incoming request. + void setFrom(const LLSD& request) + { + mReqid = request["reqid"]; + } + + /// Set ["reqid"] key into a pending response LLSD object. + void stamp(LLSD& response) const; + + /// Make a whole new response LLSD object with our ["reqid"]. + LLSD makeResponse() const + { + LLSD response; + stamp(response); + return response; + } + + /// Not really sure of a use case for this accessor... + LLSD getReqID() const { return mReqid; } + +private: + LLSD mReqid; +}; + +/** + * Conventionally send a reply to a request event. + * + * @a reply is the LLSD reply event to send + * @a request is the corresponding LLSD request event + * @a replyKey is the key in the @a request event, conventionally ["reply"], + * whose value is the name of the LLEventPump on which to send the reply. + * + * Before sending the reply event, sendReply() copies the ["reqid"] item from + * the request to the reply. + */ +LL_COMMON_API bool sendReply(const LLSD& reply, const LLSD& request, + const std::string& replyKey="reply"); + +/** + * Base class for LLListenerWrapper. See visit_and_connect() and llwrap(). We + * provide virtual @c accept_xxx() methods, customization points allowing a + * subclass access to certain data visible at LLEventPump::listen() time. + * Example subclass usage: + * + * @code + * myEventPump.listen("somename", + * llwrap(boost::bind(&MyClass::method, instance, _1))); + * @endcode + * + * Because of the anticipated usage (note the anonymous temporary + * MyListenerWrapper instance in the example above), the @c accept_xxx() + * methods must be @c const. + */ +class LL_COMMON_API LLListenerWrapperBase +{ +public: + /// New instance. The accept_xxx() machinery makes it important to use + /// shared_ptrs for our data. Many copies of this object are made before + /// the instance that actually ends up in the signal, yet accept_xxx() + /// will later be called on the @em original instance. All copies of the + /// same original instance must share the same data. + LLListenerWrapperBase(): + mName(new std::string), + mConnection(new LLBoundListener) + { + } + + /// Copy constructor. Copy shared_ptrs to original instance data. + LLListenerWrapperBase(const LLListenerWrapperBase& that): + mName(that.mName), + mConnection(that.mConnection) + { + } + virtual ~LLListenerWrapperBase() {} + + /// Ask LLEventPump::listen() for the listener name + virtual void accept_name(const std::string& name) const + { + *mName = name; + } + + /// Ask LLEventPump::listen() for the new connection + virtual void accept_connection(const LLBoundListener& connection) const + { + *mConnection = connection; + } + +protected: + /// Listener name. + boost::shared_ptr mName; + /// Connection. + boost::shared_ptr mConnection; +}; + +/***************************************************************************** +* Underpinnings +*****************************************************************************/ +/** + * We originally provided a suite of overloaded + * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call + * LLEventPump::listen(...) and then pass the returned LLBoundListener to + * LLEventTrackable::track(). This was workable but error-prone: the coder + * must remember to call listenTo() rather than the more straightforward + * listen() method. + * + * Now we publish only the single canonical listen() method, so there's a + * uniform mechanism. Having a single way to do this is good, in that there's + * no question in the coder's mind which of several alternatives to choose. + * + * To support automatic connection management, we use boost::visit_each + * (http://www.boost.org/doc/libs/1_37_0/doc/html/boost/visit_each.html) to + * inspect each argument of a boost::bind expression. (Although the visit_each + * mechanism was first introduced with the original Boost.Signals library, it + * was only later documented.) + * + * Cases: + * * At least one of the function's arguments is a boost::weak_ptr. Pass + * the corresponding shared_ptr to slot_type::track(). Ideally that would be + * the object whose method we want to call, but in fact we do the same for + * any weak_ptr we might find among the bound arguments. If we're passing + * our bound method a weak_ptr to some object, wouldn't the destruction of + * that object invalidate the call? So we disconnect automatically when any + * such object is destroyed. This is the mechanism preferred by boost:: + * signals2. + * * One of the functions's arguments is a boost::shared_ptr. This produces + * a compile error: the bound copy of the shared_ptr stored in the + * boost_bind object stored in the signal object would make the referenced + * T object immortal. We provide a weaken() function. Pass + * weaken(your_shared_ptr) instead. (We can inspect, but not modify, the + * boost::bind object. Otherwise we'd replace the shared_ptr with weak_ptr + * implicitly and just proceed.) + * * One of the function's arguments is a plain pointer/reference to an object + * derived from boost::enable_shared_from_this. We assume that this object + * is managed using boost::shared_ptr, so we implicitly extract a shared_ptr + * and track that. (UNDER CONSTRUCTION) + * * One of the function's arguments is derived from LLEventTrackable. Pass + * the LLBoundListener to its LLEventTrackable::track(). This is vulnerable + * to a couple different race conditions, as described in LLEventTrackable + * documentation. (NOTE: Now that LLEventTrackable is a typedef for + * boost::signals2::trackable, the Signals2 library handles this itself, so + * our visitor needs no special logic for this case.) + * * Any other argument type is irrelevant to automatic connection management. + */ + +namespace LLEventDetail +{ + template + const F& unwrap(const F& f) { return f; } + + template + const F& unwrap(const boost::reference_wrapper& f) { return f.get(); } + + // Most of the following is lifted from the Boost.Signals use of + // visit_each. + template struct truth {}; + + /** + * boost::visit_each() Visitor, used on a template argument const F& + * f as follows (see visit_and_connect()): + * @code + * LLEventListener listener(f); + * Visitor visitor(listener); // bind listener so it can track() shared_ptrs + * using boost::visit_each; // allow unqualified visit_each() call for ADL + * visit_each(visitor, unwrap(f)); + * @endcode + */ + class Visitor + { + public: + /** + * Visitor binds a reference to LLEventListener so we can track() any + * shared_ptrs we find in the argument list. + */ + Visitor(LLEventListener& listener): + mListener(listener) + { + } + + /** + * boost::visit_each() calls this method for each component of a + * boost::bind() expression. + */ + template + void operator()(const T& t) const + { + decode(t, 0); + } + + private: + // decode() decides between a reference wrapper and anything else + // boost::ref() variant + template + void decode(const boost::reference_wrapper& t, int) const + { +// add_if_trackable(t.get_pointer()); + } + + // decode() anything else + template + void decode(const T& t, long) const + { + typedef truth<(boost::is_pointer::value)> is_a_pointer; + maybe_get_pointer(t, is_a_pointer()); + } + + // maybe_get_pointer() decides between a pointer and a non-pointer + // plain pointer variant + template + void maybe_get_pointer(const T& t, truth) const + { +// add_if_trackable(t); + } + + // shared_ptr variant + template + void maybe_get_pointer(const boost::shared_ptr& t, truth) const + { + // If we have a shared_ptr to this object, it doesn't matter + // whether the object is derived from LLEventTrackable, so no + // further analysis of T is needed. +// mListener.track(t); + + // Make this case illegal. Passing a bound shared_ptr to + // slot_type::track() is useless, since the bound shared_ptr will + // keep the object alive anyway! Force the coder to cast to weak_ptr. + + // Trivial as it is, make the BOOST_STATIC_ASSERT() condition + // dependent on template param so the macro is only evaluated if + // this method is in fact instantiated, as described here: + // http://www.boost.org/doc/libs/1_34_1/doc/html/boost_staticassert.html + + // ATTENTION: Don't bind a shared_ptr using + // LLEventPump::listen(boost::bind()). Doing so captures a copy of + // the shared_ptr, making the referenced object effectively + // immortal. Use the weaken() function, e.g.: + // somepump.listen(boost::bind(...weaken(my_shared_ptr)...)); + // This lets us automatically disconnect when the referenced + // object is destroyed. + BOOST_STATIC_ASSERT(sizeof(T) == 0); + } + + // weak_ptr variant + template + void maybe_get_pointer(const boost::weak_ptr& t, truth) const + { + // If we have a weak_ptr to this object, it doesn't matter + // whether the object is derived from LLEventTrackable, so no + // further analysis of T is needed. + mListener.track(t); +// std::cout << "Found weak_ptr<" << typeid(T).name() << ">!\n"; + } + +#if 0 + // reference to anything derived from boost::enable_shared_from_this + template + inline void maybe_get_pointer(const boost::enable_shared_from_this& ct, + truth) const + { + // Use the slot_type::track(shared_ptr) mechanism. Cast away + // const-ness because (in our code base anyway) it's unusual + // to find shared_ptr. + boost::enable_shared_from_this& + t(const_cast&>(ct)); + std::cout << "Capturing shared_from_this()" << std::endl; + boost::shared_ptr sp(t.shared_from_this()); +/*==========================================================================*| + std::cout << "Capturing weak_ptr" << std::endl; + boost::weak_ptr wp(sp); +|*==========================================================================*/ + std::cout << "Tracking shared__ptr" << std::endl; + mListener.track(sp); + } +#endif + + // non-pointer variant + template + void maybe_get_pointer(const T& t, truth) const + { + // Take the address of this object, because the object itself may be + // trackable +// add_if_trackable(boost::addressof(t)); + } + +/*==========================================================================*| + // add_if_trackable() adds LLEventTrackable objects to mTrackables + inline void add_if_trackable(const LLEventTrackable* t) const + { + if (t) + { + } + } + + // pointer to anything not an LLEventTrackable subclass + inline void add_if_trackable(const void*) const + { + } + + // pointer to free function + // The following construct uses the preprocessor to generate + // add_if_trackable() overloads accepting pointer-to-function taking + // 0, 1, ..., LLEVENTS_LISTENER_ARITY parameters of arbitrary type. +#define BOOST_PP_LOCAL_MACRO(n) \ + template \ + inline void \ + add_if_trackable(R (*)(BOOST_PP_ENUM_PARAMS(n, T))) const \ + { \ + } +#define BOOST_PP_LOCAL_LIMITS (0, LLEVENTS_LISTENER_ARITY) +#include BOOST_PP_LOCAL_ITERATE() +#undef BOOST_PP_LOCAL_MACRO +#undef BOOST_PP_LOCAL_LIMITS +|*==========================================================================*/ + + /// Bind a reference to the LLEventListener to call its track() method. + LLEventListener& mListener; + }; + + /** + * Utility template function to use Visitor appropriately + * + * @param raw_listener Callable to connect, typically a boost::bind() + * expression. This will be visited by Visitor using boost::visit_each(). + * @param connect_funct Callable that will connect() @a raw_listener to an + * LLStandardSignal, returning LLBoundListener. + */ + template + LLBoundListener visit_and_connect(const std::string& name, + const LISTENER& raw_listener, + const ConnectFunc& connect_func) + { + // Capture the listener + LLEventListener listener(raw_listener); + // Define our Visitor, binding the listener so we can call + // listener.track() if we discover any shared_ptr. + LLEventDetail::Visitor visitor(listener); + // Allow unqualified visit_each() call for ADL + using boost::visit_each; + // Visit each component of a boost::bind() expression. Pass + // 'raw_listener', our template argument, rather than 'listener' from + // which type details have been erased. unwrap() comes from + // Boost.Signals, in case we were passed a boost::ref(). + visit_each(visitor, LLEventDetail::unwrap(raw_listener)); + // Make the connection using passed function. + LLBoundListener connection(connect_func(listener)); + // If the LISTENER is an LLListenerWrapperBase subclass, pass it the + // desired information. It's important that we pass the raw_listener + // so the compiler can make decisions based on its original type. + const LLListenerWrapperBase* lwb = + ll_template_cast(&raw_listener); + if (lwb) + { + lwb->accept_name(name); + lwb->accept_connection(connection); + } + // In any case, show new connection to caller. + return connection; + } +} // namespace LLEventDetail + +// Somewhat to my surprise, passing boost::bind(...boost::weak_ptr...) to +// listen() fails in Boost code trying to instantiate LLEventListener (i.e. +// LLStandardSignal::slot_type) because the boost::get_pointer() utility function isn't +// specialized for boost::weak_ptr. This remedies that omission. +namespace boost +{ + template + T* get_pointer(const weak_ptr& ptr) { return shared_ptr(ptr).get(); } +} + +/// Since we forbid use of listen(boost::bind(...shared_ptr...)), provide an +/// easy way to cast to the corresponding weak_ptr. +template +boost::weak_ptr weaken(const boost::shared_ptr& ptr) +{ + return boost::weak_ptr(ptr); +} + +#endif /* ! defined(LL_LLEVENTS_H) */ diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 6cc129166..8dd62f97e 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -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 diff --git a/indra/llmessage/llregionflags.h b/indra/llmessage/llregionflags.h index 232478577..7b796a0fa 100644 --- a/indra/llmessage/llregionflags.h +++ b/indra/llmessage/llregionflags.h @@ -2,31 +2,25 @@ * @file llregionflags.h * @brief Flags that are sent in the statistics message region_flags field. * - * $LicenseInfo:firstyear=2002&license=viewergpl$ - * - * Copyright (c) 2002-2009, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * Copyright (C) 2010, Linden Research, Inc. * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * 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. * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. + * 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. * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * 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$ */ @@ -48,9 +42,6 @@ const U32 REGION_FLAGS_RESET_HOME_ON_TELEPORT = (1 << 3); // Does the sun move? const U32 REGION_FLAGS_SUN_FIXED = (1 << 4); -// Tax free zone (no taxes on objects, land, etc.) -const U32 REGION_FLAGS_TAX_FREE = (1 << 5); - // Can't change the terrain heightfield, even on owned parcels, // but can plant trees and grass. const U32 REGION_FLAGS_BLOCK_TERRAFORM = (1 << 6); @@ -60,17 +51,12 @@ const U32 REGION_FLAGS_BLOCK_LAND_RESELL = (1 << 7); // All content wiped once per night const U32 REGION_FLAGS_SANDBOX = (1 << 8); -const U32 REGION_FLAGS_NULL_LAYER = (1 << 9); -// const U32 REGION_FLAGS_SKIP_AGENT_ACTION = (1 << 10); -const U32 REGION_FLAGS_HARD_ALLOW_LAND_TRANSFER = (1 << 10); // Region allows land reselling -// const U32 REGION_FLAGS_SKIP_UPDATE_INTEREST_LIST= (1 << 11); -const U32 REGION_FLAGS_HARD_ALLOW_POST_CLASSIFIED = (1 << 11); // Region allows posting of classified ads const U32 REGION_FLAGS_SKIP_COLLISIONS = (1 << 12); // Pin all non agent rigid bodies const U32 REGION_FLAGS_SKIP_SCRIPTS = (1 << 13); const U32 REGION_FLAGS_SKIP_PHYSICS = (1 << 14); // Skip all physics const U32 REGION_FLAGS_EXTERNALLY_VISIBLE = (1 << 15); -//const U32 REGION_FLAGS_MAINLAND_VISIBLE = (1 << 16); -const U32 REGION_FLAGS_PUBLIC_ALLOWED = (1 << 17); +const U32 REGION_FLAGS_ALLOW_RETURN_ENCROACHING_OBJECT = (1 << 16); +const U32 REGION_FLAGS_ALLOW_RETURN_ENCROACHING_ESTATE_OBJECT = (1 << 17); const U32 REGION_FLAGS_BLOCK_DWELL = (1 << 18); // Is flight allowed? @@ -87,18 +73,13 @@ const U32 REGION_FLAGS_ESTATE_SKIP_SCRIPTS = (1 << 21); const U32 REGION_FLAGS_RESTRICT_PUSHOBJECT = (1 << 22); const U32 REGION_FLAGS_DENY_ANONYMOUS = (1 << 23); -// const U32 REGION_FLAGS_DENY_IDENTIFIED = (1 << 24); -// const U32 REGION_FLAGS_DENY_TRANSACTED = (1 << 25); const U32 REGION_FLAGS_ALLOW_PARCEL_CHANGES = (1 << 26); -const U32 REGION_FLAGS_ABUSE_EMAIL_TO_ESTATE_OWNER = (1 << 27); - const U32 REGION_FLAGS_ALLOW_VOICE = (1 << 28); const U32 REGION_FLAGS_BLOCK_PARCEL_SEARCH = (1 << 29); const U32 REGION_FLAGS_DENY_AGEUNVERIFIED = (1 << 30); -const U32 REGION_FLAGS_SKIP_MONO_SCRIPTS = (1 << 31); const U32 REGION_FLAGS_DEFAULT = REGION_FLAGS_ALLOW_LANDMARK | REGION_FLAGS_ALLOW_SET_HOME | @@ -111,7 +92,6 @@ const U32 REGION_FLAGS_PRELUDE_UNSET = REGION_FLAGS_ALLOW_LANDMARK | REGION_FLAGS_ALLOW_SET_HOME; const U32 REGION_FLAGS_ESTATE_MASK = REGION_FLAGS_EXTERNALLY_VISIBLE - | REGION_FLAGS_PUBLIC_ALLOWED | REGION_FLAGS_SUN_FIXED | REGION_FLAGS_DENY_ANONYMOUS | REGION_FLAGS_DENY_AGEUNVERIFIED; diff --git a/indra/llmessage/llsdmessage.cpp b/indra/llmessage/llsdmessage.cpp new file mode 100644 index 000000000..9148c9dd1 --- /dev/null +++ b/indra/llmessage/llsdmessage.cpp @@ -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() +{ +} diff --git a/indra/llmessage/llsdmessage.h b/indra/llmessage/llsdmessage.h new file mode 100644 index 000000000..0d34847ff --- /dev/null +++ b/indra/llmessage/llsdmessage.h @@ -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 +#include + +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 + * for transitional purposes only. 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) */ diff --git a/indra/llui/llmemberlistener.h b/indra/llui/llmemberlistener.h index a5e019c33..0760213c7 100644 --- a/indra/llui/llmemberlistener.h +++ b/indra/llui/llmemberlistener.h @@ -62,7 +62,7 @@ #include "llevent.h" template -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 event, const LLSD& userdata) = 0; + virtual bool handleEvent(LLPointer event, const LLSD& userdata) = 0; protected: T *mPtr; // The object that this listener manipulates diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 9b60fbaaa..21a6711c4 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -66,6 +66,8 @@ #include #include +using namespace LLOldEvents; + // static LLMenuHolderGL *LLMenuGL::sMenuContainer = NULL; diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 2150d5584..c4325ac4e 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -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 diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index c513b82f9..aac44ddf1 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -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 - 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 LLNotificationResponder; typedef LLFunctorRegistry LLNotificationFunctorRegistry; typedef LLFunctorRegistration LLNotificationFunctorRegistration; -typedef boost::signals2::signal 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 diff --git a/indra/llui/lltrans.cpp b/indra/llui/lltrans.cpp index 8fb40e5d6..eee2ddcf2 100644 --- a/indra/llui/lltrans.cpp +++ b/indra/llui/lltrans.cpp @@ -113,6 +113,69 @@ std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil:: } } +//static +std::string LLTrans::getString(const std::string &xml_desc, const LLSD& msg_args) +{ + // Don't care about time as much as call count. Make sure we're not + // calling LLTrans::getString() in an inner loop. JC + //V3: LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format(text, msg_args); + return text; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return "MissingString("+xml_desc+")"; + } +} + +//static +bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& msg_args) +{ + //V3: LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format_map_t args = sDefaultArgs; + args.insert(msg_args.begin(), msg_args.end()); + LLStringUtil::format(text, args); + result = text; + return true; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return false; + } +} + +//static +bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLSD& msg_args) +{ + //V3: LLFastTimer timer(FTM_GET_TRANS); + + template_map_t::iterator iter = sStringTemplates.find(xml_desc); + if (iter != sStringTemplates.end()) + { + std::string text = iter->second.mText; + LLStringUtil::format(text, msg_args); + result = text; + return true; + } + else + { + LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL; + return false; + } +} + void LLTrans::setDefaultArg(const std::string& name, const std::string& value) { sDefaultArgs[name] = value; diff --git a/indra/llui/lltrans.h b/indra/llui/lltrans.h index 56ed08f3b..b997d6954 100644 --- a/indra/llui/lltrans.h +++ b/indra/llui/lltrans.h @@ -60,12 +60,14 @@ public: /** * @brief Parses the xml file that holds the strings. Used once on startup - * @param xml_filename Filename to parse +// *FIXME * @param xml_filename Filename to parse * @param default_args Set of strings (expected to be in the file) to use as default replacement args, e.g. "SECOND_LIFE" * @returns true if the file was parsed successfully, true if something went wrong */ static bool parseStrings(const std::string& xml_filename, const std::set& default_args); + //V3: static bool parseLanguageStrings(LLPointer & root); + /** * @brief Returns a translated string * @param xml_desc String's description @@ -73,6 +75,9 @@ public: * @returns Translated string */ static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args); + static std::string getString(const std::string &xml_desc, const LLSD& args); + static bool findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& args); + static bool findString(std::string &result, const std::string &xml_desc, const LLSD& args); /** * @brief Returns a translated string @@ -84,7 +89,20 @@ public: LLStringUtil::format_map_t empty; return getString(xml_desc, empty); } - + + static bool findString(std::string &result, const std::string &xml_desc) + { + LLStringUtil::format_map_t empty; + return findString(result, xml_desc, empty); + } + + static std::string getKeyboardString(const char* keystring) + { + std::string key_str(keystring); + std::string trans_str; + return findString(trans_str, key_str) ? trans_str : key_str; + } + // get the default args static const LLStringUtil::format_map_t& getDefaultArgs() { diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 61030a8f1..68616dc12 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -63,6 +63,8 @@ #include "lldelayeduidelete.h" // +using namespace LLOldEvents; + //HACK: this allows you to instantiate LLView from xml with "" which we don't want static LLRegisterWidget r("view"); diff --git a/indra/llui/llview.h b/indra/llui/llview.h index d69360515..51d75870c 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -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 event, const LLSD& userdata); +virtual bool handleEvent(LLPointer 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 event, const LLSD& userdata)> event_signal_t; + typedef boost::signals2::signal 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 event, const LLSD& userdata); +// virtual bool handleEvent(LLPointer 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 > dispatch_list_t; + typedef std::map > dispatch_list_t; dispatch_list_t mDispatchList; std::string mControlName; diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 66b5b4b3a..99c5b8c38 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -19,7 +19,7 @@ endif(NOT FMODEX) include(OPENAL) include(HUNSPELL) include(FindOpenGL) -include(JsonCpp) +#include(JsonCpp) include(LLAddBuildTest) include(LLAudio) include(LLCharacter) @@ -134,6 +134,7 @@ set(viewer_SOURCE_FILES llbuildnewviewsscheduler.cpp llcallbacklist.cpp llcallingcard.cpp + llcapabilitylistener.cpp llcaphttpsender.cpp llchatbar.cpp llclassifiedinfo.cpp @@ -441,7 +442,6 @@ set(viewer_SOURCE_FILES lltoolselectrect.cpp lltoolview.cpp lltracker.cpp - lltranslate.cpp lluploaddialog.cpp lluploadfloaterobservers.cpp llurl.cpp @@ -492,6 +492,7 @@ set(viewer_SOURCE_FILES llviewerregion.cpp llviewershadermgr.cpp llviewerstats.cpp + llviewerstatsrecorder.cpp llviewertexteditor.cpp llviewertexture.cpp llviewertextureanim.cpp @@ -620,6 +621,7 @@ set(viewer_HEADER_FILES llbuildnewviewsscheduler.h llcallbacklist.h llcallingcard.h + llcapabilitylistener.h llcaphttpsender.h llchatbar.h llclassifiedinfo.h @@ -982,6 +984,7 @@ set(viewer_HEADER_FILES llviewerregion.h llviewershadermgr.h llviewerstats.h + llviewerstatsrecorder.h llviewertexteditor.h llviewertexture.h llviewertextureanim.h diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index bffb687d7..b618efa72 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -57,6 +57,7 @@ #include "llnotificationsutil.h" #include "llparcel.h" #include "llrendersphere.h" +#include "llsdmessage.h" #include "llsdutil.h" #include "llsky.h" #include "llsmoothstep.h" @@ -2105,11 +2106,15 @@ LLVector3 ll_vector3_from_sdmap(const LLSD& sd) void LLAgent::setStartPosition( U32 location_id ) { + LLViewerObject *object; + if (gAgentID == LLUUID::null) { return; } - if (gObjectList.findAvatar(gAgentID) == NULL) + // we've got an ID for an agent viewerobject + object = gObjectList.findObject(gAgentID); + if (! object) { llinfos << "setStartPosition - Can't find agent viewerobject id " << gAgentID << llendl; return; @@ -2126,7 +2131,7 @@ void LLAgent::setStartPosition( U32 location_id ) if (isAgentAvatarValid()) { // the z height is at the agent's feet - agent_pos.mV[VZ] -= 0.5f * gAgentAvatarp->mBodySize.mV[VZ]; + agent_pos.mV[VZ] -= 0.5f * gAgentAvatarp->mBodySize.mV[VZ]; } agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET ); @@ -2136,46 +2141,44 @@ void LLAgent::setStartPosition( U32 location_id ) agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ], mRegionp->getLandHeightRegion( agent_pos ), LLWorld::getInstance()->getRegionMaxHeight() ); - std::string url = gAgent.getRegion()->getCapability("HomeLocation"); - if( !url.empty() ) - { - // Send the CapReq - LLSD request; - LLSD body; - LLSD homeLocation; + // Send the CapReq + LLSD request; + LLSD body; + LLSD homeLocation; - homeLocation["LocationId"] = LLSD::Integer(location_id); - homeLocation["LocationPos"] = ll_sdmap_from_vector3(agent_pos); - homeLocation["LocationLookAt"] = ll_sdmap_from_vector3(mFrameAgent.getAtAxis()); + homeLocation["LocationId"] = LLSD::Integer(location_id); + homeLocation["LocationPos"] = ll_sdmap_from_vector3(agent_pos); + homeLocation["LocationLookAt"] = ll_sdmap_from_vector3(mFrameAgent.getAtAxis()); - body["HomeLocation"] = homeLocation; + body["HomeLocation"] = homeLocation; - LLHTTPClient::post( url, body, new LLHomeLocationResponder() ); - } - else - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_SetStartLocationRequest); - msg->nextBlockFast( _PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, getID()); - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - msg->nextBlockFast( _PREHASH_StartLocationData); - // corrected by sim - msg->addStringFast(_PREHASH_SimName, ""); - msg->addU32Fast(_PREHASH_LocationID, location_id); - msg->addVector3Fast(_PREHASH_LocationPos, agent_pos); - msg->addVector3Fast(_PREHASH_LocationLookAt,mFrameAgent.getAtAxis()); - - // Reliable only helps when setting home location. Last - // location is sent on quit, and we don't have time to ack - // the packets. - msg->sendReliable(mRegionp->getHost()); - } - const U32 HOME_INDEX = 1; - if( HOME_INDEX == location_id ) - { - setHomePosRegion( mRegionp->getHandle(), getPositionAgent() ); - } + // This awkward idiom warrants explanation. + // For starters, LLSDMessage::ResponderAdapter is ONLY for testing the new + // LLSDMessage functionality with a pre-existing LLHTTPClient::Responder. + // In new code, define your reply/error methods on the same class as the + // sending method, bind them to local LLEventPump objects and pass those + // LLEventPump names in the request LLSD object. + // When testing old code, the new LLHomeLocationResponder object + // is referenced by an LLHTTPClient::ResponderPtr, so when the + // ResponderAdapter is deleted, the LLHomeLocationResponder will be too. + // We must trust that the underlying LLHTTPClient code will eventually + // fire either the reply callback or the error callback; either will cause + // the ResponderAdapter to delete itself. + LLSDMessage::ResponderAdapter* + adapter(new LLSDMessage::ResponderAdapter(new LLHomeLocationResponder())); + + request["message"] = "HomeLocation"; + request["payload"] = body; + request["reply"] = adapter->getReplyName(); + request["error"] = adapter->getErrorName(); + + gAgent.getRegion()->getCapAPI().post(request); + + const U32 HOME_INDEX = 1; + if( HOME_INDEX == location_id ) + { + setHomePosRegion( mRegionp->getHandle(), getPositionAgent() ); + } } void LLAgent::requestStopMotion( LLMotion* motion ) @@ -2818,7 +2821,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 diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index 846edf8d4..145644f7b 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -92,7 +92,7 @@ struct LLGroupData // -class LLAgent : public LLObservable +class LLAgent : public LLOldEvents::LLObservable { LOG_CLASS(LLAgent); diff --git a/indra/newview/llagentlanguage.h b/indra/newview/llagentlanguage.h index 6bc7250c6..596c58423 100644 --- a/indra/newview/llagentlanguage.h +++ b/indra/newview/llagentlanguage.h @@ -36,7 +36,7 @@ #include "llmemory.h" // LLSingleton<> #include "llevent.h" -class LLAgentLanguage: public LLSingleton, public LLSimpleListener +class LLAgentLanguage: public LLSingleton, public LLOldEvents::LLSimpleListener { public: LLAgentLanguage(); diff --git a/indra/newview/llcapabilitylistener.cpp b/indra/newview/llcapabilitylistener.cpp new file mode 100644 index 000000000..ef9b910ae --- /dev/null +++ b/indra/newview/llcapabilitylistener.cpp @@ -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 +// std headers +// external library headers +#include +// other Linden headers +#include "stringize.h" +#include "llcapabilityprovider.h" +#include "message.h" + +class LLCapabilityListener::CapabilityMappers: public LLSingleton +{ +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; + CapabilityMappers(); + + typedef std::map 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 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; +} diff --git a/indra/newview/llcapabilitylistener.h b/indra/newview/llcapabilitylistener.h new file mode 100644 index 000000000..e7535013e --- /dev/null +++ b/indra/newview/llcapabilitylistener.h @@ -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) */ diff --git a/indra/newview/llfloateractivespeakers.cpp b/indra/newview/llfloateractivespeakers.cpp index 282f9f842..6db1c30fa 100644 --- a/indra/newview/llfloateractivespeakers.cpp +++ b/indra/newview/llfloateractivespeakers.cpp @@ -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); diff --git a/indra/newview/llfloateractivespeakers.h b/indra/newview/llfloateractivespeakers.h index d85f3c076..8a70e77a0 100644 --- a/indra/newview/llfloateractivespeakers.h +++ b/indra/newview/llfloateractivespeakers.h @@ -49,7 +49,7 @@ class LLVoiceChannel; // data for a given participant in a voice channel -class LLSpeaker : public LLRefCount, public LLObservable, public LLHandleProvider +class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider { 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 event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer 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 event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer 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 event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer 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 event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); LLPanelActiveSpeakers* mPanel; }; diff --git a/indra/newview/llfloaterbuyland.cpp b/indra/newview/llfloaterbuyland.cpp index c80708879..4926453e0 100644 --- a/indra/newview/llfloaterbuyland.cpp +++ b/indra/newview/llfloaterbuyland.cpp @@ -540,7 +540,7 @@ void LLFloaterBuyLandUI::updateCovenantInfo() LLTextBox* region_type = getChild("region_type_text"); if (region_type) { - region_type->setText(region->getSimProductName()); + region_type->setText(region->getLocalizedSimProductName()); } LLTextBox* resellable_clause = getChild("resellable_clause"); diff --git a/indra/newview/llfloatergroups.cpp b/indra/newview/llfloatergroups.cpp index 0e2f8d6d0..ddc601f9d 100644 --- a/indra/newview/llfloatergroups.cpp +++ b/indra/newview/llfloatergroups.cpp @@ -62,6 +62,8 @@ #include "hippolimits.h" +using namespace LLOldEvents; + // static std::map LLFloaterGroupPicker::sInstances; diff --git a/indra/newview/llfloatergroups.h b/indra/newview/llfloatergroups.h index c0d06e7c5..5311809eb 100644 --- a/indra/newview/llfloatergroups.h +++ b/indra/newview/llfloatergroups.h @@ -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 event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); // clear the group list, and get a fresh set of info. void reset(); diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 451d4472b..14260b1dc 100644 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -525,7 +525,7 @@ void LLPanelLandGeneral::refresh() if (regionp) { mContentRating->setText(regionp->getSimAccessString()); - mLandType->setText(regionp->getSimProductName()); + mLandType->setText(regionp->getLocalizedSimProductName()); } // estate owner/manager cannot edit other parts of the parcel @@ -2907,7 +2907,7 @@ void LLPanelLandCovenant::refresh() LLTextBox* region_landtype = getChild("region_landtype_text"); if (region_landtype) { - region_landtype->setText(region->getSimProductName()); + region_landtype->setText(region->getLocalizedSimProductName()); } LLTextBox* region_maturity = getChild("region_maturity_text"); diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 54ab0701d..794e774fe 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -2720,7 +2720,7 @@ bool LLPanelEstateCovenant::refreshFromRegion(LLViewerRegion* region) LLTextBox* region_landtype = getChild("region_landtype_text"); if (region_landtype) { - region_landtype->setText(region->getSimProductName()); + region_landtype->setText(region->getLocalizedSimProductName()); } diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp index b2925b181..5888bb80f 100644 --- a/indra/newview/llfloaterreporter.cpp +++ b/indra/newview/llfloaterreporter.cpp @@ -107,7 +107,6 @@ LLMap< EReportType, LLFloaterReporter* > gReporterInstances; // keeps track of where email is going to - global to avoid a pile // of static/non-static access outside my control namespace { - static BOOL gEmailToEstateOwner = FALSE; static BOOL gDialogVisible = FALSE; } @@ -227,16 +226,10 @@ void LLFloaterReporter::processRegionInfo(LLMessageSystem* msg) { U32 region_flags; msg->getU32("RegionInfo", "RegionFlags", region_flags); - gEmailToEstateOwner = ( region_flags & REGION_FLAGS_ABUSE_EMAIL_TO_ESTATE_OWNER ); if ( gDialogVisible ) { - if ( gEmailToEstateOwner ) - { - LLNotificationsUtil::add("HelpReportAbuseEmailEO"); - } - else - LLNotificationsUtil::add("HelpReportAbuseEmailLL"); + LLNotificationsUtil::add("HelpReportAbuseEmailLL"); }; } @@ -264,18 +257,7 @@ LLFloaterReporter::~LLFloaterReporter() // virtual void LLFloaterReporter::draw() { - // this is set by a static callback sometime after the dialog is created. - // Only disable screenshot for abuse reports to estate owners - bug reports always - // allow screenshots to be taken. - if ( gEmailToEstateOwner && ( mReportType != BUG_REPORT ) ) - { - childSetValue("screen_check", FALSE ); - childSetEnabled("screen_check", FALSE ); - } - else - { - childSetEnabled("screen_check", TRUE ); - } + childSetEnabled("screen_check", TRUE ); LLFloater::draw(); } @@ -805,17 +787,7 @@ LLSD LLFloaterReporter::gatherReport() LLUUID screenshot_id = LLUUID::null; if (childGetValue("screen_check")) { - if ( mReportType != BUG_REPORT ) - { - if ( gEmailToEstateOwner == FALSE ) - { - screenshot_id = childGetValue("screenshot"); - } - } - else - { - screenshot_id = childGetValue("screenshot"); - }; + screenshot_id = childGetValue("screenshot"); }; LLSD report = LLSD::emptyMap(); diff --git a/indra/newview/llinventoryactions.cpp b/indra/newview/llinventoryactions.cpp index 5c081054d..eba565ca9 100644 --- a/indra/newview/llinventoryactions.cpp +++ b/indra/newview/llinventoryactions.cpp @@ -99,6 +99,8 @@ #include "statemachine/aifilepicker.h" // +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) diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 1b25d3fa0..993f8b583 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -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() diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp index 716dc1b8b..07adaa136 100644 --- a/indra/newview/llnetmap.cpp +++ b/indra/newview/llnetmap.cpp @@ -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; diff --git a/indra/newview/llnetmap.h b/indra/newview/llnetmap.h index 2efae83dd..a709761d6 100644 --- a/indra/newview/llnetmap.h +++ b/indra/newview/llnetmap.h @@ -130,55 +130,55 @@ private: class LLScaleMap : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLCenterMap : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLCheckCenterMap : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLRotateMap : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLCheckRotateMap : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLStopTracking : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLEnableTracking : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLShowAgentProfile : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class LLCamFollow : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; @@ -188,37 +188,37 @@ private: class mmsetred : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmsetgreen : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmsetblue : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmsetyellow : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmsetcustom : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmsetunmark : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; class mmenableunmark : public LLMemberListener //moymod { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; @@ -228,7 +228,7 @@ private: class LLEnableProfile : public LLMemberListener { public: - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); }; }; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 4feeee42f..e67c6e627 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -2038,7 +2038,7 @@ bool idle_startup() //--------------------------------------------------------------------- if(STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState()) { - /*LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(gFirstSimHandle); + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(gFirstSimHandle); if (regionp->capabilitiesReceived()) { LLStartUp::setStartupState( STATE_SEED_CAP_GRANTED ); @@ -2056,7 +2056,7 @@ bool idle_startup() { set_startup_status(0.4f, LLTrans::getString("LoginRequestSeedCapGrant"), gAgent.mMOTD); } - }*/ + } return FALSE; } diff --git a/indra/newview/lltranslate.h b/indra/newview/lltranslate.h index 2723c43ff..5fd85f534 100644 --- a/indra/newview/lltranslate.h +++ b/indra/newview/lltranslate.h @@ -35,7 +35,7 @@ #include "llhttpclient.h" #include "llbufferstream.h" -#include "jsoncpp/reader.h" +#include "json/reader.h" class LLTranslate { diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 60491e952..667b31fad 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -911,7 +911,11 @@ public: } }; -void copy_inventory_from_notecard(const LLUUID& object_id, const LLUUID& notecard_inv_id, const LLInventoryItem *src, U32 callback_id) +void copy_inventory_from_notecard(/* V3: const LLUUID& destination_id, */ + const LLUUID& object_id, + const LLUUID& notecard_inv_id, + const LLInventoryItem *src, + U32 callback_id) { if (NULL == src) { @@ -930,7 +934,7 @@ void copy_inventory_from_notecard(const LLUUID& object_id, const LLUUID& notecar // Fallback to the agents region if for some reason the // object isn't found in the viewer. - if(!viewer_region) + if (! viewer_region) { viewer_region = gAgent.getRegion(); } @@ -958,9 +962,13 @@ void copy_inventory_from_notecard(const LLUUID& object_id, const LLUUID& notecar body["object-id"] = object_id; body["item-id"] = src->getUUID(); body["folder-id"] = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(src->getType())); + // V3: body["folder-id"] = destination_id; body["callback-id"] = (LLSD::Integer)callback_id; - LLHTTPClient::post(url, body, new LLCopyInventoryFromNotecardResponder()); + request["message"] = "CopyInventoryFromNotecard"; + request["payload"] = body; + + viewer_region->getCapAPI().post(request); } diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index a2a714221..2aee848f0 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -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); diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 9c08adf8e..63a9e002a 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -96,6 +96,8 @@ #include "importtracker.h" +using namespace LLOldEvents; + std::deque gUploadQueue; typedef LLMemberListener view_listener_t; diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 97ac3eb05..e0ebcf7c4 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -145,7 +145,6 @@ #include "llfloaterworldmap.h" #include "llviewerdisplay.h" #include "llkeythrottle.h" -#include "lltranslate.h" // #include "llviewernetwork.h" // @@ -2954,6 +2953,7 @@ void process_decline_callingcard(LLMessageSystem* msg, void**) LLNotificationsUtil::add("CallingCardDeclined"); } +#if 0 // Google translate doesn't work anymore class ChatTranslationReceiver : public LLTranslate::TranslationReceiver { public : @@ -2996,6 +2996,7 @@ private: LLChat *m_chat; const BOOL m_history; }; +#endif void add_floater_chat(const LLChat &chat, const BOOL history) { @@ -3011,6 +3012,7 @@ void add_floater_chat(const LLChat &chat, const BOOL history) } } +#if 0 // Google translate doesn't work anymore void check_translate_chat(const std::string &mesg, LLChat &chat, const BOOL history) { const bool translate = LLUI::sConfigGroup->getBOOL("TranslateChat"); @@ -3031,6 +3033,7 @@ void check_translate_chat(const std::string &mesg, LLChat &chat, const BOOL hist add_floater_chat(chat, history); } } +#endif // defined in llchatbar.cpp, but not declared in any header void send_chat_from_viewer(std::string utf8_out_text, EChatType type, S32 channel); @@ -3523,29 +3526,22 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) } // truth table: - // LINDEN BUSY MUTED OWNED_BY_YOU TASK DISPLAY STORE IN HISTORY - // F F F F * Yes Yes - // F F F T * Yes Yes - // F F T F * No No - // F F T T * No No - // F T F F * No Yes - // F T F T * Yes Yes - // F T T F * No No - // F T T T * No No + // LINDEN MUTED BUSY OWNED_BY_YOU TASK DISPLAY STORE IN HISTORY + // F T * * * No No + // F F T F * No Yes + // * F F * * Yes Yes + // * F * T * Yes Yes // T * * * F Yes Yes chat.mMuted = is_muted && !is_linden; - - if (!visible_in_chat_bubble - && (is_linden || !is_busy || is_owned_by_me)) + if (!chat.mMuted) { - // show on screen and add to history - check_translate_chat(mesg, chat, FALSE); - } - else - { - // just add to chat history - check_translate_chat(mesg, chat, TRUE); + bool only_history = visible_in_chat_bubble || (!is_linden && !is_owned_by_me && is_busy); +#if 0 // Google translate doesn't work anymore + check_translate_chat(mesg, chat, only_history); +#else + add_floater_chat(chat, only_history); +#endif } } } diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 4b3660daf..48fb69023 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -34,7 +34,10 @@ #include "llviewerregion.h" +// linden libraries #include "indra_constants.h" +#include "llavatarnamecache.h" // name lookup cap url +//#include "llfloaterreg.h" #include "llmath.h" #include "llhttpclient.h" #include "llregionflags.h" @@ -49,6 +52,8 @@ #include "llagentcamera.h" #include "llcallingcard.h" #include "llcaphttpsender.h" +#include "llcapabilitylistener.h" +#include "llcommandhandler.h" #include "lldir.h" #include "lleventpoll.h" #include "llfloatergodtools.h" @@ -62,6 +67,7 @@ #include "llurldispatcher.h" #include "llviewerobjectlist.h" #include "llviewerparceloverlay.h" +#include "llviewerstatsrecorder.h" #include "llvlmanager.h" #include "llvlcomposition.h" #include "llvocache.h" @@ -71,9 +77,6 @@ #include "stringize.h" #include "llviewercontrol.h" #include "llsdserialize.h" -#include "llviewerparcelmgr.h" - -extern BOOL gNoRender; #ifdef LL_WINDOWS #pragma warning(disable:4355) @@ -81,6 +84,14 @@ extern BOOL gNoRender; const F32 WATER_TEXTURE_SCALE = 8.f; // Number of times to repeat the water texture across a region const S16 MAX_MAP_DIST = 10; +// The server only keeps our pending agent info for 60 seconds. +// We want to allow for seed cap retry, but its not useful after that 60 seconds. +// Give it 3 chances, each at 18 seconds to give ourselves a few seconds to connect anyways if we give up. +const S32 MAX_SEED_CAP_ATTEMPTS_BEFORE_LOGIN = 3; +const F32 CAP_REQUEST_TIMEOUT = 18; +// Even though we gave up on login, keep trying for caps after we are logged in: +const S32 MAX_CAP_REQUEST_ATTEMPTS = 30; + typedef std::map CapabilityMap; class LLViewerRegionImpl { @@ -88,7 +99,11 @@ public: LLViewerRegionImpl(LLViewerRegion * region, LLHost const & host) : mHost(host), mCompositionp(NULL), - mEventPoll(NULL)//, + mEventPoll(NULL), + mSeedCapMaxAttempts(MAX_CAP_REQUEST_ATTEMPTS), + mSeedCapMaxAttemptsBeforeLogin(MAX_SEED_CAP_ATTEMPTS_BEFORE_LOGIN), + mSeedCapAttempts(0), + mHttpResponderID(0), // I'd prefer to set the LLCapabilityListener name to match the region // name -- it's disappointing that's not available at construction time. // We could instead store an LLCapabilityListener*, making @@ -98,8 +113,8 @@ public: // For testability -- the new Michael Feathers paradigm -- // LLCapabilityListener binds all the globals it expects to need at // construction time. - //mCapabilityListener(host.getString(), gMessageSystem, *region, - //gAgent.getID(), gAgent.getSessionID()) + mCapabilityListener(host.getString(), gMessageSystem, *region, + gAgent.getID(), gAgent.getSessionID()) { } @@ -137,85 +152,125 @@ public: LLEventPoll* mEventPoll; + S32 mSeedCapMaxAttempts; + S32 mSeedCapMaxAttemptsBeforeLogin; + S32 mSeedCapAttempts; + + S32 mHttpResponderID; + /// Post an event to this LLCapabilityListener to invoke a capability message on /// this LLViewerRegion's server /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) - //LLCapabilityListener mCapabilityListener; + LLCapabilityListener mCapabilityListener; //spatial partitions for objects in this region std::vector mObjectPartition; - - LLHTTPClient::ResponderPtr mHttpResponderPtr ; }; +// support for secondlife:///app/region/{REGION} SLapps +// N.B. this is defined to work exactly like the classic secondlife://{REGION} +// However, the later syntax cannot support spaces in the region name because +// spaces (and %20 chars) are illegal in the hostname of an http URL. Some +// browsers let you get away with this, but some do not (such as Qt's Webkit). +// Hence we introduced the newer secondlife:///app/region alternative. +class LLRegionHandler : public LLCommandHandler +{ +public: + // requests will be throttled from a non-trusted browser + LLRegionHandler() : LLCommandHandler("region", /*V3: UNTRUSTED_THROTTLE*/ true) {} + + bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web) + { + // make sure that we at least have a region name + int num_params = params.size(); + if (num_params < 1) + { + return false; + } + + // build a secondlife://{PLACE} SLurl from this SLapp + std::string url = "secondlife://"; + for (int i = 0; i < num_params; i++) + { + if (i > 0) + { + url += "/"; + } + url += params[i].asString(); + } + + // Process the SLapp as if it was a secondlife://{PLACE} SLurl + LLURLDispatcher::dispatch(url, /*V3: "clicked",*/ web, true); + return true; + } +}; +LLRegionHandler gRegionHandler; + class BaseCapabilitiesComplete : public LLHTTPClient::Responder { LOG_CLASS(BaseCapabilitiesComplete); public: - BaseCapabilitiesComplete(LLViewerRegion* region) - : mRegion(region) + BaseCapabilitiesComplete(U64 region_handle, S32 id) + : mRegionHandle(region_handle), mID(id) { } virtual ~BaseCapabilitiesComplete() - { - if(mRegion) - { - mRegion->setHttpResponderPtrNULL() ; - } - } - - void setRegion(LLViewerRegion* region) - { - mRegion = region ; - } + { } void error(U32 statusNum, const std::string& reason) { LL_WARNS2("AppInit", "Capabilities") << statusNum << ": " << reason << LL_ENDL; - - if (STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState()) + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(mRegionHandle); + if (regionp) { - LLStartUp::setStartupState( STATE_SEED_CAP_GRANTED ); + regionp->failedSeedCapability(); } } void result(const LLSD& content) { - if(!mRegion || LLHTTPClient::ResponderPtr(this) != mRegion->getHttpResponderPtr()) //region is removed or responder is not created. + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(mRegionHandle); + if(!regionp) //region was removed { + LL_WARNS2("AppInit", "Capabilities") << "Received results for region that no longer exists!" << LL_ENDL; + return ; + } + if( mID != regionp->getHttpResponderID() ) // region is no longer referring to this responder + { + LL_WARNS2("AppInit", "Capabilities") << "Received results for a stale http responder!" << LL_ENDL; return ; } LLSD::map_const_iterator iter; for(iter = content.beginMap(); iter != content.endMap(); ++iter) { - mRegion->setCapability(iter->first, iter->second); + regionp->setCapability(iter->first, iter->second); LL_DEBUGS2("AppInit", "Capabilities") << "got capability for " << iter->first << LL_ENDL; /* HACK we're waiting for the ServerReleaseNotes */ - if (iter->first == "ServerReleaseNotes" && mRegion->getReleaseNotesRequested()) + if (iter->first == "ServerReleaseNotes" && regionp->getReleaseNotesRequested()) { - mRegion->showReleaseNotes(); + regionp->showReleaseNotes(); } } - mRegion->setCapabilitiesReceived(true); - + regionp->setCapabilitiesReceived(true); + if (STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState()) { LLStartUp::setStartupState( STATE_SEED_CAP_GRANTED ); } } - static boost::intrusive_ptr build( - LLViewerRegion* region) + static boost::intrusive_ptr build( U64 region_handle, S32 id ) { - return boost::intrusive_ptr( - new BaseCapabilitiesComplete(region)); + return boost::intrusive_ptr( + new BaseCapabilitiesComplete(region_handle, id) ); } private: - LLViewerRegion* mRegion; + U64 mRegionHandle; + S32 mID; }; @@ -251,31 +306,21 @@ LLViewerRegion::LLViewerRegion(const U64 &handle, mImpl->mLandp = new LLSurface('l', NULL); - if (!gNoRender) - { - // Create the composition layer for the surface - mImpl->mCompositionp = - new LLVLComposition(mImpl->mLandp, - grids_per_region_edge, - region_width_meters / grids_per_region_edge); - mImpl->mCompositionp->setSurface(mImpl->mLandp); + // Create the composition layer for the surface + mImpl->mCompositionp = + new LLVLComposition(mImpl->mLandp, + grids_per_region_edge, + region_width_meters / grids_per_region_edge); + mImpl->mCompositionp->setSurface(mImpl->mLandp); - // Create the surfaces - mImpl->mLandp->setRegion(this); - mImpl->mLandp->create(grids_per_region_edge, - grids_per_patch_edge, - mImpl->mOriginGlobal, - mWidth); - } + // Create the surfaces + mImpl->mLandp->setRegion(this); + mImpl->mLandp->create(grids_per_region_edge, + grids_per_patch_edge, + mImpl->mOriginGlobal, + mWidth); - if (!gNoRender) - { - mParcelOverlay = new LLViewerParcelOverlay(this, region_width_meters); - } - else - { - mParcelOverlay = NULL; - } + mParcelOverlay = new LLViewerParcelOverlay(this, region_width_meters); setOriginGlobal(from_region_handle(handle)); calculateCenterGlobal(); @@ -317,11 +362,6 @@ void LLViewerRegion::initStats() LLViewerRegion::~LLViewerRegion() { - if(mImpl->mHttpResponderPtr) - { - (static_cast(mImpl->mHttpResponderPtr.get()))->setRegion(NULL) ; - } - gVLManager.cleanupData(this); // Can't do this on destruction, because the neighbor pointers might be invalid. // This should be reference counted... @@ -345,10 +385,10 @@ LLViewerRegion::~LLViewerRegion() mImpl = NULL; } -/*LLEventPump& LLViewerRegion::getCapAPI() const +LLEventPump& LLViewerRegion::getCapAPI() const { return mImpl->mCapabilityListener.getCapAPI(); -}*/ +} /*virtual*/ const LLHost& LLViewerRegion::getHost() const @@ -537,14 +577,19 @@ const std::string LLViewerRegion::getSimAccessString() const return accessToString(mSimAccess); } +std::string LLViewerRegion::getLocalizedSimProductName() const +{ + std::string localized_spn; + return LLTrans::findString(localized_spn, mProductName) ? localized_spn : mProductName; +} // static std::string LLViewerRegion::regionFlagsToString(U32 flags) { std::string result; + if (flags & REGION_FLAGS_SANDBOX) { - if(!result.empty()) result += ", "; result += "Sandbox"; } @@ -624,6 +669,26 @@ std::string LLViewerRegion::accessToString(U8 sim_access) } } +// static +std::string LLViewerRegion::getAccessIcon(U8 sim_access) +{ + switch(sim_access) + { + case SIM_ACCESS_MATURE: + return "Parcel_M_Dark"; + + case SIM_ACCESS_ADULT: + return "Parcel_R_Light"; + + case SIM_ACCESS_PG: + return "Parcel_PG_Light"; + + case SIM_ACCESS_MIN: + default: + return ""; + } +} + // static std::string LLViewerRegion::accessToShortString(U8 sim_access) { @@ -685,6 +750,7 @@ void LLViewerRegion::dirtyHeights() BOOL LLViewerRegion::idleUpdate(F32 max_update_time) { + LLMemType mt_ivr(LLMemType::MTYPE_IDLE_UPDATE_VIEWER_REGION); // did_update returns TRUE if we did at least one significant update BOOL did_update = mImpl->mLandp->idleUpdate(max_update_time); @@ -901,14 +967,9 @@ U32 LLViewerRegion::getPacketsLost() const } } -void LLViewerRegion::setHttpResponderPtrNULL() +S32 LLViewerRegion::getHttpResponderID() const { - mImpl->mHttpResponderPtr = NULL; -} - -const LLHTTPClient::ResponderPtr LLViewerRegion::getHttpResponderPtr() const -{ - return mImpl->mHttpResponderPtr; + return mImpl->mHttpResponderID; } BOOL LLViewerRegion::pointInRegionGlobal(const LLVector3d &point_global) const @@ -1560,9 +1621,11 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) //capabilityNames.append("ViewerMetrics"); capabilityNames.append("ViewerStartAuction"); capabilityNames.append("ViewerStats"); + // Please add new capabilities alphabetically to reduce // merge conflicts. } + void LLViewerRegion::setSeedCapability(const std::string& url) { if (getCapability("Seed") == url) @@ -1578,49 +1641,101 @@ void LLViewerRegion::setSeedCapability(const std::string& url) setCapability("Seed", url); LLSD capabilityNames = LLSD::emptyArray(); - mImpl->buildCapabilityNames(capabilityNames); - llinfos << "posting to seed " << url << llendl; - mImpl->mHttpResponderPtr = BaseCapabilitiesComplete::build(this) ; - LLHTTPClient::post(url, capabilityNames, mImpl->mHttpResponderPtr); + S32 id = ++mImpl->mHttpResponderID; + LLHTTPClient::post(url, capabilityNames, + BaseCapabilitiesComplete::build(getHandle(), id), + LLSD(), CAP_REQUEST_TIMEOUT); +} + +S32 LLViewerRegion::getNumSeedCapRetries() +{ + return mImpl->mSeedCapAttempts; +} + +void LLViewerRegion::failedSeedCapability() +{ + // Should we retry asking for caps? + mImpl->mSeedCapAttempts++; + std::string url = getCapability("Seed"); + if ( url.empty() ) + { + LL_WARNS2("AppInit", "Capabilities") << "Failed to get seed capabilities, and can not determine url for retries!" << LL_ENDL; + return; + } + // After a few attempts, continue login. We will keep trying once in-world: + if ( mImpl->mSeedCapAttempts >= mImpl->mSeedCapMaxAttemptsBeforeLogin && + STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState() ) + { + LLStartUp::setStartupState( STATE_SEED_CAP_GRANTED ); + } + + if ( mImpl->mSeedCapAttempts < mImpl->mSeedCapMaxAttempts) + { + LLSD capabilityNames = LLSD::emptyArray(); + mImpl->buildCapabilityNames(capabilityNames); + + llinfos << "posting to seed " << url << " (retry " + << mImpl->mSeedCapAttempts << ")" << llendl; + + S32 id = ++mImpl->mHttpResponderID; + LLHTTPClient::post(url, capabilityNames, + BaseCapabilitiesComplete::build(getHandle(), id), + LLSD(), CAP_REQUEST_TIMEOUT); + } + else + { + // *TODO: Give a user pop-up about this error? + LL_WARNS2("AppInit", "Capabilities") << "Failed to get seed capabilities from '" << url << "' after " << mImpl->mSeedCapAttempts << " attempts. Giving up!" << LL_ENDL; + } } class SimulatorFeaturesReceived : public LLHTTPClient::Responder { LOG_CLASS(SimulatorFeaturesReceived); public: - SimulatorFeaturesReceived(LLViewerRegion* region) - : mRegion(region) + SimulatorFeaturesReceived(const std::string& retry_url, U64 region_handle, + S32 attempt = 0, S32 max_attempts = MAX_CAP_REQUEST_ATTEMPTS) + : mRetryURL(retry_url), mRegionHandle(region_handle), mAttempt(attempt), mMaxAttempts(max_attempts) { } void error(U32 statusNum, const std::string& reason) { LL_WARNS2("AppInit", "SimulatorFeatures") << statusNum << ": " << reason << LL_ENDL; + retry(); } - + void result(const LLSD& content) { - if(!mRegion) //region is removed or responder is not created. + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(mRegionHandle); + if(!regionp) //region is removed or responder is not created. { + LL_WARNS2("AppInit", "SimulatorFeatures") << "Received results for region that no longer exists!" << LL_ENDL; return ; } - mRegion->setSimulatorFeatures(content); + regionp->setSimulatorFeatures(content); + } + +private: + void retry() + { + if (mAttempt < mMaxAttempts) + { + mAttempt++; + LL_WARNS2("AppInit", "SimulatorFeatures") << "Re-trying '" << mRetryURL << "'. Retry #" << mAttempt << LL_ENDL; + LLHTTPClient::get(mRetryURL, new SimulatorFeaturesReceived(*this), LLSD(), CAP_REQUEST_TIMEOUT); + } } - static boost::intrusive_ptr build( - LLViewerRegion* region) - { - return boost::intrusive_ptr( - new SimulatorFeaturesReceived(region)); - } - -private: - LLViewerRegion* mRegion; + std::string mRetryURL; + U64 mRegionHandle; + S32 mAttempt; + S32 mMaxAttempts; }; @@ -1639,7 +1754,7 @@ void LLViewerRegion::setCapability(const std::string& name, const std::string& u else if (name == "SimulatorFeatures") { // kick off a request for simulator features - LLHTTPClient::get(url, new SimulatorFeaturesReceived(this)); + LLHTTPClient::get(url, new SimulatorFeaturesReceived(url, getHandle()), LLSD(), CAP_REQUEST_TIMEOUT); } else { @@ -1663,6 +1778,7 @@ std::string LLViewerRegion::getCapability(const std::string& name) const { return ""; } + return iter->second; } @@ -1714,6 +1830,37 @@ LLSpatialPartition* LLViewerRegion::getSpatialPartition(U32 type) return NULL; } +// the viewer can not yet distinquish between normal- and estate-owned objects +// so we collapse these two bits and enable the UI if either are set +const U32 ALLOW_RETURN_ENCROACHING_OBJECT = REGION_FLAGS_ALLOW_RETURN_ENCROACHING_OBJECT + | REGION_FLAGS_ALLOW_RETURN_ENCROACHING_ESTATE_OBJECT; + +bool LLViewerRegion::objectIsReturnable(const LLVector3& pos, const std::vector& boxes) const +{ + return (mParcelOverlay != NULL) + && (mParcelOverlay->isOwnedSelf(pos) + || mParcelOverlay->isOwnedGroup(pos) + || ((mRegionFlags & ALLOW_RETURN_ENCROACHING_OBJECT) + && mParcelOverlay->encroachesOwned(boxes)) ); +} + +bool LLViewerRegion::childrenObjectReturnable( const std::vector& boxes ) const +{ + bool result = false; + result = ( mParcelOverlay && mParcelOverlay->encroachesOnUnowned( boxes ) ) ? 1 : 0; + return result; +} + +bool LLViewerRegion::objectsCrossParcel(const std::vector& boxes) const +{ + return mParcelOverlay && mParcelOverlay->encroachesOnNearbyParcel(boxes); +} + +void LLViewerRegion::getNeighboringRegions( std::vector& uniqueRegions ) +{ + mImpl->mLandp->getNeighboringRegions( uniqueRegions ); +} + void LLViewerRegion::showReleaseNotes() { std::string url = this->getCapability("ServerReleaseNotes"); diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index 24e5f9052..3d33e9b7f 100644 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -67,7 +67,6 @@ class LLVOCache; class LLVOCacheEntry; class LLSpatialPartition; class LLEventPump; -//class LLCapabilityListener; class LLDataPacker; class LLDataPackerBinaryBuffer; class LLHost; @@ -199,7 +198,7 @@ public: S32 getSimCPURatio() const { return mCPURatio; } const std::string& getSimColoName() const { return mColoName; } const std::string& getSimProductSKU() const { return mProductSKU; } - const std::string& getSimProductName() const { return mProductName; } + std::string getLocalizedSimProductName() const; // Returns "Sandbox", "Expensive", etc. static std::string regionFlagsToString(U32 flags); @@ -209,7 +208,10 @@ public: // Returns "M", "PG", "A" etc. static std::string accessToShortString(U8 sim_access); - + + // Return access icon name + static std::string getAccessIcon(U8 sim_access); + // helper function which just makes sure all interested parties // can process the message. static void processRegionInfo(LLMessageSystem* msg, void**); @@ -230,11 +232,12 @@ public: U32 getPacketsLost() const; - void setHttpResponderPtrNULL(); - const LLHTTPClient::ResponderPtr getHttpResponderPtr() const; + S32 getHttpResponderID() const; // Get/set named capability URLs for this region. void setSeedCapability(const std::string& url); + void failedSeedCapability(); + S32 getNumSeedCapRetries(); void setCapability(const std::string& name, const std::string& url); // implements LLCapabilityProvider virtual std::string getCapability(const std::string& name) const; @@ -249,7 +252,7 @@ public: /// Get LLEventPump on which we listen for capability requests /// (https://wiki.lindenlab.com/wiki/Viewer:Messaging/Messaging_Notes#Capabilities) - // LLEventPump& getCapAPI() const; + LLEventPump& getCapAPI() const; /// implements LLCapabilityProvider /*virtual*/ const LLHost& getHost() const; @@ -328,6 +331,11 @@ public: LLSpatialPartition* getSpatialPartition(U32 type); bool objectIsReturnable(const LLVector3& pos, const std::vector& boxes) const; + bool childrenObjectReturnable( const std::vector& boxes ) const; + bool objectsCrossParcel(const std::vector& boxes) const; + + void getNeighboringRegions( std::vector& uniqueRegions ); + public: struct CompareDistance { diff --git a/indra/newview/llviewerstatsrecorder.cpp b/indra/newview/llviewerstatsrecorder.cpp new file mode 100644 index 000000000..e9d21b484 --- /dev/null +++ b/indra/newview/llviewerstatsrecorder.cpp @@ -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 + + + diff --git a/indra/newview/llviewerstatsrecorder.h b/indra/newview/llviewerstatsrecorder.h new file mode 100644 index 000000000..612ac380f --- /dev/null +++ b/indra/newview/llviewerstatsrecorder.h @@ -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 + diff --git a/indra/newview/llviewerthrottle.cpp b/indra/newview/llviewerthrottle.cpp index bf779c427..73065c5c0 100644 --- a/indra/newview/llviewerthrottle.cpp +++ b/indra/newview/llviewerthrottle.cpp @@ -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% diff --git a/indra/newview/llworldmapview.cpp b/indra/newview/llworldmapview.cpp index c88b58389..f8269833b 100644 --- a/indra/newview/llworldmapview.cpp +++ b/indra/newview/llworldmapview.cpp @@ -773,10 +773,12 @@ void LLWorldMapView::drawTiles(S32 width, S32 height) { // 1. Tiles are zoomed out small enough, or // 2. Sim's texture has not been loaded yet F32 map_scale_cutoff = SIM_MAP_SCALE; +#if 0 // This is always false, as REGION_FLAGS_NULL_LAYER doesn't exist anymore if ((info->getRegionFlags() & REGION_FLAGS_NULL_LAYER) > 0) { map_scale_cutoff = SIM_NULL_MAP_SCALE; } +#endif bool sim_visible = (sMapScale >= map_scale_cutoff) && @@ -910,7 +912,7 @@ void LLWorldMapView::drawTiles(S32 width, S32 height) { gGL.end(); } - if ((info->getRegionFlags() & REGION_FLAGS_NULL_LAYER) == 0) + if (true /*V3: REGION_FLAGS_NULL_LAYER doesn't exist... (info->getRegionFlags() & REGION_FLAGS_NULL_LAYER) == 0*/) { // draw an alpha of 1 where the sims are visible (except NULL sims) gGL.flush(); diff --git a/indra/newview/rlvcommon.cpp b/indra/newview/rlvcommon.cpp index 976ea26fb..5943f4af8 100644 --- a/indra/newview/rlvcommon.cpp +++ b/indra/newview/rlvcommon.cpp @@ -31,6 +31,8 @@ #include "rlvcommon.h" #include "rlvhandler.h" +using namespace LLOldEvents; + // ============================================================================ // RlvNotifications // diff --git a/indra/newview/rlvcommon.h b/indra/newview/rlvcommon.h index b2513a467..ac097ab5d 100644 --- a/indra/newview/rlvcommon.h +++ b/indra/newview/rlvcommon.h @@ -195,7 +195,7 @@ typedef bool (RlvCommandHandler::*rlvCommandHandler)(const RlvCommand& rlvCmd, E class RlvEnableIfNot : public LLMemberListener { - bool handleEvent(LLPointer, const LLSD&); + bool handleEvent(LLPointer, const LLSD&); }; // ============================================================================ diff --git a/indra/newview/statemachine/aistatemachine.cpp b/indra/newview/statemachine/aistatemachine.cpp index bb166a9b5..3be286418 100644 --- a/indra/newview/statemachine/aistatemachine.cpp +++ b/indra/newview/statemachine/aistatemachine.cpp @@ -254,19 +254,25 @@ void AIStateMachine::finish(void) // Fix the final state. if (mState == bs_callback) mState = default_delete ? bs_killed : bs_initialize; -} - -void AIStateMachine::kill(void) -{ - // Should only be called from finish(). - llassert(mIdle && (mState == bs_callback || mState == bs_finish)); - if (mState == bs_callback && mActive == as_idle) + if (mState == bs_killed && mActive == as_idle) { // Bump the statemachine onto the active statemachine list, or else it won't be deleted. cont(); idle(); } +} + +void AIStateMachine::kill(void) +{ + // Should only be called from finish() (or when not running (bs_initialize)). + llassert(mIdle && (mState == bs_callback || mState == bs_finish || mState == bs_initialize)); + base_state_type prev_state = mState; mState = bs_killed; + if (prev_state == bs_initialize) + { + // We're not running (ie being deleted by a parent statemachine), delete it immediately. + delete this; + } } // Return stringified 'state'.