diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 9342a22d46f9d0a3a0b0017544af3aa6a19383bd..dc9f93df3b14f8e89769faaebec5a1349fcce887 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 d6e820d793c38de10bfbf249d0dff316b5af61a4..2ab006a173a8b90878c16d14324f6eedaffd1fc0 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 dfffd59eb6481cc0064494832db7b62d41de6d22..ce0fc7b585792ecee15dce7e2412d58299cbe897 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 @@ class LL_COMMON_API LLEventDispatcher
     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 @@ class LL_COMMON_API LLEventDispatcher
 
     /**
      * 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 @@ class LL_COMMON_API LLEventDispatcher
         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 @@ class LL_COMMON_API LLEventDispatcher
         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 @@ class LL_COMMON_API LLEventDispatcher
     {
         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 @@ class LL_COMMON_API LLEventDispatcher
     /// 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 @@ class LL_COMMON_API LLEventDispatcher
 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 @@ class LL_COMMON_API LLEventDispatcher
         }
     }
     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 0000000000000000000000000000000000000000..a1d7cf9eadfcbe7b59db6d17e531d2e5e2a2c35a
--- /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
diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp
index 33e051bfab8e6286f73c64862a8fe6ee05019c54..abcd8588dc517a202dfba9d6d545b97297f7d5ff 100644
--- a/indra/newview/lllogininstance.cpp
+++ b/indra/newview/lllogininstance.cpp
@@ -480,10 +480,12 @@ LLLoginInstance::LLLoginInstance() :
 {
 	mLoginModule->getEventPump().listen("lllogininstance", 
 		boost::bind(&LLLoginInstance::handleLoginEvent, this, _1));
-	mDispatcher.add("fail.login", boost::bind(&LLLoginInstance::handleLoginFailure, this, _1));
-	mDispatcher.add("connect",    boost::bind(&LLLoginInstance::handleLoginSuccess, this, _1));
-	mDispatcher.add("disconnect", boost::bind(&LLLoginInstance::handleDisconnect, this, _1));
-	mDispatcher.add("indeterminate", boost::bind(&LLLoginInstance::handleIndeterminate, this, _1));
+	// This internal use of LLEventDispatcher doesn't really need
+	// per-function descriptions.
+	mDispatcher.add("fail.login", "", boost::bind(&LLLoginInstance::handleLoginFailure, this, _1));
+	mDispatcher.add("connect",    "", boost::bind(&LLLoginInstance::handleLoginSuccess, this, _1));
+	mDispatcher.add("disconnect", "", boost::bind(&LLLoginInstance::handleDisconnect, this, _1));
+	mDispatcher.add("indeterminate", "", boost::bind(&LLLoginInstance::handleIndeterminate, this, _1));
 }
 
 LLLoginInstance::~LLLoginInstance()
@@ -625,11 +627,7 @@ bool LLLoginInstance::handleLoginEvent(const LLSD& event)
 
 	// Call the method registered in constructor, if any, for more specific
 	// handling
-	LLEventDispatcher::Callable method(mDispatcher.get(event["change"]));
-	if (! method.empty())
-	{
-		method(event);
-	}
+	mDispatcher.try_call(event);
 	return false;
 }