summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llcommon/lleventdispatcher.cpp428
-rw-r--r--indra/llcommon/lleventdispatcher.h166
-rw-r--r--indra/llcommon/tests/lleventdispatcher_test.cpp126
3 files changed, 463 insertions, 257 deletions
diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp
index e7e73125a7..7e5723c503 100644
--- a/indra/llcommon/lleventdispatcher.cpp
+++ b/indra/llcommon/lleventdispatcher.cpp
@@ -43,21 +43,10 @@
#include "llexception.h"
#include "llsdutil.h"
#include "stringize.h"
+#include <iomanip> // std::quoted()
#include <memory> // std::auto_ptr
/*****************************************************************************
-* DispatchError
-*****************************************************************************/
-struct DispatchError: public LLException
-{
- // template constructor involving strings passes all arguments to
- // stringize() to construct LLException's what() string
- template <typename... ARGS>
- DispatchError(const std::string& arg0, ARGS&&... args):
- LLException(stringize(arg0, std::forward<ARGS>(args)...)) {}
-};
-
-/*****************************************************************************
* LLSDArgsMapper
*****************************************************************************/
/**
@@ -109,7 +98,7 @@ struct DispatchError: public LLException
* - Holes are filled with the default values.
* - Any remaining holes constitute an error.
*/
-class LL_COMMON_API LLSDArgsMapper
+class LL_COMMON_API LLEventDispatcher::LLSDArgsMapper
{
public:
/// Accept description of function: function name, param names, param
@@ -122,6 +111,8 @@ public:
private:
static std::string formatlist(const LLSD&);
+ template <typename... ARGS>
+ void callFail(ARGS&&... args) const;
// The function-name string is purely descriptive. We want error messages
// to be able to indicate which function's LLSDArgsMapper has the problem.
@@ -141,15 +132,16 @@ private:
FilledVector _has_dft;
};
-LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
- const LLSD& names, const LLSD& defaults):
+LLEventDispatcher::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()))
{
- LLTHROW(DispatchError(function, " names must be an array, not ", names));
+ callFail(" names must be an array, not ", names);
}
auto nparams(_names.size());
// From _names generate _indexes.
@@ -172,8 +164,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
// defaults is a (possibly empty) array. Right-align it with names.
if (ndefaults > nparams)
{
- LLTHROW(DispatchError(function, " names array ", names,
- " shorter than defaults array ", defaults));
+ callFail(" names array ", names, " shorter than defaults array ", defaults);
}
// Offset by which we slide defaults array right to right-align with
@@ -210,23 +201,20 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
}
if (bogus.size())
{
- LLTHROW(DispatchError(function, " defaults specified for nonexistent params ",
- formatlist(bogus)));
+ callFail(" defaults specified for nonexistent params ", formatlist(bogus));
}
}
else
{
- LLTHROW(DispatchError(function, " defaults must be a map or an array, not ",
- defaults));
+ callFail(" defaults must be a map or an array, not ", defaults);
}
}
-LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
+LLSD LLEventDispatcher::LLSDArgsMapper::map(const LLSD& argsmap) const
{
if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray()))
{
- LLTHROW(DispatchError(_function, " map() needs a map or array, not ",
- argsmap));
+ callFail(" map() needs a map or array, not ", argsmap);
}
// 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
@@ -323,15 +311,14 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
// by argsmap, that's a problem.
if (unfilled.size())
{
- LLTHROW(DispatchError(_function, " missing required arguments ",
- formatlist(unfilled), " from ", argsmap));
+ callFail(" missing required arguments ", formatlist(unfilled), " from ", argsmap);
}
// done
return args;
}
-std::string LLSDArgsMapper::formatlist(const LLSD& list)
+std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list)
{
std::ostringstream out;
const char* delim = "";
@@ -344,14 +331,26 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list)
return out.str();
}
+template <typename... ARGS>
+void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const
+{
+ LLEventDispatcher::sCallFail<LLEventDispatcher::DispatchError>
+ (_function, std::forward<ARGS>(args)...);
+}
+
/*****************************************************************************
* LLEventDispatcher
*****************************************************************************/
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
+ LLEventDispatcher(desc, key, "args")
+{}
+
+LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key,
+ const std::string& argskey):
mDesc(desc),
- mKey(key)
-{
-}
+ mKey(key),
+ mArgskey(argskey)
+{}
LLEventDispatcher::~LLEventDispatcher()
{
@@ -375,20 +374,21 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
Callable mFunc;
LLSD mRequired;
- virtual LLSD call(const std::string& desc, const LLSD& event) const
+ LLSD call(const std::string& desc, const LLSD& event, bool, const std::string&) const override
{
// Validate the syntax of the event itself.
std::string mismatch(llsd_matches(mRequired, event));
if (! mismatch.empty())
{
- LLTHROW(DispatchError(desc, ": bad request: ", mismatch));
+ LLEventDispatcher::sCallFail<LLEventDispatcher::DispatchError>
+ (desc, ": bad request: ", mismatch);
}
// Event syntax looks good, go for it!
mFunc(event);
return {};
}
- virtual LLSD addMetadata(LLSD meta) const
+ LLSD addMetadata(LLSD meta) const override
{
meta["required"] = mRequired;
return meta;
@@ -401,16 +401,35 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
*/
struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry
{
- ParamsDispatchEntry(const std::string& desc, const invoker_function& func):
+ ParamsDispatchEntry(const std::string& name, const std::string& desc,
+ const invoker_function& func):
DispatchEntry(desc),
+ mName(name),
mInvoker(func)
{}
+ std::string mName;
invoker_function mInvoker;
- virtual LLSD call(const std::string&, const LLSD& event) const
+ LLSD call(const std::string&, const LLSD& event, bool, const std::string&) const override
+ {
+ try
+ {
+ return mInvoker(event);
+ }
+ catch (const LL::apply_error& err)
+ {
+ // could hit runtime errors with LL::apply()
+ return callFail(err.what());
+ }
+ }
+
+ template <typename... ARGS>
+ LLSD callFail(ARGS&&... args) const
{
- return mInvoker(event);
+ LLEventDispatcher::sCallFail<LLEventDispatcher::DispatchError>(mName, ": ", std::forward<ARGS>(args)...);
+ // pacify the compiler
+ return {};
}
};
@@ -420,15 +439,48 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc
*/
struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
{
- ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func,
- LLSD::Integer arity):
- ParamsDispatchEntry(desc, func),
+ ArrayParamsDispatchEntry(const std::string& name, const std::string& desc,
+ const invoker_function& func, LLSD::Integer arity):
+ ParamsDispatchEntry(name, desc, func),
mArity(arity)
{}
LLSD::Integer mArity;
- virtual LLSD addMetadata(LLSD meta) const
+ LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override
+ {
+// std::string context { stringize(desc, "(", event, ") with argskey ", std::quoted(argskey), ": ") };
+ // Whether we try to extract arguments from 'event' depends on whether
+ // the LLEventDispatcher consumer called one of the (name, event)
+ // methods (! fromMap) or one of the (event) methods (fromMap). If we
+ // were called with (name, event), the passed event must itself be
+ // suitable to pass to the registered callable, no args extraction
+ // required or even attempted. Only if called with plain (event) do we
+ // consider extracting args from that event. Initially assume 'event'
+ // itself contains the arguments.
+ LLSD args{ event };
+ if (fromMap)
+ {
+ if (mArity)
+ {
+ // We only require/retrieve argskey if the target function
+ // isn't nullary. For all others, since we require an LLSD
+ // array, we must have an argskey.
+ if (argskey.empty())
+ {
+ return callFail("LLEventDispatcher has no args key");
+ }
+ if ((! event.has(argskey)))
+ {
+ return callFail("missing required key ", std::quoted(argskey));
+ }
+ args = event[argskey];
+ }
+ }
+ return ParamsDispatchEntry::call(desc, args, fromMap, argskey);
+ }
+
+ LLSD addMetadata(LLSD meta) const override
{
LLSD array(LLSD::emptyArray());
// Resize to number of arguments required
@@ -449,7 +501,7 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para
MapParamsDispatchEntry(const std::string& name, const std::string& desc,
const invoker_function& func,
const LLSD& params, const LLSD& defaults):
- ParamsDispatchEntry(desc, func),
+ ParamsDispatchEntry(name, desc, func),
mMapper(name, params, defaults),
mRequired(LLSD::emptyMap())
{
@@ -493,14 +545,25 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para
LLSD mRequired;
LLSD mOptional;
- virtual LLSD call(const std::string& desc, const LLSD& event) const
+ LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override
{
- // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass
- // to base-class call() method.
- return ParamsDispatchEntry::call(desc, mMapper.map(event));
+ // by default, pass the whole event as the arguments map
+ LLSD args{ event };
+ // Were we called by one of the (event) methods (instead of the (name,
+ // event) methods), do we have an argskey, and does the incoming event
+ // have that key?
+ if (fromMap && (! argskey.empty()) && event.has(argskey))
+ {
+ // if so, extract the value of argskey from the incoming event,
+ // and use that as the arguments map
+ args = event[argskey];
+ }
+ // Now convert args from LLSD map to LLSD array using mMapper, then
+ // pass to base-class call() method.
+ return ParamsDispatchEntry::call(desc, mMapper.map(args), fromMap, argskey);
}
- virtual LLSD addMetadata(LLSD meta) const
+ LLSD addMetadata(LLSD meta) const override
{
meta["required"] = mRequired;
meta["optional"] = mOptional;
@@ -513,7 +576,11 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name,
const invoker_function& invoker,
LLSD::Integer arity)
{
- mDispatch.emplace(name, new ArrayParamsDispatchEntry(desc, invoker, arity));
+ // The first parameter to ArrayParamsDispatchEntry is solely for error
+ // messages. Identify our instance and this entry.
+ mDispatch.emplace(
+ name,
+ new ArrayParamsDispatchEntry(stringize(*this, '[', name, ']'), desc, invoker, arity));
}
void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
@@ -522,7 +589,11 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
const LLSD& params,
const LLSD& defaults)
{
- mDispatch.emplace(name, new MapParamsDispatchEntry(name, desc, invoker, params, defaults));
+ // Pass instance info as well as this entry name for error messages.
+ mDispatch.emplace(
+ name,
+ new MapParamsDispatchEntry(stringize(*this, '[', name, ']'),
+ desc, invoker, params, defaults));
}
/// Register a callable by name
@@ -546,174 +617,101 @@ bool LLEventDispatcher::remove(const std::string& name)
/// Call a registered callable with an explicitly-specified name. It is an
/// error if no such callable exists.
-void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
+LLSD LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
{
- std::string error{ try_call_log(std::string(), name, event) };
- if (! error.empty())
- {
- callFail(event, error);
- }
+ return try_call(std::string(), name, event);
}
-/// Extract the @a key value from the incoming @a event, and call the callable
-/// whose name is specified by that map @a key. It is an error if no such
-/// callable exists.
-void LLEventDispatcher::operator()(const LLSD& event) const
+bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
{
- std::string error{ try_call_log(mKey, event[mKey], event) };
- if (! error.empty())
+ try
{
- callFail(event, error);
+ try_call(std::string(), name, event);
+ return true;
}
-}
-
-void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const
-{
- // pass back a response that includes an "error" key with the message.
- reply(llsd::map("error", msg), event);
-}
-
-void LLEventDispatcher::reply(const LLSD& response, const LLSD& event) const
-{
- static LLSD::String key{ "reply" };
- if (event.has(key))
+ // Note that we don't catch the generic DispatchError, only the specific
+ // DispatchMissing. try_call() only promises to return false if the
+ // specified callable name isn't found -- not for general errors.
+ catch (const DispatchMissing&)
{
- // Oh good, the incoming event specifies a reply pump -- pass back
- // our response.
- sendReply(response, event, key);
+ return false;
}
}
-bool LLEventDispatcher::try_call(const LLSD& event) const
-{
- return try_call_log(mKey, event[mKey], event).empty();
-}
-
-/*==========================================================================*|
- TODO:
-
-* When mInvoker returns result.isDefined(), sendReply(llsd::map("data", result))
-* When try_call finds name.isArray(), construct response array from
- dispatching each call, sendReply() as above
-* When try_call finds name.isMap(), construct response map from dispatching
- each call, sendReply() as above -- note, caller can't care about order
-* Possible future transactional behavior: look up all names before calling any
-
-|*==========================================================================*/
-bool LLEventDispatcher::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. It is an error if no such
+/// callable exists.
+LLSD LLEventDispatcher::operator()(const LLSD& event) const
{
- return try_call_log(std::string(), name, event).empty();
+ return try_call(mKey, event[mKey], event);
}
-std::string LLEventDispatcher::try_call_log(const std::string& key, const LLSD& name,
- const LLSD& event) const
+bool LLEventDispatcher::try_call(const LLSD& event) const
{
- std::string error{ try_call(key, name, event) };
- if (! error.empty())
+ try
{
- // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that.
- error = stringize(LLError::Log::classname(this), "(", mDesc, "): ", error);
- LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
+ try_call(mKey, event[mKey], event);
+ return true;
+ }
+ catch (const DispatchMissing&)
+ {
+ return false;
}
- return error;
}
-// This internal method returns empty string if the call succeeded, else
-// non-empty error message.
-std::string LLEventDispatcher::try_call(const std::string& key, const LLSD& name,
- const LLSD& event) const
+LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name,
+ const LLSD& event) const
{
- if (name.isUndefined())
+ if (name.empty())
{
if (key.empty())
{
- return "attempting to call with no name";
+ callFail<DispatchError>("attempting to call with no name");
}
else
{
- return stringize("no ", key);
- }
- }
- else if (name.isArray())
- {
- return stringize(key, " array dispatch ", name, " not yet implemented");
- }
- else if (name.isMap())
- {
- return stringize(key, " map dispatch ", name, " not yet implemented");
- }
- else if (! name.isString())
- {
- return stringize(key, " bad type ", LLSD::typeString(name.type()), ' ', name,
- " -- function names are String");
- }
- else // name is an LLSD::String
- {
- auto success{ try_call_one(key, name, event) };
- // pretend to unpack
- std::string& error{ success.first };
- LLSD& result{ success.second };
- // did try_call_one() report an error?
- if (! error.empty())
- {
- // if we have a reply key, respond to invoker
- reply(llsd::map("error", error), event);
- // now tell caller
- return error;
- }
- // try_call_one() succeeded in calling the target function --
- // should we reply to invoker?
- if (result.isUndefined())
- {
- // We would get result.isUndefined() if the target function has
- // void return. In any case, even if the target function returns
- // LLSD, isUndefined() means "don't bother sending response."
- return {};
- }
- // result.isDefined(): the target function returned something.
- // Respond to invoker if we have a "reply" key.
- if (! result.isMap())
- {
- // wrap result in a map to play well with sendReply()
- result = llsd::map("data", result);
+ callFail<DispatchError>("no ", key);
}
- reply(result, event);
- return {};
}
-}
-std::pair<std::string, LLSD>
-LLEventDispatcher::try_call_one(const std::string& key, const std::string& name,
- const LLSD& event) const
-{
DispatchMap::const_iterator found = mDispatch.find(name);
if (found == mDispatch.end())
{
+ // Here we were passed a valid name, but there's no registered
+ // callable with that name. This is the one case in which we throw
+ // DispatchMissing instead of the generic DispatchError.
+ // Distinguish the public method by which our caller reached here:
+ // key.empty() means the name was passed explicitly, non-empty means
+ // we extracted the name from the incoming event using that key.
if (key.empty())
{
- return { stringize("'", name, "' not found"), {} };
+ callFail<DispatchMissing>(std::quoted(name), " not found");
}
else
{
- return { stringize("bad ", key, " value '", name, "'"), {} };
+ callFail<DispatchMissing>("bad ", key, " value ", std::quoted(name));
}
}
- try
- {
- // Found the name, so it's plausible to even attempt the call.
- return { {}, found->second->call(stringize("calling '", name, "'"), event) };
- }
- catch (const DispatchError& err)
- {
- // trouble preparing arguments
- return { err.what(), {} };
- }
- catch (const LL::apply_error& err)
- {
- // could also hit runtime errors with LL::apply()
- return { err.what(), {} };
- }
+ // Found the name, so it's plausible to even attempt the call.
+ return found->second->call(stringize(*this, " calling ", std::quoted(name)),
+ event, (! key.empty()), mArgskey);
+}
+
+template <typename EXCEPTION, typename... ARGS>
+//static
+void LLEventDispatcher::sCallFail(ARGS&&... args)
+{
+ auto error = stringize(std::forward<ARGS>(args)...);
+ LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
+ LLTHROW(EXCEPTION(error));
+}
+
+template <typename EXCEPTION, typename... ARGS>
+void LLEventDispatcher::callFail(ARGS&&... args) const
+{
+ // Describe this instance in addition to the error itself.
+ sCallFail<EXCEPTION>(*this, ": ", std::forward<ARGS>(args)...);
}
LLSD LLEventDispatcher::getMetadata(const std::string& name) const
@@ -729,27 +727,69 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const
return found->second->addMetadata(meta);
}
+std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self)
+{
+ // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that.
+ return out << LLError::Log::classname(self) << '(' << self.mDesc << ')';
+}
+
/*****************************************************************************
* LLDispatchListener
*****************************************************************************/
-LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
- LLEventDispatcher(pumpname, key),
- // Do NOT tweak the passed pumpname. In practice, when someone
- // instantiates a subclass of our LLEventAPI subclass, they intend to
- // claim that LLEventPump name in the global LLEventPumps namespace. It
- // would be mysterious and distressing if we allowed name tweaking, and
- // someone else claimed pumpname first for a completely unrelated
- // LLEventPump. Posted events would never reach our subclass listener
- // because we would have silently changed its name; meanwhile listeners
- // (if any) on that other LLEventPump would be confused by the events
- // intended for our subclass.
- LLEventStream(pumpname, false),
- mBoundListener(listen("self", [this](const LLSD& event){ return process(event); }))
+/*==========================================================================*|
+ TODO:
+* When process() finds name.isArray(), construct response array from
+ dispatching each call -- args must also be (array of args structures)
+ (could also construct response map, IF array contains unique names)
+* When process() finds name.isMap(), construct response map from dispatching
+ each call -- value of each key is its args struct -- argskey ignored --
+ note, caller can't care about order
+* Possible future transactional behavior: look up all names before calling any
+|*==========================================================================*/
+std::string LLDispatchListener::mReplyKey{ "reply" };
+
+bool LLDispatchListener::process(const LLSD& event) const
{
+ LLSD result;
+ try
+ {
+ result = (*this)(event);
+ }
+ catch (const DispatchError& err)
+ {
+ // If the incoming event contains a "reply" key, we'll respond to the
+ // invoker with an error message (below). But if not -- silently
+ // ignoring an invocation request would be confusing at best. Escalate.
+ if (! event.has(mReplyKey))
+ {
+ throw;
+ }
+
+ // reply with a map containing an "error" key explaining the problem
+ reply(llsd::map("error", err.what()), event);
+ return false;
+ }
+
+ // We seem to have gotten a valid result. But we don't know whether the
+ // registered callable is void or non-void. If it's void,
+ // LLEventDispatcher will return isUndefined(). Otherwise, try to send it
+ // back to our invoker.
+ if (result.isDefined())
+ {
+ if (! result.isMap())
+ {
+ // wrap the result in a map as the "data" key
+ result = llsd::map("data", result);
+ }
+ reply(result, event);
+ }
+ return false;
}
-bool LLDispatchListener::process(const LLSD& event)
+void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const
{
- (*this)(event);
- return false;
+ // Call sendReply() unconditionally: sendReply() itself tests whether the
+ // specified reply key is present in the incoming request, and does
+ // nothing if there's no such key.
+ sendReply(reply, request, mReplyKey);
}
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h
index cebce618df..494fc6a366 100644
--- a/indra/llcommon/lleventdispatcher.h
+++ b/indra/llcommon/lleventdispatcher.h
@@ -57,7 +57,20 @@ class LLSD;
class LL_COMMON_API LLEventDispatcher
{
public:
+ /**
+ * Pass description and the LLSD key used by try_call(const LLSD&) and
+ * operator()(const LLSD&) to extract the name of the registered callable
+ * to invoke.
+ */
LLEventDispatcher(const std::string& desc, const std::string& key);
+ /**
+ * Pass description, the LLSD key used by try_call(const LLSD&) and
+ * operator()(const LLSD&) to extract the name of the registered callable
+ * to invoke, and the LLSD key used by try_call(const LLSD&) and
+ * operator()(const LLSD&) to extract arguments LLSD.
+ */
+ LLEventDispatcher(const std::string& desc, const std::string& key,
+ const std::string& argskey);
virtual ~LLEventDispatcher();
/// @name Register functions accepting(const LLSD&)
@@ -146,6 +159,8 @@ public:
* boost::lambda::var(instance) or boost::lambda::constant(instance_ptr)
* produce suitable callables.
*
+ * TODO: variant accepting a method of the containing class, no getter.
+ *
* When calling this name, pass an LLSD::Array. Each entry in turn will be
* converted to the corresponding parameter type using LLSDParam.
*/
@@ -185,6 +200,8 @@ public:
* boost::lambda::var(instance) or boost::lambda::constant(instance_ptr)
* produce suitable callables.
*
+ * TODO: variant accepting a method of the containing class, no getter.
+ *
* Pass an LLSD::Array of parameter names, and optionally another
* LLSD::Array of default parameter values, a la LLSDArgsMapper.
*
@@ -206,28 +223,82 @@ public:
/// Unregister a callable
bool remove(const std::string& name);
- /// Call a registered callable with an explicitly-specified name. It is an
- /// error if no such callable exists. It is an error if the @a event fails
- /// to match the @a required prototype specified at add() time.
- void operator()(const std::string& name, const LLSD& event) const;
+ /// Exception if an attempted call fails for any reason
+ struct DispatchError: public LLException
+ {
+ DispatchError(const std::string& what): LLException(what) {}
+ };
+
+ /// Specific exception for an attempt to call a nonexistent name
+ struct DispatchMissing: public DispatchError
+ {
+ DispatchMissing(const std::string& what): DispatchError(what) {}
+ };
+
+ /**
+ * Call a registered callable with an explicitly-specified name,
+ * converting its return value to LLSD (undefined for a void callable).
+ * It is an error if no such callable exists. It is an error if the @a
+ * event fails to match the @a required prototype specified at add()
+ * time.
+ *
+ * @a event must be an LLSD array for a callable registered to accept its
+ * arguments from such an array. It must be an LLSD map for a callable
+ * registered to accept its arguments from such a map.
+ */
+ LLSD 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>. It is an error if the @a event fails to match the @a
- /// required prototype specified at add() time.
+ /**
+ * Call a registered callable with an explicitly-specified name and
+ * return <tt>true</tt>. If no such callable exists, return
+ * <tt>false</tt>. It is an error if the @a event fails to match the @a
+ * required prototype specified at add() time.
+ *
+ * @a event must be an LLSD array for a callable registered to accept its
+ * arguments from such an array. It must be an LLSD map for a callable
+ * registered to accept its arguments from such a map.
+ */
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. It is an error if
- /// no such callable exists. It is an error if the @a event fails to match
- /// the @a required prototype specified at add() time.
- 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>. It is an error if
- /// the @a event fails to match the @a required prototype specified at
- /// add() time.
+ /**
+ * Extract the @a key specified to our constructor from the incoming LLSD
+ * map @a event, and call the callable whose name is specified by that @a
+ * key's value, converting its return value to LLSD (undefined for a void
+ * callable). It is an error if no such callable exists. It is an error if
+ * the @a event fails to match the @a required prototype specified at
+ * add() time.
+ *
+ * For a (non-nullary) callable registered to accept its arguments from an
+ * LLSD array, the @a event map must contain the key @a argskey specified to
+ * our constructor. The value of the @a argskey key must be an LLSD array
+ * containing the arguments to pass to the callable named by @a key.
+ *
+ * For a callable registered to accept its arguments from an LLSD map, if
+ * the @a event map contains the key @a argskey specified our constructor,
+ * extract the value of the @a argskey key and use it as the arguments map.
+ * If @a event contains no @a argskey key, use the whole @a event as the
+ * arguments map.
+ */
+ LLSD operator()(const LLSD& event) const;
+
+ /**
+ * Extract the @a key specified to our constructor from the incoming LLSD
+ * map @a event, call the callable whose name is specified by that @a
+ * key's value and return <tt>true</tt>. If no such callable exists,
+ * return <tt>false</tt>. It is an error if the @a event fails to match
+ * the @a required prototype specified at add() time.
+ *
+ * For a (non-nullary) callable registered to accept its arguments from an
+ * LLSD array, the @a event map must contain the key @a argskey specified to
+ * our constructor. The value of the @a argskey key must be an LLSD array
+ * containing the arguments to pass to the callable named by @a key.
+ *
+ * For a callable registered to accept its arguments from an LLSD map, if
+ * the @a event map contains the key @a argskey specified our constructor,
+ * extract the value of the @a argskey key and use it as the arguments map.
+ * If @a event contains no @a argskey key, use the whole @a event as the
+ * arguments map.
+ */
bool try_call(const LLSD& event) const;
/// @name Iterate over defined names
@@ -242,7 +313,8 @@ private:
std::string mDesc;
- virtual LLSD call(const std::string& desc, const LLSD& event) const = 0;
+ virtual LLSD call(const std::string& desc, const LLSD& event,
+ bool fromMap, const std::string& argskey) const = 0;
virtual LLSD addMetadata(LLSD) const = 0;
};
typedef std::map<std::string, std::unique_ptr<DispatchEntry> > DispatchMap;
@@ -269,6 +341,9 @@ public:
/// Retrieve the LLSD key we use for one-arg <tt>operator()</tt> method
std::string getDispatchKey() const { return mKey; }
+ /// description of this instance's leaf class and description
+ friend std::ostream& operator<<(std::ostream&, const LLEventDispatcher&);
+
private:
template <class CLASS, typename METHOD>
void addMethod(const std::string& name, const std::string& desc,
@@ -285,19 +360,16 @@ private:
}
}
void addFail(const std::string& name, const std::string& classname) const;
- std::string try_call_log(const std::string& key, const LLSD& name,
- const LLSD& event) const;
- std::string try_call(const std::string& key, const LLSD& name,
- const LLSD& event) const;
- // returns either (empty string, LLSD) or (error message, isUndefined)
- std::pair<std::string, LLSD>
- try_call_one(const std::string& key, const std::string& name, const LLSD& event) const;
- // Implement "it is an error" semantics for attempted call operations: if
- // the incoming event includes a "reply" key, log and send an error reply.
- void callFail(const LLSD& event, const std::string& msg) const;
- void reply(const LLSD& response, const LLSD& event) const;
-
- std::string mDesc, mKey;
+ LLSD try_call(const std::string& key, const std::string& name,
+ const LLSD& event) const;
+ // raise specified EXCEPTION with specified stringize(ARGS)
+ template <typename EXCEPTION, typename... ARGS>
+ void callFail(ARGS&&... args) const;
+ template <typename EXCEPTION, typename... ARGS>
+ static
+ void sCallFail(ARGS&&... args);
+
+ std::string mDesc, mKey, mArgskey;
DispatchMap mDispatch;
static NameDesc makeNameDesc(const DispatchMap::value_type& item)
@@ -305,6 +377,7 @@ private:
return NameDesc(item.first, item.second->mDesc);
}
+ class LLSDArgsMapper;
struct LLSDDispatchEntry;
struct ParamsDispatchEntry;
struct ArrayParamsDispatchEntry;
@@ -459,13 +532,36 @@ class LL_COMMON_API LLDispatchListener:
public LLEventStream
{
public:
- LLDispatchListener(const std::string& pumpname, const std::string& key);
+ template <typename... ARGS>
+ LLDispatchListener(const std::string& pumpname, const std::string& key,
+ ARGS&&... args);
virtual ~LLDispatchListener() {}
private:
- bool process(const LLSD& event);
+ bool process(const LLSD& event) const;
+ void reply(const LLSD& reply, const LLSD& request) const;
LLTempBoundListener mBoundListener;
+ static std::string mReplyKey;
};
+template <typename... ARGS>
+LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key,
+ ARGS&&... args):
+ // pass through any additional arguments to LLEventDispatcher ctor
+ LLEventDispatcher(pumpname, key, std::forward<ARGS>(args)...),
+ // Do NOT tweak the passed pumpname. In practice, when someone
+ // instantiates a subclass of our LLEventAPI subclass, they intend to
+ // claim that LLEventPump name in the global LLEventPumps namespace. It
+ // would be mysterious and distressing if we allowed name tweaking, and
+ // someone else claimed pumpname first for a completely unrelated
+ // LLEventPump. Posted events would never reach our subclass listener
+ // because we would have silently changed its name; meanwhile listeners
+ // (if any) on that other LLEventPump would be confused by the events
+ // intended for our subclass.
+ LLEventStream(pumpname, false),
+ mBoundListener(listen("self", [this](const LLSD& event){ return process(event); }))
+{
+}
+
#endif /* ! defined(LL_LLEVENTDISPATCHER_H) */
diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp
index f09dd63316..00bdff89e5 100644
--- a/indra/llcommon/tests/lleventdispatcher_test.cpp
+++ b/indra/llcommon/tests/lleventdispatcher_test.cpp
@@ -18,6 +18,7 @@
// external library headers
// other Linden headers
#include "../test/lltut.h"
+#include "lleventfilter.h"
#include "llsd.h"
#include "llsdutil.h"
#include "llevents.h"
@@ -640,42 +641,38 @@ namespace tut
std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
{
- // This method was written when LLEventDispatcher responded to
- // name or argument errors with LL_ERRS, hence the name: we used
- // to have to intercept LL_ERRS by making it throw. Now we set up
- // to catch an error response instead. But -- for that we need to
- // be able to sneak a "reply" key into args, which must be a Map.
- if (! (args.isUndefined() or args.isMap()))
- fail(stringize("can't test call_exc() with ", args));
- LLEventStream replypump("reply");
- LLSD reply;
- LLTempBoundListener bound{
- replypump.listen(
- "listener",
- [&reply](const LLSD& event)
- {
- reply = event;
- return false;
- }) };
- LLSD modargs{ args };
- modargs["reply"] = replypump.getName();
- if (func.empty())
+ std::string what;
+ try
{
- work(modargs);
+ if (func.empty())
+ {
+ work(args);
+ }
+ else
+ {
+ work(func, args);
+ }
}
- else
+ catch (const LLEventDispatcher::DispatchError& err)
{
- work(func, modargs);
+ what = err.what();
}
- ensure("no error response", reply.has("error"));
- ensure_has(reply["error"], exc_frag);
- return reply["error"];
+ ensure_has(what, exc_frag);
+ return what;
}
void call_logerr(const std::string& func, const LLSD& args, const std::string& frag)
{
CaptureLog capture;
- work(func, args);
+ try
+ {
+ work(func, args);
+ }
+ catch (const LLEventDispatcher::DispatchError& err)
+ {
+ // the error should also have been logged; we just need to
+ // stop the exception propagating
+ }
capture.messageWith(frag);
}
@@ -1017,6 +1014,10 @@ namespace tut
(llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"),
llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional
+ llsd::array // group
+ (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"),
+ llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional
+
for (LLSD grp: inArray(groups))
{
// Internal structure of each group in 'groups':
@@ -1196,7 +1197,7 @@ namespace tut
template<> template<>
void object::test<20>()
{
- set_test_name("call array-style functions with (just right | too long) arrays");
+ set_test_name("call array-style functions with right-size arrays");
std::vector<U8> binary;
for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i)
{
@@ -1326,4 +1327,73 @@ namespace tut
}
}
}
+
+ struct DispatchResult: public LLEventDispatcher
+ {
+ using DR = DispatchResult;
+
+ DispatchResult(): LLEventDispatcher("expect result", "op")
+ {
+ // As of 2022-12-22, LLEventDispatcher's shorthand add() methods
+ // for pointer-to-method of same instance only support methods
+ // with signature void(const LLSD&). The generic add(pointer-to-
+ // method) requires an instance getter.
+ add("strfunc", "return string", &DR::strfunc, [this](){ return this; });
+ add("voidfunc", "void function", &DR::voidfunc, [this](){ return this; });
+ add("intfunc", "return Integer LLSD", &DR::intfunc, [this](){ return this; });
+ add("mapfunc", "return map LLSD", &DR::mapfunc, [this](){ return this; });
+ add("arrayfunc", "return array LLSD", &DR::arrayfunc, [this](){ return this; });
+ }
+
+ std::string strfunc(const LLSD&) const { return "a string"; }
+ void voidfunc() const {}
+ int intfunc(const LLSD&) const { return 17; }
+ LLSD mapfunc(const LLSD&) const { return llsd::map("key", "value"); }
+ LLSD arrayfunc(const LLSD&) const { return llsd::array("a", "b", "c"); }
+ };
+
+ template<> template<>
+ void object::test<23>()
+ {
+ set_test_name("string result");
+ DispatchResult service;
+ LLSD result{ service("strfunc", "ignored") };
+ ensure_equals("strfunc() mismatch", result.asString(), "a string");
+ }
+
+ template<> template<>
+ void object::test<24>()
+ {
+ set_test_name("void result");
+ DispatchResult service;
+ LLSD result{ service("voidfunc", LLSD()) };
+ ensure("voidfunc() returned defined", result.isUndefined());
+ }
+
+ template<> template<>
+ void object::test<25>()
+ {
+ set_test_name("Integer result");
+ DispatchResult service;
+ LLSD result{ service("intfunc", "ignored") };
+ ensure_equals("intfunc() mismatch", result.asInteger(), 17);
+ }
+
+ template<> template<>
+ void object::test<26>()
+ {
+ set_test_name("map LLSD result");
+ DispatchResult service;
+ LLSD result{ service("mapfunc", "ignored") };
+ ensure_equals("mapfunc() mismatch", result, llsd::map("key", "value"));
+ }
+
+ template<> template<>
+ void object::test<27>()
+ {
+ set_test_name("array LLSD result");
+ DispatchResult service;
+ LLSD result{ service("arrayfunc", "ignored") };
+ ensure_equals("arrayfunc() mismatch", result, llsd::array("a", "b", "c"));
+ }
} // namespace tut