diff options
Diffstat (limited to 'indra/llcommon')
| -rw-r--r-- | indra/llcommon/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | indra/llcommon/lleventdispatcher.cpp | 535 | ||||
| -rw-r--r-- | indra/llcommon/lleventdispatcher.h | 374 | ||||
| -rw-r--r-- | indra/llcommon/tests/lleventdispatcher_test.cpp | 436 | 
4 files changed, 1289 insertions, 57 deletions
| diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 9342a22d46..dc9f93df3b 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -313,6 +313,7 @@ if (LL_TESTS)    LL_ADD_INTEGRATION_TEST(lluri "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(reflection "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}") +  LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")    # *TODO - reenable these once tcmalloc libs no longer break the build.    #ADD_BUILD_TEST(llallocator llcommon) diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index d6e820d793..2ab006a173 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -41,6 +41,326 @@  #include "llevents.h"  #include "llerror.h"  #include "llsdutil.h" +#include "stringize.h" +#include <memory>                   // std::auto_ptr + +/***************************************************************************** +*   LLSDArgsSource +*****************************************************************************/ +/** + * Store an LLSD array, producing its elements one at a time. Die with LL_ERRS + * if the consumer requests more elements than the array contains. + */ +class LL_COMMON_API LLSDArgsSource +{ +public: +    LLSDArgsSource(const std::string function, const LLSD& args); +    ~LLSDArgsSource(); + +    LLSD next(); + +    void done() const; + +private: +    std::string _function; +    LLSD _args; +    LLSD::Integer _index; +}; + +LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args): +    _function(function), +    _args(args), +    _index(0) +{ +    if (! (_args.isUndefined() || _args.isArray())) +    { +        LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of " +                                  << _args << LL_ENDL; +    } +} + +LLSDArgsSource::~LLSDArgsSource() +{ +    done(); +} + +LLSD LLSDArgsSource::next() +{ +    if (_index >= _args.size()) +    { +        LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the " +                                  << _args.size() << " provided: " << _args << LL_ENDL; +    } +    return _args[_index++]; +} + +void LLSDArgsSource::done() const +{ +    if (_index < _args.size()) +    { +        LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index +                                   << " of the " << _args.size() << " arguments provided: " +                                   << _args << LL_ENDL; +    } +} + +/***************************************************************************** +*   LLSDArgsMapper +*****************************************************************************/ +/** + * From a formal parameters description and a map of arguments, construct an + * arguments array. + * + * That is, given: + * - an LLSD array of length n containing parameter-name strings, + *   corresponding to the arguments of a function of interest + * - an LLSD collection specifying default parameter values, either: + *   - an LLSD array of length m <= n, matching the rightmost m params, or + *   - an LLSD map explicitly stating default name=value pairs + * - an LLSD map of parameter names and actual values for a particular + *   function call + * construct an LLSD array of actual argument values for this function call. + * + * The parameter-names array and the defaults collection describe the function + * being called. The map might vary with every call, providing argument values + * for the described parameters. + * + * The array of parameter names must match the number of parameters expected + * by the function of interest. + * + * If you pass a map of default parameter values, it provides default values + * as you might expect. It is an error to specify a default value for a name + * not listed in the parameters array. + * + * If you pass an array of default parameter values, it is mapped to the + * rightmost m of the n parameter names. It is an error if the default-values + * array is longer than the parameter-names array. Consider the following + * parameter names: ["a", "b", "c", "d"]. + * + * - An empty array of default values (or an isUndefined() value) asserts that + *   every one of the above parameter names is required. + * - An array of four default values [1, 2, 3, 4] asserts that every one of + *   the above parameters is optional. If the current parameter map is empty, + *   they will be passed to the function as [1, 2, 3, 4]. + * - An array of two default values [11, 12] asserts that parameters "a" and + *   "b" are required, while "c" and "d" are optional, having default values + *   "c"=11 and "d"=12. + * + * The arguments array is constructed as follows: + * + * - Arguments-map keys not found in the parameter-names array are ignored. + * - Entries from the map provide values for an improper subset of the + *   parameters named in the parameter-names array. This results in a + *   tentative values array with "holes." (size of map) + (number of holes) = + *   (size of names array) + * - Holes are filled with the default values. + * - Any remaining holes constitute an error. + */ +class LL_COMMON_API LLSDArgsMapper +{ +public: +    /// Accept description of function: function name, param names, param +    /// default values +    LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults); + +    /// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS. +    LLSD map(const LLSD& argsmap) const; + +private: +    static std::string formatlist(const LLSD&); + +    // The function-name string is purely descriptive. We want error messages +    // to be able to indicate which function's LLSDArgsMapper has the problem. +    std::string _function; +    // Store the names array pretty much as given. +    LLSD _names; +    // Though we're handed an array of name strings, it's more useful to us to +    // store it as a map from name string to position index. Of course that's +    // easy to generate from the incoming names array, but why do it more than +    // once? +    typedef std::map<LLSD::String, LLSD::Integer> IndexMap; +    IndexMap _indexes; +    // Generated array of default values, aligned with the array of param names. +    LLSD _defaults; +    // Indicate whether we have a default value for each param. +    typedef std::vector<char> FilledVector; +    FilledVector _has_dft; +}; + +LLSDArgsMapper::LLSDArgsMapper(const std::string& function, +                               const LLSD& names, const LLSD& defaults): +    _function(function), +    _names(names), +    _has_dft(names.size()) +{ +    if (! (_names.isUndefined() || _names.isArray())) +    { +        LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL; +    } +    LLSD::Integer nparams(_names.size()); +    // From _names generate _indexes. +    for (LLSD::Integer ni = 0, nend = _names.size(); ni < nend; ++ni) +    { +        _indexes[_names[ni]] = ni; +    } + +    // Presize _defaults() array so we don't have to resize it more than once. +    // All entries are initialized to LLSD(); but since _has_dft is still all +    // 0, they're all "holes" for now. +    if (nparams) +    { +        _defaults[nparams - 1] = LLSD(); +    } + +    if (defaults.isUndefined() || defaults.isArray()) +    { +        LLSD::Integer ndefaults = defaults.size(); +        // defaults is a (possibly empty) array. Right-align it with names. +        if (ndefaults > nparams) +        { +            LL_ERRS("LLSDArgsMapper") << function << " names array " << names +                                      << " shorter than defaults array " << defaults << LL_ENDL; +        } + +        // Offset by which we slide defaults array right to right-align with +        // _names array +        LLSD::Integer offset = nparams - ndefaults; +        // Fill rightmost _defaults entries from defaults, and mark them as +        // filled +        for (LLSD::Integer i = 0, iend = ndefaults; i < iend; ++i) +        { +            _defaults[i + offset] = defaults[i]; +            _has_dft[i + offset] = 1; +        } +    } +    else if (defaults.isMap()) +    { +        // defaults is a map. Use it to populate the _defaults array. +        LLSD bogus; +        for (LLSD::map_const_iterator mi(defaults.beginMap()), mend(defaults.endMap()); +             mi != mend; ++mi) +        { +            IndexMap::const_iterator ixit(_indexes.find(mi->first)); +            if (ixit == _indexes.end()) +            { +                bogus.append(mi->first); +                continue; +            } + +            LLSD::Integer pos = ixit->second; +            // Store default value at that position in the _defaults array. +            _defaults[pos] = mi->second; +            // Don't forget to record the fact that we've filled this +            // position. +            _has_dft[pos] = 1; +        } +        if (bogus.size()) +        { +            LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params " +                                      << formatlist(bogus) << LL_ENDL; +        } +    } +    else +    { +        LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not " +                                  << defaults << LL_ENDL; +    } +} + +LLSD LLSDArgsMapper::map(const LLSD& argsmap) const +{ +    if (! (argsmap.isUndefined() || argsmap.isMap())) +    { +        LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map, not " << argsmap << LL_ENDL; +    } +    // Initialize the args array. Indexing a non-const LLSD array grows it +    // to appropriate size, but we don't want to resize this one on each +    // new operation. Just make it as big as we need before we start +    // stuffing values into it. +    LLSD args(LLSD::emptyArray()); +    if (_defaults.size() == 0) +    { +        // If this function requires no arguments, fast exit. (Don't try to +        // assign to args[-1].) +        return args; +    } +    args[_defaults.size() - 1] = LLSD(); + +    // Get a vector of chars to indicate holes. It's tempting to just scan +    // for LLSD::isUndefined() values after filling the args array from +    // the map, but it's plausible for caller to explicitly pass +    // isUndefined() as the value of some parameter name. That's legal +    // since isUndefined() has well-defined conversions (default value) +    // for LLSD data types. So use a whole separate array for detecting +    // holes. (Avoid std::vector<bool> which is known to be odd -- can we +    // iterate?) +    FilledVector filled(args.size()); +    // Walk the map. +    for (LLSD::map_const_iterator mi(argsmap.beginMap()), mend(argsmap.endMap()); +         mi != mend; ++mi) +    { +        // mi->first is a parameter-name string, with mi->second its +        // value. Look up the name's position index in _indexes. +        IndexMap::const_iterator ixit(_indexes.find(mi->first)); +        if (ixit == _indexes.end()) +        { +            // Allow for a map containing more params than were passed in +            // our names array. Caller typically receives a map containing +            // the function name, cruft such as reqid, etc. Ignore keys +            // not defined in _indexes. +            LL_DEBUGS("LLSDArgsMapper") << _function << " ignoring " +                                        << mi->first << "=" << mi->second << LL_ENDL; +            continue; +        } +        LLSD::Integer pos = ixit->second; +        // Store the value at that position in the args array. +        args[pos] = mi->second; +        // Don't forget to record the fact that we've filled this +        // position. +        filled[pos] = 1; +    } +    // Fill any remaining holes from _defaults. +    LLSD unfilled(LLSD::emptyArray()); +    for (LLSD::Integer i = 0, iend = args.size(); i < iend; ++i) +    { +        if (! filled[i]) +        { +            // If there's no default value for this parameter, that's an +            // error. +            if (! _has_dft[i]) +            { +                unfilled.append(_names[i]); +            } +            else +            { +                args[i] = _defaults[i]; +            } +        } +    } +    // If any required args -- args without defaults -- were left unfilled +    // by argsmap, that's a problem. +    if (unfilled.size()) +    { +        LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments " +                                  << formatlist(unfilled) << " from " << argsmap << LL_ENDL; +    } + +    // done +    return args; +} + +std::string LLSDArgsMapper::formatlist(const LLSD& list) +{ +    std::ostringstream out; +    const char* delim = ""; +    for (LLSD::array_const_iterator li(list.beginArray()), lend(list.endArray()); +         li != lend; ++li) +    { +        out << delim << li->asString(); +        delim = ", "; +    } +    return out.str(); +}  LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):      mDesc(desc), @@ -52,12 +372,178 @@ LLEventDispatcher::~LLEventDispatcher()  {  } +/** + * DispatchEntry subclass used for callables accepting(const LLSD&) + */ +struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry +{ +    LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required): +        DispatchEntry(desc), +        mFunc(func), +        mRequired(required) +    {} + +    Callable mFunc; +    LLSD mRequired; + +    virtual void call(const std::string& desc, const LLSD& event) const +    { +        // Validate the syntax of the event itself. +        std::string mismatch(llsd_matches(mRequired, event)); +        if (! mismatch.empty()) +        { +            LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL; +        } +        // Event syntax looks good, go for it! +        mFunc(event); +    } + +    virtual LLSD addMetadata(LLSD meta) const +    { +        meta["required"] = mRequired; +        return meta; +    } +}; + +/** + * DispatchEntry subclass for passing LLSD to functions accepting + * arbitrary argument types (convertible via LLSDParam) + */ +struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry +{ +    ParamsDispatchEntry(const std::string& desc, const invoker_function& func): +        DispatchEntry(desc), +        mInvoker(func) +    {} + +    invoker_function mInvoker; + +    virtual void call(const std::string& desc, const LLSD& event) const +    { +        LLSDArgsSource src(desc, event); +        mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src))); +    } +}; + +/** + * DispatchEntry subclass for dispatching LLSD::Array to functions accepting + * arbitrary argument types (convertible via LLSDParam) + */ +struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry +{ +    ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func, +                             LLSD::Integer arity): +        ParamsDispatchEntry(desc, func), +        mArity(arity) +    {} + +    LLSD::Integer mArity; + +    virtual LLSD addMetadata(LLSD meta) const +    { +        LLSD array(LLSD::emptyArray()); +        // Resize to number of arguments required +        array[mArity - 1] = LLSD(); +        llassert_always(array.size() == mArity); +        meta["required"] = array; +        return meta; +    } +}; + +/** + * DispatchEntry subclass for dispatching LLSD::Map to functions accepting + * arbitrary argument types (convertible via LLSDParam) + */ +struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry +{ +    MapParamsDispatchEntry(const std::string& name, const std::string& desc, +                           const invoker_function& func, +                           const LLSD& params, const LLSD& defaults): +        ParamsDispatchEntry(desc, func), +        mMapper(name, params, defaults), +        mRequired(LLSD::emptyMap()) +    { +        // Build the set of all param keys, then delete the ones that are +        // optional. What's left are the ones that are required. +        for (LLSD::array_const_iterator pi(params.beginArray()), pend(params.endArray()); +             pi != pend; ++pi) +        { +            mRequired[pi->asString()] = LLSD(); +        } + +        if (defaults.isArray() || defaults.isUndefined()) +        { +            // Right-align the params and defaults arrays. +            LLSD::Integer offset = params.size() - defaults.size(); +            // Now the name of every defaults[i] is at params[i + offset]. +            for (LLSD::Integer i(0), iend(defaults.size()); i < iend; ++i) +            { +                // Erase this optional param from mRequired. +                mRequired.erase(params[i + offset].asString()); +                // Instead, make an entry in mOptional with the default +                // param's name and value. +                mOptional[params[i + offset].asString()] = defaults[i]; +            } +        } +        else if (defaults.isMap()) +        { +            // if defaults is already a map, then it's already in the form we +            // intend to deliver in metadata +            mOptional = defaults; +            // Just delete from mRequired every key appearing in mOptional. +            for (LLSD::map_const_iterator mi(mOptional.beginMap()), mend(mOptional.endMap()); +                 mi != mend; ++mi) +            { +                mRequired.erase(mi->first); +            } +        } +    } + +    LLSDArgsMapper mMapper; +    LLSD mRequired; +    LLSD mOptional; + +    virtual void call(const std::string& desc, const LLSD& event) const +    { +        // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass +        // to base-class call() method. +        ParamsDispatchEntry::call(desc, mMapper.map(event)); +    } + +    virtual LLSD addMetadata(LLSD meta) const +    { +        meta["required"] = mRequired; +        meta["optional"] = mOptional; +        return meta; +    } +}; + +void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, +                                                    const std::string& desc, +                                                    const invoker_function& invoker, +                                                    LLSD::Integer arity) +{ +    // Peculiar to me that boost::ptr_map() accepts std::auto_ptr but not dumb ptr +    mDispatch.insert(name, std::auto_ptr<DispatchEntry>( +                         new ArrayParamsDispatchEntry(desc, invoker, arity))); +} + +void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, +                                                  const std::string& desc, +                                                  const invoker_function& invoker, +                                                  const LLSD& params, +                                                  const LLSD& defaults) +{ +    mDispatch.insert(name, std::auto_ptr<DispatchEntry>( +                         new MapParamsDispatchEntry(name, desc, invoker, params, defaults))); +} +  /// Register a callable by name  void LLEventDispatcher::add(const std::string& name, const std::string& desc,                              const Callable& callable, const LLSD& required)  { -    mDispatch.insert(DispatchMap::value_type(name, -                                             DispatchMap::mapped_type(callable, desc, required))); +    mDispatch.insert(name, std::auto_ptr<DispatchEntry>( +                         new LLSDDispatchEntry(desc, callable, required)));  }  void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const @@ -83,7 +569,7 @@ bool LLEventDispatcher::remove(const std::string& name)  /// such callable exists, die with LL_ERRS.  void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const  { -    if (! attemptCall(name, event)) +    if (! try_call(name, event))      {          LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name                                       << "' not found" << LL_ENDL; @@ -98,44 +584,29 @@ 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)) +    if (! try_call(name, event))      {          LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey                                       << " value '" << name << "'" << LL_ENDL;      }  } -bool LLEventDispatcher::attemptCall(const std::string& name, const LLSD& event) const +bool LLEventDispatcher::try_call(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.mRequired, 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.mFunc)(event); -    return true;                    // tell caller we were able to call +    return try_call(event[mKey], event);  } -LLEventDispatcher::Callable LLEventDispatcher::get(const std::string& name) const +bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const  {      DispatchMap::const_iterator found = mDispatch.find(name);      if (found == mDispatch.end())      { -        return Callable(); +        return false;      } -    return found->second.mFunc; +    // Found the name, so it's plausible to even attempt the call. +    found->second->call(STRINGIZE("LLEventDispatcher(" << mDesc << ") calling '" << name << "'"), +                        event); +    return true;                    // tell caller we were able to call  }  LLSD LLEventDispatcher::getMetadata(const std::string& name) const @@ -147,9 +618,8 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const      }      LLSD meta;      meta["name"] = name; -    meta["desc"] = found->second.mDesc; -    meta["required"] = found->second.mRequired; -    return meta; +    meta["desc"] = found->second->mDesc; +    return found->second->addMetadata(meta);  }  LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): @@ -164,3 +634,8 @@ bool LLDispatchListener::process(const LLSD& event)      (*this)(event);      return false;  } + +LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): +    mDesc(desc) +{} + diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index dfffd59eb6..ce0fc7b585 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -27,18 +27,56 @@   *    * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA   * $/LicenseInfo$ + * + * The invoker machinery that constructs a boost::fusion argument list for use + * with boost::fusion::invoke() is derived from + * http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp + * whose license information is copied below: + * + * "(C) Copyright Tobias Schwinger + * + * Use modification and distribution are subject to the boost Software License, + * Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt)."   */  #if ! defined(LL_LLEVENTDISPATCHER_H)  #define LL_LLEVENTDISPATCHER_H +// nil is too generic a term to be allowed to be a global macro. In +// particular, boost::fusion defines a 'class nil' (properly encapsulated in a +// namespace) that a global 'nil' macro breaks badly. +#if defined(nil) +// Capture the value of the macro 'nil', hoping int is an appropriate type. +static const int nil_(nil); +// Now forget the macro. +#undef nil +// Finally, reintroduce 'nil' as a properly-scoped alias for the previously- +// defined const 'nil_'. Make it static since otherwise it produces duplicate- +// symbol link errors later. +static const int& nil(nil_); +#endif +  #include <string> -#include <map> +#include <boost/ptr_container/ptr_map.hpp>  #include <boost/function.hpp>  #include <boost/bind.hpp>  #include <boost/iterator/transform_iterator.hpp> +#include <boost/utility/enable_if.hpp> +#include <boost/function_types/is_nonmember_callable_builtin.hpp> +#include <boost/function_types/parameter_types.hpp> +#include <boost/function_types/function_arity.hpp> +#include <boost/type_traits/remove_cv.hpp> +#include <boost/type_traits/remove_reference.hpp> +#include <boost/fusion/include/push_back.hpp> +#include <boost/fusion/include/cons.hpp> +#include <boost/fusion/include/invoke.hpp> +#include <boost/mpl/begin.hpp> +#include <boost/mpl/end.hpp> +#include <boost/mpl/next.hpp> +#include <boost/mpl/deref.hpp>  #include <typeinfo>  #include "llevents.h" +#include "llsdutil.h"  class LLSD; @@ -54,12 +92,18 @@ public:      LLEventDispatcher(const std::string& desc, const std::string& key);      virtual ~LLEventDispatcher(); -    /// Accept any C++ callable, typically a boost::bind() expression +    /// @name Register functions accepting(const LLSD&) +    //@{ + +    /// Accept any C++ callable with the right signature, typically a +    /// boost::bind() expression      typedef boost::function<void(const LLSD&)> Callable;      /** -     * Register a @a callable by @a name. The optional @a required parameter -     * is used to validate the structure of each incoming event (see +     * Register a @a callable by @a name. The passed @a callable accepts a +     * single LLSD value and uses it in any way desired, e.g. extract +     * parameters and call some other function. The optional @a required +     * parameter is used to validate the structure of each incoming event (see       * llsd_matches()).       */      void add(const std::string& name, @@ -69,8 +113,9 @@ public:      /**       * Special case: a subclass of this class can pass an unbound member -     * function pointer without explicitly specifying the -     * <tt>boost::bind()</tt> expression. +     * function pointer (of an LLEventDispatcher subclass) without explicitly +     * specifying the <tt>boost::bind()</tt> expression. The passed @a method +     * accepts a single LLSD value, presumably containing other parameters.       */      template <class CLASS>      void add(const std::string& name, @@ -81,7 +126,8 @@ public:          addMethod<CLASS>(name, desc, method, required);      } -    /// Overload for both const and non-const methods +    /// Overload for both const and non-const methods. The passed @a method +    /// accepts a single LLSD value, presumably containing other parameters.      template <class CLASS>      void add(const std::string& name,               const std::string& desc, @@ -91,8 +137,10 @@ public:          addMethod<CLASS>(name, desc, method, required);      } +/*==========================================================================*|      /// Convenience: for LLEventDispatcher, not every callable needs a -    /// documentation string. +    /// documentation string. The passed @a callable accepts a single LLSD +    /// value, presumably containing other parameters.      template <typename CALLABLE>      void add(const std::string& name,               CALLABLE callable, @@ -100,6 +148,92 @@ public:      {          add(name, "", callable, required);      } +|*==========================================================================*/ + +    //@} + +    /// @name Register functions with arbitrary param lists +    //@{ + +    /** +     * Register a free function with arbitrary parameters. (This also works +     * for static class methods.) +     * +     * When calling this name, pass an LLSD::Array. Each entry in turn will be +     * converted to the corresponding parameter type using LLSDParam. +     */ +    template<typename Function> +    typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> +                               >::type add(const std::string& name, +                                           const std::string& desc, +                                           Function f); + +    /** +     * Register a nonstatic class method with arbitrary parameters. +     * +     * To cover cases such as a method on an LLSingleton we don't yet want to +     * instantiate, instead of directly storing an instance pointer, accept a +     * nullary callable returning a pointer/reference to the desired class +     * instance. If you already have an instance in hand, +     * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) +     * produce suitable callables. +     * +     * When calling this name, pass an LLSD::Array. Each entry in turn will be +     * converted to the corresponding parameter type using LLSDParam. +     */ +    template<typename Method, typename InstanceGetter> +    typename boost::enable_if< boost::function_types::is_member_function_pointer<Method> +                               >::type add(const std::string& name, +                                           const std::string& desc, +                                           Method f, +                                           const InstanceGetter& getter); + +    /** +     * Register a free function with arbitrary parameters. (This also works +     * for static class methods.) +     * +     * Pass an LLSD::Array of parameter names, and optionally another +     * LLSD::Array of default parameter values, a la LLSDArgsMapper. +     * +     * When calling this name, pass an LLSD::Map. We will internally generate +     * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn +     * to the corresponding parameter type using LLSDParam. +     */ +    template<typename Function> +    typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> +                               >::type add(const std::string& name, +                                           const std::string& desc, +                                           Function f, +                                           const LLSD& params, +                                           const LLSD& defaults=LLSD()); + +    /** +     * Register a nonstatic class method with arbitrary parameters. +     * +     * To cover cases such as a method on an LLSingleton we don't yet want to +     * instantiate, instead of directly storing an instance pointer, accept a +     * nullary callable returning a pointer/reference to the desired class +     * instance. If you already have an instance in hand, +     * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) +     * produce suitable callables. +     * +     * Pass an LLSD::Array of parameter names, and optionally another +     * LLSD::Array of default parameter values, a la LLSDArgsMapper. +     * +     * When calling this name, pass an LLSD::Map. We will internally generate +     * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn +     * to the corresponding parameter type using LLSDParam. +     */ +    template<typename Method, typename InstanceGetter> +    typename boost::enable_if< boost::function_types::is_member_function_pointer<Method> +                               >::type add(const std::string& name, +                                           const std::string& desc, +                                           Method f, +                                           const InstanceGetter& getter, +                                           const LLSD& params, +                                           const LLSD& defaults=LLSD()); + +    //@}          /// Unregister a callable      bool remove(const std::string& name); @@ -109,12 +243,25 @@ public:      /// the @a required prototype specified at add() time, die with LL_ERRS.      void operator()(const std::string& name, const LLSD& event) const; +    /// Call a registered callable with an explicitly-specified name and +    /// return <tt>true</tt>. If no such callable exists, return +    /// <tt>false</tt>. If the @a event fails to match the @a required +    /// prototype specified at add() time, die with LL_ERRS. +    bool try_call(const std::string& name, const LLSD& event) const; +      /// Extract the @a key value from the incoming @a event, and call the      /// callable whose name is specified by that map @a key. If no such      /// callable exists, die with LL_ERRS. If the @a event fails to match the      /// @a required prototype specified at add() time, die with LL_ERRS.      void operator()(const LLSD& event) const; +    /// Extract the @a key value from the incoming @a event, call the callable +    /// whose name is specified by that map @a key and return <tt>true</tt>. +    /// If no such callable exists, return <tt>false</tt>. If the @a event +    /// fails to match the @a required prototype specified at add() time, die +    /// with LL_ERRS. +    bool try_call(const LLSD& event) const; +      /// @name Iterate over defined names      //@{      typedef std::pair<std::string, std::string> NameDesc; @@ -122,37 +269,44 @@ public:  private:      struct DispatchEntry      { -        DispatchEntry(const Callable& func, const std::string& desc, const LLSD& required): -            mFunc(func), -            mDesc(desc), -            mRequired(required) -        {} -        Callable mFunc; +        DispatchEntry(const std::string& desc); +          std::string mDesc; -        LLSD mRequired; + +        virtual void call(const std::string& desc, const LLSD& event) const = 0; +        virtual LLSD addMetadata(LLSD) const = 0;      }; -    typedef std::map<std::string, DispatchEntry> DispatchMap; +    typedef boost::ptr_map<std::string, DispatchEntry> DispatchMap;  public:      /// We want the flexibility to redefine what data we store per name,      /// therefore our public interface doesn't expose DispatchMap iterators,      /// or DispatchMap itself, or DispatchEntry. Instead we explicitly      /// transform each DispatchMap item to NameDesc on dereferencing. -    typedef boost::transform_iterator<NameDesc(*)(const DispatchMap::value_type&), DispatchMap::const_iterator> const_iterator; +    typedef boost::transform_iterator<NameDesc(*)(DispatchMap::value_type), DispatchMap::iterator> const_iterator;      const_iterator begin() const      { -        return boost::make_transform_iterator(mDispatch.begin(), makeNameDesc); +        // Originally we used DispatchMap::const_iterator, which Just Worked +        // when DispatchMap was a std::map. Now that it's a boost::ptr_map, +        // using DispatchMap::const_iterator doesn't work so well: it +        // dereferences to a pair<string, const T*>, whereas +        // DispatchMap::value_type is just pair<string, T*>. Trying to pass a +        // dereferenced iterator to the value_type didn't work because the +        // compiler won't let you convert from const T* to plain T*. Changing +        // our const_iterator definition above to be based on non-const +        // DispatchMap::iterator works better, but of course we have to cast +        // away the constness of mDispatch to use non-const iterator. (Sigh.) +        return boost::make_transform_iterator(const_cast<DispatchMap&>(mDispatch).begin(), +                                              makeNameDesc);      }      const_iterator end() const      { -        return boost::make_transform_iterator(mDispatch.end(), makeNameDesc); +        // see begin() comments +        return boost::make_transform_iterator(const_cast<DispatchMap&>(mDispatch).end(), +                                              makeNameDesc);      }      //@} -    /// Fetch the Callable for the specified name. If no such name was -    /// registered, return an empty() Callable. -    Callable get(const std::string& name) const; -      /// Get information about a specific Callable      LLSD getMetadata(const std::string& name) const; @@ -175,18 +329,184 @@ private:          }      }      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;      DispatchMap mDispatch; -    static NameDesc makeNameDesc(const DispatchMap::value_type& item) +    static NameDesc makeNameDesc(DispatchMap::value_type item)      { -        return NameDesc(item.first, item.second.mDesc); +        return NameDesc(item.first, item.second->mDesc);      } + +    struct LLSDDispatchEntry; +    struct ParamsDispatchEntry; +    struct ArrayParamsDispatchEntry; +    struct MapParamsDispatchEntry; + +    // Step 2 of parameter analysis. Instantiating invoker<some_function_type> +    // implicitly sets its From and To parameters to the (compile time) begin +    // and end iterators over that function's parameter types. +    template< typename Function +              , class From = typename boost::mpl::begin< boost::function_types::parameter_types<Function> >::type +              , class To   = typename boost::mpl::end< boost::function_types::parameter_types<Function> >::type +              > +    struct invoker; + +    // deliver LLSD arguments one at a time +    typedef boost::function<LLSD()> args_source; +    // obtain args from an args_source to build param list and call target +    // function +    typedef boost::function<void(const args_source&)> invoker_function; + +    template <typename Function> +    invoker_function make_invoker(Function f); +    template <typename Method, typename InstanceGetter> +    invoker_function make_invoker(Method f, const InstanceGetter& getter); +    void addArrayParamsDispatchEntry(const std::string& name, +                                     const std::string& desc, +                                     const invoker_function& invoker, +                                     LLSD::Integer arity); +    void addMapParamsDispatchEntry(const std::string& name, +                                   const std::string& desc, +                                   const invoker_function& invoker, +                                   const LLSD& params, +                                   const LLSD& defaults);  }; +/***************************************************************************** +*   LLEventDispatcher template implementation details +*****************************************************************************/ +// Step 3 of parameter analysis, the recursive case. +template<typename Function, class From, class To> +struct LLEventDispatcher::invoker +{ +    template<typename T> +    struct remove_cv_ref +        : boost::remove_cv< typename boost::remove_reference<T>::type > +    { }; + +    // apply() accepts an arbitrary boost::fusion sequence as args. It +    // examines the next parameter type in the parameter-types sequence +    // bounded by From and To, obtains the next LLSD object from the passed +    // args_source and constructs an LLSDParam of appropriate type to try +    // to convert the value. It then recurs with the next parameter-types +    // iterator, passing the args sequence thus far. +    template<typename Args> +    static inline +    void apply(Function func, const args_source& argsrc, Args const & args) +    { +        typedef typename boost::mpl::deref<From>::type arg_type; +        typedef typename boost::mpl::next<From>::type next_iter_type; +        typedef typename remove_cv_ref<arg_type>::type plain_arg_type; + +        invoker<Function, next_iter_type, To>::apply +        ( func, argsrc, boost::fusion::push_back(args, LLSDParam<plain_arg_type>(argsrc()))); +    } + +    // Special treatment for instance (first) parameter of a non-static member +    // function. Accept the instance-getter callable, calling that to produce +    // the first args value. Since we know we're at the top of the recursion +    // chain, we need not also require a partial args sequence from our caller. +    template <typename InstanceGetter> +    static inline +    void method_apply(Function func, const args_source& argsrc, const InstanceGetter& getter) +    { +        typedef typename boost::mpl::next<From>::type next_iter_type; + +        // Instead of grabbing the first item from argsrc and making an +        // LLSDParam of it, call getter() and pass that as the instance param. +        invoker<Function, next_iter_type, To>::apply +        ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), boost::ref(getter()))); +    } +}; + +// Step 4 of parameter analysis, the leaf case. When the general +// invoker<Function, From, To> logic has advanced From until it matches To, +// the compiler will pick this template specialization. +template<typename Function, class To> +struct LLEventDispatcher::invoker<Function,To,To> +{ +    // the argument list is complete, now call the function +    template<typename Args> +    static inline +    void apply(Function func, const args_source&, Args const & args) +    { +        boost::fusion::invoke(func, args); +    } +}; + +template<typename Function> +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +{ +    // Construct an invoker_function, a callable accepting const args_source&. +    // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the +    // caller's LLSD::Array. +    addArrayParamsDispatchEntry(name, desc, make_invoker(f), +                                boost::function_types::function_arity<Function>::value); +} + +template<typename Method, typename InstanceGetter> +typename boost::enable_if< boost::function_types::is_member_function_pointer<Method> >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, +                       const InstanceGetter& getter) +{ +    // Subtract 1 from the compile-time arity because the getter takes care of +    // the first parameter. We only need (arity - 1) additional arguments. +    addArrayParamsDispatchEntry(name, desc, make_invoker(f, getter), +                                boost::function_types::function_arity<Method>::value - 1); +} + +template<typename Function> +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin<Function> >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, +                       const LLSD& params, const LLSD& defaults) +{ +    // See comments for previous is_nonmember_callable_builtin add(). +    addMapParamsDispatchEntry(name, desc, make_invoker(f), params, defaults); +} + +template<typename Method, typename InstanceGetter> +typename boost::enable_if< boost::function_types::is_member_function_pointer<Method> >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, +                       const InstanceGetter& getter, +                       const LLSD& params, const LLSD& defaults) +{ +    addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); +} + +template <typename Function> +LLEventDispatcher::invoker_function +LLEventDispatcher::make_invoker(Function f) +{ +    // Step 1 of parameter analysis, the top of the recursion. Passing a +    // suitable f (see add()'s enable_if condition) to this method causes it +    // to infer the function type; specifying that function type to invoker<> +    // causes it to fill in the begin/end MPL iterators over the function's +    // list of parameter types. +    // While normally invoker::apply() could infer its template type from the +    // boost::fusion::nil parameter value, here we must be explicit since +    // we're boost::bind()ing it rather than calling it directly. +    return boost::bind(&invoker<Function>::template apply<boost::fusion::nil>, +                       f, +                       _1, +                       boost::fusion::nil()); +} + +template <typename Method, typename InstanceGetter> +LLEventDispatcher::invoker_function +LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) +{ +    // Use invoker::method_apply() to treat the instance (first) arg specially. +    return boost::bind(&invoker<Method>::template method_apply<InstanceGetter>, +                       f, +                       _1, +                       getter); +} + +/***************************************************************************** +*   LLDispatchListener +*****************************************************************************/  /**   * Bundle an LLEventPump and a listener with an LLEventDispatcher. A class   * that contains (or derives from) LLDispatchListener need only specify the diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp new file mode 100644 index 0000000000..a1d7cf9ead --- /dev/null +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -0,0 +1,436 @@ +/** + * @file   lleventdispatcher_test.cpp + * @author Nat Goodspeed + * @date   2011-01-20 + * @brief  Test for lleventdispatcher. + *  + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Copyright (c) 2011, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventdispatcher.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "llsd.h" +#include "llsdutil.h" +#include "stringize.h" +#include "tests/wrapllerrs.h" + +// http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp +// downloaded 2011-01-20 by NRG and adapted with example usage +// (C) Copyright Tobias Schwinger +// +// Use modification and distribution are subject to the boost Software License, +// Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt). + +//------------------------------------------------------------------------------ +// +// This example implements a simple batch-style dispatcher that is capable of +// calling functions previously registered with it. The parameter types of the +// functions are used to control the parsing of the input. +// +// Implementation description +// ========================== +// +// When a function is registered, an 'invoker' template is instantiated with +// the function's type. The 'invoker' fetches a value from the 'arg_source' +// for each parameter of the function into a tuple and finally invokes the the +// function with these values as arguments. The invoker's entrypoint, which +// is a function of the callable builtin that describes the function to call and +// a reference to the 'arg_source', is partially bound to the registered +// function and put into a map so it can be found by name during parsing. + +#include <map> +#include <string> +#include <stdexcept> + +#include <boost/bind.hpp> +#include <boost/function.hpp> +#include <boost/range.hpp> + +#include <boost/lambda/lambda.hpp> + +#include <iostream> + +using boost::lambda::constant; +using boost::lambda::constant_ref; +using boost::lambda::var; + +/***************************************************************************** +*   Output control +*****************************************************************************/ +#ifdef DEBUG_ON +using std::cout; +#else +static std::ostringstream cout; +#endif + +/***************************************************************************** +*   Example data, functions, classes +*****************************************************************************/ +// sensing globals +static std::string gs; +static float gf; +static int gi; +static LLSD gl; + +void clear() +{ +    gs.clear(); +    gf = 0; +    gi = 0; +    gl = LLSD(); +} + +void abc(const std::string& message) +{ +    cout << "abc('" << message << "')\n"; +    gs = message; +} + +void def(float value, std::string desc) +{ +    cout << "def(" << value << ", '" << desc << "')\n"; +    gf = value; +    gs = desc; +} + +void ghi(const std::string& foo, int bar) +{ +    cout << "ghi('" << foo << "', " << bar << ")\n"; +    gs = foo; +    gi = bar; +} + +void jkl(const char* message) +{ +    cout << "jkl('" << message << "')\n"; +    gs = message; +} + +void somefunc(const LLSD& value) +{ +    cout << "somefunc(" << value << ")\n"; +    gl = value; +} + +class Dummy +{ +public: +    Dummy(): _id("Dummy") {} + +    void mno(const std::string& message) +    { +        cout << _id << "::mno('" << message << "')\n"; +        s = message; +    } + +    void pqr(float value, std::string desc) +    { +        cout << _id << "::pqr(" << value << ", '" << desc << "')\n"; +        f = value; +        s = desc; +    } + +    void stu(const std::string& foo, int bar) +    { +        cout << _id << "::stu('" << foo << "', " << bar << ")\n"; +        s = foo; +        i = bar; +    } + +    void vwx(const char* message) +    { +        cout << _id << "::vwx('" << message << "')\n"; +        s = message; +    } + +    static void yz1(const std::string& message) +    { +        cout << "Dummy::yz1('" << message << "')\n"; +        // can't access sensing members... +        gs = message; +    } + +    // sensing members +    std::string s; +    float f; +    int i; + +private: +    std::string _id; +}; + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct lleventdispatcher_data +    { +        WrapLL_ERRS redirect; +        LLEventDispatcher work; +        Dummy dummy; + +        lleventdispatcher_data(): +            work("test dispatcher", "op") +        { +            // This object is reconstructed for every test<n> method. But +            // clear global variables every time too. +            ::clear(); + +            work.add("abc", "abc", abc, LLSDArray("message")); +            work.add("def", "def", def); +            work.add("ghi", "ghi", ghi); +            work.add("jkl", "jkl", jkl); +            work.add("yz1", "yz1", &Dummy::yz1); +            work.add("mno", "mno", &Dummy::mno, var(dummy), +                     LLSDArray("message"), LLSDArray("default message")); +            work.add("mnoptr", "mno", &Dummy::mno, constant(&dummy)); +            work.add("pqr", "pqr", &Dummy::pqr, var(dummy), +                     LLSDArray("value")("desc")); +            work.add("stu", "stu", &Dummy::stu, var(dummy), +                     LLSDArray("foo")("bar"), LLSDArray(-1)); +            work.add("vwx", "vwx", &Dummy::vwx, var(dummy), +                     LLSDArray("message")); +        } + +        void ensure_has(const std::string& outer, const std::string& inner) +        { +            ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(), +                   outer.find(inner) != std::string::npos); +        } + +        void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) +        { +            std::string threw; +            try +            { +                work(func, args); +            } +            catch (const std::runtime_error& e) +            { +                cout << "*** " << e.what() << '\n'; +                threw = e.what(); +            } +            ensure_has(threw, exc_frag); +        } +    }; +    typedef test_group<lleventdispatcher_data> lleventdispatcher_group; +    typedef lleventdispatcher_group::object object; +    lleventdispatcher_group lleventdispatchergrp("lleventdispatcher"); + +    template<> template<> +    void object::test<1>() +    { +        LLSD hello("Hello test!"); +//        cout << std::string(hello) << "\n"; +        clear(); +        jkl(LLSDParam<const char*>(hello)); +        ensure_equals(gs, hello.asString()); +    } + +    template<> template<> +    void object::test<2>() +    { +        somefunc("abc"); +        ensure_equals(gl.asString(), "abc"); + +        somefunc(17); +        ensure_equals(gl.asInteger(), 17); + +        somefunc(3.14); +        // 7 bits is 128, just enough to express two decimal places. +        ensure_approximately_equals(gl.asReal(), 3.14, 7); + +        somefunc(LLSD().with(0, "abc").with(1, 17).with(2, 3.14)); +        ensure(gl.isArray()); +        ensure_equals(gl.size(), 4); // !!! bug in LLSD::with(Integer, const LLSD&) !!! +        ensure_equals(gl[1].asInteger(), 17); + +        somefunc(LLSD().with("alpha", "abc").with("random", 17).with("pi", 3.14)); +        ensure(gl.isMap()); +        ensure_equals(gl.size(), 3); +        ensure_equals(gl["random"].asInteger(), 17); + +        somefunc(LLSDArray("abc")(17)(3.14)); +        ensure(gl.isArray()); +        ensure_equals(gl.size(), 3); +        ensure_equals(gl[0].asString(), "abc"); + +        somefunc(LLSDMap("alpha", "abc")("random", 17)("pi", 3.14)); +        ensure(gl.isMap()); +        ensure_equals(gl.size(), 3); +        ensure_equals(gl["alpha"].asString(), "abc"); +    } + +    template<> template<> +    void object::test<3>() +    { +        call_exc("gofish", LLSDArray(1), "not found"); +    } + +    template<> template<> +    void object::test<4>() +    { +        call_exc("abc", LLSD(), "missing required"); +    } + +    template<> template<> +    void object::test<5>() +    { +        work("abc", LLSDMap("message", "something")); +        ensure_equals(gs, "something"); +    } + +    template<> template<> +    void object::test<6>() +    { +        work("abc", LLSDMap("message", "something")("plus", "more")); +        ensure_equals(gs, "something"); +    } + +    template<> template<> +    void object::test<7>() +    { +        call_exc("def", LLSDMap("value", 20)("desc", "questions"), "needs an args array"); +    } + +    template<> template<> +    void object::test<8>() +    { +        work("def", LLSDArray(20)("questions")); +        ensure_equals(gf, 20); +        ensure_equals(gs, "questions"); +    } + +    template<> template<> +    void object::test<9>() +    { +        work("def", LLSDArray(3.14)("pies")); +        ensure_approximately_equals(gf, 3.14, 7); +        ensure_equals(gs, "pies"); +    } + +    template<> template<> +    void object::test<10>() +    { +        work("ghi", LLSDArray("answer")(17)); +        ensure_equals(gs, "answer"); +        ensure_equals(gi, 17); +    } + +    template<> template<> +    void object::test<11>() +    { +        work("ghi", LLSDArray("answer")(3.14)); +        ensure_equals(gs, "answer"); +        ensure_equals(gi, 3); +    } + +    template<> template<> +    void object::test<12>() +    { +        work("jkl", LLSDArray("sample message")); +        ensure_equals(gs, "sample message"); +    } + +    template<> template<> +    void object::test<13>() +    { +        work("yz1", LLSDArray("w00t")); +        ensure_equals(gs, "w00t"); +    } + +    template<> template<> +    void object::test<14>() +    { +        std::string msg("nonstatic member function"); +        work("mno", LLSDMap("message", msg)); +        ensure_equals(dummy.s, msg); +    } + +    template<> template<> +    void object::test<15>() +    { +        std::string msg("nonstatic member function reached by ptr"); +        work("mnoptr", LLSDArray(msg)); +        ensure_equals(dummy.s, msg); +    } + +    template<> template<> +    void object::test<16>() +    { +        work("mno", LLSD()); +        ensure_equals(dummy.s, "default message"); +    } + +    template<> template<> +    void object::test<17>() +    { +        work("pqr", LLSDMap("value", 3.14)("desc", "pies")); +        ensure_approximately_equals(dummy.f, 3.14, 7); +        ensure_equals(dummy.s, "pies"); +    } + +    template<> template<> +    void object::test<18>() +    { +        call_exc("pqr", LLSD(), "missing required"); +    } + +    template<> template<> +    void object::test<19>() +    { +        call_exc("pqr", LLSDMap("value", 3.14), "missing required"); +    } + +    template<> template<> +    void object::test<20>() +    { +        call_exc("pqr", LLSDMap("desc", "pies"), "missing required"); +    } + +    template<> template<> +    void object::test<21>() +    { +        work("stu", LLSDMap("bar", 3.14)("foo", "pies")); +        ensure_equals(dummy.s, "pies"); +        ensure_equals(dummy.i, 3); +    } + +    template<> template<> +    void object::test<22>() +    { +        call_exc("stu", LLSD(), "missing required"); +    } + +    template<> template<> +    void object::test<23>() +    { +        call_exc("stu", LLSDMap("bar", 3.14), "missing required"); +    } + +    template<> template<> +    void object::test<24>() +    { +        work("stu", LLSDMap("foo", "pies")); +        ensure_equals(dummy.s, "pies"); +        ensure_equals(dummy.i, -1); +    } + +    template<> template<> +    void object::test<25>() +    { +        std::string msg("nonstatic method(const char*)"); +        work("vwx", LLSDMap("message", msg)); +        ensure_equals(dummy.s, msg); +    } +} // namespace tut | 
