diff options
Diffstat (limited to 'indra/llcommon')
93 files changed, 4219 insertions, 468 deletions
| diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 910ba958f6..f785698612 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -4,12 +4,19 @@ project(llcommon)  include(00-Common)  include(LLCommon) +include(Linking)  include(Boost) +include (Pth) + +if (WINDOWS) +    include(CopyWinLibs) +endif (WINDOWS)  include_directories(      ${EXPAT_INCLUDE_DIRS}      ${LLCOMMON_INCLUDE_DIRS}      ${ZLIB_INCLUDE_DIRS} +    ${PTH_INCLUDE_DIRS}      )  # add_executable(lltreeiterators lltreeiterators.cpp) @@ -26,6 +33,7 @@ set(llcommon_SOURCE_FILES      llbase32.cpp      llbase64.cpp      llcommon.cpp +    llcoros.cpp      llcrc.cpp      llcriticaldamp.cpp      llcursortypes.cpp @@ -34,6 +42,9 @@ set(llcommon_SOURCE_FILES      llerror.cpp      llerrorthread.cpp      llevent.cpp +    lleventcoro.cpp +    lleventdispatcher.cpp +    lleventfilter.cpp      llevents.cpp      llfasttimer.cpp      llfile.cpp @@ -65,6 +76,7 @@ set(llcommon_SOURCE_FILES      llsdserialize_xml.cpp      llsdutil.cpp      llsecondlifeurls.cpp +    llsingleton.cpp      llstat.cpp      llstacktrace.cpp      llstreamtools.cpp @@ -107,6 +119,7 @@ set(llcommon_HEADER_FILES      llchat.h      llclickaction.h      llcommon.h +    llcoros.h      llcrc.h      llcriticaldamp.h      llcursortypes.h @@ -128,6 +141,9 @@ set(llcommon_HEADER_FILES      llerrorlegacy.h      llerrorthread.h      llevent.h +    lleventcoro.h +    lleventdispatcher.h +    lleventfilter.h      llevents.h      lleventemitter.h      llextendedstatus.h @@ -143,6 +159,7 @@ set(llcommon_HEADER_FILES      llhttpstatuscodes.h      llindexedqueue.h      llinstancetracker.h +    llinstancetracker.h      llkeythrottle.h      lllazy.h      lllinkedqueue.h @@ -224,25 +241,63 @@ set_source_files_properties(${llcommon_HEADER_FILES}  list(APPEND llcommon_SOURCE_FILES ${llcommon_HEADER_FILES}) -add_library (llcommon ${llcommon_SOURCE_FILES}) -target_link_libraries(llcommon +if(LLCOMMON_LINK_SHARED) +    add_library (llcommon SHARED ${llcommon_SOURCE_FILES}) + +    if(SHARED_LIB_STAGING_DIR) +        # *FIX:Mani --- +        # llcommon.dll get written to the DLL staging directory. +        # Also this directory is shared with RunBuildTest.cmake, y'know, for the tests. +        set_target_properties(llcommon PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${SHARED_LIB_STAGING_DIR}) +        if(NOT WINDOWS) +          get_target_property(LLCOMMON_PATH llcommon LOCATION) +          get_filename_component(LLCOMMON_FILE ${LLCOMMON_PATH} NAME) +          add_custom_command( +            TARGET llcommon POST_BUILD +            COMMAND ${CMAKE_COMMAND} +            ARGS +              -E +              copy_if_different +              ${LLCOMMON_PATH} +              ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/${LLCOMMON_FILE} +            COMMENT "Copying llcommon to the staging folder." +            ) +        endif(NOT WINDOWS) +    endif(SHARED_LIB_STAGING_DIR) + +    if (DARWIN) +      set_target_properties(llcommon PROPERTIES +        BUILD_WITH_INSTALL_RPATH 1 +        INSTALL_NAME_DIR "@executable_path/../Resources" +        ) +    endif(DARWIN) + +else(LLCOMMON_LINK_SHARED) +    add_library (llcommon ${llcommon_SOURCE_FILES}) +endif(LLCOMMON_LINK_SHARED) + +target_link_libraries( +    llcommon      ${APRUTIL_LIBRARIES}      ${APR_LIBRARIES}      ${EXPAT_LIBRARIES}      ${ZLIB_LIBRARIES} +    ${WINDOWS_LIBRARIES}      ${BOOST_PROGRAM_OPTIONS_LIBRARY}      ${BOOST_REGEX_LIBRARY} +    ${PTH_LIBRARIES}      ) -#add unit tests -INCLUDE(LLAddBuildTest) +add_dependencies(llcommon stage_third_party_libs) + +include(LLAddBuildTest)  SET(llcommon_TEST_SOURCE_FILES    # unit-testing llcommon is not possible right now as the test-harness *itself* depends upon llcommon, causing a circular dependency.  Add your 'unit' tests as integration tests for now.    )  LL_ADD_PROJECT_UNIT_TESTS(llcommon "${llcommon_TEST_SOURCE_FILES}")  #set(TEST_DEBUG on) -set(test_libs llcommon ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES}) +set(test_libs llcommon ${LLCOMMON_LIBRARIES} ${WINDOWS_LIBRARIES} ${GOOGLEMOCK_LIBRARIES})  LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")  LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")  LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}") diff --git a/indra/llcommon/linden_common.h b/indra/llcommon/linden_common.h index 8687a24655..c2eb867795 100644 --- a/indra/llcommon/linden_common.h +++ b/indra/llcommon/linden_common.h @@ -72,13 +72,7 @@  #ifdef LL_WINDOWS  // Reenable warnings we disabled above  #pragma warning (3 : 4702) // unreachable code, we like level 3, not 4 -// level 4 warnings that we need to disable: -#pragma warning (disable : 4100) // unreferenced formal parameter -#pragma warning (disable : 4127) // conditional expression is constant (e.g. while(1) ) -#pragma warning (disable : 4244) // possible loss of data on conversions -#pragma warning (disable : 4396) // the inline specifier cannot be used when a friend declaration refers to a specialization of a function template -#pragma warning (disable : 4512) // assignment operator could not be generated -#pragma warning (disable : 4706) // assignment within conditional (even if((x = y)) ) +// moved msvc warnings to llpreprocessor.h  *TODO - delete this comment after merge conflicts are unlikely -brad  #endif	//	LL_WINDOWS  // Linden only libs in alpha-order other than stdtypes.h diff --git a/indra/llcommon/llallocator.h b/indra/llcommon/llallocator.h index 2b70fee0b8..50129b4526 100644 --- a/indra/llcommon/llallocator.h +++ b/indra/llcommon/llallocator.h @@ -38,7 +38,7 @@  #include "llmemtype.h"  #include "llallocator_heap_profile.h" -class LLAllocator { +class LL_COMMON_API LLAllocator {      friend class LLMemoryView;      friend class LLMemType; diff --git a/indra/llcommon/llallocator_heap_profile.cpp b/indra/llcommon/llallocator_heap_profile.cpp index d82ee9ed81..0a807702d0 100644 --- a/indra/llcommon/llallocator_heap_profile.cpp +++ b/indra/llcommon/llallocator_heap_profile.cpp @@ -38,6 +38,7 @@  // disable warning about boost::lexical_cast returning uninitialized data  // when it fails to parse the string  #pragma warning (disable:4701) +#pragma warning (disable:4702)  #endif  #include <boost/algorithm/string/split.hpp> diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index e32a293f1c..1a052ce62d 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -62,7 +62,7 @@ public:  };  #endif -class LLApp : public LLOptionInterface +class LL_COMMON_API LLApp : public LLOptionInterface  {  	friend class LLErrorThread;  public: diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index a1a4c6db4a..b08bb617c5 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -48,25 +48,25 @@  #include "apr_atomic.h"  #include "llstring.h" -extern apr_thread_mutex_t* gLogMutexp; +extern LL_COMMON_API apr_thread_mutex_t* gLogMutexp;  extern apr_thread_mutex_t* gCallStacksLogMutexp;  /**    * @brief initialize the common apr constructs -- apr itself, the   * global pool, and a mutex.   */ -void ll_init_apr(); +void LL_COMMON_API ll_init_apr();  /**    * @brief Cleanup those common apr constructs.   */ -void ll_cleanup_apr(); +void LL_COMMON_API ll_cleanup_apr();  //  //LL apr_pool  //manage apr_pool_t, destroy allocated apr_pool in the destruction function.  // -class LLAPRPool +class LL_COMMON_API LLAPRPool  {  public:  	LLAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, BOOL releasePoolFlag = TRUE) ; @@ -92,7 +92,7 @@ protected:  //which clears memory automatically.  //so it can not hold static data or data after memory is cleared  // -class LLVolatileAPRPool : public LLAPRPool +class LL_COMMON_API LLVolatileAPRPool : public LLAPRPool  {  public:  	LLVolatileAPRPool(BOOL is_local = TRUE, apr_pool_t *parent = NULL, apr_size_t size = 0, BOOL releasePoolFlag = TRUE); @@ -121,7 +121,7 @@ private:   * destructor handles the unlock. Instances of this class are   * <b>not</b> thread safe.   */ -class LLScopedLock : private boost::noncopyable +class LL_COMMON_API LLScopedLock : private boost::noncopyable  {  public:  	/** @@ -196,7 +196,7 @@ typedef LLAtomic32<S32> LLAtomicS32;  //      2, a global pool.  // -class LLAPRFile : boost::noncopyable +class LL_COMMON_API LLAPRFile : boost::noncopyable  {  	// make this non copyable since a copy closes the file  private: @@ -256,10 +256,10 @@ public:   * APR_SUCCESS.   * @return Returns <code>true</code> if status is an error condition.   */ -bool ll_apr_warn_status(apr_status_t status); +bool LL_COMMON_API ll_apr_warn_status(apr_status_t status); -void ll_apr_assert_status(apr_status_t status); +void LL_COMMON_API ll_apr_assert_status(apr_status_t status); -extern "C" apr_pool_t* gAPRPoolp; // Global APR memory pool +extern "C" LL_COMMON_API apr_pool_t* gAPRPoolp; // Global APR memory pool  #endif // LL_LLAPR_H diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index 41f0a46115..6d5b12d840 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -223,4 +223,5 @@ const std::string &LLAssetType::badLookup()  {  	static const std::string sBadLookup = "llassettype_bad_lookup";  	return sBadLookup; +  } diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h index 10e21b4d1f..ec2290d30e 100644 --- a/indra/llcommon/llassettype.h +++ b/indra/llcommon/llassettype.h @@ -37,7 +37,7 @@  #include "stdenums.h" 	// for EDragAndDropType -class LLAssetType +class LL_COMMON_API LLAssetType  {  public:  	enum EType @@ -121,6 +121,7 @@ public:  			// Inventory folder link  		AT_COUNT = 26, +  			// +*********************************************************+  			// |  TO ADD AN ELEMENT TO THIS ENUM:                        |  			// +*********************************************************+ diff --git a/indra/llcommon/llbase32.h b/indra/llcommon/llbase32.h index 63a93e11ab..0697f7b8e2 100644 --- a/indra/llcommon/llbase32.h +++ b/indra/llcommon/llbase32.h @@ -32,9 +32,9 @@   */  #ifndef LLBASE32_H -#define LLBASE32_h +#define LLBASE32_H -class LLBase32 +class LL_COMMON_API LLBase32  {  public:  	static std::string encode(const U8* input, size_t input_size); diff --git a/indra/llcommon/llbase64.h b/indra/llcommon/llbase64.h index 58414bba8b..c48fea2478 100644 --- a/indra/llcommon/llbase64.h +++ b/indra/llcommon/llbase64.h @@ -32,9 +32,9 @@   */  #ifndef LLBASE64_H -#define LLBASE64_h +#define LLBASE64_H -class LLBase64 +class LL_COMMON_API LLBase64  {  public:  	static std::string encode(const U8* input, size_t input_size); diff --git a/indra/llcommon/llcommon.h b/indra/llcommon/llcommon.h index a1808e8a6c..b36471f9f8 100644 --- a/indra/llcommon/llcommon.h +++ b/indra/llcommon/llcommon.h @@ -37,7 +37,7 @@  #include "lltimer.h"  #include "llfile.h" -class LLCommon +class LL_COMMON_API LLCommon  {  public:  	static void initClass(); diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp new file mode 100644 index 0000000000..377bfaa247 --- /dev/null +++ b/indra/llcommon/llcoros.cpp @@ -0,0 +1,137 @@ +/** + * @file   llcoros.cpp + * @author Nat Goodspeed + * @date   2009-06-03 + * @brief  Implementation for llcoros. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llcoros.h" +// STL headers +// std headers +// external library headers +#include <boost/bind.hpp> +// other Linden headers +#include "llevents.h" +#include "llerror.h" +#include "stringize.h" + +LLCoros::LLCoros() +{ +    // Register our cleanup() method for "mainloop" ticks +    LLEventPumps::instance().obtain("mainloop").listen( +        "LLCoros", boost::bind(&LLCoros::cleanup, this, _1)); +} + +bool LLCoros::cleanup(const LLSD&) +{ +    // Walk the mCoros map, checking and removing completed coroutines. +    for (CoroMap::iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; ) +    { +        // Has this coroutine exited (normal return, exception, exit() call) +        // since last tick? +        if (mi->second->exited()) +        { +            LL_INFOS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL; +            // The erase() call will invalidate its passed iterator value -- +            // so increment mi FIRST -- but pass its original value to +            // erase(). This is what postincrement is all about. +            mCoros.erase(mi++); +        } +        else +        { +            // Still live, just skip this entry as if incrementing at the top +            // of the loop as usual. +            ++mi; +        } +    } +    return false; +} + +std::string LLCoros::generateDistinctName(const std::string& prefix) const +{ +    // Allowing empty name would make getName()'s not-found return ambiguous. +    if (prefix.empty()) +    { +        LL_ERRS("LLCoros") << "LLCoros::launch(): pass non-empty name string" << LL_ENDL; +    } + +    // If the specified name isn't already in the map, just use that. +    std::string name(prefix); + +    // Find the lowest numeric suffix that doesn't collide with an existing +    // entry. Start with 2 just to make it more intuitive for any interested +    // parties: e.g. "joe", "joe2", "joe3"... +    for (int i = 2; ; name = STRINGIZE(prefix << i++)) +    { +        if (mCoros.find(name) == mCoros.end()) +        { +            LL_INFOS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL; +            return name; +        } +    } +} + +bool LLCoros::kill(const std::string& name) +{ +    CoroMap::iterator found = mCoros.find(name); +    if (found == mCoros.end()) +    { +        return false; +    } +    // Because this is a boost::ptr_map, erasing the map entry also destroys +    // the referenced heap object, in this case the boost::coroutine object, +    // which will terminate the coroutine. +    mCoros.erase(found); +    return true; +} + +std::string LLCoros::getNameByID(const void* self_id) const +{ +    // Walk the existing coroutines, looking for one from which the 'self_id' +    // passed to us comes. +    for (CoroMap::const_iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; ++mi) +    { +        namespace coro_private = boost::coroutines::detail; +        if (static_cast<void*>(coro_private::coroutine_accessor::get_impl(const_cast<coro&>(*mi->second)).get()) +            == self_id) +        { +            return mi->first; +        } +    } +    return ""; +} + +/***************************************************************************** +*   MUST BE LAST +*****************************************************************************/ +// Turn off MSVC optimizations for just LLCoros::launchImpl() -- see +// DEV-32777. But MSVC doesn't support push/pop for optimization flags as it +// does for warning suppression, and we really don't want to force +// optimization ON for other code even in Debug or RelWithDebInfo builds. + +#if LL_MSVC +// work around broken optimizations +#pragma warning(disable: 4748) +#pragma optimize("", off) +#endif // LL_MSVC + +std::string LLCoros::launchImpl(const std::string& prefix, coro* newCoro) +{ +    std::string name(generateDistinctName(prefix)); +    mCoros.insert(name, newCoro); +    /* Run the coroutine until its first wait, then return here */ +    (*newCoro)(std::nothrow); +    return name; +} + +#if LL_MSVC +// reenable optimizations +#pragma optimize("", on) +#endif // LL_MSVC diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h new file mode 100644 index 0000000000..141b0df43c --- /dev/null +++ b/indra/llcommon/llcoros.h @@ -0,0 +1,149 @@ +/** + * @file   llcoros.h + * @author Nat Goodspeed + * @date   2009-06-02 + * @brief  Manage running boost::coroutine instances + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLCOROS_H) +#define LL_LLCOROS_H + +#include <boost/coroutine/coroutine.hpp> +#include "llsingleton.h" +#include <boost/ptr_container/ptr_map.hpp> +#include <string> +#include <boost/preprocessor/repetition/enum_params.hpp> +#include <boost/preprocessor/repetition/enum_binary_params.hpp> +#include <boost/preprocessor/iteration/local.hpp> +#include <stdexcept> + +/** + * Registry of named Boost.Coroutine instances + * + * The Boost.Coroutine library supports the general case of a coroutine + * accepting arbitrary parameters and yielding multiple (sets of) results. For + * such use cases, it's natural for the invoking code to retain the coroutine + * instance: the consumer repeatedly calls into the coroutine, perhaps passing + * new parameter values, prompting it to yield its next result. + * + * Our typical coroutine usage is different, though. For us, coroutines + * provide an alternative to the @c Responder pattern. Our typical coroutine + * has @c void return, invoked in fire-and-forget mode: the handler for some + * user gesture launches the coroutine and promptly returns to the main loop. + * The coroutine initiates some action that will take multiple frames (e.g. a + * capability request), waits for its result, processes it and silently steals + * away. + * + * This usage poses two (related) problems: + * + * # Who should own the coroutine instance? If it's simply local to the + *   handler code that launches it, return from the handler will destroy the + *   coroutine object, terminating the coroutine. + * # Once the coroutine terminates, in whatever way, who's responsible for + *   cleaning up the coroutine object? + * + * LLCoros is a Singleton collection of currently-active coroutine instances. + * Each has a name. You ask LLCoros to launch a new coroutine with a suggested + * name prefix; from your prefix it generates a distinct name, registers the + * new coroutine and returns the actual name. + * + * The name can be used to kill off the coroutine prematurely, if needed. It + * can also provide diagnostic info: we can look up the name of the + * currently-running coroutine. + * + * Finally, the next frame ("mainloop" event) after the coroutine terminates, + * LLCoros will notice its demise and destroy it. + */ +class LL_COMMON_API LLCoros: public LLSingleton<LLCoros> +{ +public: +    /// Canonical boost::coroutines::coroutine signature we use +    typedef boost::coroutines::coroutine<void()> coro; +    /// Canonical 'self' type +    typedef coro::self self; + +    /** +     * Create and start running a new coroutine with specified name. The name +     * string you pass is a suggestion; it will be tweaked for uniqueness. The +     * actual name is returned to you. +     * +     * Usage looks like this, for (e.g.) two coroutine parameters: +     * @code +     * class MyClass +     * { +     * public: +     *     ... +     *     // Do NOT NOT NOT accept reference params other than 'self'! +     *     // Pass by value only! +     *     void myCoroutineMethod(LLCoros::self& self, std::string, LLSD); +     *     ... +     * }; +     * ... +     * std::string name = LLCoros::instance().launch( +     *    "mycoro", boost::bind(&MyClass::myCoroutineMethod, this, _1, +     *                          "somestring", LLSD(17)); +     * @endcode +     * +     * Your function/method must accept LLCoros::self& as its first parameter. +     * It can accept any other parameters you want -- but ONLY BY VALUE! +     * Other reference parameters are a BAD IDEA! You Have Been Warned. See +     * DEV-32777 comments for an explanation. +     * +     * Pass a callable that accepts the single LLCoros::self& parameter. It +     * may work to pass a free function whose only parameter is 'self'; for +     * all other cases use boost::bind(). Of course, for a non-static class +     * method, the first parameter must be the class instance. Use the +     * placeholder _1 for the 'self' parameter. Any other parameters should be +     * passed via the bind() expression. +     * +     * launch() tweaks the suggested name so it won't collide with any +     * existing coroutine instance, creates the coroutine instance, registers +     * it with the tweaked name and runs it until its first wait. At that +     * point it returns the tweaked name. +     */ +    template <typename CALLABLE> +    std::string launch(const std::string& prefix, const CALLABLE& callable) +    { +        return launchImpl(prefix, new coro(callable)); +    } + +    /** +     * Abort a running coroutine by name. Normally, when a coroutine either +     * runs to completion or terminates with an exception, LLCoros quietly +     * cleans it up. This is for use only when you must explicitly interrupt +     * one prematurely. Returns @c true if the specified name was found and +     * still running at the time. +     */ +    bool kill(const std::string& name); + +    /** +     * From within a coroutine, pass its @c self object to look up the +     * (tweaked) name string by which this coroutine is registered. Returns +     * the empty string if not found (e.g. if the coroutine was launched by +     * hand rather than using LLCoros::launch()). +     */ +    template <typename COROUTINE_SELF> +    std::string getName(const COROUTINE_SELF& self) const +    { +        return getNameByID(self.get_id()); +    } + +    /// getName() by self.get_id() +    std::string getNameByID(const void* self_id) const; + +private: +    friend class LLSingleton<LLCoros>; +    LLCoros(); +    std::string launchImpl(const std::string& prefix, coro* newCoro); +    std::string generateDistinctName(const std::string& prefix) const; +    bool cleanup(const LLSD&); + +    typedef boost::ptr_map<std::string, coro> CoroMap; +    CoroMap mCoros; +}; + +#endif /* ! defined(LL_LLCOROS_H) */ diff --git a/indra/llcommon/llcrc.h b/indra/llcommon/llcrc.h index 27fae7d269..74369062cc 100644 --- a/indra/llcommon/llcrc.h +++ b/indra/llcommon/llcrc.h @@ -50,7 +50,7 @@  //  llinfos << "File crc: " << crc.getCRC() << llendl;  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLCRC +class LL_COMMON_API LLCRC  {  protected:  	U32 mCurrent; diff --git a/indra/llcommon/llcriticaldamp.h b/indra/llcommon/llcriticaldamp.h index ad98284a6c..1ea5914b5b 100644 --- a/indra/llcommon/llcriticaldamp.h +++ b/indra/llcommon/llcriticaldamp.h @@ -38,7 +38,7 @@  #include "llframetimer.h" -class LLCriticalDamp  +class LL_COMMON_API LLCriticalDamp   {  public:  	LLCriticalDamp(); diff --git a/indra/llcommon/llcursortypes.h b/indra/llcommon/llcursortypes.h index 35dbeaf16e..a1b8178bfe 100644 --- a/indra/llcommon/llcursortypes.h +++ b/indra/llcommon/llcursortypes.h @@ -71,6 +71,6 @@ enum ECursorType {  	UI_CURSOR_COUNT			// Number of elements in this enum (NOT a cursor)  }; -ECursorType getCursorFromString(const std::string& cursor_string); +LL_COMMON_API ECursorType getCursorFromString(const std::string& cursor_string);  #endif // LL_LLCURSORTYPES_H diff --git a/indra/llcommon/lldate.h b/indra/llcommon/lldate.h index 40b5f782d6..f8b2f2f163 100644 --- a/indra/llcommon/lldate.h +++ b/indra/llcommon/lldate.h @@ -46,7 +46,7 @@   *   * The date class represents a point in time after epoch - 1970-01-01.   */ -class LLDate +class LL_COMMON_API LLDate  {  public:  	/**  @@ -156,10 +156,10 @@ private:  };  // Helper function to stream out a date -std::ostream& operator<<(std::ostream& s, const LLDate& date); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLDate& date);  // Helper function to stream in a date -std::istream& operator>>(std::istream& s, LLDate& date); +LL_COMMON_API std::istream& operator>>(std::istream& s, LLDate& date); diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h index 82f53c6e17..e6229db834 100644 --- a/indra/llcommon/lldependencies.h +++ b/indra/llcommon/lldependencies.h @@ -81,7 +81,7 @@ struct instance_from_range: public TYPE   * LLDependencies components that should not be reinstantiated for each KEY,   * NODE specialization   */ -class LLDependenciesBase +class LL_COMMON_API LLDependenciesBase  {  public:      virtual ~LLDependenciesBase() {} diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 37e922d4b7..5a4c644859 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -131,7 +131,7 @@ namespace LLError  	class CallSite; -	class Log +	class LL_COMMON_API Log  	{  	public:  		static bool shouldLog(CallSite&); @@ -140,7 +140,7 @@ namespace LLError  		static void flush(std::ostringstream*, const CallSite&);  	}; -	class CallSite +	class LL_COMMON_API CallSite  	{  		// Represents a specific place in the code where a message is logged  		// This is public because it is used by the macros below.  It is not @@ -189,7 +189,7 @@ namespace LLError  	//LLCallStacks is designed not to be thread-safe.     //so try not to use it in multiple parallel threads at same time.     //Used in a single thread at a time is fine. -   class LLCallStacks +   class LL_COMMON_API LLCallStacks     {     private:         static char**  sBuffer ; diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index fab0a1ef9f..233e9d3389 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -63,12 +63,12 @@ public:  namespace LLError  { -	void initForServer(const std::string& identity); +	LL_COMMON_API void initForServer(const std::string& identity);  		// resets all logging settings to defaults needed by server processes  		// logs to stderr, syslog, and windows debug log  		// the identity string is used for in the syslog -	void initForApplication(const std::string& dir); +	LL_COMMON_API void initForApplication(const std::string& dir);  		// resets all logging settings to defaults needed by applicaitons  		// logs to stderr and windows debug log  		// sets up log configuration from the file logcontrol.xml in dir @@ -79,14 +79,14 @@ namespace LLError  		Setting a level means log messages at that level or above.  	*/ -	void setPrintLocation(bool); -	void setDefaultLevel(LLError::ELevel); -	void setFunctionLevel(const std::string& function_name, LLError::ELevel); -	void setClassLevel(const std::string& class_name, LLError::ELevel); -	void setFileLevel(const std::string& file_name, LLError::ELevel); -	void setTagLevel(const std::string& file_name, LLError::ELevel); +	LL_COMMON_API void setPrintLocation(bool); +	LL_COMMON_API void setDefaultLevel(LLError::ELevel); +	LL_COMMON_API void setFunctionLevel(const std::string& function_name, LLError::ELevel); +	LL_COMMON_API void setClassLevel(const std::string& class_name, LLError::ELevel); +	LL_COMMON_API void setFileLevel(const std::string& file_name, LLError::ELevel); +	LL_COMMON_API void setTagLevel(const std::string& file_name, LLError::ELevel); -	void configure(const LLSD&); +	LL_COMMON_API void configure(const LLSD&);  		// the LLSD can configure all of the settings  		// usually read automatically from the live errorlog.xml file @@ -96,21 +96,21 @@ namespace LLError  	*/  	typedef boost::function<void(const std::string&)> FatalFunction; -	void crashAndLoop(const std::string& message); +	LL_COMMON_API void crashAndLoop(const std::string& message);  		// Default fatal function: access null pointer and loops forever -	void setFatalFunction(const FatalFunction&); +	LL_COMMON_API void setFatalFunction(const FatalFunction&);  		// The fatal function will be called when an message of LEVEL_ERROR  		// is logged.  Note: supressing a LEVEL_ERROR message from being logged  		// (by, for example, setting a class level to LEVEL_NONE), will keep  		// the that message from causing the fatal funciton to be invoked. -    FatalFunction getFatalFunction(); +    LL_COMMON_API FatalFunction getFatalFunction();          // Retrieve the previously-set FatalFunction      /// temporarily override the FatalFunction for the duration of a      /// particular scope, e.g. for unit tests -    class OverrideFatalFunction +    class LL_COMMON_API OverrideFatalFunction      {      public:          OverrideFatalFunction(const FatalFunction& func): @@ -128,15 +128,15 @@ namespace LLError      };  	typedef std::string (*TimeFunction)(); -	std::string utcTime(); +	LL_COMMON_API std::string utcTime(); -	void setTimeFunction(TimeFunction); +	LL_COMMON_API void setTimeFunction(TimeFunction);  		// The function is use to return the current time, formatted for  		// display by those error recorders that want the time included. -	class Recorder +	class LL_COMMON_API Recorder  	{  		// An object that handles the actual output or error messages.  	public: @@ -150,17 +150,17 @@ namespace LLError  			// included in the text of the message  	}; -	void addRecorder(Recorder*); -	void removeRecorder(Recorder*); +	LL_COMMON_API void addRecorder(Recorder*); +	LL_COMMON_API void removeRecorder(Recorder*);  		// each error message is passed to each recorder via recordMessage() -	void logToFile(const std::string& filename); -	void logToFixedBuffer(LLLineBuffer*); +	LL_COMMON_API void logToFile(const std::string& filename); +	LL_COMMON_API void logToFixedBuffer(LLLineBuffer*);  		// Utilities to add recorders for logging to a file or a fixed buffer  		// A second call to the same function will remove the logger added  		// with the first.  		// Passing the empty string or NULL to just removes any prior. -	std::string logFileName(); +	LL_COMMON_API std::string logFileName();  		// returns name of current logging file, empty string if none @@ -169,11 +169,11 @@ namespace LLError  	*/  	class Settings; -	Settings* saveAndResetSettings(); -	void restoreSettings(Settings *); +	LL_COMMON_API Settings* saveAndResetSettings(); +	LL_COMMON_API void restoreSettings(Settings *); -	std::string abbreviateFile(const std::string& filePath); -	int shouldLogCallCount(); +	LL_COMMON_API std::string abbreviateFile(const std::string& filePath); +	LL_COMMON_API int shouldLogCallCount();  }; diff --git a/indra/llcommon/llerrorthread.h b/indra/llcommon/llerrorthread.h index f1d6ffc34f..3121d29675 100644 --- a/indra/llcommon/llerrorthread.h +++ b/indra/llcommon/llerrorthread.h @@ -35,7 +35,7 @@  #include "llthread.h" -class LLErrorThread : public LLThread +class LL_COMMON_API LLErrorThread : public LLThread  {  public:  	LLErrorThread(); diff --git a/indra/llcommon/llevent.h b/indra/llcommon/llevent.h index 2cc8577219..0ea7cf4ae8 100644 --- a/indra/llcommon/llevent.h +++ b/indra/llcommon/llevent.h @@ -47,7 +47,7 @@ class LLEventDispatcher;  class LLObservable;  // Abstract event. All events derive from LLEvent -class LLEvent : public LLThreadSafeRefCount +class LL_COMMON_API LLEvent : public LLThreadSafeRefCount  {  protected:  	virtual ~LLEvent(); @@ -75,7 +75,7 @@ private:  };  // Abstract listener. All listeners derive from LLEventListener -class LLEventListener : public LLThreadSafeRefCount +class LL_COMMON_API LLEventListener : public LLThreadSafeRefCount  {  protected:  	virtual ~LLEventListener(); @@ -92,7 +92,7 @@ public:  };  // A listener which tracks references to it and cleans up when it's deallocated -class LLSimpleListener : public LLEventListener +class LL_COMMON_API LLSimpleListener : public LLEventListener  {  public:  	void clearDispatchers(); @@ -117,7 +117,7 @@ struct LLListenerEntry  // Base class for a dispatcher - an object which listens  // to events being fired and relays them to their  // appropriate destinations. -class LLEventDispatcher : public LLThreadSafeRefCount +class LL_COMMON_API LLEventDispatcher : public LLThreadSafeRefCount  {  protected:  	virtual ~LLEventDispatcher(); @@ -160,7 +160,7 @@ private:  // In order for this class to work properly, it needs  // an instance of an LLEventDispatcher to route events to their  // listeners. -class LLObservable +class LL_COMMON_API LLObservable  {  public:  	// Initialize with the default Dispatcher diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp new file mode 100644 index 0000000000..d598f1cc4a --- /dev/null +++ b/indra/llcommon/lleventcoro.cpp @@ -0,0 +1,129 @@ +/** + * @file   lleventcoro.cpp + * @author Nat Goodspeed + * @date   2009-04-29 + * @brief  Implementation for lleventcoro. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventcoro.h" +// STL headers +#include <map> +// std headers +// external library headers +// other Linden headers +#include "llsdserialize.h" +#include "llerror.h" +#include "llcoros.h" + +std::string LLEventDetail::listenerNameForCoroImpl(const void* self_id) +{ +    // First, if this coroutine was launched by LLCoros::launch(), find that name. +    std::string name(LLCoros::instance().getNameByID(self_id)); +    if (! name.empty()) +    { +        return name; +    } +    // Apparently this coroutine wasn't launched by LLCoros::launch(). Check +    // whether we have a memo for this self_id. +    typedef std::map<const void*, std::string> MapType; +    static MapType memo; +    MapType::const_iterator found = memo.find(self_id); +    if (found != memo.end()) +    { +        // this coroutine instance has called us before, reuse same name +        return found->second; +    } +    // this is the first time we've been called for this coroutine instance +    name = LLEventPump::inventName("coro"); +    memo[self_id] = name; +    LL_INFOS("LLEventCoro") << "listenerNameForCoroImpl(" << self_id << "): inventing coro name '" +                            << name << "'" << LL_ENDL; +    return name; +} + +void LLEventDetail::storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value) +{ +    if (rawPath.isUndefined()) +    { +        // no-op case +        return; +    } + +    // Arrange to treat rawPath uniformly as an array. If it's not already an +    // array, store it as the only entry in one. +    LLSD path; +    if (rawPath.isArray()) +    { +        path = rawPath; +    } +    else +    { +        path.append(rawPath); +    } + +    // Need to indicate a current destination -- but that current destination +    // needs to change as we step through the path array. Where normally we'd +    // use an LLSD& to capture a subscripted LLSD lvalue, this time we must +    // instead use a pointer -- since it must be reassigned. +    LLSD* pdest = &dest; + +    // Now loop through that array +    for (LLSD::Integer i = 0; i < path.size(); ++i) +    { +        if (path[i].isString()) +        { +            // *pdest is an LLSD map +            pdest = &((*pdest)[path[i].asString()]); +        } +        else if (path[i].isInteger()) +        { +            // *pdest is an LLSD array +            pdest = &((*pdest)[path[i].asInteger()]); +        } +        else +        { +            // What do we do with Real or Array or Map or ...? +            // As it's a coder error -- not a user error -- rub the coder's +            // face in it so it gets fixed. +            LL_ERRS("lleventcoro") << "storeToLLSDPath(" << dest << ", " << rawPath << ", " << value +                                   << "): path[" << i << "] bad type " << path[i].type() << LL_ENDL; +        } +    } + +    // Here *pdest is where we should store value. +    *pdest = value; +} + +LLSD errorException(const LLEventWithID& result, const std::string& desc) +{ +    // If the result arrived on the error pump (pump 1), instead of +    // returning it, deliver it via exception. +    if (result.second) +    { +        throw LLErrorEvent(desc, result.first); +    } +    // That way, our caller knows a simple return must be from the reply +    // pump (pump 0). +    return result.first; +} + +LLSD errorLog(const LLEventWithID& result, const std::string& desc) +{ +    // If the result arrived on the error pump (pump 1), log it as a fatal +    // error. +    if (result.second) +    { +        LL_ERRS("errorLog") << desc << ":" << std::endl; +        LLSDSerialize::toPrettyXML(result.first, LL_CONT); +        LL_CONT << LL_ENDL; +    } +    // A simple return must therefore be from the reply pump (pump 0). +    return result.first; +} diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h new file mode 100644 index 0000000000..c6d9de171d --- /dev/null +++ b/indra/llcommon/lleventcoro.h @@ -0,0 +1,549 @@ +/** + * @file   lleventcoro.h + * @author Nat Goodspeed + * @date   2009-04-29 + * @brief  Utilities to interface between coroutines and events. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTCORO_H) +#define LL_LLEVENTCORO_H + +#include <boost/coroutine/coroutine.hpp> +#include <boost/coroutine/future.hpp> +#include <boost/optional.hpp> +#include <string> +#include <stdexcept> +#include "llevents.h" +#include "llerror.h" + +/** + * Like LLListenerOrPumpName, this is a class intended for parameter lists: + * accept a <tt>const LLEventPumpOrPumpName&</tt> and you can accept either an + * <tt>LLEventPump&</tt> or its string name. For a single parameter that could + * be either, it's not hard to overload the function -- but as soon as you + * want to accept two such parameters, this is cheaper than four overloads. + */ +class LLEventPumpOrPumpName +{ +public: +    /// Pass an actual LLEventPump& +    LLEventPumpOrPumpName(LLEventPump& pump): +        mPump(pump) +    {} +    /// Pass the string name of an LLEventPump +    LLEventPumpOrPumpName(const std::string& pumpname): +        mPump(LLEventPumps::instance().obtain(pumpname)) +    {} +    /// Pass string constant name of an LLEventPump. This override must be +    /// explicit, since otherwise passing <tt>const char*</tt> to a function +    /// accepting <tt>const LLEventPumpOrPumpName&</tt> would require two +    /// different implicit conversions: <tt>const char*</tt> -> <tt>const +    /// std::string&</tt> -> <tt>const LLEventPumpOrPumpName&</tt>. +    LLEventPumpOrPumpName(const char* pumpname): +        mPump(LLEventPumps::instance().obtain(pumpname)) +    {} +    /// Unspecified: "I choose not to identify an LLEventPump." +    LLEventPumpOrPumpName() {} +    operator LLEventPump& () const { return *mPump; } +    LLEventPump& getPump() const { return *mPump; } +    operator bool() const { return mPump; } +    bool operator!() const { return ! mPump; } + +private: +    boost::optional<LLEventPump&> mPump; +}; + +/// This is an adapter for a signature like void LISTENER(const LLSD&), which +/// isn't a valid LLEventPump listener: such listeners should return bool. +template <typename LISTENER> +class LLVoidListener +{ +public: +    LLVoidListener(const LISTENER& listener): +        mListener(listener) +    {} +    bool operator()(const LLSD& event) +    { +        mListener(event); +        // don't swallow the event, let other listeners see it +        return false; +    } +private: +    LISTENER mListener; +}; + +/// LLVoidListener helper function to infer the type of the LISTENER +template <typename LISTENER> +LLVoidListener<LISTENER> voidlistener(const LISTENER& listener) +{ +    return LLVoidListener<LISTENER>(listener); +} + +namespace LLEventDetail +{ +    /** +     * waitForEventOn() permits a coroutine to temporarily listen on an +     * LLEventPump any number of times. We don't really want to have to ask +     * the caller to label each such call with a distinct string; the whole +     * point of waitForEventOn() is to present a nice sequential interface to +     * the underlying LLEventPump-with-named-listeners machinery. So we'll use +     * LLEventPump::inventName() to generate a distinct name for each +     * temporary listener. On the other hand, because a given coroutine might +     * call waitForEventOn() any number of times, we don't really want to +     * consume an arbitrary number of generated inventName()s: that namespace, +     * though large, is nonetheless finite. So we memoize an invented name for +     * each distinct coroutine instance (each different 'self' object). We +     * can't know the type of 'self', because it depends on the coroutine +     * body's signature. So we cast its address to void*, looking for distinct +     * pointer values. Yes, that means that an early coroutine could cache a +     * value here, then be destroyed, only to be supplanted by a later +     * coroutine (of the same or different type), and we'll end up +     * "recognizing" the second one and reusing the listener name -- but +     * that's okay, since it won't collide with any listener name used by the +     * earlier coroutine since that earlier coroutine no longer exists. +     */ +    template <typename COROUTINE_SELF> +    std::string listenerNameForCoro(COROUTINE_SELF& self) +    { +        return listenerNameForCoroImpl(self.get_id()); +    } + +    /// Implementation for listenerNameForCoro() +    LL_COMMON_API std::string listenerNameForCoroImpl(const void* self_id); + +    /** +     * Implement behavior described for postAndWait()'s @a replyPumpNamePath +     * parameter: +     * +     * * If <tt>path.isUndefined()</tt>, do nothing. +     * * If <tt>path.isString()</tt>, @a dest is an LLSD map: store @a value +     *   into <tt>dest[path.asString()]</tt>. +     * * If <tt>path.isInteger()</tt>, @a dest is an LLSD array: store @a +     *   value into <tt>dest[path.asInteger()]</tt>. +     * * If <tt>path.isArray()</tt>, iteratively apply the rules above to step +     *   down through the structure of @a dest. The last array entry in @a +     *   path specifies the entry in the lowest-level structure in @a dest +     *   into which to store @a value. +     * +     * @note +     * In the degenerate case in which @a path is an empty array, @a dest will +     * @em become @a value rather than @em containing it. +     */ +    LL_COMMON_API void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value); +} // namespace LLEventDetail + +/** + * Post specified LLSD event on the specified LLEventPump, then wait for a + * response on specified other LLEventPump. This is more than mere + * convenience: the difference between this function and the sequence + * @code + * requestPump.post(myEvent); + * LLSD reply = waitForEventOn(self, replyPump); + * @endcode + * is that the sequence above fails if the reply is posted immediately on + * @a replyPump, that is, before <tt>requestPump.post()</tt> returns. In the + * sequence above, the running coroutine isn't even listening on @a replyPump + * until <tt>requestPump.post()</tt> returns and @c waitForEventOn() is + * entered. Therefore, the coroutine completely misses an immediate reply + * event, making it wait indefinitely. + * + * By contrast, postAndWait() listens on the @a replyPump @em before posting + * the specified LLSD event on the specified @a requestPump. + * + * @param self The @c self object passed into a coroutine + * @param event LLSD data to be posted on @a requestPump + * @param requestPump an LLEventPump on which to post @a event. Pass either + * the LLEventPump& or its string name. However, if you pass a + * default-constructed @c LLEventPumpOrPumpName, we skip the post() call. + * @param replyPump an LLEventPump on which postAndWait() will listen for a + * reply. Pass either the LLEventPump& or its string name. The calling + * coroutine will wait until that reply arrives. (If you're concerned about a + * reply that might not arrive, please see also LLEventTimeout.) + * @param replyPumpNamePath specifies the location within @a event in which to + * store <tt>replyPump.getName()</tt>. This is a strictly optional convenience + * feature; obviously you can store the name in @a event "by hand" if desired. + * @a replyPumpNamePath can be specified in any of four forms: + * * @c isUndefined() (default-constructed LLSD object): do nothing. This is + *   the default behavior if you omit @a replyPumpNamePath. + * * @c isInteger(): @a event is an array. Store <tt>replyPump.getName()</tt> + *   in <tt>event[replyPumpNamePath.asInteger()]</tt>. + * * @c isString(): @a event is a map. Store <tt>replyPump.getName()</tt> in + *   <tt>event[replyPumpNamePath.asString()]</tt>. + * * @c isArray(): @a event has several levels of structure, e.g. map of + *   maps, array of arrays, array of maps, map of arrays, ... Store + *   <tt>replyPump.getName()</tt> in + *   <tt>event[replyPumpNamePath[0]][replyPumpNamePath[1]]...</tt> In other + *   words, examine each array entry in @a replyPumpNamePath in turn. If it's an + *   <tt>LLSD::String</tt>, the current level of @a event is a map; step down to + *   that map entry. If it's an <tt>LLSD::Integer</tt>, the current level of @a + *   event is an array; step down to that array entry. The last array entry in + *   @a replyPumpNamePath specifies the entry in the lowest-level structure in + *   @a event into which to store <tt>replyPump.getName()</tt>. + */ +template <typename SELF> +LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, +                 const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath=LLSD()) +{ +    // declare the future +    boost::coroutines::future<LLSD> future(self); +    // make a callback that will assign a value to the future, and listen on +    // the specified LLEventPump with that callback +    std::string listenerName(LLEventDetail::listenerNameForCoro(self)); +    LLTempBoundListener connection( +        replyPump.getPump().listen(listenerName, +                                   voidlistener(boost::coroutines::make_callback(future)))); +    // skip the "post" part if requestPump is default-constructed +    if (requestPump) +    { +        // If replyPumpNamePath is non-empty, store the replyPump name in the +        // request event. +        LLSD modevent(event); +        LLEventDetail::storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName()); +        LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName +                                 << " posting to " << requestPump.getPump().getName() +                                 << ": " << modevent << LL_ENDL; +        requestPump.getPump().post(modevent); +    } +    LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName +                             << " about to wait on LLEventPump " << replyPump.getPump().getName() +                             << LL_ENDL; +    // trying to dereference ("resolve") the future makes us wait for it +    LLSD value(*future); +    LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << listenerName +                             << " resuming with " << value << LL_ENDL; +    // returning should disconnect the connection +    return value; +} + +/// Wait for the next event on the specified LLEventPump. Pass either the +/// LLEventPump& or its string name. +template <typename SELF> +LLSD waitForEventOn(SELF& self, const LLEventPumpOrPumpName& pump) +{ +    // This is now a convenience wrapper for postAndWait(). +    return postAndWait(self, LLSD(), LLEventPumpOrPumpName(), pump); +} + +/// return type for two-pump variant of waitForEventOn() +typedef std::pair<LLSD, int> LLEventWithID; + +namespace LLEventDetail +{ +    /** +     * This helper is specifically for the two-pump version of waitForEventOn(). +     * We use a single future object, but we want to listen on two pumps with it. +     * Since we must still adapt from (the callable constructed by) +     * boost::coroutines::make_callback() (void return) to provide an event +     * listener (bool return), we've adapted LLVoidListener for the purpose. The +     * basic idea is that we construct a distinct instance of WaitForEventOnHelper +     * -- binding different instance data -- for each of the pumps. Then, when a +     * pump delivers an LLSD value to either WaitForEventOnHelper, it can combine +     * that LLSD with its discriminator to feed the future object. +     */ +    template <typename LISTENER> +    class WaitForEventOnHelper +    { +    public: +        WaitForEventOnHelper(const LISTENER& listener, int discriminator): +            mListener(listener), +            mDiscrim(discriminator) +        {} +        // this signature is required for an LLEventPump listener +        bool operator()(const LLSD& event) +        { +            // our future object is defined to accept LLEventWithID +            mListener(LLEventWithID(event, mDiscrim)); +            // don't swallow the event, let other listeners see it +            return false; +        } +    private: +        LISTENER mListener; +        const int mDiscrim; +    }; + +    /// WaitForEventOnHelper type-inference helper +    template <typename LISTENER> +    WaitForEventOnHelper<LISTENER> wfeoh(const LISTENER& listener, int discriminator) +    { +        return WaitForEventOnHelper<LISTENER>(listener, discriminator); +    } +} // namespace LLEventDetail + +/** + * This function waits for a reply on either of two specified LLEventPumps. + * Otherwise, it closely resembles postAndWait(); please see the documentation + * for that function for detailed parameter info. + * + * While we could have implemented the single-pump variant in terms of this + * one, there's enough added complexity here to make it worthwhile to give the + * single-pump variant its own straightforward implementation. Conversely, + * though we could use preprocessor logic to generate n-pump overloads up to + * BOOST_COROUTINE_WAIT_MAX, we don't foresee a use case. This two-pump + * overload exists because certain event APIs are defined in terms of a reply + * LLEventPump and an error LLEventPump. + * + * The LLEventWithID return value provides not only the received event, but + * the index of the pump on which it arrived (0 or 1). + * + * @note + * I'd have preferred to overload the name postAndWait() for both signatures. + * But consider the following ambiguous call: + * @code + * postAndWait(self, LLSD(), requestPump, replyPump, "someString"); + * @endcode + * "someString" could be converted to either LLSD (@a replyPumpNamePath for + * the single-pump function) or LLEventOrPumpName (@a replyPump1 for two-pump + * function). + * + * It seems less burdensome to write postAndWait2() than to write either + * LLSD("someString") or LLEventOrPumpName("someString"). + */ +template <typename SELF> +LLEventWithID postAndWait2(SELF& self, const LLSD& event, +                           const LLEventPumpOrPumpName& requestPump, +                           const LLEventPumpOrPumpName& replyPump0, +                           const LLEventPumpOrPumpName& replyPump1, +                           const LLSD& replyPump0NamePath=LLSD(), +                           const LLSD& replyPump1NamePath=LLSD()) +{ +    // declare the future +    boost::coroutines::future<LLEventWithID> future(self); +    // either callback will assign a value to this future; listen on +    // each specified LLEventPump with a callback +    std::string name(LLEventDetail::listenerNameForCoro(self)); +    LLTempBoundListener connection0( +        replyPump0.getPump().listen(name + "a", +                               LLEventDetail::wfeoh(boost::coroutines::make_callback(future), 0))); +    LLTempBoundListener connection1( +        replyPump1.getPump().listen(name + "b", +                               LLEventDetail::wfeoh(boost::coroutines::make_callback(future), 1))); +    // skip the "post" part if requestPump is default-constructed +    if (requestPump) +    { +        // If either replyPumpNamePath is non-empty, store the corresponding +        // replyPump name in the request event. +        LLSD modevent(event); +        LLEventDetail::storeToLLSDPath(modevent, replyPump0NamePath, +                                       replyPump0.getPump().getName()); +        LLEventDetail::storeToLLSDPath(modevent, replyPump1NamePath, +                                       replyPump1.getPump().getName()); +        LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name +                                 << " posting to " << requestPump.getPump().getName() +                                 << ": " << modevent << LL_ENDL; +        requestPump.getPump().post(modevent); +    } +    LL_DEBUGS("lleventcoro") << "postAndWait2(): coroutine " << name +                             << " about to wait on LLEventPumps " << replyPump0.getPump().getName() +                             << ", " << replyPump1.getPump().getName() << LL_ENDL; +    // trying to dereference ("resolve") the future makes us wait for it +    LLEventWithID value(*future); +    LL_DEBUGS("lleventcoro") << "postAndWait(): coroutine " << name +                             << " resuming with (" << value.first << ", " << value.second << ")" +                             << LL_ENDL; +    // returning should disconnect both connections +    return value; +} + +/** + * Wait for the next event on either of two specified LLEventPumps. + */ +template <typename SELF> +LLEventWithID +waitForEventOn(SELF& self, +               const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1) +{ +    // This is now a convenience wrapper for postAndWait2(). +    return postAndWait2(self, LLSD(), LLEventPumpOrPumpName(), pump0, pump1); +} + +/** + * Helper for the two-pump variant of waitForEventOn(), e.g.: + * + * @code + * LLSD reply = errorException(waitForEventOn(self, replyPump, errorPump), + *                             "error response from login.cgi"); + * @endcode + * + * Examines an LLEventWithID, assuming that the second pump (pump 1) is + * listening for an error indication. If the incoming data arrived on pump 1, + * throw an LLErrorEvent exception. If the incoming data arrived on pump 0, + * just return it. Since a normal return can only be from pump 0, we no longer + * need the LLEventWithID's discriminator int; we can just return the LLSD. + * + * @note I'm not worried about introducing the (fairly generic) name + * errorException() into global namespace, because how many other overloads of + * the same name are going to accept an LLEventWithID parameter? + */ +LLSD errorException(const LLEventWithID& result, const std::string& desc); + +/** + * Exception thrown by errorException(). We don't call this LLEventError + * because it's not an error in event processing: rather, this exception + * announces an event that bears error information (for some other API). + */ +class LL_COMMON_API LLErrorEvent: public std::runtime_error +{ +public: +    LLErrorEvent(const std::string& what, const LLSD& data): +        std::runtime_error(what), +        mData(data) +    {} +    virtual ~LLErrorEvent() throw() {} + +    LLSD getData() const { return mData; } + +private: +    LLSD mData; +}; + +/** + * Like errorException(), save that this trips a fatal error using LL_ERRS + * rather than throwing an exception. + */ +LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc); + +/** + * Certain event APIs require the name of an LLEventPump on which they should + * post results. While it works to invent a distinct name and let + * LLEventPumps::obtain() instantiate the LLEventPump as a "named singleton," + * in a certain sense it's more robust to instantiate a local LLEventPump and + * provide its name instead. This class packages the following idiom: + * + * 1. Instantiate a local LLCoroEventPump, with an optional name prefix. + * 2. Provide its actual name to the event API in question as the name of the + *    reply LLEventPump. + * 3. Initiate the request to the event API. + * 4. Call your LLEventTempStream's wait() method to wait for the reply. + * 5. Let the LLCoroEventPump go out of scope. + */ +class LL_COMMON_API LLCoroEventPump +{ +public: +    LLCoroEventPump(const std::string& name="coro"): +        mPump(name, true)           // allow tweaking the pump instance name +    {} +    /// It's typical to request the LLEventPump name to direct an event API to +    /// send its response to this pump. +    std::string getName() const { return mPump.getName(); } +    /// Less typically, we'd request the pump itself for some reason. +    LLEventPump& getPump() { return mPump; } + +    /** +     * Wait for an event on this LLEventPump. +     * +     * @note +     * The other major usage pattern we considered was to bind @c self at +     * LLCoroEventPump construction time, which would avoid passing the +     * parameter to each wait() call. But if we were going to bind @c self as +     * a class member, we'd need to specify a class template parameter +     * indicating its type. The big advantage of passing it to the wait() call +     * is that the type can be implicit. +     */ +    template <typename SELF> +    LLSD wait(SELF& self) +    { +        return waitForEventOn(self, mPump); +    } + +    template <typename SELF> +    LLSD postAndWait(SELF& self, const LLSD& event, const LLEventPumpOrPumpName& requestPump, +                     const LLSD& replyPumpNamePath=LLSD()) +    { +        return ::postAndWait(self, event, requestPump, mPump, replyPumpNamePath); +    } + +private: +    LLEventStream mPump; +}; + +/** + * Other event APIs require the names of two different LLEventPumps: one for + * success response, the other for error response. Extend LLCoroEventPump + * for the two-pump use case. + */ +class LL_COMMON_API LLCoroEventPumps +{ +public: +    LLCoroEventPumps(const std::string& name="coro", +                     const std::string& suff0="Reply", +                     const std::string& suff1="Error"): +        mPump0(name + suff0, true),   // allow tweaking the pump instance name +        mPump1(name + suff1, true) +    {} +    /// request pump 0's name +    std::string getName0() const { return mPump0.getName(); } +    /// request pump 1's name +    std::string getName1() const { return mPump1.getName(); } +    /// request both names +    std::pair<std::string, std::string> getNames() const +    { +        return std::pair<std::string, std::string>(mPump0.getName(), mPump1.getName()); +    } + +    /// request pump 0 +    LLEventPump& getPump0() { return mPump0; } +    /// request pump 1 +    LLEventPump& getPump1() { return mPump1; } + +    /// waitForEventOn(self, either of our two LLEventPumps) +    template <typename SELF> +    LLEventWithID wait(SELF& self) +    { +        return waitForEventOn(self, mPump0, mPump1); +    } + +    /// errorException(wait(self)) +    template <typename SELF> +    LLSD waitWithException(SELF& self) +    { +        return errorException(wait(self), std::string("Error event on ") + getName1()); +    } + +    /// errorLog(wait(self)) +    template <typename SELF> +    LLSD waitWithLog(SELF& self) +    { +        return errorLog(wait(self), std::string("Error event on ") + getName1()); +    } + +    template <typename SELF> +    LLEventWithID postAndWait(SELF& self, const LLSD& event, +                              const LLEventPumpOrPumpName& requestPump, +                              const LLSD& replyPump0NamePath=LLSD(), +                              const LLSD& replyPump1NamePath=LLSD()) +    { +        return postAndWait2(self, event, requestPump, mPump0, mPump1, +                            replyPump0NamePath, replyPump1NamePath); +    } + +    template <typename SELF> +    LLSD postAndWaitWithException(SELF& self, const LLSD& event, +                                  const LLEventPumpOrPumpName& requestPump, +                                  const LLSD& replyPump0NamePath=LLSD(), +                                  const LLSD& replyPump1NamePath=LLSD()) +    { +        return errorException(postAndWait(self, event, requestPump, +                                          replyPump0NamePath, replyPump1NamePath), +                              std::string("Error event on ") + getName1()); +    } + +    template <typename SELF> +    LLSD postAndWaitWithLog(SELF& self, const LLSD& event, +                            const LLEventPumpOrPumpName& requestPump, +                            const LLSD& replyPump0NamePath=LLSD(), +                            const LLSD& replyPump1NamePath=LLSD()) +    { +        return errorLog(postAndWait(self, event, requestPump, +                                    replyPump0NamePath, replyPump1NamePath), +                        std::string("Error event on ") + getName1()); +    } + +private: +    LLEventStream mPump0, mPump1; +}; + +#endif /* ! defined(LL_LLEVENTCORO_H) */ diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp new file mode 100644 index 0000000000..6b1413d054 --- /dev/null +++ b/indra/llcommon/lleventdispatcher.cpp @@ -0,0 +1,133 @@ +/** + * @file   lleventdispatcher.cpp + * @author Nat Goodspeed + * @date   2009-06-18 + * @brief  Implementation for lleventdispatcher. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/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" + +LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): +    mDesc(desc), +    mKey(key) +{ +} + +LLEventDispatcher::~LLEventDispatcher() +{ +} + +/// Register a callable by name +void LLEventDispatcher::add(const std::string& name, const Callable& callable, const LLSD& required) +{ +    mDispatch[name] = DispatchMap::mapped_type(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 (! attemptCall(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 (! attemptCall(name, event)) +    { +        LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey +                                     << " value '" << name << "'" << LL_ENDL; +    } +} + +bool LLEventDispatcher::attemptCall(const std::string& name, const LLSD& event) const +{ +    DispatchMap::const_iterator found = mDispatch.find(name); +    if (found == mDispatch.end()) +    { +        // The reason we only return false, leaving it up to our caller to die +        // with LL_ERRS, is that different callers have different amounts of +        // available information. +        return false; +    } +    // Found the name, so it's plausible to even attempt the call. But first, +    // validate the syntax of the event itself. +    std::string mismatch(llsd_matches(found->second.second, event)); +    if (! mismatch.empty()) +    { +        LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ") calling '" << name +                                     << "': bad request: " << mismatch << LL_ENDL; +    } +    // Event syntax looks good, go for it! +    (found->second.first)(event); +    return true;                    // tell caller we were able to call +} + +LLEventDispatcher::Callable LLEventDispatcher::get(const std::string& name) const +{ +    DispatchMap::const_iterator found = mDispatch.find(name); +    if (found == mDispatch.end()) +    { +        return Callable(); +    } +    return found->second.first; +} + +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; +} diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h new file mode 100644 index 0000000000..5a86b90bff --- /dev/null +++ b/indra/llcommon/lleventdispatcher.h @@ -0,0 +1,130 @@ +/** + * @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=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTDISPATCHER_H) +#define LL_LLEVENTDISPATCHER_H + +#include <string> +#include <map> +#include <boost/function.hpp> +#include <boost/bind.hpp> +#include <typeinfo> +#include "llevents.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(); + +    /// Accept any C++ callable, typically a boost::bind() expression +    typedef boost::function<void(const LLSD&)> Callable; + +    /** +     * Register a @a callable by @a name. 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 Callable& callable, const LLSD& required=LLSD()); + +    /** +     * Special case: a subclass of this class can pass an unbound member +     * function pointer without explicitly specifying the +     * <tt>boost::bind()</tt> expression. +     */ +    template <class CLASS> +    void add(const std::string& name, void (CLASS::*method)(const LLSD&), +             const LLSD& required=LLSD()) +    { +        addMethod<CLASS>(name, method, required); +    } + +    /// Overload for both const and non-const methods +    template <class CLASS> +    void add(const std::string& name, void (CLASS::*method)(const LLSD&) const, +             const LLSD& required=LLSD()) +    { +        addMethod<CLASS>(name, method, required); +    } + +    /// 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; + +    /// 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; + +    /// Fetch the Callable for the specified name. If no such name was +    /// registered, return an empty() Callable. +    Callable get(const std::string& name) const; + +private: +    template <class CLASS, typename METHOD> +    void addMethod(const std::string& name, const METHOD& method, const LLSD& required) +    { +        CLASS* downcast = dynamic_cast<CLASS*>(this); +        if (! downcast) +        { +            addFail(name, typeid(CLASS).name()); +        } +        else +        { +            add(name, boost::bind(method, downcast, _1), required); +        } +    } +    void addFail(const std::string& name, const std::string& classname) const; +    /// try to dispatch, return @c true if success +    bool attemptCall(const std::string& name, const LLSD& event) const; + +    std::string mDesc, mKey; +    typedef std::map<std::string, std::pair<Callable, LLSD> > DispatchMap; +    DispatchMap mDispatch; +}; + +/** + * 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 0000000000..74133781be --- /dev/null +++ b/indra/llcommon/lleventfilter.cpp @@ -0,0 +1,149 @@ +/** + * @file   lleventfilter.cpp + * @author Nat Goodspeed + * @date   2009-03-05 + * @brief  Implementation for lleventfilter. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventfilter.h" +// STL headers +// std headers +// external library headers +#include <boost/bind.hpp> +// other Linden headers +#include "llerror.h"                // LL_ERRS +#include "llsdutil.h"               // llsd_matches() + +LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak): +    LLEventStream(name, tweak) +{ +    source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1)); +} + +LLEventMatching::LLEventMatching(const LLSD& pattern): +    LLEventFilter("matching"), +    mPattern(pattern) +{ +} + +LLEventMatching::LLEventMatching(LLEventPump& source, const LLSD& pattern): +    LLEventFilter(source, "matching"), +    mPattern(pattern) +{ +} + +bool LLEventMatching::post(const LLSD& event) +{ +    if (! llsd_matches(mPattern, event).empty()) +        return false; + +    return LLEventStream::post(event); +} + +LLEventTimeoutBase::LLEventTimeoutBase(): +    LLEventFilter("timeout") +{ +} + +LLEventTimeoutBase::LLEventTimeoutBase(LLEventPump& source): +    LLEventFilter(source, "timeout") +{ +} + +void LLEventTimeoutBase::actionAfter(F32 seconds, const Action& action) +{ +    setCountdown(seconds); +    mAction = action; +    if (! mMainloop.connected()) +    { +        LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); +        mMainloop = mainloop.listen(getName(), boost::bind(&LLEventTimeoutBase::tick, this, _1)); +    } +} + +class ErrorAfter +{ +public: +    ErrorAfter(const std::string& message): mMessage(message) {} + +    void operator()() +    { +        LL_ERRS("LLEventTimeout") << mMessage << LL_ENDL; +    } + +private: +    std::string mMessage; +}; + +void LLEventTimeoutBase::errorAfter(F32 seconds, const std::string& message) +{ +    actionAfter(seconds, ErrorAfter(message)); +} + +class EventAfter +{ +public: +    EventAfter(LLEventPump& pump, const LLSD& event): +        mPump(pump), +        mEvent(event) +    {} + +    void operator()() +    { +        mPump.post(mEvent); +    } + +private: +    LLEventPump& mPump; +    LLSD mEvent; +}; + +void LLEventTimeoutBase::eventAfter(F32 seconds, const LLSD& event) +{ +    actionAfter(seconds, EventAfter(*this, event)); +} + +bool LLEventTimeoutBase::post(const LLSD& event) +{ +    cancel(); +    return LLEventStream::post(event); +} + +void LLEventTimeoutBase::cancel() +{ +    mMainloop.disconnect(); +} + +bool LLEventTimeoutBase::tick(const LLSD&) +{ +    if (countdownElapsed()) +    { +        cancel(); +        mAction(); +    } +    return false;                   // show event to other listeners +} + +LLEventTimeout::LLEventTimeout() {} + +LLEventTimeout::LLEventTimeout(LLEventPump& source): +    LLEventTimeoutBase(source) +{ +} + +void LLEventTimeout::setCountdown(F32 seconds) +{ +    mTimer.setTimerExpirySec(seconds); +} + +bool LLEventTimeout::countdownElapsed() const +{ +    return mTimer.hasExpired(); +} diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h new file mode 100644 index 0000000000..89f0c7ea43 --- /dev/null +++ b/indra/llcommon/lleventfilter.h @@ -0,0 +1,186 @@ +/** + * @file   lleventfilter.h + * @author Nat Goodspeed + * @date   2009-03-05 + * @brief  Define LLEventFilter: LLEventStream subclass with conditions + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLEVENTFILTER_H) +#define LL_LLEVENTFILTER_H + +#include "llevents.h" +#include "stdtypes.h" +#include "lltimer.h" +#include <boost/function.hpp> + +/** + * Generic base class + */ +class LL_COMMON_API LLEventFilter: public LLEventStream +{ +public: +    /// construct a standalone LLEventFilter +    LLEventFilter(const std::string& name="filter", bool tweak=true): +        LLEventStream(name, tweak) +    {} +    /// construct LLEventFilter and connect it to the specified LLEventPump +    LLEventFilter(LLEventPump& source, const std::string& name="filter", bool tweak=true); + +    /// Post an event to all listeners +    virtual bool post(const LLSD& event) = 0; +}; + +/** + * Pass through only events matching a specified pattern + */ +class LLEventMatching: public LLEventFilter +{ +public: +    /// Pass an LLSD map with keys and values the incoming event must match +    LLEventMatching(const LLSD& pattern); +    /// instantiate and connect +    LLEventMatching(LLEventPump& source, const LLSD& pattern); + +    /// Only pass through events matching the pattern +    virtual bool post(const LLSD& event); + +private: +    LLSD mPattern; +}; + +/** + * Wait for an event to be posted. If no such event arrives within a specified + * time, take a specified action. See LLEventTimeout for production + * implementation. + * + * @NOTE This is an abstract base class so that, for testing, we can use an + * alternate "timer" that doesn't actually consume real time. + */ +class LL_COMMON_API LLEventTimeoutBase: public LLEventFilter +{ +public: +    /// construct standalone +    LLEventTimeoutBase(); +    /// construct and connect +    LLEventTimeoutBase(LLEventPump& source); + +    /// Callable, can be constructed with boost::bind() +    typedef boost::function<void()> Action; + +    /** +     * Start countdown timer for the specified number of @a seconds. Forward +     * all events. If any event arrives before timer expires, cancel timer. If +     * no event arrives before timer expires, take specified @a action. +     * +     * This is a one-shot timer. Once it has either expired or been canceled, +     * it is inert until another call to actionAfter(). +     * +     * Calling actionAfter() while an existing timer is running cheaply +     * replaces that original timer. Thus, a valid use case is to detect +     * idleness of some event source by calling actionAfter() on each new +     * event. A rapid sequence of events will keep the timer from expiring; +     * the first gap in events longer than the specified timer will fire the +     * specified Action. +     * +     * Any post() call cancels the timer. To be satisfied with only a +     * particular event, chain on an LLEventMatching that only passes such +     * events: +     * +     * @code +     * event                                                 ultimate +     * source ---> LLEventMatching ---> LLEventTimeout  ---> listener +     * @endcode +     * +     * @NOTE +     * The implementation relies on frequent events on the LLEventPump named +     * "mainloop". +     */ +    void actionAfter(F32 seconds, const Action& action); + +    /** +     * Like actionAfter(), but where the desired Action is LL_ERRS +     * termination. Pass the timeout time and the desired LL_ERRS @a message. +     * +     * This method is useful when, for instance, some async API guarantees an +     * event, whether success or failure, within a stated time window. +     * Instantiate an LLEventTimeout listening to that API and call +     * errorAfter() on each async request with a timeout comfortably longer +     * than the API's time guarantee (much longer than the anticipated +     * "mainloop" granularity). +     * +     * Then if the async API breaks its promise, the program terminates with +     * the specified LL_ERRS @a message. The client of the async API can +     * therefore assume the guarantee is upheld. +     * +     * @NOTE +     * errorAfter() is implemented in terms of actionAfter(), so all remarks +     * about calling actionAfter() also apply to errorAfter(). +     */ +    void errorAfter(F32 seconds, const std::string& message); + +    /** +     * Like actionAfter(), but where the desired Action is a particular event +     * for all listeners. Pass the timeout time and the desired @a event data. +     *  +     * Suppose the timeout should only be satisfied by a particular event, but +     * the ultimate listener must see all other incoming events as well, plus +     * the timeout @a event if any: +     *  +     * @code +     * some        LLEventMatching                           LLEventMatching +     * event  ---> for particular  ---> LLEventTimeout  ---> for timeout +     * source      event                                     event \ +     *       \                                                      \ ultimate +     *        `-----------------------------------------------------> listener +     * @endcode +     *  +     * Since a given listener can listen on more than one LLEventPump, we can +     * set things up so it sees the set union of events from LLEventTimeout +     * and the original event source. However, as LLEventTimeout passes +     * through all incoming events, the "particular event" that satisfies the +     * left LLEventMatching would reach the ultimate listener twice. So we add +     * an LLEventMatching that only passes timeout events. +     * +     * @NOTE +     * eventAfter() is implemented in terms of actionAfter(), so all remarks +     * about calling actionAfter() also apply to eventAfter(). +     */ +    void eventAfter(F32 seconds, const LLSD& event); + +    /// Pass event through, canceling the countdown timer +    virtual bool post(const LLSD& event); + +    /// Cancel timer without event +    void cancel(); + +protected: +    virtual void setCountdown(F32 seconds) = 0; +    virtual bool countdownElapsed() const = 0; + +private: +    bool tick(const LLSD&); + +    LLBoundListener mMainloop; +    Action mAction; +}; + +/// Production implementation of LLEventTimoutBase +class LL_COMMON_API LLEventTimeout: public LLEventTimeoutBase +{ +public: +    LLEventTimeout(); +    LLEventTimeout(LLEventPump& source); + +protected: +    virtual void setCountdown(F32 seconds); +    virtual bool countdownElapsed() const; + +private: +    LLTimer mTimer; +}; + +#endif /* ! defined(LL_LLEVENTFILTER_H) */ diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index eb380ba7c8..4bdfe5a867 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -38,6 +38,12 @@  #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 @@ -56,14 +62,12 @@ const char* queue_names[] =  /*****************************************************************************  *   If there's a "mainloop" pump, listen on that to flush all LLEventQueues  *****************************************************************************/ -struct RegisterFlush +struct RegisterFlush : public LLEventTrackable  {      RegisterFlush(): -        pumps(LLEventPumps::instance()), -        mainloop(pumps.obtain("mainloop")), -        name("flushLLEventQueues") +        pumps(LLEventPumps::instance())      { -        mainloop.listen(name, boost::bind(&RegisterFlush::flush, this, _1)); +        pumps.obtain("mainloop").listen("flushLLEventQueues", boost::bind(&RegisterFlush::flush, this, _1));      }      bool flush(const LLSD&)      { @@ -72,11 +76,9 @@ struct RegisterFlush      }      ~RegisterFlush()      { -        mainloop.stopListening(name); +        // LLEventTrackable handles stopListening for us.      }      LLEventPumps& pumps; -    LLEventPump& mainloop; -    const std::string name;  };  static RegisterFlush registerFlush; @@ -124,6 +126,16 @@ void LLEventPumps::flush()      }  } +void LLEventPumps::reset() +{ +    // Reset every known LLEventPump instance. Leave it up to each instance to +    // decide what to do with the reset() call. +    for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi) +    { +        pmi->second->reset(); +    } +} +  std::string LLEventPumps::registerNew(const LLEventPump& pump, const std::string& name, bool tweak)  {      std::pair<PumpMap::iterator, bool> inserted = @@ -240,6 +252,7 @@ LLEventPumps::~LLEventPumps()  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)  {} @@ -256,6 +269,19 @@ LLEventPump::~LLEventPump()  // 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) @@ -397,7 +423,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL      }      // Now that newNode has a value that places it appropriately in mSignal,      // connect it. -    LLBoundListener bound = mSignal.connect(newNode, listener); +    LLBoundListener bound = mSignal->connect(newNode, listener);      mConnections[name] = bound;      return bound;  } @@ -437,7 +463,7 @@ bool LLEventStream::post(const LLSD& event)      // 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 mSignal(event); +    return (*mSignal)(event);  }  /***************************************************************************** @@ -468,7 +494,7 @@ void LLEventQueue::flush()      mEventQueue.clear();      for ( ; ! queue.empty(); queue.pop_front())      { -        mSignal(queue.front()); +        (*mSignal)(queue.front());      }  } @@ -499,3 +525,26 @@ bool LLListenerOrPumpName::operator()(const LLSD& event) const      }      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; +} diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 240adcdd41..192d79b27d 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -19,7 +19,6 @@  #include <map>  #include <set>  #include <vector> -#include <list>  #include <deque>  #include <stdexcept>  #if LL_WINDOWS @@ -37,13 +36,9 @@  #include <boost/enable_shared_from_this.hpp>  #include <boost/utility.hpp>        // noncopyable  #include <boost/optional/optional.hpp> -#include <boost/ptr_container/ptr_vector.hpp>  #include <boost/visit_each.hpp>  #include <boost/ref.hpp>            // reference_wrapper  #include <boost/type_traits/is_pointer.hpp> -#include <boost/utility/addressof.hpp> -#include <boost/preprocessor/repetition/enum_params.hpp> -#include <boost/preprocessor/iteration/local.hpp>  #include <boost/function.hpp>  #include <boost/static_assert.hpp>  #include "llsd.h" @@ -120,6 +115,9 @@ typedef LLStandardSignal::slot_type LLEventListener;  /// Result of registering a listener, supports <tt>connected()</tt>,  /// <tt>disconnect()</tt> and <tt>blocked()</tt>  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 -- @@ -136,7 +134,7 @@ typedef boost::signals2::connection LLBoundListener;   * LLListenerOrPumpName::Empty. Test for this condition beforehand using   * either <tt>if (param)</tt> or <tt>if (! param)</tt>.   */ -class LLListenerOrPumpName +class LL_COMMON_API LLListenerOrPumpName  {  public:      /// passing string name of LLEventPump @@ -189,7 +187,7 @@ class LLEventPump;   * LLEventPumps is a Singleton manager through which one typically accesses   * this subsystem.   */ -class LLEventPumps: public LLSingleton<LLEventPumps> +class LL_COMMON_API LLEventPumps: public LLSingleton<LLEventPumps>  {      friend class LLSingleton<LLEventPumps>;  public: @@ -204,6 +202,12 @@ public:       */      void flush(); +    /** +     * Reset all known LLEventPump instances +     * workaround for DEV-35406 crash on shutdown +     */ +    void reset(); +  private:      friend class LLEventPump;      /** @@ -264,13 +268,61 @@ namespace LLEventDetail  } // namespace LLEventDetail  /***************************************************************************** +*   LLEventTrackable +*****************************************************************************/ +/** + * LLEventTrackable wraps boost::signals2::trackable, which resembles + * boost::trackable. Derive your listener class from LLEventTrackable instead, + * and use something like + * <tt>LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, + * instance, _1))</tt>. 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 + *     <tt>LLEventPump::listen()</tt>. + *   - The @c MySubclass object begins destruction. <tt>~MySubclass()</tt> + *     runs, destroying state specific to the subclass. (For instance, a + *     <tt>Foo*</tt> data member is <tt>delete</tt>d but not zeroed.) + *   - The listening method will not be disconnected until + *     <tt>~LLEventTrackable()</tt> 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 <tt>Foo*</tt> pointer that was + *     <tt>delete</tt>d 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 <tt>boost::shared_ptr</tt>. + * Passing <tt>LLEventPump::listen()</tt> a <tt>boost::bind()</tt> expression + * involving a <tt>boost::weak_ptr<Foo></tt> 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 LLEventPump: boost::noncopyable +class LL_COMMON_API LLEventPump: public LLEventTrackable  {  public:      /** @@ -373,10 +425,22 @@ public:       * themselves. listen() can throw any ListenError; see ListenError       * subclasses.       * -     * If (as is typical) you pass a <tt>boost::bind()</tt> expression, -     * 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. +     * 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 <tt>boost::bind()</tt> 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 <tt>boost::weak_ptr</tt>.       * * Binding a <tt>boost::shared_ptr</tt> that way would ensure that the @@ -438,11 +502,16 @@ public:      /// 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, @@ -451,7 +520,8 @@ private:  protected:      /// implement the dispatching -    LLStandardSignal mSignal; +    boost::scoped_ptr<LLStandardSignal> mSignal; +      /// valve open?      bool mEnabled;      /// Map of named listeners. This tracks the listeners that actually exist @@ -476,7 +546,7 @@ protected:   * LLEventStream is a thin wrapper around LLStandardSignal. Posting an   * event immediately calls all registered listeners.   */ -class LLEventStream: public LLEventPump +class LL_COMMON_API LLEventStream: public LLEventPump  {  public:      LLEventStream(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} @@ -493,7 +563,7 @@ public:   * LLEventQueue isa LLEventPump whose post() method defers calling registered   * listeners until flush() is called.   */ -class LLEventQueue: public LLEventPump +class LL_COMMON_API LLEventQueue: public LLEventPump  {  public:      LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {} @@ -512,47 +582,89 @@ private:  };  /***************************************************************************** -*   LLEventTrackable and underpinnings +*   LLReqID  *****************************************************************************/  /** - * LLEventTrackable wraps boost::signals2::trackable, which resembles - * boost::trackable. Derive your listener class from LLEventTrackable instead, - * and use something like - * <tt>LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, - * instance, _1))</tt>. This will implicitly disconnect when the object - * referenced by @c instance is destroyed. + * 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 - * 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 - *     <tt>LLEventPump::listen()</tt>. - *   - The @c MySubclass object begins destruction. <tt>~MySubclass()</tt> - *     runs, destroying state specific to the subclass. (For instance, a - *     <tt>Foo*</tt> data member is <tt>delete</tt>d but not zeroed.) - *   - The listening method will not be disconnected until - *     <tt>~LLEventTrackable()</tt> 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 <tt>Foo*</tt> pointer that was - *     <tt>delete</tt>d 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 <tt>boost::shared_ptr</tt>. - * Passing <tt>LLEventPump::listen()</tt> a <tt>boost::bind()</tt> expression - * involving a <tt>boost::weak_ptr<Foo></tt> is recognized specially, engaging - * thread-safe Boost.Signals2 machinery. + * 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.   */ -typedef boost::signals2::trackable LLEventTrackable; +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; +}; + +/***************************************************************************** +*   Underpinnings +*****************************************************************************/  /**   * We originally provided a suite of overloaded   * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h index 576e45d2ae..45b84ea3ea 100644 --- a/indra/llcommon/llfasttimer.h +++ b/indra/llcommon/llfasttimer.h @@ -113,11 +113,11 @@ class LLMutex;  #include "llsd.h" -class LLFastTimer +class LL_COMMON_API LLFastTimer  {  public:  	// stores a "named" timer instance to be reused via multiple LLFastTimer stack instances -	class NamedTimer  +	class LL_COMMON_API NamedTimer   	:	public LLInstanceTracker<NamedTimer>  	{  		friend class DeclareTimer; @@ -210,7 +210,7 @@ public:  	};  	// used to statically declare a new named timer -	class DeclareTimer  +	class LL_COMMON_API DeclareTimer  	:	public LLInstanceTracker<DeclareTimer>  	{  	public: diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index c6092f7b9c..fea5d3ed2b 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -70,7 +70,7 @@ typedef struct stat		llstat;  #include "llstring.h" // safe char* -> std::string conversion -class	LLFile +class LL_COMMON_API LLFile  {  public:  	// All these functions take UTF8 path/filenames. @@ -95,7 +95,7 @@ public:  #if USE_LLFILESTREAMS -class	llifstream	:	public	std::basic_istream < char , std::char_traits < char > > +class LL_COMMON_API llifstream	:	public	std::basic_istream < char , std::char_traits < char > >  {  	// input stream associated with a C stream  public: @@ -136,7 +136,7 @@ private:  }; -class	llofstream	:	public	std::basic_ostream< char , std::char_traits < char > > +class LL_COMMON_API llofstream	:	public	std::basic_ostream< char , std::char_traits < char > >  {  public:  	typedef std::basic_ostream< char , std::char_traits < char > > _Myt; @@ -185,7 +185,7 @@ private:  //#define	llifstream	std::ifstream  //#define	llofstream	std::ofstream -class	llifstream	:	public	std::ifstream +class LL_COMMON_API llifstream	:	public	std::ifstream  {  public:  	llifstream() : std::ifstream() @@ -203,7 +203,7 @@ public:  }; -class	llofstream	:	public	std::ofstream +class LL_COMMON_API llofstream	:	public	std::ofstream  {  public:  	llofstream() : std::ofstream() @@ -231,7 +231,7 @@ public:   * and should only be used for config files and the like -- not in a   * loop.   */ -std::streamsize llifstream_size(llifstream& fstr); -std::streamsize llofstream_size(llofstream& fstr); +std::streamsize LL_COMMON_API llifstream_size(llifstream& fstr); +std::streamsize LL_COMMON_API llofstream_size(llofstream& fstr);  #endif // not LL_LLFILE_H diff --git a/indra/llcommon/llfindlocale.h b/indra/llcommon/llfindlocale.h index f17c7740f3..b812a065db 100644 --- a/indra/llcommon/llfindlocale.h +++ b/indra/llcommon/llfindlocale.h @@ -59,8 +59,8 @@ typedef enum {  /* This allocates/fills in a FL_Locale structure with pointers to     strings (which should be treated as static), or NULL for inappropriate /     undetected fields. */ -FL_Success FL_FindLocale(FL_Locale **locale, FL_Domain domain); +LL_COMMON_API FL_Success FL_FindLocale(FL_Locale **locale, FL_Domain domain);  /* This should be used to free the struct written by FL_FindLocale */ -void FL_FreeLocale(FL_Locale **locale); +LL_COMMON_API void FL_FreeLocale(FL_Locale **locale);  #endif /*__findlocale_h_*/ diff --git a/indra/llcommon/llfixedbuffer.h b/indra/llcommon/llfixedbuffer.h index 01b46d327a..17fdef27d7 100644 --- a/indra/llcommon/llfixedbuffer.h +++ b/indra/llcommon/llfixedbuffer.h @@ -41,7 +41,7 @@  #include "llerrorcontrol.h"  //  fixed buffer implementation -class LLFixedBuffer : public LLLineBuffer +class LL_COMMON_API LLFixedBuffer : public LLLineBuffer  {  public:  	LLFixedBuffer(const U32 max_lines = 20); diff --git a/indra/llcommon/llfoldertype.h b/indra/llcommon/llfoldertype.h index ecb37d6dde..5374ffd829 100644 --- a/indra/llcommon/llfoldertype.h +++ b/indra/llcommon/llfoldertype.h @@ -38,7 +38,7 @@  // This class handles folder types (similar to assettype, except for folders)  // and operations on those. -class LLFolderType +class LL_COMMON_API LLFolderType  {  public:  	// ! BACKWARDS COMPATIBILITY ! Folder type enums must match asset type enums. diff --git a/indra/llcommon/llformat.h b/indra/llcommon/llformat.h index 44c62d9710..dc64edb26d 100644 --- a/indra/llcommon/llformat.h +++ b/indra/llcommon/llformat.h @@ -40,6 +40,6 @@  // *NOTE: buffer limited to 1024, (but vsnprintf prevents overrun)  // should perhaps be replaced with boost::format. -std::string llformat(const char *fmt, ...); +std::string LL_COMMON_API llformat(const char *fmt, ...);  #endif // LL_LLFORMAT_H diff --git a/indra/llcommon/llframetimer.h b/indra/llcommon/llframetimer.h index 8f51272af2..be2d9b0703 100644 --- a/indra/llcommon/llframetimer.h +++ b/indra/llcommon/llframetimer.h @@ -43,7 +43,7 @@  #include "lltimer.h"  #include "timing.h" -class LLFrameTimer  +class LL_COMMON_API LLFrameTimer   {  public:  	LLFrameTimer() : mStartTime( sFrameTime ), mExpiry(0), mStarted(TRUE) {} diff --git a/indra/llcommon/llheartbeat.h b/indra/llcommon/llheartbeat.h index fecb5b1e54..6f7026970f 100644 --- a/indra/llcommon/llheartbeat.h +++ b/indra/llcommon/llheartbeat.h @@ -40,7 +40,7 @@  // Note: Win32 does not support the heartbeat/smackdown system;  //   heartbeat-delivery turns into a no-op there. -class LLHeartbeat +class LL_COMMON_API LLHeartbeat  {  public:  	// secs_between_heartbeat: after a heartbeat is successfully delivered, diff --git a/indra/llcommon/llkeythrottle.h b/indra/llcommon/llkeythrottle.h index 873f50a65e..7544ab1d11 100644 --- a/indra/llcommon/llkeythrottle.h +++ b/indra/llcommon/llkeythrottle.h @@ -118,6 +118,63 @@ public:  		THROTTLE_BLOCKED,		// rate exceed, block key  	}; +	F64 getActionCount(const T& id) +	{ +		U64 now = 0; +		if ( mIsRealtime ) +		{ +			now = LLKeyThrottleImpl<T>::getTime(); +		} +		else +		{ +			now = LLKeyThrottleImpl<T>::getFrame(); +		} + +		if (now >= (m.startTime + m.intervalLength)) +		{ +			if (now < (m.startTime + 2 * m.intervalLength)) +			{ +				// prune old data +				delete m.prevMap; +				m.prevMap = m.currMap; +				m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; + +				m.startTime += m.intervalLength; +			} +			else +			{ +				// lots of time has passed, all data is stale +				delete m.prevMap; +				delete m.currMap; +				m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap; +				m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; + +				m.startTime = now; +			} +		} + +		U32 prevCount = 0; + +		typename LLKeyThrottleImpl<T>::EntryMap::const_iterator prev = m.prevMap->find(id); +		if (prev != m.prevMap->end()) +		{ +			prevCount = prev->second.count; +		} + +		typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id]; + +		// curr.count is the number of keys in +		// this current 'time slice' from the beginning of it until now +		// prevCount is the number of keys in the previous +		// time slice scaled to be one full time slice back from the current  +		// (now) time. + +		// compute current, windowed rate +		F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength); +		F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent); +		return averageCount; +	} +  	// call each time the key wants use  	State noteAction(const T& id, S32 weight = 1)  	{ diff --git a/indra/llcommon/llliveappconfig.h b/indra/llcommon/llliveappconfig.h index a6ece6e8b3..73b3a23352 100644 --- a/indra/llcommon/llliveappconfig.h +++ b/indra/llcommon/llliveappconfig.h @@ -45,7 +45,7 @@   * loop.  The traditional name for it is live_config.  Be sure to call   * <code>live_config.checkAndReload()</code> periodically.   */ -class LLLiveAppConfig : public LLLiveFile +class LL_COMMON_API LLLiveAppConfig : public LLLiveFile  {  public: diff --git a/indra/llcommon/lllivefile.h b/indra/llcommon/lllivefile.h index 89b5d95e44..2453d7a125 100644 --- a/indra/llcommon/lllivefile.h +++ b/indra/llcommon/lllivefile.h @@ -36,7 +36,7 @@  extern const F32 DEFAULT_CONFIG_FILE_REFRESH; -class LLLiveFile +class LL_COMMON_API LLLiveFile  {  public:  	LLLiveFile(const std::string& filename, const F32 refresh_period = 5.f); diff --git a/indra/llcommon/lllog.h b/indra/llcommon/lllog.h index 7ac6c8aa42..4b6777bb9c 100644 --- a/indra/llcommon/lllog.h +++ b/indra/llcommon/lllog.h @@ -41,7 +41,7 @@ class LLLogImpl;  class LLApp;  class LLSD; -class LLLog +class LL_COMMON_API LLLog  {  public:  	LLLog(LLApp* app); diff --git a/indra/llcommon/llmd5.h b/indra/llcommon/llmd5.h index d8bca03e4e..df9d7324ab 100644 --- a/indra/llcommon/llmd5.h +++ b/indra/llcommon/llmd5.h @@ -80,7 +80,7 @@ const int MD5RAW_BYTES = 16;  const int MD5HEX_STR_SIZE = 33;  // char hex[MD5HEX_STR_SIZE]; with null  const int MD5HEX_STR_BYTES = 32; // message system fixed size -class LLMD5 { +class LL_COMMON_API LLMD5 {  // first, some types:    typedef unsigned       int uint4; // assumes integer is 4 words long    typedef unsigned short int uint2; // assumes short integer is 2 words long diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index f41da37ba6..1c6f64dd8b 100644 --- a/indra/llcommon/llmemory.h +++ b/indra/llcommon/llmemory.h @@ -41,7 +41,7 @@ extern S32 gDACount;  extern void* ll_allocate (size_t size);  extern void ll_release (void *p); -class LLMemory +class LL_COMMON_API LLMemory  {  public:  	static void initClass(); diff --git a/indra/llcommon/llmemorystream.h b/indra/llcommon/llmemorystream.h index f3486324c5..fa0f5d22f2 100644 --- a/indra/llcommon/llmemorystream.h +++ b/indra/llcommon/llmemorystream.h @@ -52,7 +52,7 @@   * be careful to always pass in a valid memory location that exists   * for at least as long as this streambuf.   */ -class LLMemoryStreamBuf : public std::streambuf +class LL_COMMON_API LLMemoryStreamBuf : public std::streambuf  {  public:  	LLMemoryStreamBuf(const U8* start, S32 length); @@ -74,7 +74,7 @@ protected:   * be careful to always pass in a valid memory location that exists   * for at least as long as this streambuf.   */ -class LLMemoryStream : public std::istream +class LL_COMMON_API LLMemoryStream : public std::istream  {  public:  	LLMemoryStream(const U8* start, S32 length); diff --git a/indra/llcommon/llmemtype.h b/indra/llcommon/llmemtype.h index 12310fcdb4..677fad3034 100644 --- a/indra/llcommon/llmemtype.h +++ b/indra/llcommon/llmemtype.h @@ -48,14 +48,14 @@  #define MEM_TYPE_NEW(T) -class LLMemType +class LL_COMMON_API LLMemType  {  public:  	// class we'll initialize all instances of as  	// static members of MemType.  Then use  	// to construct any new mem type. -	class DeclareMemType +	class LL_COMMON_API DeclareMemType  	{  	public:  		DeclareMemType(char const * st); diff --git a/indra/llcommon/llmetrics.h b/indra/llcommon/llmetrics.h index 1d91e8c8a2..f6f49eb456 100644 --- a/indra/llcommon/llmetrics.h +++ b/indra/llcommon/llmetrics.h @@ -38,7 +38,7 @@  class LLMetricsImpl;  class LLSD; -class LLMetrics +class LL_COMMON_API LLMetrics  {  public:  	LLMetrics(); diff --git a/indra/llcommon/llmortician.h b/indra/llcommon/llmortician.h index fcda3df58e..27bd8cd9b5 100644 --- a/indra/llcommon/llmortician.h +++ b/indra/llcommon/llmortician.h @@ -35,7 +35,7 @@  #include "stdtypes.h" -class LLMortician  +class LL_COMMON_API LLMortician   {  public:  	LLMortician() { mIsDead = FALSE; } diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index bb598a2be1..48244480b1 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -93,19 +93,8 @@  #endif -// Deal with the differeneces on Windows -#if LL_MSVC -namespace snprintf_hack -{ -	int snprintf(char *str, size_t size, const char *format, ...); -} - -// #define snprintf safe_snprintf		/* Flawfinder: ignore */ -using snprintf_hack::snprintf; -#endif	// LL_MSVC -  // Static linking with apr on windows needs to be declared. -#ifdef LL_WINDOWS +#if LL_WINDOWS && !LL_COMMON_LINK_SHARED  #ifndef APR_DECLARE_STATIC  #define APR_DECLARE_STATIC // For APR on Windows  #endif @@ -137,6 +126,43 @@ using snprintf_hack::snprintf;  #pragma warning( disable : 4503 )	// 'decorated name length exceeded, name was truncated'. Does not seem to affect compilation.  #pragma warning( disable : 4800 )	// 'BOOL' : forcing value to bool 'true' or 'false' (performance warning)  #pragma warning( disable : 4996 )	// warning: deprecated + +// level 4 warnings that we need to disable: +#pragma warning (disable : 4100) // unreferenced formal parameter +#pragma warning (disable : 4127) // conditional expression is constant (e.g. while(1) ) +#pragma warning (disable : 4244) // possible loss of data on conversions +#pragma warning (disable : 4396) // the inline specifier cannot be used when a friend declaration refers to a specialization of a function template +#pragma warning (disable : 4512) // assignment operator could not be generated +#pragma warning (disable : 4706) // assignment within conditional (even if((x = y)) ) + +#pragma warning (disable : 4251) // member needs to have dll-interface to be used by clients of class +#pragma warning (disable : 4275) // non dll-interface class used as base for dll-interface class  #endif	//	LL_MSVC +#if LL_WINDOWS +#define LL_DLLEXPORT __declspec(dllexport) +#define LL_DLLIMPORT __declspec(dllimport) +#elif LL_LINUX +#define LL_DLLEXPORT __attribute__ ((visibility("default"))) +#define LL_DLLIMPORT +#else +#define LL_DLLEXPORT +#define LL_DLLIMPORT +#endif // LL_WINDOWS + +#if LL_COMMON_LINK_SHARED +// CMake automagically defines llcommon_EXPORTS only when building llcommon +// sources, and only when llcommon is a shared library (i.e. when +// LL_COMMON_LINK_SHARED). We must still test LL_COMMON_LINK_SHARED because +// otherwise we can't distinguish between (non-llcommon source) and (llcommon +// not shared). +# if defined(llcommon_EXPORTS) +#   define LL_COMMON_API LL_DLLEXPORT +# else //llcommon_EXPORTS +#   define LL_COMMON_API LL_DLLIMPORT +# endif //llcommon_EXPORTS +#else // LL_COMMON_LINK_SHARED +# define LL_COMMON_API +#endif // LL_COMMON_LINK_SHARED +  #endif	//	not LL_LINDEN_PREPROCESSOR_H diff --git a/indra/llcommon/llprocesslauncher.h b/indra/llcommon/llprocesslauncher.h index a1b8e22691..880562157f 100644 --- a/indra/llcommon/llprocesslauncher.h +++ b/indra/llcommon/llprocesslauncher.h @@ -42,7 +42,7 @@  	It also keeps track of whether the process is still running, and can kill it if required.  */ -class LLProcessLauncher +class LL_COMMON_API LLProcessLauncher  {  	LOG_CLASS(LLProcessLauncher);  public: diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index bcd154da0b..9a9dbb18cc 100644 --- a/indra/llcommon/llqueuedthread.h +++ b/indra/llcommon/llqueuedthread.h @@ -47,7 +47,7 @@  // Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small  //   It is assumed that LLQueuedThreads are rarely created/destroyed. -class LLQueuedThread : public LLThread +class LL_COMMON_API LLQueuedThread : public LLThread  {  	//------------------------------------------------------------------------  public: @@ -80,7 +80,7 @@ public:  	//------------------------------------------------------------------------  public: -	class QueuedRequest : public LLSimpleHashEntry<handle_t> +	class LL_COMMON_API QueuedRequest : public LLSimpleHashEntry<handle_t>  	{  		friend class LLQueuedThread; @@ -148,6 +148,7 @@ protected:  		}  	}; +  	//------------------------------------------------------------------------  public: diff --git a/indra/llcommon/llrand.h b/indra/llcommon/llrand.h index d12597bb53..30fec9b982 100644 --- a/indra/llcommon/llrand.h +++ b/indra/llcommon/llrand.h @@ -65,32 +65,32 @@  /**   *@brief Generate a float from [0, RAND_MAX).   */ -S32 ll_rand(); +S32 LL_COMMON_API ll_rand();  /**   *@brief Generate a float from [0, val) or (val, 0].   */ -S32 ll_rand(S32 val); +S32 LL_COMMON_API ll_rand(S32 val);  /**   *@brief Generate a float from [0, 1.0).   */ -F32 ll_frand(); +F32 LL_COMMON_API ll_frand();  /**   *@brief Generate a float from [0, val) or (val, 0].   */ -F32 ll_frand(F32 val); +F32 LL_COMMON_API ll_frand(F32 val);  /**   *@brief Generate a double from [0, 1.0).   */ -F64 ll_drand(); +F64 LL_COMMON_API ll_drand();  /**   *@brief Generate a double from [0, val) or (val, 0].   */ -F64 ll_drand(F64 val); +F64 LL_COMMON_API ll_drand(F64 val);  /**   * @brief typedefs for good boost lagged fibonacci. diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h index d3597b454c..9ab844eb22 100644 --- a/indra/llcommon/llrefcount.h +++ b/indra/llcommon/llrefcount.h @@ -39,9 +39,9 @@  // see llthread.h for LLThreadSafeRefCount  //---------------------------------------------------------------------------- -class LLRefCount +class LL_COMMON_API LLRefCount  { -protected: +private:  	LLRefCount(const LLRefCount& other); // no implementation  private:  	LLRefCount& operator=(const LLRefCount&); // no implementation diff --git a/indra/llcommon/llrun.h b/indra/llcommon/llrun.h index 77b23d9051..1fc9925df9 100644 --- a/indra/llcommon/llrun.h +++ b/indra/llcommon/llrun.h @@ -48,7 +48,7 @@ class LLRunnable;   * which are scheduled to run on a repeating or one time basis.   * @see LLRunnable   */ -class LLRunner +class LL_COMMON_API LLRunner  {  public:  	/** @@ -149,7 +149,7 @@ protected:   * something useful.   * @see LLRunner   */ -class LLRunnable +class LL_COMMON_API LLRunnable  {  public:  	LLRunnable(); diff --git a/indra/llcommon/llsd.h b/indra/llcommon/llsd.h index d2845a3757..552bb57498 100644 --- a/indra/llcommon/llsd.h +++ b/indra/llcommon/llsd.h @@ -89,7 +89,7 @@  	@nosubgrouping  */ -class LLSD +class LL_COMMON_API LLSD  {  public:  		LLSD();		///< initially Undefined @@ -387,7 +387,7 @@ struct llsd_select_string : public std::unary_function<LLSD, LLSD::String>  	}  }; -std::ostream& operator<<(std::ostream& s, const LLSD& llsd); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLSD& llsd);  /** QUESTIONS & TO DOS  	- Would Binary be more convenient as usigned char* buffer semantics? diff --git a/indra/llcommon/llsdserialize.h b/indra/llcommon/llsdserialize.h index 7463d1e5dd..2f2b292189 100644 --- a/indra/llcommon/llsdserialize.h +++ b/indra/llcommon/llsdserialize.h @@ -44,7 +44,7 @@   * @class LLSDParser   * @brief Abstract base class for LLSD parsers.   */ -class LLSDParser : public LLRefCount +class LL_COMMON_API LLSDParser : public LLRefCount  {  protected:  	/**  @@ -221,7 +221,7 @@ protected:   * @class LLSDNotationParser   * @brief Parser which handles the original notation format for LLSD.   */ -class LLSDNotationParser : public LLSDParser +class LL_COMMON_API LLSDNotationParser : public LLSDParser  {  protected:  	/**  @@ -294,7 +294,7 @@ private:   * @class LLSDXMLParser   * @brief Parser which handles XML format LLSD.   */ -class LLSDXMLParser : public LLSDParser +class LL_COMMON_API LLSDXMLParser : public LLSDParser  {  protected:  	/**  @@ -342,7 +342,7 @@ private:   * @class LLSDBinaryParser   * @brief Parser which handles binary formatted LLSD.   */ -class LLSDBinaryParser : public LLSDParser +class LL_COMMON_API LLSDBinaryParser : public LLSDParser  {  protected:  	/**  @@ -407,7 +407,7 @@ private:   * @class LLSDFormatter   * @brief Abstract base class for formatting LLSD.   */ -class LLSDFormatter : public LLRefCount +class LL_COMMON_API LLSDFormatter : public LLRefCount  {  protected:  	/**  @@ -479,7 +479,7 @@ protected:   * @class LLSDNotationFormatter   * @brief Formatter which outputs the original notation format for LLSD.   */ -class LLSDNotationFormatter : public LLSDFormatter +class LL_COMMON_API LLSDNotationFormatter : public LLSDFormatter  {  protected:  	/**  @@ -520,7 +520,7 @@ public:   * @class LLSDXMLFormatter   * @brief Formatter which outputs the LLSD as XML.   */ -class LLSDXMLFormatter : public LLSDFormatter +class LL_COMMON_API LLSDXMLFormatter : public LLSDFormatter  {  protected:  	/**  @@ -588,7 +588,7 @@ protected:   * Map: '{' + 4 byte integer size  every(key + value) + '}'<br>   *  map keys are serialized as 'k' + 4 byte integer size + string   */ -class LLSDBinaryFormatter : public LLSDFormatter +class LL_COMMON_API LLSDBinaryFormatter : public LLSDFormatter  {  protected:  	/**  @@ -638,9 +638,14 @@ protected:   *	params << "[{'version':i1}," << LLSDOStreamer<LLSDNotationFormatter>(sd)   *    << "]";   *  </code> + * + * *NOTE - formerly this class inherited from its template parameter Formatter, + * but all insnatiations passed in LLRefCount subclasses.  This conflicted with + * the auto allocation intended for this class template (demonstrated in the + * example above).  -brad   */  template <class Formatter> -class LLSDOStreamer : public Formatter +class LLSDOStreamer  {  public:  	/**  @@ -661,7 +666,8 @@ public:  		std::ostream& str,  		const LLSDOStreamer<Formatter>& formatter)  	{ -		formatter.format(formatter.mSD, str, formatter.mOptions); +		LLPointer<Formatter> f = new Formatter; +		f->format(formatter.mSD, str, formatter.mOptions);  		return str;  	} @@ -677,7 +683,7 @@ typedef LLSDOStreamer<LLSDXMLFormatter>			LLSDXMLStreamer;   * @class LLSDSerialize   * @brief Serializer / deserializer for the various LLSD formats   */ -class LLSDSerialize +class LL_COMMON_API LLSDSerialize  {  public:  	enum ELLSD_Serialize diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp index c12ca350de..7e1c2e35e0 100644 --- a/indra/llcommon/llsdserialize_xml.cpp +++ b/indra/llcommon/llsdserialize_xml.cpp @@ -37,6 +37,7 @@  #include <deque>  #include "apr_base64.h" +#include <boost/regex.hpp>  extern "C"  { @@ -777,10 +778,17 @@ void LLSDXMLParser::Impl::endElementHandler(const XML_Char* name)  		case ELEMENT_BINARY:  		{ -			S32 len = apr_base64_decode_len(mCurrentContent.c_str()); +			// Regex is expensive, but only fix for whitespace in base64, +			// created by python and other non-linden systems - DEV-39358 +			// Fortunately we have very little binary passing now, +			// so performance impact shold be negligible. + poppy 2009-09-04 +			boost::regex r; +			r.assign("\\s"); +			std::string stripped = boost::regex_replace(mCurrentContent, r, ""); +			S32 len = apr_base64_decode_len(stripped.c_str());  			std::vector<U8> data;  			data.resize(len); -			len = apr_base64_decode_binary(&data[0], mCurrentContent.c_str()); +			len = apr_base64_decode_binary(&data[0], stripped.c_str());  			data.resize(len);  			value = data;  			break; diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index 0202a033c3..c8d8030e87 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -46,6 +46,11 @@  #endif  #include "llsdserialize.h" +#include "stringize.h" + +#include <map> +#include <set> +#include <boost/range.hpp>  // U32  LLSD ll_sd_from_U32(const U32 val) @@ -313,3 +318,353 @@ BOOL compare_llsd_with_template(  	return TRUE;  } + +/***************************************************************************** +*   Helpers for llsd_matches() +*****************************************************************************/ +// raw data used for LLSD::Type lookup +struct Data +{ +    LLSD::Type type; +    const char* name; +} typedata[] = +{ +#define def(type) { LLSD::type, #type + 4 } +    def(TypeUndefined), +    def(TypeBoolean), +    def(TypeInteger), +    def(TypeReal), +    def(TypeString), +    def(TypeUUID), +    def(TypeDate), +    def(TypeURI), +    def(TypeBinary), +    def(TypeMap), +    def(TypeArray) +#undef  def +}; + +// LLSD::Type lookup class into which we load the above static data +class TypeLookup +{ +    typedef std::map<LLSD::Type, std::string> MapType; + +public: +    TypeLookup() +    { +        for (const Data *di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di) +        { +            mMap[di->type] = di->name; +        } +    } + +    std::string lookup(LLSD::Type type) const +    { +        MapType::const_iterator found = mMap.find(type); +        if (found != mMap.end()) +        { +            return found->second; +        } +        return STRINGIZE("<unknown LLSD type " << type << ">"); +    } + +private: +    MapType mMap; +}; + +// static instance of the lookup class +static const TypeLookup sTypes; + +// describe a mismatch; phrasing may want tweaking +const std::string op(" required instead of "); + +// llsd_matches() wants to identify specifically where in a complex prototype +// structure the mismatch occurred. This entails passing a prefix string, +// empty for the top-level call. If the prototype contains an array of maps, +// and the mismatch occurs in the second map in a key 'foo', we want to +// decorate the returned string with: "[1]['foo']: etc." On the other hand, we +// want to omit the entire prefix -- including colon -- if the mismatch is at +// top level. This helper accepts the (possibly empty) recursively-accumulated +// prefix string, returning either empty or the original string with colon +// appended. +static std::string colon(const std::string& pfx) +{ +    if (pfx.empty()) +        return pfx; +    return pfx + ": "; +} + +// param type for match_types +typedef std::vector<LLSD::Type> TypeVector; + +// The scalar cases in llsd_matches() use this helper. In most cases, we can +// accept not only the exact type specified in the prototype, but also other +// types convertible to the expected type. That implies looping over an array +// of such types. If the actual type doesn't match any of them, we want to +// provide a list of acceptable conversions as well as the exact type, e.g.: +// "Integer (or Boolean, Real, String) required instead of UUID". Both the +// implementation and the calling logic are simplified by separating out the +// expected type from the convertible types. +static std::string match_types(LLSD::Type expect, // prototype.type() +                               const TypeVector& accept, // types convertible to that type +                               LLSD::Type actual,        // type we're checking +                               const std::string& pfx)   // as for llsd_matches +{ +    // Trivial case: if the actual type is exactly what we expect, we're good. +    if (actual == expect) +        return ""; + +    // For the rest of the logic, build up a suitable error string as we go so +    // we only have to make a single pass over the list of acceptable types. +    // If we detect success along the way, we'll simply discard the partial +    // error string. +    std::ostringstream out; +    out << colon(pfx) << sTypes.lookup(expect); + +    // If there are any convertible types, append that list. +    if (! accept.empty()) +    { +        out << " ("; +        const char* sep = "or "; +        for (TypeVector::const_iterator ai(accept.begin()), aend(accept.end()); +             ai != aend; ++ai, sep = ", ") +        { +            // Don't forget to return success if we match any of those types... +            if (actual == *ai) +                return ""; +            out << sep << sTypes.lookup(*ai); +        } +        out << ')'; +    } +    // If we got this far, it's because 'actual' was not one of the acceptable +    // types, so we must return an error. 'out' already contains colon(pfx) +    // and the formatted list of acceptable types, so just append the mismatch +    // phrase and the actual type. +    out << op << sTypes.lookup(actual); +    return out.str(); +} + +// see docstring in .h file +std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx) +{ +    // An undefined prototype means that any data is valid. +    // An undefined slot in an array or map prototype means that any data +    // may fill that slot. +    if (prototype.isUndefined()) +        return ""; +    // A prototype array must match a data array with at least as many +    // entries. Moreover, every prototype entry must match the +    // corresponding data entry. +    if (prototype.isArray()) +    { +        if (! data.isArray()) +        { +            return STRINGIZE(colon(pfx) << "Array" << op << sTypes.lookup(data.type())); +        } +        if (data.size() < prototype.size()) +        { +            return STRINGIZE(colon(pfx) << "Array size " << prototype.size() << op +                             << "Array size " << data.size()); +        } +        for (LLSD::Integer i = 0; i < prototype.size(); ++i) +        { +            std::string match(llsd_matches(prototype[i], data[i], STRINGIZE('[' << i << ']'))); +            if (! match.empty()) +            { +                return match; +            } +        } +        return ""; +    } +    // A prototype map must match a data map. Every key in the prototype +    // must have a corresponding key in the data map; every value in the +    // prototype must match the corresponding key's value in the data. +    if (prototype.isMap()) +    { +        if (! data.isMap()) +        { +            return STRINGIZE(colon(pfx) << "Map" << op << sTypes.lookup(data.type())); +        } +        // If there are a number of keys missing from the data, it would be +        // frustrating to a coder to discover them one at a time, with a big +        // build each time. Enumerate all missing keys. +        std::ostringstream out; +        out << colon(pfx); +        const char* init = "Map missing keys: "; +        const char* sep = init; +        for (LLSD::map_const_iterator mi = prototype.beginMap(); mi != prototype.endMap(); ++mi) +        { +            if (! data.has(mi->first)) +            { +                out << sep << mi->first; +                sep = ", "; +            } +        } +        // So... are we missing any keys? +        if (sep != init) +        { +            return out.str(); +        } +        // Good, the data block contains all the keys required by the +        // prototype. Now match the prototype entries. +        for (LLSD::map_const_iterator mi2 = prototype.beginMap(); mi2 != prototype.endMap(); ++mi2) +        { +            std::string match(llsd_matches(mi2->second, data[mi2->first], +                                           STRINGIZE("['" << mi2->first << "']"))); +            if (! match.empty()) +            { +                return match; +            } +        } +        return ""; +    } +    // A String prototype can match String, Boolean, Integer, Real, UUID, +    // Date and URI, because any of these can be converted to String. +    if (prototype.isString()) +    { +        static LLSD::Type accept[] = +        { +            LLSD::TypeBoolean, +            LLSD::TypeInteger, +            LLSD::TypeReal, +            LLSD::TypeUUID, +            LLSD::TypeDate, +            LLSD::TypeURI +        }; +        return match_types(prototype.type(), +                           TypeVector(boost::begin(accept), boost::end(accept)), +                           data.type(), +                           pfx); +    } +    // Boolean, Integer, Real match each other or String. TBD: ensure that +    // a String value is numeric. +    if (prototype.isBoolean() || prototype.isInteger() || prototype.isReal()) +    { +        static LLSD::Type all[] = +        { +            LLSD::TypeBoolean, +            LLSD::TypeInteger, +            LLSD::TypeReal, +            LLSD::TypeString +        }; +        // Funny business: shuffle the set of acceptable types to include all +        // but the prototype's type. Get the acceptable types in a set. +        std::set<LLSD::Type> rest(boost::begin(all), boost::end(all)); +        // Remove the prototype's type because we pass that separately. +        rest.erase(prototype.type()); +        return match_types(prototype.type(), +                           TypeVector(rest.begin(), rest.end()), +                           data.type(), +                           pfx); +    } +    // UUID, Date and URI match themselves or String. +    if (prototype.isUUID() || prototype.isDate() || prototype.isURI()) +    { +        static LLSD::Type accept[] = +        { +            LLSD::TypeString +        }; +        return match_types(prototype.type(), +                           TypeVector(boost::begin(accept), boost::end(accept)), +                           data.type(), +                           pfx); +    } +    // We don't yet know the conversion semantics associated with any new LLSD +    // data type that might be added, so until we've been extended to handle +    // them, assume it's strict: the new type matches only itself. (This is +    // true of Binary, which is why we don't handle that case separately.) Too +    // bad LLSD doesn't define isConvertible(Type to, Type from). +    return match_types(prototype.type(), TypeVector(), data.type(), pfx); +} + +bool llsd_equals(const LLSD& lhs, const LLSD& rhs) +{ +    // We're comparing strict equality of LLSD representation rather than +    // performing any conversions. So if the types aren't equal, the LLSD +    // values aren't equal. +    if (lhs.type() != rhs.type()) +    { +        return false; +    } + +    // Here we know both types are equal. Now compare values. +    switch (lhs.type()) +    { +    case LLSD::TypeUndefined: +        // Both are TypeUndefined. There's nothing more to know. +        return true; + +#define COMPARE_SCALAR(type)                                    \ +    case LLSD::Type##type:                                      \ +        /* LLSD::URI has operator!=() but not operator==() */   \ +        /* rely on the optimizer for all others */              \ +        return (! (lhs.as##type() != rhs.as##type())) + +    COMPARE_SCALAR(Boolean); +    COMPARE_SCALAR(Integer); +    // The usual caveats about comparing floating-point numbers apply. This is +    // only useful when we expect identical bit representation for a given +    // Real value, e.g. for integer-valued Reals. +    COMPARE_SCALAR(Real); +    COMPARE_SCALAR(String); +    COMPARE_SCALAR(UUID); +    COMPARE_SCALAR(Date); +    COMPARE_SCALAR(URI); +    COMPARE_SCALAR(Binary); + +#undef COMPARE_SCALAR + +    case LLSD::TypeArray: +    { +        LLSD::array_const_iterator +            lai(lhs.beginArray()), laend(lhs.endArray()), +            rai(rhs.beginArray()), raend(rhs.endArray()); +        // Compare array elements, walking the two arrays in parallel. +        for ( ; lai != laend && rai != raend; ++lai, ++rai) +        { +            // If any one array element is unequal, the arrays are unequal. +            if (! llsd_equals(*lai, *rai)) +                return false; +        } +        // Here we've reached the end of one or the other array. They're equal +        // only if they're BOTH at end: that is, if they have equal length too. +        return (lai == laend && rai == raend); +    } + +    case LLSD::TypeMap: +    { +        // Build a set of all rhs keys. +        std::set<LLSD::String> rhskeys; +        for (LLSD::map_const_iterator rmi(rhs.beginMap()), rmend(rhs.endMap()); +             rmi != rmend; ++rmi) +        { +            rhskeys.insert(rmi->first); +        } +        // Now walk all the lhs keys. +        for (LLSD::map_const_iterator lmi(lhs.beginMap()), lmend(lhs.endMap()); +             lmi != lmend; ++lmi) +        { +            // Try to erase this lhs key from the set of rhs keys. If rhs has +            // no such key, the maps are unequal. erase(key) returns count of +            // items erased. +            if (rhskeys.erase(lmi->first) != 1) +                return false; +            // Both maps have the current key. Compare values. +            if (! llsd_equals(lmi->second, rhs[lmi->first])) +                return false; +        } +        // We've now established that all the lhs keys have equal values in +        // both maps. The maps are equal unless rhs contains a superset of +        // those keys. +        return rhskeys.empty(); +    } + +    default: +        // We expect that every possible type() value is specifically handled +        // above. Failing to extend this switch to support a new LLSD type is +        // an error that must be brought to the coder's attention. +        LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << "): " +            "unknown type " << lhs.type() << LL_ENDL; +        return false;               // pacify the compiler +    } +} diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 501600f1d9..6a6c396687 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -35,62 +35,32 @@  #ifndef LL_LLSDUTIL_H  #define LL_LLSDUTIL_H -#include "llsd.h" - -// vector3 -class LLVector3; -LLSD ll_sd_from_vector3(const LLVector3& vec); -LLVector3 ll_vector3_from_sd(const LLSD& sd, S32 start_index = 0); - -// vector4 -class LLVector4; -LLSD ll_sd_from_vector4(const LLVector4& vec); -LLVector4 ll_vector4_from_sd(const LLSD& sd, S32 start_index = 0); - -// vector3d (double) -class LLVector3d; -LLSD ll_sd_from_vector3d(const LLVector3d& vec); -LLVector3d ll_vector3d_from_sd(const LLSD& sd, S32 start_index = 0); - -// vector2 -class LLVector2; -LLSD ll_sd_from_vector2(const LLVector2& vec); -LLVector2 ll_vector2_from_sd(const LLSD& sd); - -// Quaternion -class LLQuaternion; -LLSD ll_sd_from_quaternion(const LLQuaternion& quat); -LLQuaternion ll_quaternion_from_sd(const LLSD& sd); - -// color4 -class LLColor4; -LLSD ll_sd_from_color4(const LLColor4& c); -LLColor4 ll_color4_from_sd(const LLSD& sd); +class LLSD;  // U32 -LLSD ll_sd_from_U32(const U32); -U32 ll_U32_from_sd(const LLSD& sd); +LL_COMMON_API LLSD ll_sd_from_U32(const U32); +LL_COMMON_API U32 ll_U32_from_sd(const LLSD& sd);  // U64 -LLSD ll_sd_from_U64(const U64); -U64 ll_U64_from_sd(const LLSD& sd); +LL_COMMON_API LLSD ll_sd_from_U64(const U64); +LL_COMMON_API U64 ll_U64_from_sd(const LLSD& sd);  // IP Address -LLSD ll_sd_from_ipaddr(const U32); -U32 ll_ipaddr_from_sd(const LLSD& sd); +LL_COMMON_API LLSD ll_sd_from_ipaddr(const U32); +LL_COMMON_API U32 ll_ipaddr_from_sd(const LLSD& sd);  // Binary to string -LLSD ll_string_from_binary(const LLSD& sd); +LL_COMMON_API LLSD ll_string_from_binary(const LLSD& sd);  //String to binary -LLSD ll_binary_from_string(const LLSD& sd); +LL_COMMON_API LLSD ll_binary_from_string(const LLSD& sd);  // Serializes sd to static buffer and returns pointer, useful for gdb debugging. -char* ll_print_sd(const LLSD& sd); +LL_COMMON_API char* ll_print_sd(const LLSD& sd);  // Serializes sd to static buffer and returns pointer, using "pretty printing" mode. -char* ll_pretty_print_sd_ptr(const LLSD* sd); -char* ll_pretty_print_sd(const LLSD& sd); +LL_COMMON_API char* ll_pretty_print_sd_ptr(const LLSD* sd); +LL_COMMON_API char* ll_pretty_print_sd(const LLSD& sd);  //compares the structure of an LLSD to a template LLSD and stores the  //"valid" values in a 3rd LLSD. Default values @@ -99,11 +69,69 @@ char* ll_pretty_print_sd(const LLSD& sd);  //Returns false if the test is of same type but values differ in type  //Otherwise, returns true -BOOL compare_llsd_with_template( +LL_COMMON_API BOOL compare_llsd_with_template(  	const LLSD& llsd_to_test,  	const LLSD& template_llsd,  	LLSD& resultant_llsd); +/** + * Recursively determine whether a given LLSD data block "matches" another + * LLSD prototype. The returned string is empty() on success, non-empty() on + * mismatch. + * + * This function tests structure (types) rather than data values. It is + * intended for when a consumer expects an LLSD block with a particular + * structure, and must succinctly detect whether the arriving block is + * well-formed. For instance, a test of the form: + * @code + * if (! (data.has("request") && data.has("target") && data.has("modifier") ...)) + * @endcode + * could instead be expressed by initializing a prototype LLSD map with the + * required keys and writing: + * @code + * if (! llsd_matches(prototype, data).empty()) + * @endcode + * + * A non-empty return value is an error-message fragment intended to indicate + * to (English-speaking) developers where in the prototype structure the + * mismatch occurred. + * + * * If a slot in the prototype isUndefined(), then anything is valid at that + *   place in the real object. (Passing prototype == LLSD() matches anything + *   at all.) + * * An array in the prototype must match a data array at least that large. + *   (Additional entries in the data array are ignored.) Every isDefined() + *   entry in the prototype array must match the corresponding entry in the + *   data array. + * * A map in the prototype must match a map in the data. Every key in the + *   prototype map must match a corresponding key in the data map. (Additional + *   keys in the data map are ignored.) Every isDefined() value in the + *   prototype map must match the corresponding key's value in the data map. + * * Scalar values in the prototype are tested for @em type rather than value. + *   For instance, a String in the prototype matches any String at all. In + *   effect, storing an Integer at a particular place in the prototype asserts + *   that the caller intends to apply asInteger() to the corresponding slot in + *   the data. + * * A String in the prototype matches String, Boolean, Integer, Real, UUID, + *   Date and URI, because asString() applied to any of these produces a + *   meaningful result. + * * Similarly, a Boolean, Integer or Real in the prototype can match any of + *   Boolean, Integer or Real in the data -- or even String. + * * UUID matches UUID or String. + * * Date matches Date or String. + * * URI matches URI or String. + * * Binary in the prototype matches only Binary in the data. + * + * @TODO: when a Boolean, Integer or Real in the prototype matches a String in + * the data, we should examine the String @em value to ensure it can be + * meaningfully converted to the requested type. The same goes for UUID, Date + * and URI. + */ +LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); + +/// Deep equality +LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs); +  // Simple function to copy data out of input & output iterators if  // there is no need for casting.  template<typename Input> LLSD llsd_copy_array(Input iter, Input end) diff --git a/indra/llcommon/llsecondlifeurls.h b/indra/llcommon/llsecondlifeurls.h index a2e5f0b9c6..bd2f9f7604 100644 --- a/indra/llcommon/llsecondlifeurls.h +++ b/indra/llcommon/llsecondlifeurls.h @@ -34,49 +34,49 @@  #define LL_LLSECONDLIFEURLS_H  /*  // Account registration web page -extern const std::string CREATE_ACCOUNT_URL; +LL_COMMON_API extern const std::string CREATE_ACCOUNT_URL;  // Manage Account -extern const std::string MANAGE_ACCOUNT; +LL_COMMON_API extern const std::string MANAGE_ACCOUNT; -extern const std::string AUCTION_URL;  +LL_COMMON_API extern const std::string AUCTION_URL;  -extern const std::string EVENTS_URL; +LL_COMMON_API extern const std::string EVENTS_URL;  */  // Tier up to a new land level. -extern const std::string TIER_UP_URL; +LL_COMMON_API extern const std::string TIER_UP_URL;  // Tier up to a new land level. -extern const std::string LAND_URL; +LL_COMMON_API extern const std::string LAND_URL;  // How to get DirectX 9 -extern const std::string DIRECTX_9_URL; +LL_COMMON_API extern const std::string DIRECTX_9_URL;  /*  // Upgrade from basic membership to premium membership -extern const std::string UPGRADE_TO_PREMIUM_URL; +LL_COMMON_API extern const std::string UPGRADE_TO_PREMIUM_URL;  // Out of date VIA chipset -extern const std::string VIA_URL; +LL_COMMON_API extern const std::string VIA_URL;  // Support URL -extern const std::string SUPPORT_URL; +LL_COMMON_API extern const std::string SUPPORT_URL;  // Linden Blogs page -extern const std::string BLOGS_URL; +LL_COMMON_API extern const std::string BLOGS_URL;  // Currency page -extern const std::string BUY_CURRENCY_URL; +LL_COMMON_API extern const std::string BUY_CURRENCY_URL;  // LSL script wiki -extern const std::string LSL_DOC_URL; +LL_COMMON_API extern const std::string LSL_DOC_URL;  // SL KnowledgeBase page -extern const std::string SL_KB_URL; +LL_COMMON_API extern const std::string SL_KB_URL;  // Release Notes Redirect URL for Server and Viewer -extern const std::string RELEASE_NOTES_BASE_URL; +LL_COMMON_API extern const std::string RELEASE_NOTES_BASE_URL;  */  #endif diff --git a/indra/llcommon/llsimplehash.h b/indra/llcommon/llsimplehash.h index 0ba2a3014c..5df93b646e 100644 --- a/indra/llcommon/llsimplehash.h +++ b/indra/llcommon/llsimplehash.h @@ -64,7 +64,7 @@ public:  };  template <typename HASH_KEY_TYPE, int TABLE_SIZE> -class LLSimpleHash +class LL_COMMON_API LLSimpleHash  {  public:  	LLSimpleHash() diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp new file mode 100644 index 0000000000..6b5feaf1c4 --- /dev/null +++ b/indra/llcommon/llsingleton.cpp @@ -0,0 +1,38 @@ +/**  + * @file llsingleton.cpp + * @author Brad Kittenbrink + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + *  + * Copyright (c) 2009-2009, Linden Research, Inc. + *  + * 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 + *  + * 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 + *  + * 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. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llsingleton.h" + +std::map<std::string, void *> * LLSingletonRegistry::sSingletonMap = NULL; + diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 2e7d845bf7..f55fafadd8 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -33,7 +33,41 @@  #include "llerror.h"	// *TODO: eliminate this +#include <typeinfo>  #include <boost/noncopyable.hpp> +#include <boost/any.hpp> + +/// @brief A global registry of all singletons to prevent duplicate allocations +/// across shared library boundaries +class LL_COMMON_API LLSingletonRegistry { +	private: +		typedef std::map<std::string, void *> TypeMap; +		static TypeMap * sSingletonMap; + +		static void checkInit() +		{ +			if(sSingletonMap == NULL) +			{ +				sSingletonMap = new TypeMap(); +			} +		} + +	public: +		template<typename T> static void * & get() +		{ +			std::string name(typeid(T).name()); + +			checkInit(); + +			// the first entry of the pair returned by insert will be either the existing +			// iterator matching our key, or the newly inserted NULL initialized entry +			// see "Insert element" in http://www.sgi.com/tech/stl/UniqueAssociativeContainer.html +			TypeMap::iterator result = +				sSingletonMap->insert(std::make_pair(name, (void*)NULL)).first; + +			return result->second; +		} +};  // LLSingleton implements the getInstance() method part of the Singleton  // pattern. It can't make the derived class constructors protected, though, so @@ -107,8 +141,17 @@ public:  	static SingletonInstanceData& getData()  	{ -		static SingletonInstanceData data; -		return data; +		// this is static to cache the lookup results +		static void * & registry = LLSingletonRegistry::get<DERIVED_TYPE>(); + +		// *TODO - look into making this threadsafe +		if(NULL == registry) +		{ +			static SingletonInstanceData data; +			registry = &data; +		} + +		return *static_cast<SingletonInstanceData *>(registry);  	}  	static DERIVED_TYPE* getInstance() diff --git a/indra/llcommon/llstacktrace.cpp b/indra/llcommon/llstacktrace.cpp index 4be91b5b11..6558df70a4 100644 --- a/indra/llcommon/llstacktrace.cpp +++ b/indra/llcommon/llstacktrace.cpp @@ -30,6 +30,7 @@   * $/LicenseInfo$   */ +#include "linden_common.h"  #include "llstacktrace.h"  #ifdef LL_WINDOWS diff --git a/indra/llcommon/llstacktrace.h b/indra/llcommon/llstacktrace.h index 609b934a97..9f857f0fd3 100644 --- a/indra/llcommon/llstacktrace.h +++ b/indra/llcommon/llstacktrace.h @@ -38,7 +38,7 @@  #include <vector>  #include <string> -bool ll_get_stack_trace(std::vector<std::string>& lines); +LL_COMMON_API bool ll_get_stack_trace(std::vector<std::string>& lines);  #endif diff --git a/indra/llcommon/llstat.cpp b/indra/llcommon/llstat.cpp index 90dae11793..0bd2609f4a 100644 --- a/indra/llcommon/llstat.cpp +++ b/indra/llcommon/llstat.cpp @@ -43,7 +43,7 @@  // statics -BOOL            LLPerfBlock::sStatsEnabled = FALSE;    // Flag for detailed information +S32	            LLPerfBlock::sStatsFlags = LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS;       // Control what is being recorded  LLPerfBlock::stat_map_t    LLPerfBlock::sStatMap;    // Map full path string to LLStatTime objects, tracks all active objects  std::string        LLPerfBlock::sCurrentStatPath = "";    // Something like "/total_time/physics/physics step"  LLStat::stat_map_t LLStat::sStatList; @@ -130,6 +130,7 @@ bool LLStatsConfigFile::loadFile()      F32 duration = 0.f;      F32 interval = 0.f; +	S32 flags = LLPerfBlock::LLSTATS_BASIC_STATS;      const char * w = "duration";      if (stats_config.has(w)) @@ -141,8 +142,18 @@ bool LLStatsConfigFile::loadFile()      {          interval = (F32)stats_config[w].asReal();      }  +    w = "flags"; +    if (stats_config.has(w)) +    { +		flags = (S32)stats_config[w].asInteger(); +		if (flags == LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS && +			duration > 0) +		{   // No flags passed in, but have a duration, so reset to basic stats +			flags = LLPerfBlock::LLSTATS_BASIC_STATS; +		} +    }  -    mStatsp->setReportPerformanceDuration( duration ); +    mStatsp->setReportPerformanceDuration( duration, flags );      mStatsp->setReportPerformanceInterval( interval );      if ( duration > 0 ) @@ -254,13 +265,14 @@ void LLPerfStats::dumpIntervalPerformanceStats()      }  } -// Set length of performance stat recording -void    LLPerfStats::setReportPerformanceDuration( F32 seconds ) +// Set length of performance stat recording.   +// If turning stats on, caller must provide flags +void    LLPerfStats::setReportPerformanceDuration( F32 seconds, S32 flags /* = LLSTATS_NO_OPTIONAL_STATS */ )  {   	if ( seconds <= 0.f )  	{  		mReportPerformanceStatEnd = 0.0; -		LLPerfBlock::setStatsEnabled( FALSE ); +		LLPerfBlock::setStatsFlags(LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS);		// Make sure all recording is off  		mFrameStatsFile.close();  		LLPerfBlock::clearDynamicStats();  	} @@ -269,8 +281,8 @@ void    LLPerfStats::setReportPerformanceDuration( F32 seconds )  		mReportPerformanceStatEnd = LLFrameTimer::getElapsedSeconds() + ((F64) seconds);  		// Clear failure flag to try and create the log file once  		mFrameStatsFileFailure = FALSE; -		LLPerfBlock::setStatsEnabled( TRUE );  		mSkipFirstFrameStats = TRUE;		// Skip the first report (at the end of this frame) +		LLPerfBlock::setStatsFlags(flags);  	}  } @@ -612,11 +624,26 @@ LLPerfBlock::LLPerfBlock(LLStatTime* stat ) : mPredefinedStat(stat), mDynamicSta      }  } -// Use this constructor for dynamically created LLStatTime objects (not pre-defined) with a multi-part key. -// These are also turned on or off via the switch passed in -LLPerfBlock::LLPerfBlock( const char* key1, const char* key2 ) : mPredefinedStat(NULL), mDynamicStat(NULL) +// Use this constructor for normal, optional LLPerfBlock time slices +LLPerfBlock::LLPerfBlock( const char* key ) : mPredefinedStat(NULL), mDynamicStat(NULL)  { -    if (!sStatsEnabled) return; +    if ((sStatsFlags & LLSTATS_BASIC_STATS) == 0) +	{	// These are off unless the base set is enabled +		return; +	} + +	initDynamicStat(key); +} + +	 +// Use this constructor for dynamically created LLPerfBlock time slices +// that are only enabled by specific control flags +LLPerfBlock::LLPerfBlock( const char* key1, const char* key2, S32 flags ) : mPredefinedStat(NULL), mDynamicStat(NULL) +{ +    if ((sStatsFlags & flags) == 0) +	{ +		return; +	}      if (NULL == key2 || strlen(key2) == 0)      { @@ -630,10 +657,12 @@ LLPerfBlock::LLPerfBlock( const char* key1, const char* key2 ) : mPredefinedStat      }  } +// Set up the result data map if dynamic stats are enabled  void LLPerfBlock::initDynamicStat(const std::string& key)  {      // Early exit if dynamic stats aren't enabled. -    if (!sStatsEnabled) return; +    if (sStatsFlags == LLSTATS_NO_OPTIONAL_STATS)  +		return;      mLastPath = sCurrentStatPath;		// Save and restore current path      sCurrentStatPath += "/" + key;		// Add key to current path diff --git a/indra/llcommon/llstat.h b/indra/llcommon/llstat.h index bad18f46a0..bd73c9a6bb 100644 --- a/indra/llcommon/llstat.h +++ b/indra/llcommon/llstat.h @@ -52,7 +52,7 @@ class	LLSD;  // amounts of time with very low memory cost.  // -class LLStatAccum +class LL_COMMON_API LLStatAccum  {  protected:  	LLStatAccum(bool use_frame_timer); @@ -116,7 +116,7 @@ public:  	F64 	mLastSampleValue;  }; -class LLStatMeasure : public LLStatAccum +class LL_COMMON_API LLStatMeasure : public LLStatAccum  	// gathers statistics about things that are measured  	// ex.: tempature, time dilation  { @@ -131,7 +131,7 @@ public:  }; -class LLStatRate : public LLStatAccum +class LL_COMMON_API LLStatRate : public LLStatAccum  	// gathers statistics about things that can be counted over time  	// ex.: LSL instructions executed, messages sent, simulator frames completed  	// renders it in terms of rate of thing per second @@ -147,7 +147,7 @@ public:  }; -class LLStatTime : public LLStatAccum +class LL_COMMON_API LLStatTime : public LLStatAccum  	// gathers statistics about time spent in a block of code  	// measure average duration per second in the block  { @@ -178,7 +178,7 @@ private:  // Use this class on the stack to record statistics about an area of code -class LLPerfBlock +class LL_COMMON_API LLPerfBlock  {  public:      struct StatEntry @@ -192,14 +192,23 @@ public:  	// Use this constructor for pre-defined LLStatTime objects  	LLPerfBlock(LLStatTime* stat); -	// Use this constructor for dynamically created LLStatTime objects (not pre-defined) with a multi-part key -	LLPerfBlock( const char* key1, const char* key2 = NULL); +	// Use this constructor for normal, optional LLPerfBlock time slices +	LLPerfBlock( const char* key ); +	// Use this constructor for dynamically created LLPerfBlock time slices +	// that are only enabled by specific control flags +	LLPerfBlock( const char* key1, const char* key2, S32 flags = LLSTATS_BASIC_STATS );  	~LLPerfBlock(); -	static void setStatsEnabled( BOOL enable )		{ sStatsEnabled = enable;	}; -	static S32  getStatsEnabled()					{ return sStatsEnabled;		}; +	enum +	{	// Stats bitfield flags +		LLSTATS_NO_OPTIONAL_STATS	= 0x00,		// No optional stats gathering, just pre-defined LLStatTime objects +		LLSTATS_BASIC_STATS			= 0x01,		// Gather basic optional runtime stats +		LLSTATS_SCRIPT_FUNCTIONS	= 0x02,		// Include LSL function calls +	}; +	static void setStatsFlags( S32 flags )	{ sStatsFlags = flags;	}; +	static S32  getStatsFlags()				{ return sStatsFlags;	};  	static void clearDynamicStats();		// Reset maps to clear out dynamic objects  	static void addStatsToLLSDandReset( LLSD & stats,		// Get current information and clear time bin @@ -213,14 +222,14 @@ private:  	LLStatTime * 			mPredefinedStat;		// LLStatTime object to get data  	StatEntry *				mDynamicStat;   		// StatEntryobject to get data -	static BOOL				sStatsEnabled;			// Normally FALSE +	static S32				sStatsFlags;			// Control what is being recorded      static stat_map_t		sStatMap;				// Map full path string to LLStatTime objects  	static std::string		sCurrentStatPath;		// Something like "frame/physics/physics step"  };  // ---------------------------------------------------------------------------- -class LLPerfStats +class LL_COMMON_API LLPerfStats  {  public:      LLPerfStats(const std::string& process_name = "unknown", S32 process_pid = 0); @@ -236,7 +245,7 @@ public:      BOOL    frameStatsIsRunning()                                { return (mReportPerformanceStatEnd > 0.);        };      F32     getReportPerformanceInterval() const                { return mReportPerformanceStatInterval;        };      void    setReportPerformanceInterval( F32 interval )        { mReportPerformanceStatInterval = interval;    }; -    void    setReportPerformanceDuration( F32 seconds ); +    void    setReportPerformanceDuration( F32 seconds, S32 flags = LLPerfBlock::LLSTATS_NO_OPTIONAL_STATS );      void    setProcessName(const std::string& process_name) { mProcessName = process_name; }      void    setProcessPID(S32 process_pid) { mProcessPID = process_pid; } @@ -256,7 +265,7 @@ private:  };  // ---------------------------------------------------------------------------- -class LLStat +class LL_COMMON_API LLStat  {  private:  	typedef std::multimap<std::string, LLStat*> stat_map_t; diff --git a/indra/llcommon/llstreamtools.h b/indra/llcommon/llstreamtools.h index a6dc4d51e2..f64e761409 100644 --- a/indra/llcommon/llstreamtools.h +++ b/indra/llcommon/llstreamtools.h @@ -39,23 +39,23 @@  // unless specifed otherwise these all return input_stream.good()  // skips spaces and tabs -bool skip_whitespace(std::istream& input_stream); +LL_COMMON_API bool skip_whitespace(std::istream& input_stream);  // skips whitespace and newlines -bool skip_emptyspace(std::istream& input_stream); +LL_COMMON_API bool skip_emptyspace(std::istream& input_stream);  // skips emptyspace and lines that start with a # -bool skip_comments_and_emptyspace(std::istream& input_stream); +LL_COMMON_API bool skip_comments_and_emptyspace(std::istream& input_stream);  // skips to character after next newline -bool skip_line(std::istream& input_stream); +LL_COMMON_API bool skip_line(std::istream& input_stream);  // skips to beginning of next non-emptyspace -bool skip_to_next_word(std::istream& input_stream); +LL_COMMON_API bool skip_to_next_word(std::istream& input_stream);  // skips to character after the end of next keyword   // a 'keyword' is defined as the first word on a line -bool skip_to_end_of_next_keyword(const char* keyword, std::istream& input_stream); +LL_COMMON_API bool skip_to_end_of_next_keyword(const char* keyword, std::istream& input_stream);  // skip_to_start_of_next_keyword() is disabled -- might tickle corruption bug   // in windows iostream @@ -65,14 +65,14 @@ bool skip_to_end_of_next_keyword(const char* keyword, std::istream& input_stream  // characters are pulled out of input_stream and appended to output_string  // returns result of input_stream.good() after characters are pulled -bool get_word(std::string& output_string, std::istream& input_stream); -bool get_line(std::string& output_string, std::istream& input_stream); +LL_COMMON_API bool get_word(std::string& output_string, std::istream& input_stream); +LL_COMMON_API bool get_line(std::string& output_string, std::istream& input_stream);  // characters are pulled out of input_stream (up to a max of 'n')  // and appended to output_string   // returns result of input_stream.good() after characters are pulled -bool get_word(std::string& output_string, std::istream& input_stream, int n); -bool get_line(std::string& output_string, std::istream& input_stream, int n); +LL_COMMON_API bool get_word(std::string& output_string, std::istream& input_stream, int n); +LL_COMMON_API bool get_line(std::string& output_string, std::istream& input_stream, int n);  // unget_line() is disabled -- might tickle corruption bug in windows iostream  //// backs up the input_stream by line_size + 1 characters @@ -82,28 +82,28 @@ bool get_line(std::string& output_string, std::istream& input_stream, int n);  // removes the last char in 'line' if it matches 'c'  // returns true if removed last char -bool remove_last_char(char c, std::string& line); +LL_COMMON_API bool remove_last_char(char c, std::string& line);  // replaces escaped characters with the correct characters from left to right  // "\\" ---> '\\'   // "\n" ---> '\n'  -void unescape_string(std::string& line); +LL_COMMON_API void unescape_string(std::string& line);  // replaces unescaped characters with expanded equivalents from left to right  // '\\' ---> "\\"   // '\n' ---> "\n"  -void escape_string(std::string& line); +LL_COMMON_API void escape_string(std::string& line);  // replaces each '\n' character with ' ' -void replace_newlines_with_whitespace(std::string& line); +LL_COMMON_API void replace_newlines_with_whitespace(std::string& line);  // erases any double-quote characters in line -void remove_double_quotes(std::string& line); +LL_COMMON_API void remove_double_quotes(std::string& line);  // the 'keyword' is defined as the first word on a line  // the 'value' is everything after the keyword on the same line  // starting at the first non-whitespace and ending right before the newline -void get_keyword_and_value(std::string& keyword,  +LL_COMMON_API void get_keyword_and_value(std::string& keyword,   						   std::string& value,   						   const std::string& line); @@ -111,13 +111,13 @@ void get_keyword_and_value(std::string& keyword,  // read anymore or until we hit the count.  Some istream  // implimentations have a max that they will read.  // Returns the number of bytes read. -std::streamsize fullread( +LL_COMMON_API std::streamsize fullread(  	std::istream& istr,  	char* buf,  	std::streamsize requested); -std::istream& operator>>(std::istream& str, const char *tocheck); +LL_COMMON_API std::istream& operator>>(std::istream& str, const char *tocheck);  #endif diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index f2edd5c559..c027aa7bdd 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -671,9 +671,9 @@ std::string ll_convert_wide_to_string(const wchar_t* in)  }  #endif // LL_WINDOWS -long LLStringOps::sltOffset; -long LLStringOps::localTimeOffset; -bool LLStringOps::daylightSavings; +long LLStringOps::sPacificTimeOffset = 0; +long LLStringOps::sLocalTimeOffset = 0; +bool LLStringOps::sPacificDaylightTime = 0;  std::map<std::string, std::string> LLStringOps::datetimeToCodes;  S32	LLStringOps::collate(const llwchar* a, const llwchar* b) @@ -700,11 +700,11 @@ void LLStringOps::setupDatetimeInfo (bool daylight)  	tmpT = gmtime (&nowT);  	gmtT = mktime (tmpT); -	localTimeOffset = (long) (gmtT - localT); +	sLocalTimeOffset = (long) (gmtT - localT); -	daylightSavings = daylight; -	sltOffset = (daylightSavings? 7 : 8 ) * 60 * 60; +	sPacificDaylightTime = daylight; +	sPacificTimeOffset = (sPacificDaylightTime? 7 : 8 ) * 60 * 60;  	datetimeToCodes["wkday"]	= "%a";		// Thu  	datetimeToCodes["weekday"]	= "%A";		// Thursday @@ -957,36 +957,35 @@ bool LLStringUtil::formatDatetime(std::string& replacement, std::string token,  	}  	else if (param != "utc") // slt  	{ -		secFromEpoch -= LLStringOps::getSltOffset(); +		secFromEpoch -= LLStringOps::getPacificTimeOffset();  	}  	// if never fell into those two ifs above, param must be utc  	if (secFromEpoch < 0) secFromEpoch = 0; -	LLDate * datetime = new LLDate((F64)secFromEpoch); +	LLDate datetime((F64)secFromEpoch);  	std::string code = LLStringOps::getDatetimeCode (token);  	// special case to handle timezone  	if (code == "%Z") {  		if (param == "utc") +		{  			replacement = "GMT"; -		else if (param == "slt") -			replacement = "SLT"; -		else if (param != "local") // *TODO Vadim: not local? then what? -			replacement = LLStringOps::getDaylightSavings() ? "PDT" : "PST"; - -		return true; -	} -	replacement = datetime->toHTTPDateString(code); - -	if (code.empty()) -	{ -		return false; -	} -	else -	{ +		} +		else if (param == "local") +		{ +			replacement = "";		// user knows their own timezone +		} +		else +		{ +			// "slt" = Second Life Time, which is deprecated. +			// If not utc or user local time, fallback to Pacific time +			replacement = LLStringOps::getPacificDaylightTime() ? "PDT" : "PST"; +		}  		return true;  	} +	replacement = datetime.toHTTPDateString(code); +	return !code.empty();  }  // LLStringUtil::format recogizes the following patterns. diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index eca7e922fd..31e70e0fe4 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -148,12 +148,12 @@ struct char_traits<U16>  };  #endif -class LLStringOps +class LL_COMMON_API LLStringOps  {  private: -	static long sltOffset; -	static long localTimeOffset; -	static bool daylightSavings; +	static long sPacificTimeOffset; +	static long sLocalTimeOffset; +	static bool sPacificDaylightTime;  	static std::map<std::string, std::string> datetimeToCodes;  public: @@ -184,10 +184,13 @@ public:  	static S32	collate(const char* a, const char* b) { return strcoll(a, b); }  	static S32	collate(const llwchar* a, const llwchar* b); -	static void setupDatetimeInfo (bool daylight); -	static long getSltOffset (void) {return sltOffset;} -	static long getLocalTimeOffset (void) {return localTimeOffset;} -	static bool getDaylightSavings (void) {return daylightSavings;} +	static void setupDatetimeInfo(bool pacific_daylight_time); +	static long getPacificTimeOffset(void) { return sPacificTimeOffset;} +	static long getLocalTimeOffset(void) { return sLocalTimeOffset;} +	// Is the Pacific time zone (aka server time zone) +	// currently in daylight savings time? +	static bool getPacificDaylightTime(void) { return sPacificDaylightTime;} +  	static std::string getDatetimeCode (std::string key);  }; @@ -195,8 +198,8 @@ public:   * @brief Return a string constructed from in without crashing if the   * pointer is NULL.   */ -std::string ll_safe_string(const char* in); -std::string ll_safe_string(const char* in, S32 maxlen); +LL_COMMON_API std::string ll_safe_string(const char* in); +LL_COMMON_API std::string ll_safe_string(const char* in, S32 maxlen);  // Allowing assignments from non-strings into format_map_t is apparently @@ -231,13 +234,13 @@ public:  	static std::basic_string<T> null;  	typedef std::map<LLFormatMapString, LLFormatMapString> format_map_t; -	static void getTokens(const std::basic_string<T>& instr, std::vector<std::basic_string<T> >& tokens, const std::basic_string<T>& delims); -	static void formatNumber(std::basic_string<T>& numStr, std::basic_string<T> decimals); -	static bool formatDatetime(std::basic_string<T>& replacement, std::basic_string<T> token, std::basic_string<T> param, S32 secFromEpoch); -	static S32 format(std::basic_string<T>& s, const format_map_t& substitutions); -	static S32 format(std::basic_string<T>& s, const LLSD& substitutions); -	static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const format_map_t& substitutions); -	static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const LLSD& substitutions); +	LL_COMMON_API static void getTokens(const std::basic_string<T>& instr, std::vector<std::basic_string<T> >& tokens, const std::basic_string<T>& delims); +	LL_COMMON_API static void formatNumber(std::basic_string<T>& numStr, std::basic_string<T> decimals); +	LL_COMMON_API static bool formatDatetime(std::basic_string<T>& replacement, std::basic_string<T> token, std::basic_string<T> param, S32 secFromEpoch); +	LL_COMMON_API static S32 format(std::basic_string<T>& s, const format_map_t& substitutions); +	LL_COMMON_API static S32 format(std::basic_string<T>& s, const LLSD& substitutions); +	LL_COMMON_API static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const format_map_t& substitutions); +	LL_COMMON_API static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const LLSD& substitutions);  	static void setLocale (std::string inLocale) {sLocale = inLocale;};  	static std::string getLocale (void) {return sLocale;}; @@ -343,11 +346,11 @@ public:  #ifdef _DEBUG	 -	static void		testHarness(); +	LL_COMMON_API static void		testHarness();  #endif  private: -	static size_type getSubstitution(const std::basic_string<T>& instr, size_type& start, std::vector<std::basic_string<T> >& tokens); +	LL_COMMON_API static size_type getSubstitution(const std::basic_string<T>& instr, size_type& start, std::vector<std::basic_string<T> >& tokens);  };  template<class T> std::basic_string<T> LLStringUtilBase<T>::null; @@ -401,7 +404,7 @@ inline std::string chop_tail_copy(   * @brief This translates a nybble stored as a hex value from 0-f back   * to a nybble in the low order bits of the return byte.   */ -U8 hex_as_nybble(char hex); +LL_COMMON_API U8 hex_as_nybble(char hex);  /**   * @brief read the contents of a file into a string. @@ -412,8 +415,8 @@ U8 hex_as_nybble(char hex);   * @param filename The full name of the file to read.   * @return Returns true on success. If false, str is unmodified.   */ -bool _read_file_into_string(std::string& str, const std::string& filename); -bool iswindividual(llwchar elem); +LL_COMMON_API bool _read_file_into_string(std::string& str, const std::string& filename); +LL_COMMON_API bool iswindividual(llwchar elem);  /**   * Unicode support @@ -422,52 +425,52 @@ bool iswindividual(llwchar elem);  // Make the incoming string a utf8 string. Replaces any unknown glyph  // with the UNKOWN_CHARACTER. Once any unknown glph is found, the rest  // of the data may not be recovered. -std::string rawstr_to_utf8(const std::string& raw); +LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw);  //  // We should never use UTF16 except when communicating with Win32!  //  typedef std::basic_string<U16> llutf16string; -LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len); -LLWString utf16str_to_wstring(const llutf16string &utf16str); +LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len); +LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str); -llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len); -llutf16string wstring_to_utf16str(const LLWString &utf32str); +LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len); +LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str); -llutf16string utf8str_to_utf16str ( const std::string& utf8str, S32 len); -llutf16string utf8str_to_utf16str ( const std::string& utf8str ); +LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str, S32 len); +LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str ); -LLWString utf8str_to_wstring(const std::string &utf8str, S32 len); -LLWString utf8str_to_wstring(const std::string &utf8str); +LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str, S32 len); +LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str);  // Same function, better name. JC  inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); }  // -S32 wchar_to_utf8chars(llwchar inchar, char* outchars); +LL_COMMON_API S32 wchar_to_utf8chars(llwchar inchar, char* outchars); -std::string wstring_to_utf8str(const LLWString &utf32str, S32 len); -std::string wstring_to_utf8str(const LLWString &utf32str); +LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str, S32 len); +LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str); -std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len); -std::string utf16str_to_utf8str(const llutf16string &utf16str); +LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len); +LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str);  // Length of this UTF32 string in bytes when transformed to UTF8 -S32 wstring_utf8_length(const LLWString& wstr);  +LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr);   // Length in bytes of this wide char in a UTF8 string -S32 wchar_utf8_length(const llwchar wc);  +LL_COMMON_API S32 wchar_utf8_length(const llwchar wc);  -std::string utf8str_tolower(const std::string& utf8str); +LL_COMMON_API std::string utf8str_tolower(const std::string& utf8str);  // Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. -S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len); +LL_COMMON_API S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len);  // Length in utf16string (UTF-16) of wlen wchars beginning at woffset. -S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); +LL_COMMON_API S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen);  // Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.) -S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, BOOL *unaligned = NULL); +LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, BOOL *unaligned = NULL);  /**   * @brief Properly truncate a utf8 string to a maximum byte count. @@ -479,11 +482,11 @@ S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset   * @param max_len The maximum number of bytes in the return value.   * @return Returns a valid utf8 string with byte count <= max_len.   */ -std::string utf8str_truncate(const std::string& utf8str, const S32 max_len); +LL_COMMON_API std::string utf8str_truncate(const std::string& utf8str, const S32 max_len); -std::string utf8str_trim(const std::string& utf8str); +LL_COMMON_API std::string utf8str_trim(const std::string& utf8str); -S32 utf8str_compare_insensitive( +LL_COMMON_API S32 utf8str_compare_insensitive(  	const std::string& lhs,  	const std::string& rhs); @@ -494,17 +497,17 @@ S32 utf8str_compare_insensitive(   * @param target_char The wchar to be replaced   * @param replace_char The wchar which is written on replace   */ -std::string utf8str_substChar( +LL_COMMON_API std::string utf8str_substChar(  	const std::string& utf8str,  	const llwchar target_char,  	const llwchar replace_char); -std::string utf8str_makeASCII(const std::string& utf8str); +LL_COMMON_API std::string utf8str_makeASCII(const std::string& utf8str);  // Hack - used for evil notecards. -std::string mbcsstring_makeASCII(const std::string& str);  +LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str);  -std::string utf8str_removeCRLF(const std::string& utf8str); +LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str);  #if LL_WINDOWS @@ -529,14 +532,21 @@ std::string utf8str_removeCRLF(const std::string& utf8str);   * formatted string.   *   */ -int safe_snprintf(char* str, size_t size, const char* format, ...); + +// Deal with the differeneces on Windows +namespace snprintf_hack +{ +	LL_COMMON_API int snprintf(char *str, size_t size, const char *format, ...); +} + +using snprintf_hack::snprintf;  /**   * @brief Convert a wide string to std::string   *   * This replaces the unsafe W2A macro from ATL.   */ -std::string ll_convert_wide_to_string(const wchar_t* in); +LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in);  //@}  #endif // LL_WINDOWS @@ -559,7 +569,7 @@ namespace LLStringFn  	 * with zero non-printable characters.  	 * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.  	 */ -	void replace_nonprintable_in_ascii( +	LL_COMMON_API void replace_nonprintable_in_ascii(  		std::basic_string<char>& string,  		char replacement); @@ -573,7 +583,7 @@ namespace LLStringFn  	 * with zero non-printable characters and zero pipe characters.  	 * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.  	 */ -	void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str, +	LL_COMMON_API void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str,  									   char replacement); @@ -582,7 +592,7 @@ namespace LLStringFn  	 * Returns a copy of the string with those characters removed.  	 * Works with US ASCII and UTF-8 encoded strings.  JC  	 */ -	std::string strip_invalid_xml(const std::string& input); +	LL_COMMON_API std::string strip_invalid_xml(const std::string& input);  	/** @@ -593,7 +603,7 @@ namespace LLStringFn  	 * with zero non-printable characters.  	 * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.  	 */ -	void replace_ascii_controlchars( +	LL_COMMON_API void replace_ascii_controlchars(  		std::basic_string<char>& string,  		char replacement);  } diff --git a/indra/llcommon/llstringtable.h b/indra/llcommon/llstringtable.h index 888361b0b9..d40c9d8dfd 100644 --- a/indra/llcommon/llstringtable.h +++ b/indra/llcommon/llstringtable.h @@ -48,15 +48,17 @@  //# define STRING_TABLE_HASH_MAP 1  #endif -#if LL_WINDOWS -#include <hash_map> -#else -#include <ext/hash_map> +#if STRING_TABLE_HASH_MAP +# if LL_WINDOWS +#  include <hash_map> +# else +#  include <ext/hash_map> +# endif  #endif  const U32 MAX_STRINGS_LENGTH = 256; -class LLStringTableEntry +class LL_COMMON_API LLStringTableEntry  {  public:  	LLStringTableEntry(const char *str); @@ -69,7 +71,7 @@ public:  	S32  mCount;  }; -class LLStringTable +class LL_COMMON_API LLStringTable  {  public:  	LLStringTable(int tablesize); @@ -103,7 +105,7 @@ public:  #endif	  }; -extern LLStringTable gStringTable; +extern LL_COMMON_API LLStringTable gStringTable;  //============================================================================ @@ -113,7 +115,7 @@ extern LLStringTable gStringTable;  typedef const std::string* LLStdStringHandle; -class LLStdStringTable +class LL_COMMON_API LLStdStringTable  {  public:  	LLStdStringTable(S32 tablesize = 0) diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 4737421289..3652eeba72 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -161,8 +161,16 @@ LLOSInfo::LLOSInfo() :  						mOSStringSimple = "Microsoft Windows Vista Server ";  				}  			} +			else if(osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) +			{ +				 if(osvi.wProductType == VER_NT_WORKSTATION) +					mOSStringSimple = "Microsoft Windows 7 "; +				 else mOSStringSimple = "Microsoft Windows 7 Server "; +			}  			else   // Use the registry on early versions of Windows NT.  			{ +				mOSStringSimple = "Microsoft Windows (unrecognized) "; +  				HKEY hKey;  				WCHAR szProductType[80];  				DWORD dwBufLen; diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h index 03f48ca018..c2c45bec9a 100644 --- a/indra/llcommon/llsys.h +++ b/indra/llcommon/llsys.h @@ -45,7 +45,7 @@  #include <iosfwd>  #include <string> -class LLOSInfo +class LL_COMMON_API LLOSInfo  {  public:  	LLOSInfo(); @@ -70,7 +70,7 @@ private:  }; -class LLCPUInfo +class LL_COMMON_API LLCPUInfo  {  public:  	LLCPUInfo();	 @@ -99,7 +99,7 @@ private:  //  //	CLASS		LLMemoryInfo -class LLMemoryInfo +class LL_COMMON_API LLMemoryInfo  /*!	@brief		Class to query the memory subsystem @@ -123,15 +123,15 @@ public:  }; -std::ostream& operator<<(std::ostream& s, const LLOSInfo& info); -std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); -std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLOSInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info);  // gunzip srcfile into dstfile.  Returns FALSE on error. -BOOL gunzip_file(const std::string& srcfile, const std::string& dstfile); +BOOL LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& dstfile);  // gzip srcfile into dstfile.  Returns FALSE on error. -BOOL gzip_file(const std::string& srcfile, const std::string& dstfile); +BOOL LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile); -extern LLCPUInfo gSysCPU; +extern LL_COMMON_API LLCPUInfo gSysCPU;  #endif // LL_LLSYS_H diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 1470dca14c..932d96d940 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -40,7 +40,7 @@ class LLThread;  class LLMutex;  class LLCondition; -class LLThread +class LL_COMMON_API LLThread  {  public:  	typedef enum e_thread_status @@ -128,7 +128,7 @@ protected:  //============================================================================ -class LLMutex +class LL_COMMON_API LLMutex  {  public:  	LLMutex(apr_pool_t *apr_poolp); // NULL pool constructs a new pool for the mutex @@ -145,7 +145,7 @@ protected:  };  // Actually a condition/mutex pair (since each condition needs to be associated with a mutex). -class LLCondition : public LLMutex +class LL_COMMON_API LLCondition : public LLMutex  {  public:  	LLCondition(apr_pool_t *apr_poolp); // Defaults to global pool, could use the thread pool as well. @@ -192,7 +192,7 @@ void LLThread::unlockData()  // see llmemory.h for LLPointer<> definition -class LLThreadSafeRefCount +class LL_COMMON_API LLThreadSafeRefCount  {  public:  	static void initThreadSafeRefCount(); // creates sMutex @@ -244,7 +244,7 @@ private:  // Simple responder for self destructing callbacks  // Pure virtual class -class LLResponder : public LLThreadSafeRefCount +class LL_COMMON_API LLResponder : public LLThreadSafeRefCount  {  protected:  	virtual ~LLResponder(); diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h index 0319bec45b..d009c0f5f7 100644 --- a/indra/llcommon/lltimer.h +++ b/indra/llcommon/lltimer.h @@ -55,7 +55,7 @@ const U32	USEC_PER_HOUR	= USEC_PER_MIN * MIN_PER_HOUR;  const U32	SEC_PER_HOUR	= SEC_PER_MIN * MIN_PER_HOUR;  const F64 	SEC_PER_USEC 	= 1.0 / (F64) USEC_PER_SEC; -class LLTimer  +class LL_COMMON_API LLTimer   {  public:  	static LLTimer *sTimer;				// global timer @@ -114,17 +114,17 @@ public:  //  // Various functions for initializing/accessing clock and timing stuff.  Don't use these without REALLY knowing how they work.  // -U64 get_clock_count(); -F64 calc_clock_frequency(U32 msecs); -void update_clock_frequencies(); +LL_COMMON_API U64 get_clock_count(); +LL_COMMON_API F64 calc_clock_frequency(U32 msecs); +LL_COMMON_API void update_clock_frequencies();  // Sleep for milliseconds -void ms_sleep(U32 ms); -U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF); +LL_COMMON_API void ms_sleep(U32 ms); +LL_COMMON_API U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF);  // Returns the correct UTC time in seconds, like time(NULL).  // Useful on the viewer, which may have its local clock set wrong. -time_t time_corrected(); +LL_COMMON_API time_t time_corrected();  static inline time_t time_min()  { @@ -155,24 +155,24 @@ static inline time_t time_max()  }  // Correction factor used by time_corrected() above. -extern S32 gUTCOffset; +extern LL_COMMON_API S32 gUTCOffset;  // Is the current computer (in its current time zone)  // observing daylight savings time? -BOOL is_daylight_savings(); +LL_COMMON_API BOOL is_daylight_savings();  // Converts internal "struct tm" time buffer to Pacific Standard/Daylight Time  // Usage:  // S32 utc_time;  // utc_time = time_corrected();  // struct tm* internal_time = utc_to_pacific_time(utc_time, gDaylight); -struct tm* utc_to_pacific_time(time_t utc_time, BOOL pacific_daylight_time); +LL_COMMON_API struct tm* utc_to_pacific_time(time_t utc_time, BOOL pacific_daylight_time); -void microsecondsToTimecodeString(U64 current_time, std::string& tcstring); -void secondsToTimecodeString(F32 current_time, std::string& tcstring); +LL_COMMON_API void microsecondsToTimecodeString(U64 current_time, std::string& tcstring); +LL_COMMON_API void secondsToTimecodeString(F32 current_time, std::string& tcstring);  // class for scheduling a function to be called at a given frequency (approximate, inprecise) -class LLEventTimer : protected LLInstanceTracker<LLEventTimer> +class LL_COMMON_API LLEventTimer : protected LLInstanceTracker<LLEventTimer>  {  public:  	LLEventTimer(F32 period);	// period is the amount of time between each call to tick() in seconds @@ -190,4 +190,6 @@ protected:  	F32 mPeriod;  }; +U64 LL_COMMON_API totalTime();					// Returns current system time in microseconds +  #endif diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h index 8e46e2e89e..8e69e8558a 100644 --- a/indra/llcommon/lluri.h +++ b/indra/llcommon/lluri.h @@ -47,7 +47,7 @@ class LLApp;   * See: http://www.ietf.org/rfc/rfc3986.txt   *   */ -class LLURI +class LL_COMMON_API LLURI  {  public:    LLURI(); @@ -178,6 +178,6 @@ private:  };  // this operator required for tut -bool operator!=(const LLURI& first, const LLURI& second); +LL_COMMON_API bool operator!=(const LLURI& first, const LLURI& second);  #endif // LL_LLURI_H diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index 4b32138a06..c78fb12018 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -35,6 +35,7 @@  #include <iostream>  #include <set>  #include "stdtypes.h" +#include "llpreprocessor.h"  const S32 UUID_BYTES = 16;  const S32 UUID_WORDS = 4; @@ -47,7 +48,7 @@ struct uuid_time_t {  	U32 low;  		}; -class LLUUID +class LL_COMMON_API LLUUID  {  public:  	// @@ -106,8 +107,8 @@ public:  	LLUUID combine(const LLUUID& other) const;  	void combine(const LLUUID& other, LLUUID& result) const;   -	friend std::ostream&	 operator<<(std::ostream& s, const LLUUID &uuid); -	friend std::istream&	 operator>>(std::istream& s, LLUUID &uuid); +	friend LL_COMMON_API std::ostream&	 operator<<(std::ostream& s, const LLUUID &uuid); +	friend LL_COMMON_API std::istream&	 operator>>(std::istream& s, LLUUID &uuid);  	void toString(char *out) const;		// Does not allocate memory, needs 36 characters (including \0)  	void toString(std::string& out) const; @@ -323,7 +324,7 @@ typedef std::set<LLUUID, lluuid_less> uuid_list_t;   */  typedef LLUUID LLAssetID; -class LLTransactionID : public LLUUID +class LL_COMMON_API LLTransactionID : public LLUUID  {  public:  	LLTransactionID() : LLUUID() { } diff --git a/indra/llcommon/llversionserver.h b/indra/llcommon/llversionserver.h index 23e39ceb08..71c6fc0591 100644 --- a/indra/llcommon/llversionserver.h +++ b/indra/llcommon/llversionserver.h @@ -34,9 +34,9 @@  #define LL_LLVERSIONSERVER_H  const S32 LL_VERSION_MAJOR = 1; -const S32 LL_VERSION_MINOR = 29; +const S32 LL_VERSION_MINOR = 31;  const S32 LL_VERSION_PATCH = 0; -const S32 LL_VERSION_BUILD = 0; +const S32 LL_VERSION_BUILD = 3256;  const char * const LL_CHANNEL = "Second Life Server"; diff --git a/indra/llcommon/llversionviewer.h b/indra/llcommon/llversionviewer.h index 2c3e9c7333..082d054ba2 100644 --- a/indra/llcommon/llversionviewer.h +++ b/indra/llcommon/llversionviewer.h @@ -36,7 +36,7 @@  const S32 LL_VERSION_MAJOR = 2;  const S32 LL_VERSION_MINOR = 0;  const S32 LL_VERSION_PATCH = 0; -const S32 LL_VERSION_BUILD = 0; +const S32 LL_VERSION_BUILD = 3256;  const char * const LL_CHANNEL = "Second Life Developer"; diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h index 4a4cd6c85f..a1e85d2ecc 100644 --- a/indra/llcommon/llworkerthread.h +++ b/indra/llcommon/llworkerthread.h @@ -50,7 +50,7 @@ class LLWorkerClass;  // Note: ~LLWorkerThread is O(N) N=# of worker threads, assumed to be small  //   It is assumed that LLWorkerThreads are rarely created/destroyed. -class LLWorkerThread : public LLQueuedThread +class LL_COMMON_API LLWorkerThread : public LLQueuedThread  {  	friend class LLWorkerClass;  public: @@ -117,7 +117,7 @@ private:  // Only one background task can be active at a time (per instance).  //  i.e. don't call addWork() if haveWork() returns true -class LLWorkerClass +class LL_COMMON_API LLWorkerClass  {  	friend class LLWorkerThread;  	friend class LLWorkerThread::WorkRequest; diff --git a/indra/llcommon/metaclass.h b/indra/llcommon/metaclass.h index cc10f1675f..f38bcd2d57 100644 --- a/indra/llcommon/metaclass.h +++ b/indra/llcommon/metaclass.h @@ -43,7 +43,7 @@  class LLReflective;  class LLMetaProperty;  class LLMetaMethod; -class LLMetaClass +class LL_COMMON_API LLMetaClass  {  public: diff --git a/indra/llcommon/metaproperty.h b/indra/llcommon/metaproperty.h index e5ac35907c..6c016c56dd 100644 --- a/indra/llcommon/metaproperty.h +++ b/indra/llcommon/metaproperty.h @@ -41,7 +41,7 @@  class LLMetaClass;  class LLReflective; -class LLMetaProperty +class LL_COMMON_API LLMetaProperty  {  public:  	LLMetaProperty(const std::string& name, const LLMetaClass& object_class); diff --git a/indra/llcommon/metapropertyt.h b/indra/llcommon/metapropertyt.h index 79a536a224..5ad230d1d5 100644 --- a/indra/llcommon/metapropertyt.h +++ b/indra/llcommon/metapropertyt.h @@ -94,6 +94,13 @@ inline const LLReflective* LLMetaPropertyT<LLUUID>::get(const LLReflective* obje  }  template <> +inline const LLReflective* LLMetaPropertyT<bool>::get(const LLReflective* object) const +{ +	checkObjectClass(object); +	return NULL; +} + +template <>  inline LLSD LLMetaPropertyT<S32>::getLLSD(const LLReflective* object) const  {  	return *(getProperty(object)); @@ -111,6 +118,12 @@ inline LLSD LLMetaPropertyT<LLUUID>::getLLSD(const LLReflective* object) const  	return *(getProperty(object));  } +template <> +inline LLSD LLMetaPropertyT<bool>::getLLSD(const LLReflective* object) const +{ +	return *(getProperty(object)); +} +  template<class TObject, class TProperty>  class LLMetaPropertyTT : public LLMetaPropertyT<TProperty>  { diff --git a/indra/llcommon/reflective.h b/indra/llcommon/reflective.h index e2c18ebc6d..a13537681d 100644 --- a/indra/llcommon/reflective.h +++ b/indra/llcommon/reflective.h @@ -36,7 +36,7 @@  #define LL_REFLECTIVE_H  class LLMetaClass; -class LLReflective +class LL_COMMON_API LLReflective  {  public:  	LLReflective(); diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h index 1b2958020f..6399547f5e 100644 --- a/indra/llcommon/stringize.h +++ b/indra/llcommon/stringize.h @@ -13,6 +13,7 @@  #define LL_STRINGIZE_H  #include <sstream> +#include <boost/lambda/lambda.hpp>  /**   * stringize(item) encapsulates an idiom we use constantly, using @@ -28,6 +29,17 @@ std::string stringize(const T& item)  }  /** + * stringize_f(functor) + */ +template <typename Functor> +std::string stringize_f(Functor const & f) +{ +    std::ostringstream out; +    f(out); +    return out.str(); +} + +/**   * STRINGIZE(item1 << item2 << item3 ...) effectively expands to the   * following:   * @code @@ -36,40 +48,43 @@ std::string stringize(const T& item)   * return out.str();   * @endcode   */ -#define STRINGIZE(EXPRESSION) (static_cast<std::ostringstream&>(Stringize() << EXPRESSION).str()) +#define STRINGIZE(EXPRESSION) (stringize_f(boost::lambda::_1 << EXPRESSION)) +  /** - * Helper class for STRINGIZE() macro. Ideally the body of - * STRINGIZE(EXPRESSION) would look something like this: + * destringize(str) + * defined for symmetry with stringize + * *NOTE - this has distinct behavior from boost::lexical_cast<T> regarding + * leading/trailing whitespace and handling of bad_lexical_cast exceptions + */ +template <typename T> +T destringize(std::string const & str) +{ +	T val; +    std::istringstream in(str); +	in >> val; +    return val; +} + +/** + * destringize_f(str, functor) + */ +template <typename Functor> +void destringize_f(std::string const & str, Functor const & f) +{ +    std::istringstream in(str); +    f(in); +} + +/** + * DESTRINGIZE(str, item1 >> item2 >> item3 ...) effectively expands to the + * following:   * @code - * (std::ostringstream() << EXPRESSION).str() + * std::istringstream in(str); + * in >> item1 >> item2 >> item3 ... ;   * @endcode - * That doesn't work because each of the relevant operator<<() functions - * accepts a non-const std::ostream&, to which you can't pass a temp instance - * of std::ostringstream. Stringize plays the necessary const tricks to make - * the whole thing work.   */ -class Stringize -{ -public: -    /** -     * This is the essence of Stringize. The leftmost << operator (the one -     * coded in the STRINGIZE() macro) engages this operator<<() const method -     * on the temp Stringize instance. Every other << operator (ones embedded -     * in EXPRESSION) simply sees the std::ostream& returned by the first one. -     * -     * Finally, the STRINGIZE() macro downcasts that std::ostream& to -     * std::ostringstream&. -     */ -    template <typename T> -    std::ostream& operator<<(const T& item) const -    { -        mOut << item; -        return mOut; -    } +#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), (boost::lambda::_1 >> EXPRESSION))) -private: -    mutable std::ostringstream mOut; -};  #endif /* ! defined(LL_STRINGIZE_H) */ diff --git a/indra/llcommon/tests/listener.h b/indra/llcommon/tests/listener.h new file mode 100644 index 0000000000..fa12f944ef --- /dev/null +++ b/indra/llcommon/tests/listener.h @@ -0,0 +1,139 @@ +/** + * @file   listener.h + * @author Nat Goodspeed + * @date   2009-03-06 + * @brief  Useful for tests of the LLEventPump family of classes + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LISTENER_H) +#define LL_LISTENER_H + +#include "llsd.h" +#include <iostream> + +/***************************************************************************** +*   test listener class +*****************************************************************************/ +class Listener; +std::ostream& operator<<(std::ostream&, const Listener&); + +/// Bear in mind that this is strictly for testing +class Listener +{ +public: +    /// Every Listener is instantiated with a name +    Listener(const std::string& name): +        mName(name) +    { +//      std::cout << *this << ": ctor\n"; +    } +/*==========================================================================*| +    // These methods are only useful when trying to track Listener instance +    // lifespan +    Listener(const Listener& that): +        mName(that.mName), +        mLastEvent(that.mLastEvent) +    { +        std::cout << *this << ": copy\n"; +    } +    virtual ~Listener() +    { +        std::cout << *this << ": dtor\n"; +    } +|*==========================================================================*/ +    /// You can request the name +    std::string getName() const { return mName; } +    /// This is a typical listener method that returns 'false' when done, +    /// allowing subsequent listeners on the LLEventPump to process the +    /// incoming event. +    bool call(const LLSD& event) +    { +//      std::cout << *this << "::call(" << event << ")\n"; +        mLastEvent = event; +        return false; +    } +    /// This is an alternate listener that returns 'true' when done, which +    /// stops processing of the incoming event. +    bool callstop(const LLSD& event) +    { +//      std::cout << *this << "::callstop(" << event << ")\n"; +        mLastEvent = event; +        return true; +    } +    /// ListenMethod can represent either call() or callstop(). +    typedef bool (Listener::*ListenMethod)(const LLSD&); +    /** +     * This helper method is only because our test code makes so many +     * repetitive listen() calls to ListenerMethods. In real code, you should +     * call LLEventPump::listen() directly so it can examine the specific +     * object you pass to boost::bind(). +     */ +    LLBoundListener listenTo(LLEventPump& pump, +                             ListenMethod method=&Listener::call, +                             const LLEventPump::NameList& after=LLEventPump::empty, +                             const LLEventPump::NameList& before=LLEventPump::empty) +    { +        return pump.listen(getName(), boost::bind(method, this, _1), after, before); +    } +    /// Both call() and callstop() set mLastEvent. Retrieve it. +    LLSD getLastEvent() const +    { +//      std::cout << *this << "::getLastEvent() -> " << mLastEvent << "\n"; +        return mLastEvent; +    } +    /// Reset mLastEvent to a known state. +    void reset(const LLSD& to = LLSD()) +    { +//      std::cout << *this << "::reset(" << to << ")\n"; +        mLastEvent = to; +    } + +private: +    std::string mName; +    LLSD mLastEvent; +}; + +std::ostream& operator<<(std::ostream& out, const Listener& listener) +{ +    out << "Listener(" << listener.getName() /* << "@" << &listener */ << ')'; +    return out; +} + +/** + * This class tests the relative order in which various listeners on a given + * LLEventPump are called. Each listen() call binds a particular string, which + * we collect for later examination. The actual event is ignored. + */ +struct Collect +{ +    bool add(const std::string& bound, const LLSD& event) +    { +        result.push_back(bound); +        return false; +    } +    void clear() { result.clear(); } +    typedef std::vector<std::string> StringList; +    StringList result; +}; + +std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings) +{ +    out << '('; +    Collect::StringList::const_iterator begin(strings.begin()), end(strings.end()); +    if (begin != end) +    { +        out << '"' << *begin << '"'; +        while (++begin != end) +        { +            out << ", \"" << *begin << '"'; +        } +    } +    out << ')'; +    return out; +} + +#endif /* ! defined(LL_LISTENER_H) */ diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp new file mode 100644 index 0000000000..3a2cda7735 --- /dev/null +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -0,0 +1,782 @@ +/** + * @file   coroutine_test.cpp + * @author Nat Goodspeed + * @date   2009-04-22 + * @brief  Test for coroutine. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +/*****************************************************************************/ +//  test<1>() is cloned from a Boost.Coroutine example program whose copyright +//  info is reproduced here: +/*---------------------------------------------------------------------------*/ +//  Copyright (c) 2006, Giovanni P. Deretta +// +//  This code may be used under either of the following two licences: +// +//  Permission is hereby granted, free of charge, to any person obtaining a copy  +//  of this software and associated documentation files (the "Software"), to deal  +//  in the Software without restriction, including without limitation the rights  +//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  +//  copies of the Software, and to permit persons to whom the Software is  +//  furnished to do so, subject to the following conditions: +// +//  The above copyright notice and this permission notice shall be included in  +//  all copies or substantial portions of the Software. +// +//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  +//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  +//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL  +//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  +//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  +//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN  +//  THE SOFTWARE. OF SUCH DAMAGE. +// +//  Or: +// +//  Distributed under the Boost Software License, Version 1.0. +//  (See accompanying file LICENSE_1_0.txt or copy at +//  http://www.boost.org/LICENSE_1_0.txt) +/*****************************************************************************/ + +// On some platforms, Boost.Coroutine must #define magic symbols before +// #including platform-API headers. Naturally, that's ineffective unless the +// Boost.Coroutine #include is the *first* #include of the platform header. +// That means that client code must generally #include Boost.Coroutine headers +// before anything else. +#include <boost/coroutine/coroutine.hpp> +// Normally, lleventcoro.h obviates future.hpp. We only include this because +// we implement a "by hand" test of future functionality. +#include <boost/coroutine/future.hpp> +#include <boost/bind.hpp> +#include <boost/range.hpp> + +#include "linden_common.h" + +#include <iostream> +#include <string> + +#include "../test/lltut.h" +#include "llsd.h" +#include "llevents.h" +#include "tests/wrapllerrs.h" +#include "stringize.h" +#include "lleventcoro.h" +#include "../test/debug.h" + +/***************************************************************************** +*   from the banana.cpp example program borrowed for test<1>() +*****************************************************************************/ +namespace coroutines = boost::coroutines; +using coroutines::coroutine; + +template<typename Iter> +bool match(Iter first, Iter last, std::string match) { +  std::string::iterator i = match.begin(); +  i != match.end(); +  for(; (first != last) && (i != match.end()); ++i) { +    if (*first != *i) +      return false; +    ++first; +  } +  return i == match.end(); +} + +template<typename BidirectionalIterator>  +BidirectionalIterator  +match_substring(BidirectionalIterator begin,  +		BidirectionalIterator end,  +		std::string xmatch, +		BOOST_DEDUCED_TYPENAME coroutine<BidirectionalIterator(void)>::self& self) {  +  BidirectionalIterator begin_ = begin; +  for(; begin != end; ++begin)  +    if(match(begin, end, xmatch)) { +      self.yield(begin); +    } +  return end; +}  + +typedef coroutine<std::string::iterator(void)> match_coroutine_type; + +/***************************************************************************** +*   Test helpers +*****************************************************************************/ +// I suspect this will be typical of coroutines used in Linden software +typedef boost::coroutines::coroutine<void()> coroutine_type; + +/// Simulate an event API whose response is immediate: sent on receipt of the +/// initial request, rather than after some delay. This is the case that +/// distinguishes postAndWait() from calling post(), then calling +/// waitForEventOn(). +class ImmediateAPI +{ +public: +    ImmediateAPI(): +        mPump("immediate", true) +    { +        mPump.listen("API", boost::bind(&ImmediateAPI::operator(), this, _1)); +    } + +    LLEventPump& getPump() { return mPump; } + +    // Invoke this with an LLSD map containing: +    // ["value"]: Integer value. We will reply with ["value"] + 1. +    // ["reply"]: Name of LLEventPump on which to send success response. +    // ["error"]: Name of LLEventPump on which to send error response. +    // ["fail"]: Presence of this key selects ["error"], else ["success"] as +    // the name of the pump on which to send the response. +    bool operator()(const LLSD& event) const +    { +        LLSD::Integer value(event["value"]); +        LLSD::String replyPumpName(event.has("fail")? "error" : "reply"); +        LLEventPumps::instance().obtain(event[replyPumpName]).post(value + 1); +        return false; +    } + +private: +    LLEventStream mPump; +}; + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct coroutine_data +    { +        // Define coroutine bodies as methods here so they can use ensure*() + +        void explicit_wait(coroutine_type::self& self) +        { +            BEGIN +            { +                // ... do whatever preliminary stuff must happen ... + +                // declare the future +                boost::coroutines::future<LLSD> future(self); +                // tell the future what to wait for +                LLTempBoundListener connection( +                    LLEventPumps::instance().obtain("source").listen("coro", voidlistener(boost::coroutines::make_callback(future)))); +                ensure("Not yet", ! future); +                // attempting to dereference ("resolve") the future causes the calling +                // coroutine to wait for it +                debug("about to wait"); +                result = *future; +                ensure("Got it", future); +            } +            END +        } + +        void waitForEventOn1(coroutine_type::self& self) +        { +            BEGIN +            { +                result = waitForEventOn(self, "source"); +            } +            END +        } + +        void waitForEventOn2(coroutine_type::self& self) +        { +            BEGIN +            { +                LLEventWithID pair = waitForEventOn(self, "reply", "error"); +                result = pair.first; +                which  = pair.second; +                debug(STRINGIZE("result = " << result << ", which = " << which)); +            } +            END +        } + +        void postAndWait1(coroutine_type::self& self) +        { +            BEGIN +            { +                result = postAndWait(self, +                                     LLSD().insert("value", 17), // request event +                                     immediateAPI.getPump(),     // requestPump +                                     "reply1",                   // replyPump +                                     "reply");                   // request["reply"] = name +            } +            END +        } + +        void postAndWait2(coroutine_type::self& self) +        { +            BEGIN +            { +                LLEventWithID pair = ::postAndWait2(self, +                                                    LLSD().insert("value", 18), +                                                    immediateAPI.getPump(), +                                                    "reply2", +                                                    "error2", +                                                    "reply", +                                                    "error"); +                result = pair.first; +                which  = pair.second; +                debug(STRINGIZE("result = " << result << ", which = " << which)); +            } +            END +        } + +        void postAndWait2_1(coroutine_type::self& self) +        { +            BEGIN +            { +                LLEventWithID pair = ::postAndWait2(self, +                                                    LLSD().insert("value", 18).insert("fail", LLSD()), +                                                    immediateAPI.getPump(), +                                                    "reply2", +                                                    "error2", +                                                    "reply", +                                                    "error"); +                result = pair.first; +                which  = pair.second; +                debug(STRINGIZE("result = " << result << ", which = " << which)); +            } +            END +        } + +        void coroPump(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPump waiter; +                replyName = waiter.getName(); +                result = waiter.wait(self); +            } +            END +        } + +        void coroPumpPost(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPump waiter; +                result = waiter.postAndWait(self, LLSD().insert("value", 17), +                                            immediateAPI.getPump(), "reply"); +            } +            END +        } + +        void coroPumps(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                replyName = waiter.getName0(); +                errorName = waiter.getName1(); +                LLEventWithID pair(waiter.wait(self)); +                result = pair.first; +                which  = pair.second; +            } +            END +        } + +        void coroPumpsNoEx(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                replyName = waiter.getName0(); +                errorName = waiter.getName1(); +                result = waiter.waitWithException(self); +            } +            END +        } + +        void coroPumpsEx(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                replyName = waiter.getName0(); +                errorName = waiter.getName1(); +                try +                { +                    result = waiter.waitWithException(self); +                    debug("no exception"); +                } +                catch (const LLErrorEvent& e) +                { +                    debug(STRINGIZE("exception " << e.what())); +                    errordata = e.getData(); +                } +            } +            END +        } + +        void coroPumpsNoLog(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                replyName = waiter.getName0(); +                errorName = waiter.getName1(); +                result = waiter.waitWithLog(self); +            } +            END +        } + +        void coroPumpsLog(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                replyName = waiter.getName0(); +                errorName = waiter.getName1(); +                WrapLL_ERRS capture; +                try +                { +                    result = waiter.waitWithLog(self); +                    debug("no exception"); +                } +                catch (const WrapLL_ERRS::FatalException& e) +                { +                    debug(STRINGIZE("exception " << e.what())); +                    threw = e.what(); +                } +            } +            END +        } + +        void coroPumpsPost(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                LLEventWithID pair(waiter.postAndWait(self, LLSD().insert("value", 23), +                                                      immediateAPI.getPump(), "reply", "error")); +                result = pair.first; +                which  = pair.second; +            } +            END +        } + +        void coroPumpsPost_1(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                LLEventWithID pair( +                    waiter.postAndWait(self, LLSD().insert("value", 23).insert("fail", LLSD()), +                                       immediateAPI.getPump(), "reply", "error")); +                result = pair.first; +                which  = pair.second; +            } +            END +        } + +        void coroPumpsPostNoEx(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                result = waiter.postAndWaitWithException(self, LLSD().insert("value", 8), +                                                         immediateAPI.getPump(), "reply", "error"); +            } +            END +        } + +        void coroPumpsPostEx(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                try +                { +                    result = waiter.postAndWaitWithException(self, +                        LLSD().insert("value", 9).insert("fail", LLSD()), +                        immediateAPI.getPump(), "reply", "error"); +                    debug("no exception"); +                } +                catch (const LLErrorEvent& e) +                { +                    debug(STRINGIZE("exception " << e.what())); +                    errordata = e.getData(); +                } +            } +            END +        } + +        void coroPumpsPostNoLog(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                result = waiter.postAndWaitWithLog(self, LLSD().insert("value", 30), +                                                   immediateAPI.getPump(), "reply", "error"); +            } +            END +        } + +        void coroPumpsPostLog(coroutine_type::self& self) +        { +            BEGIN +            { +                LLCoroEventPumps waiter; +                WrapLL_ERRS capture; +                try +                { +                    result = waiter.postAndWaitWithLog(self, +                        LLSD().insert("value", 31).insert("fail", LLSD()), +                        immediateAPI.getPump(), "reply", "error"); +                    debug("no exception"); +                } +                catch (const WrapLL_ERRS::FatalException& e) +                { +                    debug(STRINGIZE("exception " << e.what())); +                    threw = e.what(); +                } +            } +            END +        } + +        void ensure_done(coroutine_type& coro) +        { +            ensure("coroutine complete", ! coro); +        } + +        ImmediateAPI immediateAPI; +        std::string replyName, errorName, threw; +        LLSD result, errordata; +        int which; +    }; +    typedef test_group<coroutine_data> coroutine_group; +    typedef coroutine_group::object object; +    coroutine_group coroutinegrp("coroutine"); + +    template<> template<> +    void object::test<1>() +    { +        set_test_name("From banana.cpp example program in Boost.Coroutine distro"); +        std::string buffer = "banananana";  +        std::string match = "nana";  +        std::string::iterator begin = buffer.begin(); +        std::string::iterator end = buffer.end(); + +#if defined(BOOST_CORO_POSIX_IMPL) +//      std::cout << "Using Boost.Coroutine " << BOOST_CORO_POSIX_IMPL << '\n'; +#else +//      std::cout << "Using non-Posix Boost.Coroutine implementation" << std::endl; +#endif + +        typedef std::string::iterator signature(std::string::iterator,  +                                                std::string::iterator,  +                                                std::string, +                                                match_coroutine_type::self&); + +        coroutine<std::string::iterator(void)> matcher +            (boost::bind(static_cast<signature*>(match_substring),  +                         begin,  +                         end,  +                         match,  +                         _1));  + +        std::string::iterator i = matcher(); +/*==========================================================================*| +        while(matcher && i != buffer.end()) { +            std::cout <<"Match at: "<< std::distance(buffer.begin(), i)<<'\n';  +            i = matcher(); +        } +|*==========================================================================*/ +        size_t matches[] = { 2, 4, 6 }; +        for (size_t *mi(boost::begin(matches)), *mend(boost::end(matches)); +             mi != mend; ++mi, i = matcher()) +        { +            ensure("more", matcher); +            ensure("found", i != buffer.end()); +            ensure_equals("value", std::distance(buffer.begin(), i), *mi); +        } +        ensure("done", ! matcher); +    } + +    template<> template<> +    void object::test<2>() +    { +        set_test_name("explicit_wait"); +        DEBUG; + +        // Construct the coroutine instance that will run explicit_wait. +        // Pass the ctor a callable that accepts the coroutine_type::self +        // param passed by the library. +        coroutine_type coro(boost::bind(&coroutine_data::explicit_wait, this, _1)); +        // Start the coroutine +        coro(std::nothrow); +        // When the coroutine waits for the event pump, it returns here. +        debug("about to send"); +        // Satisfy the wait. +        LLEventPumps::instance().obtain("source").post("received"); +        // Now wait for the coroutine to complete. +        ensure_done(coro); +        // ensure the coroutine ran and woke up again with the intended result +        ensure_equals(result.asString(), "received"); +    } + +    template<> template<> +    void object::test<3>() +    { +        set_test_name("waitForEventOn1"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn1, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain("source").post("received"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "received"); +    } + +    template<> template<> +    void object::test<4>() +    { +        set_test_name("waitForEventOn2 reply"); +        { +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn2, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain("reply").post("received"); +        debug("back from send"); +        ensure_done(coro); +        } +        ensure_equals(result.asString(), "received"); +        ensure_equals("which pump", which, 0); +    } + +    template<> template<> +    void object::test<5>() +    { +        set_test_name("waitForEventOn2 error"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn2, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain("error").post("badness"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "badness"); +        ensure_equals("which pump", which, 1); +    } + +    template<> template<> +    void object::test<6>() +    { +        set_test_name("coroPump"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPump, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(replyName).post("received"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "received"); +    } + +    template<> template<> +    void object::test<7>() +    { +        set_test_name("coroPumps reply"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumps, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(replyName).post("received"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "received"); +        ensure_equals("which pump", which, 0); +    } + +    template<> template<> +    void object::test<8>() +    { +        set_test_name("coroPumps error"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumps, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(errorName).post("badness"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "badness"); +        ensure_equals("which pump", which, 1); +    } + +    template<> template<> +    void object::test<9>() +    { +        set_test_name("coroPumpsNoEx"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsNoEx, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(replyName).post("received"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "received"); +    } + +    template<> template<> +    void object::test<10>() +    { +        set_test_name("coroPumpsEx"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsEx, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(errorName).post("badness"); +        debug("back from send"); +        ensure_done(coro); +        ensure("no result", result.isUndefined()); +        ensure_equals("got error", errordata.asString(), "badness"); +    } + +    template<> template<> +    void object::test<11>() +    { +        set_test_name("coroPumpsNoLog"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsNoLog, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(replyName).post("received"); +        debug("back from send"); +        ensure_done(coro); +        ensure_equals(result.asString(), "received"); +    } + +    template<> template<> +    void object::test<12>() +    { +        set_test_name("coroPumpsLog"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsLog, this, _1)); +        coro(std::nothrow); +        debug("about to send"); +        LLEventPumps::instance().obtain(errorName).post("badness"); +        debug("back from send"); +        ensure_done(coro); +        ensure("no result", result.isUndefined()); +        ensure_contains("got error", threw, "badness"); +    } + +    template<> template<> +    void object::test<13>() +    { +        set_test_name("postAndWait1"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::postAndWait1, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 18); +    } + +    template<> template<> +    void object::test<14>() +    { +        set_test_name("postAndWait2"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::postAndWait2, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 19); +        ensure_equals(which, 0); +    } + +    template<> template<> +    void object::test<15>() +    { +        set_test_name("postAndWait2_1"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::postAndWait2_1, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 19); +        ensure_equals(which, 1); +    } + +    template<> template<> +    void object::test<16>() +    { +        set_test_name("coroPumpPost"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpPost, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 18); +    } + +    template<> template<> +    void object::test<17>() +    { +        set_test_name("coroPumpsPost reply"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPost, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 24); +        ensure_equals("which pump", which, 0); +    } + +    template<> template<> +    void object::test<18>() +    { +        set_test_name("coroPumpsPost error"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPost_1, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 24); +        ensure_equals("which pump", which, 1); +    } + +    template<> template<> +    void object::test<19>() +    { +        set_test_name("coroPumpsPostNoEx"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostNoEx, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 9); +    } + +    template<> template<> +    void object::test<20>() +    { +        set_test_name("coroPumpsPostEx"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostEx, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure("no result", result.isUndefined()); +        ensure_equals("got error", errordata.asInteger(), 10); +    } + +    template<> template<> +    void object::test<21>() +    { +        set_test_name("coroPumpsPostNoLog"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostNoLog, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure_equals(result.asInteger(), 31); +    } + +    template<> template<> +    void object::test<22>() +    { +        set_test_name("coroPumpsPostLog"); +        DEBUG; +        coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostLog, this, _1)); +        coro(std::nothrow); +        ensure_done(coro); +        ensure("no result", result.isUndefined()); +        ensure_contains("got error", threw, "32"); +    } +} // namespace tut diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp new file mode 100644 index 0000000000..28b909298e --- /dev/null +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -0,0 +1,276 @@ +/** + * @file   lleventfilter_test.cpp + * @author Nat Goodspeed + * @date   2009-03-06 + * @brief  Test for lleventfilter. + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventfilter.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "stringize.h" +#include "listener.h" +#include "tests/wrapllerrs.h" + +/***************************************************************************** +*   Test classes +*****************************************************************************/ +// Strictly speaking, we're testing LLEventTimeoutBase rather than the +// production LLEventTimeout (using LLTimer) because we don't want every test +// run to pause for some number of seconds until we reach a real timeout. But +// as we've carefully put all functionality except actual LLTimer calls into +// LLEventTimeoutBase, that should suffice. We're not not not trying to test +// LLTimer here. +class TestEventTimeout: public LLEventTimeoutBase +{ +public: +    TestEventTimeout(): +        mElapsed(true) +    {} +    TestEventTimeout(LLEventPump& source): +        LLEventTimeoutBase(source), +        mElapsed(true) +    {} + +    // test hook +    void forceTimeout(bool timeout=true) { mElapsed = timeout; } + +protected: +    virtual void setCountdown(F32 seconds) { mElapsed = false; } +    virtual bool countdownElapsed() const { return mElapsed; } + +private: +    bool mElapsed; +}; + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct filter_data +    { +        // The resemblance between this test data and that in llevents_tut.cpp +        // is not coincidental. +        filter_data(): +            pumps(LLEventPumps::instance()), +            mainloop(pumps.obtain("mainloop")), +            listener0("first"), +            listener1("second") +        {} +        LLEventPumps& pumps; +        LLEventPump& mainloop; +        Listener listener0; +        Listener listener1; + +        void check_listener(const std::string& desc, const Listener& listener, const LLSD& got) +        { +            ensure_equals(STRINGIZE(listener << ' ' << desc), +                          listener.getLastEvent(), got); +        } +    }; +    typedef test_group<filter_data> filter_group; +    typedef filter_group::object filter_object; +    filter_group filtergrp("lleventfilter"); + +    template<> template<> +    void filter_object::test<1>() +    { +        set_test_name("LLEventMatching"); +        LLEventPump& driver(pumps.obtain("driver")); +        listener0.reset(0); +        // Listener isn't derived from LLEventTrackable specifically to test +        // various connection-management mechanisms. But that means we have a +        // couple of transient Listener objects, one of which is listening to +        // a persistent LLEventPump. Capture those connections in local +        // LLTempBoundListener instances so they'll disconnect +        // on destruction. +        LLTempBoundListener temp1( +            listener0.listenTo(driver)); +        // Construct a pattern LLSD: desired Event must have a key "foo" +        // containing string "bar" +        LLEventMatching filter(driver, LLSD().insert("foo", "bar")); +        listener1.reset(0); +        LLTempBoundListener temp2( +            listener1.listenTo(filter)); +        driver.post(1); +        check_listener("direct", listener0, LLSD(1)); +        check_listener("filtered", listener1, LLSD(0)); +        // Okay, construct an LLSD map matching the pattern +        LLSD data; +        data["foo"] = "bar"; +        data["random"] = 17; +        driver.post(data); +        check_listener("direct", listener0, data); +        check_listener("filtered", listener1, data); +    } + +    template<> template<> +    void filter_object::test<2>() +    { +        set_test_name("LLEventTimeout::actionAfter()"); +        LLEventPump& driver(pumps.obtain("driver")); +        TestEventTimeout filter(driver); +        listener0.reset(0); +        LLTempBoundListener temp1( +            listener0.listenTo(filter)); +        // Use listener1.call() as the Action for actionAfter(), since it +        // already provides a way to sense the call +        listener1.reset(0); +        // driver --> filter --> listener0 +        filter.actionAfter(20, +                           boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); +        // Okay, (fake) timer is ticking. 'filter' can only sense the timer +        // when we pump mainloop. Do that right now to take the logic path +        // before either the anticipated event arrives or the timer expires. +        mainloop.post(17); +        check_listener("no timeout 1", listener1, LLSD(0)); +        // Expected event arrives... +        driver.post(1); +        check_listener("event passed thru", listener0, LLSD(1)); +        // Should have canceled the timer. Verify that by asserting that the +        // time has expired, then pumping mainloop again. +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 2", listener1, LLSD(0)); +        // Verify chained actionAfter() calls, that is, that a second +        // actionAfter() resets the timer established by the first +        // actionAfter(). +        filter.actionAfter(20, +                           boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); +        // Since our TestEventTimeout class isn't actually manipulating time +        // (quantities of seconds), only a bool "elapsed" flag, sense that by +        // forcing the flag between actionAfter() calls. +        filter.forceTimeout(); +        // Pumping mainloop here would result in a timeout (as we'll verify +        // below). This state simulates a ticking timer that has not yet timed +        // out. But now, before a mainloop event lets 'filter' recognize +        // timeout on the previous actionAfter() call, pretend we're pushing +        // that timeout farther into the future. +        filter.actionAfter(20, +                           boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); +        // Look ma, no timeout! +        mainloop.post(17); +        check_listener("no timeout 3", listener1, LLSD(0)); +        // Now let the updated actionAfter() timer expire. +        filter.forceTimeout(); +        // Notice the timeout. +        mainloop.post(17); +        check_listener("timeout", listener1, LLSD("timeout")); +        // Timing out cancels the timer. Verify that. +        listener1.reset(0); +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 4", listener1, LLSD(0)); +        // Reset the timer and then cancel() it. +        filter.actionAfter(20, +                           boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); +        // neither expired nor satisified +        mainloop.post(17); +        check_listener("no timeout 5", listener1, LLSD(0)); +        // cancel +        filter.cancel(); +        // timeout! +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 6", listener1, LLSD(0)); +    } + +    template<> template<> +    void filter_object::test<3>() +    { +        set_test_name("LLEventTimeout::eventAfter()"); +        LLEventPump& driver(pumps.obtain("driver")); +        TestEventTimeout filter(driver); +        listener0.reset(0); +        LLTempBoundListener temp1( +            listener0.listenTo(filter)); +        filter.eventAfter(20, LLSD("timeout")); +        // Okay, (fake) timer is ticking. 'filter' can only sense the timer +        // when we pump mainloop. Do that right now to take the logic path +        // before either the anticipated event arrives or the timer expires. +        mainloop.post(17); +        check_listener("no timeout 1", listener0, LLSD(0)); +        // Expected event arrives... +        driver.post(1); +        check_listener("event passed thru", listener0, LLSD(1)); +        // Should have canceled the timer. Verify that by asserting that the +        // time has expired, then pumping mainloop again. +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 2", listener0, LLSD(1)); +        // Set timer again. +        filter.eventAfter(20, LLSD("timeout")); +        // Now let the timer expire. +        filter.forceTimeout(); +        // Notice the timeout. +        mainloop.post(17); +        check_listener("timeout", listener0, LLSD("timeout")); +        // Timing out cancels the timer. Verify that. +        listener0.reset(0); +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 3", listener0, LLSD(0)); +    } + +    template<> template<> +    void filter_object::test<4>() +    { +        set_test_name("LLEventTimeout::errorAfter()"); +        WrapLL_ERRS capture; +        LLEventPump& driver(pumps.obtain("driver")); +        TestEventTimeout filter(driver); +        listener0.reset(0); +        LLTempBoundListener temp1( +            listener0.listenTo(filter)); +        filter.errorAfter(20, "timeout"); +        // Okay, (fake) timer is ticking. 'filter' can only sense the timer +        // when we pump mainloop. Do that right now to take the logic path +        // before either the anticipated event arrives or the timer expires. +        mainloop.post(17); +        check_listener("no timeout 1", listener0, LLSD(0)); +        // Expected event arrives... +        driver.post(1); +        check_listener("event passed thru", listener0, LLSD(1)); +        // Should have canceled the timer. Verify that by asserting that the +        // time has expired, then pumping mainloop again. +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 2", listener0, LLSD(1)); +        // Set timer again. +        filter.errorAfter(20, "timeout"); +        // Now let the timer expire. +        filter.forceTimeout(); +        // Notice the timeout. +        std::string threw; +        try +        { +            mainloop.post(17); +        } +        catch (const WrapLL_ERRS::FatalException& e) +        { +            threw = e.what(); +        } +        ensure_contains("errorAfter() timeout exception", threw, "timeout"); +        // Timing out cancels the timer. Verify that. +        listener0.reset(0); +        filter.forceTimeout(); +        mainloop.post(17); +        check_listener("no timeout 3", listener0, LLSD(0)); +    } +} // namespace tut + +/***************************************************************************** +*   Link dependencies +*****************************************************************************/ +#include "llsdutil.cpp" diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index f13c69f1e3..6ab48ec34a 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -48,6 +48,18 @@  typedef U32 uint32_t;  #endif +std::vector<U8> string_to_vector(std::string str) +{ +	// bc LLSD can't... +	size_t len = (size_t)str.length(); +	std::vector<U8> v(len); +	for (size_t i = 0; i < len ; i++) +	{ +		v[i] = str[i]; +	} +	return v; +} +  namespace tut  {  	struct sd_xml_data @@ -107,7 +119,16 @@ namespace tut  		expected = "<llsd><date>2006-04-24T16:11:33Z</date></llsd>\n";  		xml_test("date", expected); -		// *FIX: test binary +		// Generated by: echo -n 'hello' | openssl enc -e -base64 +		std::vector<U8> hello; +		hello.push_back('h'); +		hello.push_back('e'); +		hello.push_back('l'); +		hello.push_back('l'); +		hello.push_back('o'); +		mSD = hello; +		expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; +		xml_test("binary", expected);  	}  	template<> template<> @@ -199,6 +220,21 @@ namespace tut  		xml_test("2 element map", expected);  	} +	template<> template<> +	void sd_xml_object::test<6>() +	{ +		// tests with binary +		std::string expected; + +		// Generated by: echo -n 'hello' | openssl enc -e -base64 +		mSD = string_to_vector("hello"); +		expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; +		xml_test("binary", expected); + +		mSD = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); +		expected = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; +		xml_test("binary", expected); +	}  	class TestLLSDSerializeData  	{ @@ -637,6 +673,42 @@ namespace tut  			v.size() + 1);  	} +	template<> template<>  +	void TestLLSDXMLParsingObject::test<4>() +	{ +		// test handling of binary object in XML +		std::string xml; +		LLSD expected; + +		// Generated by: echo -n 'hello' | openssl enc -e -base64 +		expected = string_to_vector("hello"); +		xml = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; +		ensureParse( +			"the word 'hello' packed in binary encoded base64", +			xml, +			expected, +			1); + +		expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); +		xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; +		ensureParse( +			"a common binary blob for object -> agent offline inv transfer", +			xml, +			expected, +			1); + +		expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); +		xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBl\n"; +		xml += "NDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5\n"; +		xml += "LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZm\n"; +		xml += "ZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMy\n"; +		xml += "OXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; +		ensureParse( +			"a common binary blob for object -> agent offline inv transfer", +			xml, +			expected, +			1); +	}  	/*  	TODO:  		test XML parsing diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index 6a2ebc61f5..beba55416a 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -32,6 +32,7 @@   * $/LicenseInfo$   */ +#include "linden_common.h"  #include "../test/lltut.h"  #include "../llstring.h" diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h new file mode 100644 index 0000000000..1001ebc466 --- /dev/null +++ b/indra/llcommon/tests/wrapllerrs.h @@ -0,0 +1,56 @@ +/** + * @file   wrapllerrs.h + * @author Nat Goodspeed + * @date   2009-03-11 + * @brief  Define a class useful for unit tests that engage llerrs (LL_ERRS) functionality + *  + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_WRAPLLERRS_H) +#define LL_WRAPLLERRS_H + +#include "llerrorcontrol.h" + +struct WrapLL_ERRS +{ +    WrapLL_ERRS(): +        // Resetting Settings discards the default Recorder that writes to +        // stderr. Otherwise, expected llerrs (LL_ERRS) messages clutter the +        // console output of successful tests, potentially confusing things. +        mPriorErrorSettings(LLError::saveAndResetSettings()), +        // Save shutdown function called by LL_ERRS +        mPriorFatal(LLError::getFatalFunction()) +    { +        // Make LL_ERRS call our own operator() method +        LLError::setFatalFunction(boost::bind(&WrapLL_ERRS::operator(), this, _1)); +    } + +    ~WrapLL_ERRS() +    { +        LLError::setFatalFunction(mPriorFatal); +        LLError::restoreSettings(mPriorErrorSettings); +    } + +    struct FatalException: public std::runtime_error +    { +        FatalException(const std::string& what): std::runtime_error(what) {} +    }; + +    void operator()(const std::string& message) +    { +        // Save message for later in case consumer wants to sense the result directly +        error = message; +        // Also throw an appropriate exception since calling code is likely to +        // assume that control won't continue beyond LL_ERRS. +        throw FatalException(message); +    } + +    std::string error; +    LLError::Settings* mPriorErrorSettings; +    LLError::FatalFunction mPriorFatal; +}; + +#endif /* ! defined(LL_WRAPLLERRS_H) */ diff --git a/indra/llcommon/timing.h b/indra/llcommon/timing.h index 2b9f60adad..140ce1fcaa 100644 --- a/indra/llcommon/timing.h +++ b/indra/llcommon/timing.h @@ -43,7 +43,6 @@ const F32 SEC_TO_MICROSEC = 1000000.f;  const U64 SEC_TO_MICROSEC_U64 = 1000000;  const U32 SEC_PER_DAY = 86400; -// This is just a stub, implementation in lltimer.cpp.  This file will be deprecated in the future. -U64 totalTime();					// Returns current system time in microseconds +// functionality has been moved lltimer.{cpp,h}.  This file will be deprecated in the future.  #endif diff --git a/indra/llcommon/u64.h b/indra/llcommon/u64.h index 09a6b3e18d..eb51131e94 100644 --- a/indra/llcommon/u64.h +++ b/indra/llcommon/u64.h @@ -39,14 +39,14 @@   * @param str The string to parse.   * @return Returns the first U64 value found in the string or 0 on failure.   */ -U64 str_to_U64(const std::string& str); +LL_COMMON_API U64 str_to_U64(const std::string& str);  /**   * @brief Given a U64 value, return a printable representation.   * @param value The U64 to turn into a printable character array.   * @return Returns the result string.   */ -std::string U64_to_str(U64 value); +LL_COMMON_API std::string U64_to_str(U64 value);  /**   * @brief Given a U64 value, return a printable representation. @@ -65,16 +65,16 @@ std::string U64_to_str(U64 value);   * @param result_size The size of the buffer allocated. Use U64_BUF.   * @return Returns the result pointer.   */ -char* U64_to_str(U64 value, char* result, S32 result_size); +LL_COMMON_API char* U64_to_str(U64 value, char* result, S32 result_size);  /**   * @brief Convert a U64 to the closest F64 value.   */ -F64 U64_to_F64(const U64 value); +LL_COMMON_API F64 U64_to_F64(const U64 value);  /**   * @brief Helper function to wrap strtoull() which is not available on windows.   */ -U64 llstrtou64(const char* str, char** end, S32 base); +LL_COMMON_API U64 llstrtou64(const char* str, char** end, S32 base);  #endif | 
