Merge branch 'DRTVWR-587-maint-V' of https://github.com/secondlife/viewer
commit
05d35a062e
|
|
@ -16,7 +16,9 @@ include(Tracy)
|
|||
|
||||
|
||||
set(llcommon_SOURCE_FILES
|
||||
apply.cpp
|
||||
indra_constants.cpp
|
||||
lazyeventapi.cpp
|
||||
llallocator.cpp
|
||||
llallocator_heap_profile.cpp
|
||||
llapp.cpp
|
||||
|
|
@ -114,11 +116,15 @@ set(llcommon_SOURCE_FILES
|
|||
set(llcommon_HEADER_FILES
|
||||
CMakeLists.txt
|
||||
|
||||
always_return.h
|
||||
apply.h
|
||||
chrono.h
|
||||
classic_callback.h
|
||||
ctype_workaround.h
|
||||
fix_macros.h
|
||||
function_types.h
|
||||
indra_constants.h
|
||||
lazyeventapi.h
|
||||
linden_common.h
|
||||
llalignedarray.h
|
||||
llallocator.h
|
||||
|
|
@ -326,9 +332,11 @@ if (LL_TESTS)
|
|||
|
||||
#set(TEST_DEBUG on)
|
||||
set(test_libs llcommon)
|
||||
LL_ADD_INTEGRATION_TEST(apply "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* @file always_return.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2023-01-20
|
||||
* @brief Call specified callable with arbitrary arguments, but always return
|
||||
* specified type.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
|
||||
* Copyright (c) 2023, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_ALWAYS_RETURN_H)
|
||||
#define LL_ALWAYS_RETURN_H
|
||||
|
||||
#include <type_traits> // std::enable_if, std::is_convertible
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
||||
#if __cpp_lib_is_invocable >= 201703L // C++17
|
||||
template <typename CALLABLE, typename... ARGS>
|
||||
using invoke_result = std::invoke_result<CALLABLE, ARGS...>;
|
||||
#else // C++14
|
||||
template <typename CALLABLE, typename... ARGS>
|
||||
using invoke_result = std::result_of<CALLABLE(ARGS...)>;
|
||||
#endif // C++14
|
||||
|
||||
/**
|
||||
* AlwaysReturn<T>()(some_function, some_args...) calls
|
||||
* some_function(some_args...). It is guaranteed to return a value of type
|
||||
* T, regardless of the return type of some_function(). If some_function()
|
||||
* returns a type convertible to T, it will convert and return that value.
|
||||
* Otherwise (notably if some_function() is void), AlwaysReturn returns
|
||||
* T().
|
||||
*
|
||||
* When some_function() returns a type not convertible to T, if
|
||||
* you want AlwaysReturn to return some T value other than
|
||||
* default-constructed T(), pass that value to AlwaysReturn's constructor.
|
||||
*/
|
||||
template <typename DESIRED>
|
||||
class AlwaysReturn
|
||||
{
|
||||
public:
|
||||
/// pass explicit default value if other than default-constructed type
|
||||
AlwaysReturn(const DESIRED& dft=DESIRED()): mDefault(dft) {}
|
||||
|
||||
// callable returns a type not convertible to DESIRED, return default
|
||||
template <typename CALLABLE, typename... ARGS,
|
||||
typename std::enable_if<
|
||||
! std::is_convertible<
|
||||
typename invoke_result<CALLABLE, ARGS...>::type,
|
||||
DESIRED
|
||||
>::value,
|
||||
bool
|
||||
>::type=true>
|
||||
DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
|
||||
{
|
||||
// discard whatever callable(args) returns
|
||||
std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...);
|
||||
return mDefault;
|
||||
}
|
||||
|
||||
// callable returns a type convertible to DESIRED
|
||||
template <typename CALLABLE, typename... ARGS,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<
|
||||
typename invoke_result<CALLABLE, ARGS...>::type,
|
||||
DESIRED
|
||||
>::value,
|
||||
bool
|
||||
>::type=true>
|
||||
DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
|
||||
{
|
||||
return { std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...) };
|
||||
}
|
||||
|
||||
private:
|
||||
DESIRED mDefault;
|
||||
};
|
||||
|
||||
/**
|
||||
* always_return<T>(some_function, some_args...) calls
|
||||
* some_function(some_args...). It is guaranteed to return a value of type
|
||||
* T, regardless of the return type of some_function(). If some_function()
|
||||
* returns a type convertible to T, it will convert and return that value.
|
||||
* Otherwise (notably if some_function() is void), always_return() returns
|
||||
* T().
|
||||
*/
|
||||
template <typename DESIRED, typename CALLABLE, typename... ARGS>
|
||||
DESIRED always_return(CALLABLE&& callable, ARGS&&... args)
|
||||
{
|
||||
return AlwaysReturn<DESIRED>()(std::forward<CALLABLE>(callable),
|
||||
std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* make_always_return<T>(some_function) returns a callable which, when
|
||||
* called with appropriate some_function() arguments, always returns a
|
||||
* value of type T, regardless of the return type of some_function(). If
|
||||
* some_function() returns a type convertible to T, the returned callable
|
||||
* will convert and return that value. Otherwise (notably if
|
||||
* some_function() is void), the returned callable returns T().
|
||||
*
|
||||
* When some_function() returns a type not convertible to T, if
|
||||
* you want the returned callable to return some T value other than
|
||||
* default-constructed T(), pass that value to make_always_return() as its
|
||||
* optional second argument.
|
||||
*/
|
||||
template <typename DESIRED, typename CALLABLE>
|
||||
auto make_always_return(CALLABLE&& callable, const DESIRED& dft=DESIRED())
|
||||
{
|
||||
return
|
||||
[dft, callable = std::forward<CALLABLE>(callable)]
|
||||
(auto&&... args)
|
||||
{
|
||||
return AlwaysReturn<DESIRED>(dft)(callable,
|
||||
std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_ALWAYS_RETURN_H) */
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* @file apply.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-12-21
|
||||
* @brief Implementation for apply.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "apply.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "stringize.h"
|
||||
|
||||
void LL::apply_validate_size(size_t size, size_t arity)
|
||||
{
|
||||
if (size != arity)
|
||||
{
|
||||
LLTHROW(apply_error(stringize("LL::apply(func(", arity, " args), "
|
||||
"std::vector(", size, " elements))")));
|
||||
}
|
||||
}
|
||||
|
|
@ -12,8 +12,11 @@
|
|||
#if ! defined(LL_APPLY_H)
|
||||
#define LL_APPLY_H
|
||||
|
||||
#include "llexception.h"
|
||||
#include <boost/type_traits/function_traits.hpp>
|
||||
#include <functional> // std::mem_fn()
|
||||
#include <tuple>
|
||||
#include <type_traits> // std::is_member_pointer
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
|
@ -54,20 +57,67 @@ namespace LL
|
|||
}, \
|
||||
(ARGS))
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
/*****************************************************************************
|
||||
* invoke()
|
||||
*****************************************************************************/
|
||||
#if __cpp_lib_invoke >= 201411L
|
||||
|
||||
// C++17 implementation
|
||||
using std::apply;
|
||||
using std::invoke;
|
||||
|
||||
#else // no std::invoke
|
||||
|
||||
// Use invoke() to handle pointer-to-method:
|
||||
// derived from https://stackoverflow.com/a/38288251
|
||||
template<typename Fn, typename... Args,
|
||||
typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
|
||||
int>::type = 0 >
|
||||
auto invoke(Fn&& f, Args&&... args)
|
||||
{
|
||||
return std::mem_fn(std::forward<Fn>(f))(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename Fn, typename... Args,
|
||||
typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
|
||||
int>::type = 0 >
|
||||
auto invoke(Fn&& f, Args&&... args)
|
||||
{
|
||||
return std::forward<Fn>(f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
#endif // no std::invoke
|
||||
|
||||
/*****************************************************************************
|
||||
* apply(function, tuple); apply(function, array)
|
||||
*****************************************************************************/
|
||||
#if __cpp_lib_apply >= 201603L
|
||||
|
||||
// C++17 implementation
|
||||
// We don't just say 'using std::apply;' because that template is too general:
|
||||
// it also picks up the apply(function, vector) case, which we want to handle
|
||||
// below.
|
||||
template <typename CALLABLE, typename... ARGS>
|
||||
auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
|
||||
{
|
||||
return std::apply(std::forward<CALLABLE>(func), args);
|
||||
}
|
||||
|
||||
#else // C++14
|
||||
|
||||
// Derived from https://stackoverflow.com/a/20441189
|
||||
// and https://en.cppreference.com/w/cpp/utility/apply
|
||||
template <typename CALLABLE, typename TUPLE, std::size_t... I>
|
||||
auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence<I...>)
|
||||
template <typename CALLABLE, typename... ARGS, std::size_t... I>
|
||||
auto apply_impl(CALLABLE&& func, const std::tuple<ARGS...>& args, std::index_sequence<I...>)
|
||||
{
|
||||
// We accept const std::tuple& so a caller can construct an tuple on the
|
||||
// fly. But std::get<I>(const tuple) adds a const qualifier to everything
|
||||
// it extracts. Get a non-const ref to this tuple so we can extract
|
||||
// without the extraneous const.
|
||||
auto& non_const_args{ const_cast<std::tuple<ARGS...>&>(args) };
|
||||
|
||||
// call func(unpacked args)
|
||||
return std::forward<CALLABLE>(func)(std::move(std::get<I>(args))...);
|
||||
return invoke(std::forward<CALLABLE>(func),
|
||||
std::forward<ARGS>(std::get<I>(non_const_args))...);
|
||||
}
|
||||
|
||||
template <typename CALLABLE, typename... ARGS>
|
||||
|
|
@ -81,6 +131,8 @@ auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
|
|||
std::index_sequence_for<ARGS...>{});
|
||||
}
|
||||
|
||||
#endif // C++14
|
||||
|
||||
// per https://stackoverflow.com/a/57510428/5533635
|
||||
template <typename CALLABLE, typename T, size_t SIZE>
|
||||
auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
|
||||
|
|
@ -88,28 +140,92 @@ auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
|
|||
return apply(std::forward<CALLABLE>(func), std::tuple_cat(args));
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* bind_front()
|
||||
*****************************************************************************/
|
||||
// To invoke a non-static member function with a tuple, you need a callable
|
||||
// that binds your member function with an instance pointer or reference.
|
||||
// std::bind_front() is perfect: std::bind_front(&cls::method, instance).
|
||||
// Unfortunately bind_front() only enters the standard library in C++20.
|
||||
#if __cpp_lib_bind_front >= 201907L
|
||||
|
||||
// C++20 implementation
|
||||
using std::bind_front;
|
||||
|
||||
#else // no std::bind_front()
|
||||
|
||||
template<typename Fn, typename... Args,
|
||||
typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
|
||||
int>::type = 0 >
|
||||
auto bind_front(Fn&& f, Args&&... args)
|
||||
{
|
||||
// Don't use perfect forwarding for f or args: we must bind them for later.
|
||||
return [f, pfx_args=std::make_tuple(args...)]
|
||||
(auto&&... sfx_args)
|
||||
{
|
||||
// Use perfect forwarding for sfx_args because we use them as soon as
|
||||
// we receive them.
|
||||
return apply(
|
||||
f,
|
||||
std::tuple_cat(pfx_args,
|
||||
std::make_tuple(std::forward<decltype(sfx_args)>(sfx_args)...)));
|
||||
};
|
||||
}
|
||||
|
||||
template<typename Fn, typename... Args,
|
||||
typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
|
||||
int>::type = 0 >
|
||||
auto bind_front(Fn&& f, Args&&... args)
|
||||
{
|
||||
return bind_front(std::mem_fn(std::forward<Fn>(f)), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
#endif // C++20 with std::bind_front()
|
||||
|
||||
/*****************************************************************************
|
||||
* apply(function, std::vector)
|
||||
*****************************************************************************/
|
||||
// per https://stackoverflow.com/a/28411055/5533635
|
||||
template <typename CALLABLE, typename T, std::size_t... I>
|
||||
auto apply_impl(CALLABLE&& func, const std::vector<T>& args, std::index_sequence<I...>)
|
||||
{
|
||||
return apply_impl(std::forward<CALLABLE>(func),
|
||||
std::make_tuple(std::forward<T>(args[I])...),
|
||||
I...);
|
||||
return apply(std::forward<CALLABLE>(func),
|
||||
std::make_tuple(args[I]...));
|
||||
}
|
||||
|
||||
// this goes beyond C++17 std::apply()
|
||||
// produce suitable error if apply(func, vector) is the wrong size for func()
|
||||
void apply_validate_size(size_t size, size_t arity);
|
||||
|
||||
/// possible exception from apply() validation
|
||||
struct apply_error: public LLException
|
||||
{
|
||||
apply_error(const std::string& what): LLException(what) {}
|
||||
};
|
||||
|
||||
template <size_t ARITY, typename CALLABLE, typename T>
|
||||
auto apply_n(CALLABLE&& func, const std::vector<T>& args)
|
||||
{
|
||||
apply_validate_size(args.size(), ARITY);
|
||||
return apply_impl(std::forward<CALLABLE>(func),
|
||||
args,
|
||||
std::make_index_sequence<ARITY>());
|
||||
}
|
||||
|
||||
/**
|
||||
* apply(function, std::vector) goes beyond C++17 std::apply(). For this case
|
||||
* @a function @emph cannot be variadic: the compiler must know at compile
|
||||
* time how many arguments to pass. This isn't Python. (But see apply_n() to
|
||||
* pass a specific number of args to a variadic function.)
|
||||
*/
|
||||
template <typename CALLABLE, typename T>
|
||||
auto apply(CALLABLE&& func, const std::vector<T>& args)
|
||||
{
|
||||
// infer arity from the definition of func
|
||||
constexpr auto arity = boost::function_traits<CALLABLE>::arity;
|
||||
assert(args.size() == arity);
|
||||
return apply_impl(std::forward<CALLABLE>(func),
|
||||
args,
|
||||
std::make_index_sequence<arity>());
|
||||
// now that we have a compile-time arity, apply_n() works
|
||||
return apply_n<arity>(std::forward<CALLABLE>(func), args);
|
||||
}
|
||||
|
||||
#endif // C++14
|
||||
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_APPLY_H) */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @file function_types.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2023-01-20
|
||||
* @brief Extend boost::function_types to examine boost::function and
|
||||
* std::function
|
||||
*
|
||||
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
|
||||
* Copyright (c) 2023, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_FUNCTION_TYPES_H)
|
||||
#define LL_FUNCTION_TYPES_H
|
||||
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/function_types/function_arity.hpp>
|
||||
#include <functional>
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
||||
template <typename F>
|
||||
struct function_arity_impl
|
||||
{
|
||||
static constexpr auto value = boost::function_types::function_arity<F>::value;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct function_arity_impl<std::function<F>>
|
||||
{
|
||||
static constexpr auto value = function_arity_impl<F>::value;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct function_arity_impl<boost::function<F>>
|
||||
{
|
||||
static constexpr auto value = function_arity_impl<F>::value;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct function_arity
|
||||
{
|
||||
static constexpr auto value = function_arity_impl<typename std::decay<F>::type>::value;
|
||||
};
|
||||
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_FUNCTION_TYPES_H) */
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* @file lazyeventapi.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-06-17
|
||||
* @brief Implementation for lazyeventapi.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "lazyeventapi.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
#include <algorithm> // std::find_if
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llevents.h"
|
||||
#include "llsdutil.h"
|
||||
|
||||
LL::LazyEventAPIBase::LazyEventAPIBase(
|
||||
const std::string& name, const std::string& desc, const std::string& field)
|
||||
{
|
||||
// populate embedded LazyEventAPIParams instance
|
||||
mParams.name = name;
|
||||
mParams.desc = desc;
|
||||
mParams.field = field;
|
||||
// mParams.init and mOperations are populated by subsequent add() calls.
|
||||
|
||||
// Our raison d'etre: register as an LLEventPumps::PumpFactory
|
||||
// so obtain() will notice any request for this name and call us.
|
||||
// Of course, our subclass constructor must finish running (making add()
|
||||
// calls) before mParams will be fully populated, but we expect that to
|
||||
// happen well before the first LLEventPumps::obtain(name) call.
|
||||
mRegistered = LLEventPumps::instance().registerPumpFactory(
|
||||
name,
|
||||
[this](const std::string& name){ return construct(name); });
|
||||
}
|
||||
|
||||
LL::LazyEventAPIBase::~LazyEventAPIBase()
|
||||
{
|
||||
// If our constructor's registerPumpFactory() call was unsuccessful, that
|
||||
// probably means somebody else claimed the name first. If that's the
|
||||
// case, do NOT unregister their name out from under them!
|
||||
// If this is a static instance being destroyed at process shutdown,
|
||||
// LLEventPumps will probably have been cleaned up already.
|
||||
if (mRegistered && ! LLEventPumps::wasDeleted())
|
||||
{
|
||||
// unregister the callback to this doomed instance
|
||||
LLEventPumps::instance().unregisterPumpFactory(mParams.name);
|
||||
}
|
||||
}
|
||||
|
||||
LLSD LL::LazyEventAPIBase::getMetadata(const std::string& name) const
|
||||
{
|
||||
// Since mOperations is a vector rather than a map, just search.
|
||||
auto found = std::find_if(mOperations.begin(), mOperations.end(),
|
||||
[&name](const auto& namedesc)
|
||||
{ return (namedesc.first == name); });
|
||||
if (found == mOperations.end())
|
||||
return {};
|
||||
|
||||
// LLEventDispatcher() supplements the returned metadata in different
|
||||
// ways, depending on metadata provided to the specific add() method.
|
||||
// Don't try to emulate all that. At some point we might consider more
|
||||
// closely unifying LLEventDispatcher machinery with LazyEventAPI, but for
|
||||
// now this will have to do.
|
||||
return llsd::map("name", found->first, "desc", found->second);
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* @file lazyeventapi.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-06-16
|
||||
* @brief Declaring a static module-scope LazyEventAPI registers a specific
|
||||
* LLEventAPI for future on-demand instantiation.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LAZYEVENTAPI_H)
|
||||
#define LL_LAZYEVENTAPI_H
|
||||
|
||||
#include "apply.h"
|
||||
#include "lleventapi.h"
|
||||
#include "llinstancetracker.h"
|
||||
#include <boost/signals2/signal.hpp>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility> // std::pair
|
||||
#include <vector>
|
||||
|
||||
namespace LL
|
||||
{
|
||||
/**
|
||||
* Bundle params we want to pass to LLEventAPI's protected constructor. We
|
||||
* package them this way so a subclass constructor can simply forward an
|
||||
* opaque reference to the LLEventAPI constructor.
|
||||
*/
|
||||
// This is a class instead of a plain struct mostly so when we forward-
|
||||
// declare it we don't have to remember the distinction.
|
||||
class LazyEventAPIParams
|
||||
{
|
||||
public:
|
||||
// package the parameters used by the normal LLEventAPI constructor
|
||||
std::string name, desc, field;
|
||||
// bundle LLEventAPI::add() calls collected by LazyEventAPI::add(), so
|
||||
// the special LLEventAPI constructor we engage can "play back" those
|
||||
// add() calls
|
||||
boost::signals2::signal<void(LLEventAPI*)> init;
|
||||
};
|
||||
|
||||
/**
|
||||
* LazyEventAPIBase implements most of the functionality of LazyEventAPI
|
||||
* (q.v.), but we need the LazyEventAPI template subclass so we can accept
|
||||
* the specific LLEventAPI subclass type.
|
||||
*/
|
||||
// No LLInstanceTracker key: we don't need to find a specific instance,
|
||||
// LLLeapListener just needs to be able to enumerate all instances.
|
||||
class LazyEventAPIBase: public LLInstanceTracker<LazyEventAPIBase>
|
||||
{
|
||||
public:
|
||||
LazyEventAPIBase(const std::string& name, const std::string& desc,
|
||||
const std::string& field);
|
||||
virtual ~LazyEventAPIBase();
|
||||
|
||||
// Do not copy or move: once constructed, LazyEventAPIBase must stay
|
||||
// put: we bind its instance pointer into a callback.
|
||||
LazyEventAPIBase(const LazyEventAPIBase&) = delete;
|
||||
LazyEventAPIBase(LazyEventAPIBase&&) = delete;
|
||||
LazyEventAPIBase& operator=(const LazyEventAPIBase&) = delete;
|
||||
LazyEventAPIBase& operator=(LazyEventAPIBase&&) = delete;
|
||||
|
||||
// capture add() calls we want to play back on LLEventAPI construction
|
||||
template <typename... ARGS>
|
||||
void add(const std::string& name, const std::string& desc, ARGS&&... rest)
|
||||
{
|
||||
// capture the metadata separately
|
||||
mOperations.push_back(std::make_pair(name, desc));
|
||||
// Use connect_extended() so the lambda is passed its own
|
||||
// connection.
|
||||
// We can't bind an unexpanded parameter pack into a lambda --
|
||||
// shame really. Instead, capture it as a std::tuple and then, in
|
||||
// the lambda, use apply() to convert back to function args.
|
||||
mParams.init.connect_extended(
|
||||
[name, desc, rest = std::make_tuple(std::forward<ARGS>(rest)...)]
|
||||
(const boost::signals2::connection& conn, LLEventAPI* instance)
|
||||
{
|
||||
// we only need this connection once
|
||||
conn.disconnect();
|
||||
// Our add() method distinguishes name and desc because we
|
||||
// capture them separately. But now, because apply()
|
||||
// expects a tuple specifying ALL the arguments, expand to
|
||||
// a tuple including add_trampoline() arguments: instance,
|
||||
// name, desc, rest.
|
||||
// apply() can't accept a template per se; it needs a
|
||||
// particular specialization.
|
||||
apply(&LazyEventAPIBase::add_trampoline<const std::string&, const std::string&, ARGS...>,
|
||||
std::tuple_cat(std::make_tuple(instance, name, desc),
|
||||
rest));
|
||||
});
|
||||
}
|
||||
|
||||
// The following queries mimic the LLEventAPI / LLEventDispatcher
|
||||
// query API.
|
||||
|
||||
// Get the string name of the subject LLEventAPI
|
||||
std::string getName() const { return mParams.name; }
|
||||
// Get the documentation string
|
||||
std::string getDesc() const { return mParams.desc; }
|
||||
// Retrieve the LLSD key we use for dispatching
|
||||
std::string getDispatchKey() const { return mParams.field; }
|
||||
|
||||
// operations
|
||||
using NameDesc = std::pair<std::string, std::string>;
|
||||
|
||||
private:
|
||||
// metadata that might be queried by LLLeapListener
|
||||
std::vector<NameDesc> mOperations;
|
||||
|
||||
public:
|
||||
using const_iterator = decltype(mOperations)::const_iterator;
|
||||
const_iterator begin() const { return mOperations.begin(); }
|
||||
const_iterator end() const { return mOperations.end(); }
|
||||
LLSD getMetadata(const std::string& name) const;
|
||||
|
||||
protected:
|
||||
// Params with which to instantiate the companion LLEventAPI subclass
|
||||
LazyEventAPIParams mParams;
|
||||
|
||||
private:
|
||||
// true if we successfully registered our LLEventAPI on construction
|
||||
bool mRegistered;
|
||||
|
||||
// actually instantiate the companion LLEventAPI subclass
|
||||
virtual LLEventPump* construct(const std::string& name) = 0;
|
||||
|
||||
// Passing an overloaded function to any function that accepts an
|
||||
// arbitrary callable is a PITB because you have to specify the
|
||||
// correct overload. What we want is for the compiler to select the
|
||||
// correct overload, based on the carefully-wrought enable_ifs in
|
||||
// LLEventDispatcher. This (one and only) add_trampoline() method
|
||||
// exists solely to pass to LL::apply(). Once add_trampoline() is
|
||||
// called with the expanded arguments, we hope the compiler will Do
|
||||
// The Right Thing in selecting the correct LLEventAPI::add()
|
||||
// overload.
|
||||
template <typename... ARGS>
|
||||
static
|
||||
void add_trampoline(LLEventAPI* instance, ARGS&&... args)
|
||||
{
|
||||
instance->add(std::forward<ARGS>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* LazyEventAPI provides a way to register a particular LLEventAPI to be
|
||||
* instantiated on demand, that is, when its name is passed to
|
||||
* LLEventPumps::obtain().
|
||||
*
|
||||
* Derive your listener from LLEventAPI as usual, with its various
|
||||
* operation methods, but code your constructor to accept
|
||||
* <tt>(const LL::LazyEventAPIParams& params)</tt>
|
||||
* and forward that reference to (the protected)
|
||||
* <tt>LLEventAPI(const LL::LazyEventAPIParams&)</tt> constructor.
|
||||
*
|
||||
* Then derive your listener registrar from
|
||||
* <tt>LazyEventAPI<your LLEventAPI subclass></tt>. The constructor should
|
||||
* look very like a traditional LLEventAPI constructor:
|
||||
*
|
||||
* * pass (name, desc [, field]) to LazyEventAPI's constructor
|
||||
* * in the body, make a series of add() calls referencing your LLEventAPI
|
||||
* subclass methods.
|
||||
*
|
||||
* You may use any LLEventAPI::add() methods, that is, any
|
||||
* LLEventDispatcher::add() methods. But the target methods you pass to
|
||||
* add() must belong to your LLEventAPI subclass, not the LazyEventAPI
|
||||
* subclass.
|
||||
*
|
||||
* Declare a static instance of your LazyEventAPI listener registrar
|
||||
* class. When it's constructed at static initialization time, it will
|
||||
* register your LLEventAPI subclass with LLEventPumps. It will also
|
||||
* collect metadata for the LLEventAPI and its operations to provide to
|
||||
* LLLeapListener's introspection queries.
|
||||
*
|
||||
* When someone later calls LLEventPumps::obtain() to post an event to
|
||||
* your LLEventAPI subclass, obtain() will instantiate it using
|
||||
* LazyEventAPI's name, desc, field and add() calls.
|
||||
*/
|
||||
template <class EVENTAPI>
|
||||
class LazyEventAPI: public LazyEventAPIBase
|
||||
{
|
||||
public:
|
||||
// for subclass constructor to reference handler methods
|
||||
using listener = EVENTAPI;
|
||||
|
||||
LazyEventAPI(const std::string& name, const std::string& desc,
|
||||
const std::string& field="op"):
|
||||
// Forward ctor params to LazyEventAPIBase
|
||||
LazyEventAPIBase(name, desc, field)
|
||||
{}
|
||||
|
||||
private:
|
||||
LLEventPump* construct(const std::string& /*name*/) override
|
||||
{
|
||||
// base class has carefully assembled LazyEventAPIParams embedded
|
||||
// in this instance, just pass to LLEventAPI subclass constructor
|
||||
return new EVENTAPI(mParams);
|
||||
}
|
||||
};
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_LAZYEVENTAPI_H) */
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llerror.h"
|
||||
#include "lazyeventapi.h"
|
||||
|
||||
LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const std::string& field):
|
||||
lbase(name, field),
|
||||
|
|
@ -43,6 +44,13 @@ LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const s
|
|||
{
|
||||
}
|
||||
|
||||
LLEventAPI::LLEventAPI(const LL::LazyEventAPIParams& params):
|
||||
LLEventAPI(params.name, params.desc, params.field)
|
||||
{
|
||||
// call initialization functions with our brand-new instance pointer
|
||||
params.init(this);
|
||||
}
|
||||
|
||||
LLEventAPI::~LLEventAPI()
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@
|
|||
#include "llinstancetracker.h"
|
||||
#include <string>
|
||||
|
||||
namespace LL
|
||||
{
|
||||
class LazyEventAPIParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* LLEventAPI not only provides operation dispatch functionality, inherited
|
||||
* from LLDispatchListener -- it also gives us event API introspection.
|
||||
|
|
@ -64,19 +69,6 @@ public:
|
|||
/// Get the documentation string
|
||||
std::string getDesc() const { return mDesc; }
|
||||
|
||||
/**
|
||||
* Publish only selected add() methods from LLEventDispatcher.
|
||||
* Every LLEventAPI add() @em must have a description string.
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
void add(const std::string& name,
|
||||
const std::string& desc,
|
||||
CALLABLE callable,
|
||||
const LLSD& required=LLSD())
|
||||
{
|
||||
LLEventDispatcher::add(name, desc, callable, required);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a Response object in any LLEventAPI subclass method that
|
||||
* wants to guarantee a reply (if requested) will be sent on exit from the
|
||||
|
|
@ -150,16 +142,20 @@ public:
|
|||
* @endcode
|
||||
*/
|
||||
LLSD& operator[](const LLSD::String& key) { return mResp[key]; }
|
||||
|
||||
/**
|
||||
* set the response to the given data
|
||||
*/
|
||||
void setResponse(LLSD const & response){ mResp = response; }
|
||||
|
||||
/**
|
||||
* set the response to the given data
|
||||
*/
|
||||
void setResponse(LLSD const & response){ mResp = response; }
|
||||
|
||||
LLSD mResp, mReq;
|
||||
LLSD::String mKey;
|
||||
};
|
||||
|
||||
protected:
|
||||
// constructor used only by subclasses registered by LazyEventAPI
|
||||
LLEventAPI(const LL::LazyEventAPIParams&);
|
||||
|
||||
private:
|
||||
std::string mDesc;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -40,70 +40,12 @@
|
|||
// other Linden headers
|
||||
#include "llevents.h"
|
||||
#include "llerror.h"
|
||||
#include "llexception.h"
|
||||
#include "llsdutil.h"
|
||||
#include "stringize.h"
|
||||
#include <iomanip> // std::quoted()
|
||||
#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
|
||||
*****************************************************************************/
|
||||
|
|
@ -156,19 +98,26 @@ void LLSDArgsSource::done() const
|
|||
* - 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
|
||||
/// default values
|
||||
LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults);
|
||||
LLSDArgsMapper(LLEventDispatcher* parent, const std::string& function,
|
||||
const LLSD& names, const LLSD& defaults);
|
||||
|
||||
/// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS.
|
||||
/// Given arguments map, return LLSD::Array of parameter values, or
|
||||
/// trigger error.
|
||||
LLSD map(const LLSD& argsmap) const;
|
||||
|
||||
private:
|
||||
static std::string formatlist(const LLSD&);
|
||||
template <typename... ARGS>
|
||||
void callFail(ARGS&&... args) const;
|
||||
|
||||
// store a plain dumb back-pointer because we don't have to manage the
|
||||
// parent LLEventDispatcher's lifespan
|
||||
LLEventDispatcher* _parent;
|
||||
// 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;
|
||||
|
|
@ -187,15 +136,18 @@ private:
|
|||
FilledVector _has_dft;
|
||||
};
|
||||
|
||||
LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
|
||||
const LLSD& names, const LLSD& defaults):
|
||||
LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(LLEventDispatcher* parent,
|
||||
const std::string& function,
|
||||
const LLSD& names,
|
||||
const LLSD& defaults):
|
||||
_parent(parent),
|
||||
_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;
|
||||
callFail(" names must be an array, not ", names);
|
||||
}
|
||||
auto nparams(_names.size());
|
||||
// From _names generate _indexes.
|
||||
|
|
@ -218,8 +170,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
|
|||
// 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;
|
||||
callFail(" names array ", names, " shorter than defaults array ", defaults);
|
||||
}
|
||||
|
||||
// Offset by which we slide defaults array right to right-align with
|
||||
|
|
@ -256,23 +207,20 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
|
|||
}
|
||||
if (bogus.size())
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params "
|
||||
<< formatlist(bogus) << LL_ENDL;
|
||||
callFail(" defaults specified for nonexistent params ", formatlist(bogus));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not "
|
||||
<< defaults << LL_ENDL;
|
||||
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()))
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not "
|
||||
<< argsmap << LL_ENDL;
|
||||
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
|
||||
|
|
@ -369,15 +317,14 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
|
|||
// by argsmap, that's a problem.
|
||||
if (unfilled.size())
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments "
|
||||
<< formatlist(unfilled) << " from " << argsmap << LL_ENDL;
|
||||
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 = "";
|
||||
|
|
@ -390,23 +337,44 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list)
|
|||
return out.str();
|
||||
}
|
||||
|
||||
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
|
||||
mDesc(desc),
|
||||
mKey(key)
|
||||
template <typename... ARGS>
|
||||
void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const
|
||||
{
|
||||
_parent->callFail<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),
|
||||
mArgskey(argskey)
|
||||
{}
|
||||
|
||||
LLEventDispatcher::~LLEventDispatcher()
|
||||
{
|
||||
}
|
||||
|
||||
LLEventDispatcher::DispatchEntry::DispatchEntry(LLEventDispatcher* parent, const std::string& desc):
|
||||
mParent(parent),
|
||||
mDesc(desc)
|
||||
{}
|
||||
|
||||
/**
|
||||
* 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),
|
||||
LLSDDispatchEntry(LLEventDispatcher* parent, const std::string& desc,
|
||||
const Callable& func, const LLSD& required):
|
||||
DispatchEntry(parent, desc),
|
||||
mFunc(func),
|
||||
mRequired(required)
|
||||
{}
|
||||
|
|
@ -414,22 +382,21 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
|
|||
Callable mFunc;
|
||||
LLSD mRequired;
|
||||
|
||||
virtual void 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())
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL;
|
||||
return callFail(desc, ": bad request: ", mismatch);
|
||||
}
|
||||
// Event syntax looks good, go for it!
|
||||
mFunc(event);
|
||||
return mFunc(event);
|
||||
}
|
||||
|
||||
virtual LLSD addMetadata(LLSD meta) const
|
||||
LLSD getMetadata() const override
|
||||
{
|
||||
meta["required"] = mRequired;
|
||||
return meta;
|
||||
return llsd::map("required", mRequired);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -439,17 +406,27 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
|
|||
*/
|
||||
struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry
|
||||
{
|
||||
ParamsDispatchEntry(const std::string& desc, const invoker_function& func):
|
||||
DispatchEntry(desc),
|
||||
ParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
|
||||
const std::string& desc, const invoker_function& func):
|
||||
DispatchEntry(parent, desc),
|
||||
mName(name),
|
||||
mInvoker(func)
|
||||
{}
|
||||
|
||||
std::string mName;
|
||||
invoker_function mInvoker;
|
||||
|
||||
virtual void call(const std::string& desc, const LLSD& event) const
|
||||
LLSD call(const std::string&, const LLSD& event, bool, const std::string&) const override
|
||||
{
|
||||
LLSDArgsSource src(desc, event);
|
||||
mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src)));
|
||||
try
|
||||
{
|
||||
return mInvoker(event);
|
||||
}
|
||||
catch (const LL::apply_error& err)
|
||||
{
|
||||
// could hit runtime errors with LL::apply()
|
||||
return callFail(err.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -459,23 +436,62 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc
|
|||
*/
|
||||
struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
|
||||
{
|
||||
ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func,
|
||||
ArrayParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
|
||||
const std::string& desc, const invoker_function& func,
|
||||
LLSD::Integer arity):
|
||||
ParamsDispatchEntry(desc, func),
|
||||
ParamsDispatchEntry(parent, 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)
|
||||
{
|
||||
// When the target function is nullary, and we're called from
|
||||
// an (event) method, just ignore the rest of the map entries.
|
||||
args.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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 getMetadata() const override
|
||||
{
|
||||
LLSD array(LLSD::emptyArray());
|
||||
// Resize to number of arguments required
|
||||
if (mArity)
|
||||
array[mArity - 1] = LLSD();
|
||||
llassert_always(array.size() == mArity);
|
||||
meta["required"] = array;
|
||||
return meta;
|
||||
return llsd::map("required", array);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -485,11 +501,11 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa
|
|||
*/
|
||||
struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
|
||||
{
|
||||
MapParamsDispatchEntry(const std::string& name, const std::string& desc,
|
||||
const invoker_function& func,
|
||||
MapParamsDispatchEntry(LLEventDispatcher* parent, 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),
|
||||
ParamsDispatchEntry(parent, name, desc, func),
|
||||
mMapper(parent, name, params, defaults),
|
||||
mRequired(LLSD::emptyMap())
|
||||
{
|
||||
// Build the set of all param keys, then delete the ones that are
|
||||
|
|
@ -532,18 +548,27 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para
|
|||
LLSD mRequired;
|
||||
LLSD mOptional;
|
||||
|
||||
virtual void 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.
|
||||
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 getMetadata() const override
|
||||
{
|
||||
meta["required"] = mRequired;
|
||||
meta["optional"] = mOptional;
|
||||
return meta;
|
||||
return llsd::map("required", mRequired, "optional", mOptional);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -552,9 +577,9 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name,
|
|||
const invoker_function& invoker,
|
||||
LLSD::Integer arity)
|
||||
{
|
||||
mDispatch.insert(
|
||||
DispatchMap::value_type(name, DispatchMap::mapped_type(
|
||||
new ArrayParamsDispatchEntry(desc, invoker, arity))));
|
||||
mDispatch.emplace(
|
||||
name,
|
||||
new ArrayParamsDispatchEntry(this, "", desc, invoker, arity));
|
||||
}
|
||||
|
||||
void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
|
||||
|
|
@ -563,25 +588,25 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
|
|||
const LLSD& params,
|
||||
const LLSD& defaults)
|
||||
{
|
||||
mDispatch.insert(
|
||||
DispatchMap::value_type(name, DispatchMap::mapped_type(
|
||||
new MapParamsDispatchEntry(name, desc, invoker, params, defaults))));
|
||||
// Pass instance info as well as this entry name for error messages.
|
||||
mDispatch.emplace(
|
||||
name,
|
||||
new MapParamsDispatchEntry(this, "", 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)
|
||||
void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc,
|
||||
const Callable& callable, const LLSD& required)
|
||||
{
|
||||
mDispatch.insert(
|
||||
DispatchMap::value_type(name, DispatchMap::mapped_type(
|
||||
new LLSDDispatchEntry(desc, callable, required))));
|
||||
mDispatch.emplace(name, new LLSDDispatchEntry(this, desc, callable, required));
|
||||
}
|
||||
|
||||
void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const
|
||||
void LLEventDispatcher::addFail(const std::string& name, const char* classname) const
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name
|
||||
<< "): " << classname << " is not a subclass "
|
||||
<< "of LLEventDispatcher" << LL_ENDL;
|
||||
<< "): " << LLError::Log::demangle(classname)
|
||||
<< " is not a subclass of LLEventDispatcher"
|
||||
<< LL_ENDL;
|
||||
}
|
||||
|
||||
/// Unregister a callable
|
||||
|
|
@ -596,48 +621,105 @@ bool LLEventDispatcher::remove(const std::string& name)
|
|||
return true;
|
||||
}
|
||||
|
||||
/// Call a registered callable with an explicitly-specified name. If no
|
||||
/// such callable exists, die with LL_ERRS.
|
||||
void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
|
||||
/// Call a registered callable with an explicitly-specified name. It is an
|
||||
/// error if no such callable exists.
|
||||
LLSD LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
|
||||
{
|
||||
if (! try_call(name, event))
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name
|
||||
<< "' not found" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the @a key value from the incoming @a event, and call the
|
||||
/// callable whose name is specified by that map @a key. If no such
|
||||
/// callable exists, die with LL_ERRS.
|
||||
void LLEventDispatcher::operator()(const LLSD& event) const
|
||||
{
|
||||
// This could/should be implemented in terms of the two-arg overload.
|
||||
// However -- we can produce a more informative error message.
|
||||
std::string name(event[mKey]);
|
||||
if (! try_call(name, event))
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey
|
||||
<< " value '" << name << "'" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
bool LLEventDispatcher::try_call(const LLSD& event) const
|
||||
{
|
||||
return try_call(event[mKey], event);
|
||||
return try_call(std::string(), name, event);
|
||||
}
|
||||
|
||||
bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
|
||||
{
|
||||
DispatchMap::const_iterator found = mDispatch.find(name);
|
||||
if (found == mDispatch.end())
|
||||
try
|
||||
{
|
||||
try_call(std::string(), name, event);
|
||||
return true;
|
||||
}
|
||||
// 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&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(mKey, event[mKey], event);
|
||||
}
|
||||
|
||||
bool LLEventDispatcher::try_call(const LLSD& event) const
|
||||
{
|
||||
try
|
||||
{
|
||||
try_call(mKey, event[mKey], event);
|
||||
return true;
|
||||
}
|
||||
catch (const DispatchMissing&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name,
|
||||
const LLSD& event) const
|
||||
{
|
||||
if (name.empty())
|
||||
{
|
||||
if (key.empty())
|
||||
{
|
||||
callFail<DispatchError>("attempting to call with no name");
|
||||
}
|
||||
else
|
||||
{
|
||||
callFail<DispatchError>("no ", key);
|
||||
}
|
||||
}
|
||||
|
||||
DispatchMap::const_iterator found = mDispatch.find(name);
|
||||
if (found == mDispatch.end())
|
||||
{
|
||||
// Here we were passed a non-empty 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())
|
||||
{
|
||||
callFail<DispatchMissing>(std::quoted(name), " not found");
|
||||
}
|
||||
else
|
||||
{
|
||||
callFail<DispatchMissing>("bad ", key, " value ", std::quoted(name));
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
const char* delim = (key.empty()? "" : "=");
|
||||
// append either "[key=name]" or just "[name]"
|
||||
SetState transient(this, '[', key, delim, name, ']');
|
||||
return found->second->call("", 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
|
||||
|
|
@ -647,26 +729,243 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const
|
|||
{
|
||||
return LLSD();
|
||||
}
|
||||
LLSD meta;
|
||||
LLSD meta{ found->second->getMetadata() };
|
||||
meta["name"] = name;
|
||||
meta["desc"] = found->second->mDesc;
|
||||
return found->second->addMetadata(meta);
|
||||
return meta;
|
||||
}
|
||||
|
||||
LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
|
||||
LLEventDispatcher(pumpname, key),
|
||||
mPump(pumpname, true), // allow tweaking for uniqueness
|
||||
mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1)))
|
||||
std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self)
|
||||
{
|
||||
// If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that.
|
||||
// Also report whatever transient state is active.
|
||||
return out << LLError::Log::classname(self) << '(' << self.mDesc << ')'
|
||||
<< self.getState();
|
||||
}
|
||||
|
||||
bool LLDispatchListener::process(const LLSD& event)
|
||||
std::string LLEventDispatcher::getState() const
|
||||
{
|
||||
(*this)(event);
|
||||
// default value of fiber_specific_ptr is nullptr, and ~SetState() reverts
|
||||
// to that; infer empty string
|
||||
if (! mState.get())
|
||||
return {};
|
||||
else
|
||||
return *mState;
|
||||
}
|
||||
|
||||
bool LLEventDispatcher::setState(SetState&, const std::string& state) const
|
||||
{
|
||||
// If SetState is instantiated at multiple levels of function call, ignore
|
||||
// the lower-level call because the outer call presumably provides more
|
||||
// context.
|
||||
if (mState.get())
|
||||
return false;
|
||||
|
||||
// Pass us empty string (a la ~SetState()) to reset to nullptr, else take
|
||||
// a heap copy of the passed state string so we can delete it on
|
||||
// subsequent reset().
|
||||
mState.reset(state.empty()? nullptr : new std::string(state));
|
||||
return true;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* LLDispatchListener
|
||||
*****************************************************************************/
|
||||
std::string LLDispatchListener::mReplyKey{ "reply" };
|
||||
|
||||
bool LLDispatchListener::process(const LLSD& event) const
|
||||
{
|
||||
// Decide what to do based on the incoming value of the specified dispatch
|
||||
// key.
|
||||
LLSD name{ event[getDispatchKey()] };
|
||||
if (name.isMap())
|
||||
{
|
||||
call_map(name, event);
|
||||
}
|
||||
else if (name.isArray())
|
||||
{
|
||||
call_array(name, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
call_one(name, event);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
|
||||
mDesc(desc)
|
||||
{}
|
||||
void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const
|
||||
{
|
||||
LLSD result;
|
||||
try
|
||||
{
|
||||
result = (*this)(event);
|
||||
}
|
||||
catch (const DispatchError& err)
|
||||
{
|
||||
if (! event.has(mReplyKey))
|
||||
{
|
||||
// Without a reply key, let the exception propagate.
|
||||
throw;
|
||||
}
|
||||
|
||||
// Here there was an error and the incoming event has mReplyKey. Reply
|
||||
// with a map containing an "error" key explaining the problem.
|
||||
return reply(llsd::map("error", err.what()), event);
|
||||
}
|
||||
|
||||
// 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 returned 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);
|
||||
}
|
||||
}
|
||||
|
||||
void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const
|
||||
{
|
||||
// LLSD map containing returned values
|
||||
LLSD result;
|
||||
// cache dispatch key
|
||||
std::string key{ getDispatchKey() };
|
||||
// collect any error messages here
|
||||
std::ostringstream errors;
|
||||
const char* delim = "";
|
||||
|
||||
for (const auto& pair : llsd::inMap(reqmap))
|
||||
{
|
||||
const LLSD::String& name{ pair.first };
|
||||
const LLSD& args{ pair.second };
|
||||
try
|
||||
{
|
||||
// in case of errors, tell user the dispatch key, the fact that
|
||||
// we're processing a request map and the current key in that map
|
||||
SetState(this, '[', key, '[', name, "]]");
|
||||
// With this form, capture return value even if undefined:
|
||||
// presence of the key in the response map can be used to detect
|
||||
// which request keys succeeded.
|
||||
result[name] = (*this)(name, args);
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
// Catch not only DispatchError, but any C++ exception thrown by
|
||||
// the target callable. Collect exception name and message in
|
||||
// 'errors'.
|
||||
errors << delim << LLError::Log::classname(err) << ": " << err.what();
|
||||
delim = "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// so, were there any errors?
|
||||
std::string error = errors.str();
|
||||
if (! error.empty())
|
||||
{
|
||||
if (! event.has(mReplyKey))
|
||||
{
|
||||
// can't send reply, throw
|
||||
sCallFail<DispatchError>(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// reply key present
|
||||
result["error"] = error;
|
||||
}
|
||||
}
|
||||
|
||||
reply(result, event);
|
||||
}
|
||||
|
||||
void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) const
|
||||
{
|
||||
// LLSD array containing returned values
|
||||
LLSD results;
|
||||
// cache the dispatch key
|
||||
std::string key{ getDispatchKey() };
|
||||
// arguments array, if present -- const because, if it's shorter than
|
||||
// reqarray, we don't want to grow it
|
||||
const LLSD argsarray{ event[getArgsKey()] };
|
||||
// error message, if any
|
||||
std::string error;
|
||||
|
||||
// classic index loop because we need the index
|
||||
for (size_t i = 0, size = reqarray.size(); i < size; ++i)
|
||||
{
|
||||
const auto& reqentry{ reqarray[i] };
|
||||
std::string name;
|
||||
LLSD args;
|
||||
if (reqentry.isString())
|
||||
{
|
||||
name = reqentry.asString();
|
||||
args = argsarray[i];
|
||||
}
|
||||
else if (reqentry.isArray() && reqentry.size() == 2 && reqentry[0].isString())
|
||||
{
|
||||
name = reqentry[0].asString();
|
||||
args = reqentry[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// reqentry isn't in either of the documented forms
|
||||
error = stringize(*this, ": ", getDispatchKey(), '[', i, "] ",
|
||||
reqentry, " unsupported");
|
||||
break;
|
||||
}
|
||||
|
||||
// reqentry is one of the valid forms, got name and args
|
||||
try
|
||||
{
|
||||
// in case of errors, tell user the dispatch key, the fact that
|
||||
// we're processing a request array, the current entry in that
|
||||
// array and the corresponding callable name
|
||||
SetState(this, '[', key, '[', i, "]=", name, ']');
|
||||
// With this form, capture return value even if undefined
|
||||
results.append((*this)(name, args));
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
// Catch not only DispatchError, but any C++ exception thrown by
|
||||
// the target callable. Report the exception class as well as the
|
||||
// error string.
|
||||
error = stringize(LLError::Log::classname(err), ": ", err.what());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LLSD result;
|
||||
// was there an error?
|
||||
if (! error.empty())
|
||||
{
|
||||
if (! event.has(mReplyKey))
|
||||
{
|
||||
// can't send reply, throw
|
||||
sCallFail<DispatchError>(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// reply key present
|
||||
result["error"] = error;
|
||||
}
|
||||
}
|
||||
|
||||
// wrap the results array as response map "data" key, as promised
|
||||
if (results.isDefined())
|
||||
{
|
||||
result["data"] = results;
|
||||
}
|
||||
|
||||
reply(result, event);
|
||||
}
|
||||
|
||||
void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -435,16 +435,61 @@ public:
|
|||
// generic type-appropriate store through mTarget, construct an
|
||||
// LLSDParam<T> and store that, thus engaging LLSDParam's custom
|
||||
// conversions.
|
||||
mTarget = LLSDParam<T>(llsd::drill(event, mPath));
|
||||
storeTarget(LLSDParam<T>(llsd::drill(event, mPath)));
|
||||
return mConsume;
|
||||
}
|
||||
|
||||
private:
|
||||
// This method disambiguates LLStoreListener<LLSD>. Directly assigning
|
||||
// some_LLSD_var = LLSDParam<LLSD>(some_LLSD_value);
|
||||
// is problematic because the compiler has too many choices: LLSD has
|
||||
// multiple assignment operator overloads, and LLSDParam<LLSD> has a
|
||||
// templated conversion operator. But LLSDParam<LLSD> can convert to a
|
||||
// (const LLSD&) parameter, and LLSD::operator=(const LLSD&) works.
|
||||
void storeTarget(const T& value)
|
||||
{
|
||||
mTarget = value;
|
||||
}
|
||||
|
||||
T& mTarget;
|
||||
const LLSD mPath;
|
||||
const bool mConsume;
|
||||
};
|
||||
|
||||
/**
|
||||
* LLVarHolder bundles a target variable of the specified type. We use it as a
|
||||
* base class so the target variable will be fully constructed by the time a
|
||||
* subclass constructor tries to pass a reference to some other base class.
|
||||
*/
|
||||
template <typename T>
|
||||
struct LLVarHolder
|
||||
{
|
||||
T mVar;
|
||||
};
|
||||
|
||||
/**
|
||||
* LLCaptureListener isa LLStoreListener that bundles the target variable of
|
||||
* interest.
|
||||
*/
|
||||
template <typename T>
|
||||
class LLCaptureListener: public LLVarHolder<T>,
|
||||
public LLStoreListener<T>
|
||||
{
|
||||
private:
|
||||
using holder = LLVarHolder<T>;
|
||||
using super = LLStoreListener<T>;
|
||||
|
||||
public:
|
||||
LLCaptureListener(const LLSD& path=LLSD(), bool consume=false):
|
||||
super(*this, holder::mVar, path, consume)
|
||||
{}
|
||||
|
||||
void set(T&& newval=T()) { holder::mVar = std::forward<T>(newval); }
|
||||
|
||||
const T& get() const { return holder::mVar; }
|
||||
operator const T&() { return holder::mVar; }
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* LLEventLogProxy
|
||||
*****************************************************************************/
|
||||
|
|
|
|||
|
|
@ -68,19 +68,78 @@
|
|||
LLEventPumps::LLEventPumps():
|
||||
mFactories
|
||||
{
|
||||
{ "LLEventStream", [](const std::string& name, bool tweak)
|
||||
{ "LLEventStream", [](const std::string& name, bool tweak, const std::string& /*type*/)
|
||||
{ return new LLEventStream(name, tweak); } },
|
||||
{ "LLEventMailDrop", [](const std::string& name, bool tweak)
|
||||
{ "LLEventMailDrop", [](const std::string& name, bool tweak, const std::string& /*type*/)
|
||||
{ return new LLEventMailDrop(name, tweak); } }
|
||||
},
|
||||
mTypes
|
||||
{
|
||||
// LLEventStream is the default for obtain(), so even if somebody DOES
|
||||
// call obtain("placeholder"), this sample entry won't break anything.
|
||||
{ "placeholder", "LLEventStream" }
|
||||
// { "placeholder", "LLEventStream" }
|
||||
}
|
||||
{}
|
||||
|
||||
bool LLEventPumps::registerTypeFactory(const std::string& type, const TypeFactory& factory)
|
||||
{
|
||||
auto found = mFactories.find(type);
|
||||
// can't re-register a TypeFactory for a type name that's already registered
|
||||
if (found != mFactories.end())
|
||||
return false;
|
||||
// doesn't already exist, go ahead and register
|
||||
mFactories[type] = factory;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LLEventPumps::unregisterTypeFactory(const std::string& type)
|
||||
{
|
||||
auto found = mFactories.find(type);
|
||||
if (found != mFactories.end())
|
||||
mFactories.erase(found);
|
||||
}
|
||||
|
||||
bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactory& factory)
|
||||
{
|
||||
// Do we already have a pump by this name?
|
||||
if (mPumpMap.find(name) != mPumpMap.end())
|
||||
return false;
|
||||
// Do we already have an override for this pump name?
|
||||
if (mTypes.find(name) != mTypes.end())
|
||||
return false;
|
||||
// Leverage the two-level lookup implemented by mTypes (pump name -> type
|
||||
// name) and mFactories (type name -> factory). We could instead create a
|
||||
// whole separate (pump name -> factory) map, and look in both; or we
|
||||
// could change mTypes to (pump name -> factory) and, for typical type-
|
||||
// based lookups, use a "factory" that looks up the real factory in
|
||||
// mFactories. But this works, and we don't expect many calls to make() -
|
||||
// either explicit or implicit via obtain().
|
||||
// Create a bogus type name extremely unlikely to collide with an actual type.
|
||||
static std::string nul(1, '\0');
|
||||
std::string type_name{ nul + name };
|
||||
mTypes[name] = type_name;
|
||||
// TypeFactory is called with (name, tweak, type), whereas PumpFactory
|
||||
// accepts only name. We could adapt with std::bind(), but this lambda
|
||||
// does the trick.
|
||||
mFactories[type_name] =
|
||||
[factory]
|
||||
(const std::string& name, bool /*tweak*/, const std::string& /*type*/)
|
||||
{ return factory(name); };
|
||||
return true;
|
||||
}
|
||||
|
||||
void LLEventPumps::unregisterPumpFactory(const std::string& name)
|
||||
{
|
||||
auto tfound = mTypes.find(name);
|
||||
if (tfound != mTypes.end())
|
||||
{
|
||||
auto ffound = mFactories.find(tfound->second);
|
||||
if (ffound != mFactories.end())
|
||||
{
|
||||
mFactories.erase(ffound);
|
||||
}
|
||||
mTypes.erase(tfound);
|
||||
}
|
||||
}
|
||||
|
||||
LLEventPump& LLEventPumps::obtain(const std::string& name)
|
||||
{
|
||||
PumpMap::iterator found = mPumpMap.find(name);
|
||||
|
|
@ -114,7 +173,7 @@ LLEventPump& LLEventPumps::make(const std::string& name, bool tweak,
|
|||
// Passing an unrecognized type name is a no-no
|
||||
LLTHROW(BadType(type));
|
||||
}
|
||||
auto newInstance = (found->second)(name, tweak);
|
||||
auto newInstance = (found->second)(name, tweak, type);
|
||||
// LLEventPump's constructor implicitly registers each new instance in
|
||||
// mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll
|
||||
// delete it later.
|
||||
|
|
|
|||
|
|
@ -270,6 +270,45 @@ public:
|
|||
LLEventPump& make(const std::string& name, bool tweak=false,
|
||||
const std::string& type=std::string());
|
||||
|
||||
/// function passed to registerTypeFactory()
|
||||
typedef std::function<LLEventPump*(const std::string& name, bool tweak, const std::string& type)> TypeFactory;
|
||||
|
||||
/**
|
||||
* Register a TypeFactory for use with make(). When make() is called with
|
||||
* the specified @a type string, call @a factory(name, tweak, type) to
|
||||
* instantiate it.
|
||||
*
|
||||
* Returns true if successfully registered, false if there already exists
|
||||
* a TypeFactory for the specified @a type name.
|
||||
*/
|
||||
bool registerTypeFactory(const std::string& type, const TypeFactory& factory);
|
||||
void unregisterTypeFactory(const std::string& type);
|
||||
|
||||
/// function passed to registerPumpFactory()
|
||||
typedef std::function<LLEventPump*(const std::string&)> PumpFactory;
|
||||
|
||||
/**
|
||||
* Register a PumpFactory for use with obtain(). When obtain() is called
|
||||
* with the specified @a name string, if an LLEventPump with the specified
|
||||
* @a name doesn't already exist, call @a factory(name) to instantiate it.
|
||||
*
|
||||
* Returns true if successfully registered, false if there already exists
|
||||
* a factory override for the specified @a name.
|
||||
*
|
||||
* PumpFactory does not support @a tweak because it's only called when
|
||||
* <i>that particular</i> @a name is passed to obtain(). Bear in mind that
|
||||
* <tt>obtain(name)</tt> might still bypass the caller's PumpFactory for a
|
||||
* couple different reasons:
|
||||
*
|
||||
* * registerPumpFactory() returns false because there's already a factory
|
||||
* override for the specified @name
|
||||
* * between a successful <tt>registerPumpFactory(name)</tt> call (returns
|
||||
* true) and a call to <tt>obtain(name)</tt>, someone explicitly
|
||||
* instantiated an LLEventPump(name), so obtain(name) returned that.
|
||||
*/
|
||||
bool registerPumpFactory(const std::string& name, const PumpFactory& factory);
|
||||
void unregisterPumpFactory(const std::string& name);
|
||||
|
||||
/**
|
||||
* Find the named LLEventPump instance. If it exists post the message to it.
|
||||
* If the pump does not exist, do nothing.
|
||||
|
|
@ -327,13 +366,13 @@ testable:
|
|||
typedef std::set<LLEventPump*> PumpSet;
|
||||
PumpSet mOurPumps;
|
||||
// for make(), map string type name to LLEventPump subclass factory function
|
||||
typedef std::map<std::string, std::function<LLEventPump*(const std::string&, bool)>> PumpFactories;
|
||||
typedef std::map<std::string, TypeFactory> TypeFactories;
|
||||
// Data used by make().
|
||||
// One might think mFactories and mTypes could reasonably be static. So
|
||||
// they could -- if not for the fact that make() or obtain() might be
|
||||
// called before this module's static variables have been initialized.
|
||||
// This is why we use singletons in the first place.
|
||||
PumpFactories mFactories;
|
||||
TypeFactories mFactories;
|
||||
|
||||
// for obtain(), map desired string instance name to string type when
|
||||
// obtain() must create the instance
|
||||
|
|
|
|||
|
|
@ -327,11 +327,28 @@ public:
|
|||
}
|
||||
else
|
||||
{
|
||||
// The LLSD object we got from our stream contains the keys we
|
||||
// need.
|
||||
LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
|
||||
// Block calls to this method; resetting mBlocker unblocks calls
|
||||
// to the other method.
|
||||
try
|
||||
{
|
||||
// The LLSD object we got from our stream contains the
|
||||
// keys we need.
|
||||
LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
// No plugin should be allowed to crash the viewer by
|
||||
// driving an exception -- intentionally or not.
|
||||
LOG_UNHANDLED_EXCEPTION(stringize("handling request ", data));
|
||||
// Whether or not the plugin added a "reply" key to the
|
||||
// request, send a reply. We happen to know who originated
|
||||
// this request, and the reply LLEventPump of interest.
|
||||
// Not our problem if the plugin ignores the reply event.
|
||||
data["reply"] = mReplyPump.getName();
|
||||
sendReply(llsd::map("error",
|
||||
stringize(LLError::Log::classname(err), ": ", err.what())),
|
||||
data);
|
||||
}
|
||||
// Block calls to this method; resetting mBlocker unblocks
|
||||
// calls to the other method.
|
||||
mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
|
||||
// Go check for any more pending events in the buffer.
|
||||
if (childout.size())
|
||||
|
|
|
|||
|
|
@ -14,14 +14,16 @@
|
|||
// associated header
|
||||
#include "llleaplistener.h"
|
||||
// STL headers
|
||||
#include <map>
|
||||
#include <algorithm> // std::find_if
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/foreach.hpp>
|
||||
// other Linden headers
|
||||
#include "lluuid.h"
|
||||
#include "lazyeventapi.h"
|
||||
#include "llsdutil.h"
|
||||
#include "lluuid.h"
|
||||
#include "stringize.h"
|
||||
|
||||
/*****************************************************************************
|
||||
|
|
@ -110,7 +112,7 @@ LLLeapListener::~LLLeapListener()
|
|||
// value_type, and Bad Things would happen if you copied an
|
||||
// LLTempBoundListener. (Destruction of the original would disconnect the
|
||||
// listener, invalidating every stored connection.)
|
||||
BOOST_FOREACH(ListenersMap::value_type& pair, mListeners)
|
||||
for (ListenersMap::value_type& pair : mListeners)
|
||||
{
|
||||
pair.second.disconnect();
|
||||
}
|
||||
|
|
@ -208,31 +210,65 @@ void LLLeapListener::getAPIs(const LLSD& request) const
|
|||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
// first, traverse existing LLEventAPI instances
|
||||
std::set<std::string> instances;
|
||||
for (auto& ea : LLEventAPI::instance_snapshot())
|
||||
{
|
||||
LLSD info;
|
||||
info["desc"] = ea.getDesc();
|
||||
reply[ea.getName()] = info;
|
||||
// remember which APIs are actually instantiated
|
||||
instances.insert(ea.getName());
|
||||
reply[ea.getName()] = llsd::map("desc", ea.getDesc());
|
||||
}
|
||||
// supplement that with *potential* instances: that is, instances of
|
||||
// LazyEventAPI that can each instantiate an LLEventAPI on demand
|
||||
for (const auto& lea : LL::LazyEventAPIBase::instance_snapshot())
|
||||
{
|
||||
// skip any LazyEventAPI that's already instantiated its LLEventAPI
|
||||
if (instances.find(lea.getName()) == instances.end())
|
||||
{
|
||||
reply[lea.getName()] = llsd::map("desc", lea.getDesc());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Because LazyEventAPI deliberately mimics LLEventAPI's query API, this
|
||||
// function can be passed either -- even though they're unrelated types.
|
||||
template <typename API>
|
||||
void reportAPI(LLEventAPI::Response& reply, const API& api)
|
||||
{
|
||||
reply["name"] = api.getName();
|
||||
reply["desc"] = api.getDesc();
|
||||
reply["key"] = api.getDispatchKey();
|
||||
LLSD ops;
|
||||
for (const auto& namedesc : api)
|
||||
{
|
||||
ops.append(api.getMetadata(namedesc.first));
|
||||
}
|
||||
reply["ops"] = ops;
|
||||
}
|
||||
|
||||
void LLLeapListener::getAPI(const LLSD& request) const
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
auto found = LLEventAPI::getInstance(request["api"]);
|
||||
if (found)
|
||||
// check first among existing LLEventAPI instances
|
||||
auto foundea = LLEventAPI::getInstance(request["api"]);
|
||||
if (foundea)
|
||||
{
|
||||
reply["name"] = found->getName();
|
||||
reply["desc"] = found->getDesc();
|
||||
reply["key"] = found->getDispatchKey();
|
||||
LLSD ops;
|
||||
for (LLEventAPI::const_iterator oi(found->begin()), oend(found->end());
|
||||
oi != oend; ++oi)
|
||||
reportAPI(reply, *foundea);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here the requested LLEventAPI doesn't yet exist, but do we have a
|
||||
// registered LazyEventAPI for it?
|
||||
LL::LazyEventAPIBase::instance_snapshot snap;
|
||||
auto foundlea = std::find_if(snap.begin(), snap.end(),
|
||||
[api = request["api"].asString()]
|
||||
(const auto& lea)
|
||||
{ return (lea.getName() == api); });
|
||||
if (foundlea != snap.end())
|
||||
{
|
||||
ops.append(found->getMetadata(oi->first));
|
||||
reportAPI(reply, *foundlea);
|
||||
}
|
||||
reply["ops"] = ops;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,9 +33,12 @@
|
|||
|
||||
#include "llpointer.h"
|
||||
#include "llrefcount.h" // LLRefCount
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/type_traits/is_base_of.hpp>
|
||||
#include <boost/type_traits/remove_pointer.hpp>
|
||||
#include <boost/utility/enable_if.hpp>
|
||||
#include <memory> // std::shared_ptr, std::unique_ptr
|
||||
#include <type_traits>
|
||||
|
||||
/**
|
||||
* LLPtrTo<TARGET>::type is either of two things:
|
||||
|
|
@ -55,14 +58,14 @@ struct LLPtrTo
|
|||
|
||||
/// specialize for subclasses of LLRefCount
|
||||
template <class T>
|
||||
struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLRefCount, T> >::type>
|
||||
struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLRefCount, T>::value >::type>
|
||||
{
|
||||
typedef LLPointer<T> type;
|
||||
};
|
||||
|
||||
/// specialize for subclasses of LLThreadSafeRefCount
|
||||
template <class T>
|
||||
struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLThreadSafeRefCount, T> >::type>
|
||||
struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLThreadSafeRefCount, T>::value >::type>
|
||||
{
|
||||
typedef LLPointer<T> type;
|
||||
};
|
||||
|
|
@ -83,4 +86,83 @@ struct LLRemovePointer< LLPointer<SOMECLASS> >
|
|||
typedef SOMECLASS type;
|
||||
};
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
||||
/*****************************************************************************
|
||||
* get_ref()
|
||||
*****************************************************************************/
|
||||
template <typename T>
|
||||
struct GetRef
|
||||
{
|
||||
// return const ref or non-const ref, depending on whether we can bind
|
||||
// a non-const lvalue ref to the argument
|
||||
const auto& operator()(const T& obj) const { return obj; }
|
||||
auto& operator()(T& obj) const { return obj; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct GetRef<const T*>
|
||||
{
|
||||
const auto& operator()(const T* ptr) const { return *ptr; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct GetRef<T*>
|
||||
{
|
||||
auto& operator()(T* ptr) const { return *ptr; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct GetRef< LLPointer<T> >
|
||||
{
|
||||
auto& operator()(LLPointer<T> ptr) const { return *ptr; }
|
||||
};
|
||||
|
||||
/// whether we're passed a pointer or a reference, return a reference
|
||||
template <typename T>
|
||||
auto& get_ref(T& ptr_or_ref)
|
||||
{
|
||||
return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const auto& get_ref(const T& ptr_or_ref)
|
||||
{
|
||||
return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* get_ptr()
|
||||
*****************************************************************************/
|
||||
// if T is any pointer type we recognize, return it unchanged
|
||||
template <typename T>
|
||||
const T* get_ptr(const T* ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
T* get_ptr(T* ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
const std::shared_ptr<T>& get_ptr(const std::shared_ptr<T>& ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
const std::unique_ptr<T>& get_ptr(const std::unique_ptr<T>& ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
const boost::shared_ptr<T>& get_ptr(const boost::shared_ptr<T>& ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
const boost::intrusive_ptr<T>& get_ptr(const boost::intrusive_ptr<T>& ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
const LLPointer<T>& get_ptr(const LLPointer<T>& ptr) { return ptr; }
|
||||
|
||||
// T is not any pointer type we recognize, take a pointer to the parameter
|
||||
template <typename T>
|
||||
const T* get_ptr(const T& obj) { return &obj; }
|
||||
|
||||
template <typename T>
|
||||
T* get_ptr(T& obj) { return &obj; }
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_LLPTRTO_H) */
|
||||
|
|
|
|||
|
|
@ -1051,3 +1051,38 @@ LLSD llsd_shallow(LLSD value, LLSD filter)
|
|||
|
||||
return shallow;
|
||||
}
|
||||
|
||||
LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args)
|
||||
{
|
||||
// LLSD supports a number of types, two of which are aggregates: Map and
|
||||
// Array. We don't try to support Map: supporting Map would seem to
|
||||
// promise that we could somehow match the string key to 'func's parameter
|
||||
// names. Uh sorry, maybe in some future version of C++ with reflection.
|
||||
if (args.isMap())
|
||||
{
|
||||
LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported"));
|
||||
}
|
||||
// We expect an LLSD array, but what the heck, treat isUndefined() as a
|
||||
// zero-length array for calling a nullary 'func'.
|
||||
if (args.isUndefined() || args.isArray())
|
||||
{
|
||||
// this works because LLSD().size() == 0
|
||||
if (args.size() != arity)
|
||||
{
|
||||
LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ",
|
||||
args.size(), "-entry LLSD array)")));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
// args is one of the scalar types
|
||||
// scalar_LLSD.size() == 0, so don't test that here.
|
||||
// You can pass a scalar LLSD only to a unary 'func'.
|
||||
if (arity != 1)
|
||||
{
|
||||
LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), "
|
||||
"LLSD ", LLSD::typeString(args.type()), ")")));
|
||||
}
|
||||
// make an array of it
|
||||
return llsd::array(args);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,12 @@
|
|||
#ifndef LL_LLSDUTIL_H
|
||||
#define LL_LLSDUTIL_H
|
||||
|
||||
#include "apply.h" // LL::invoke()
|
||||
#include "function_types.h" // LL::function_arity
|
||||
#include "llsd.h"
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
|
||||
// U32
|
||||
LL_COMMON_API LLSD ll_sd_from_U32(const U32);
|
||||
|
|
@ -332,6 +336,31 @@ private:
|
|||
T _value;
|
||||
};
|
||||
|
||||
/**
|
||||
* LLSDParam<LLSD> is for when you don't already have the target parameter
|
||||
* type in hand. Instantiate LLSDParam<LLSD>(your LLSD object), and the
|
||||
* templated conversion operator will try to select a more specific LLSDParam
|
||||
* specialization.
|
||||
*/
|
||||
template <>
|
||||
class LLSDParam<LLSD>
|
||||
{
|
||||
private:
|
||||
LLSD value_;
|
||||
|
||||
public:
|
||||
LLSDParam(const LLSD& value): value_(value) {}
|
||||
|
||||
/// if we're literally being asked for an LLSD parameter, avoid infinite
|
||||
/// recursion
|
||||
operator LLSD() const { return value_; }
|
||||
|
||||
/// otherwise, instantiate a more specific LLSDParam<T> to convert; that
|
||||
/// preserves the existing customization mechanism
|
||||
template <typename T>
|
||||
operator T() const { return LLSDParam<T>(value_); }
|
||||
};
|
||||
|
||||
/**
|
||||
* Turns out that several target types could accept an LLSD param using any of
|
||||
* a few different conversions, e.g. LLUUID's constructor can accept LLUUID or
|
||||
|
|
@ -350,7 +379,7 @@ class LLSDParam<T> \
|
|||
{ \
|
||||
public: \
|
||||
LLSDParam(const LLSD& value): \
|
||||
_value((T)value.AS()) \
|
||||
_value((T)value.AS()) \
|
||||
{} \
|
||||
\
|
||||
operator T() const { return _value; } \
|
||||
|
|
@ -555,4 +584,56 @@ struct hash<LLSD>
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
||||
/*****************************************************************************
|
||||
* apply(function, LLSD array)
|
||||
*****************************************************************************/
|
||||
// validate incoming LLSD blob, and return an LLSD array suitable to pass to
|
||||
// apply_impl()
|
||||
LLSD apply_llsd_fix(size_t arity, const LLSD& args);
|
||||
|
||||
// Derived from https://stackoverflow.com/a/20441189
|
||||
// and https://en.cppreference.com/w/cpp/utility/apply .
|
||||
// We can't simply make a tuple from the LLSD array and then apply() that
|
||||
// tuple to the function -- how would make_tuple() deduce the correct
|
||||
// parameter type for each entry? We must go directly to the target function.
|
||||
template <typename CALLABLE, std::size_t... I>
|
||||
auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
|
||||
{
|
||||
// call func(unpacked args), using generic LLSDParam<LLSD> to convert each
|
||||
// entry in 'array' to the target parameter type
|
||||
return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...);
|
||||
}
|
||||
|
||||
// use apply_n<ARITY>(function, LLSD) to call a specific arity of a variadic
|
||||
// function with (that many) items from the passed LLSD array
|
||||
template <size_t ARITY, typename CALLABLE>
|
||||
auto apply_n(CALLABLE&& func, const LLSD& args)
|
||||
{
|
||||
return apply_impl(std::forward<CALLABLE>(func),
|
||||
apply_llsd_fix(ARITY, args),
|
||||
std::make_index_sequence<ARITY>());
|
||||
}
|
||||
|
||||
/**
|
||||
* apply(function, LLSD) goes beyond C++17 std::apply(). For this case
|
||||
* @a function @emph cannot be variadic: the compiler must know at compile
|
||||
* time how many arguments to pass. This isn't Python. (But see apply_n() to
|
||||
* pass a specific number of args to a variadic function.)
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
auto apply(CALLABLE&& func, const LLSD& args)
|
||||
{
|
||||
// infer arity from the definition of func
|
||||
constexpr auto arity = function_arity<
|
||||
typename std::remove_reference<CALLABLE>::type>::value;
|
||||
// now that we have a compile-time arity, apply_n() works
|
||||
return apply_n<arity>(std::forward<CALLABLE>(func), args);
|
||||
}
|
||||
|
||||
} // namespace LL
|
||||
|
||||
#endif // LL_LLSDUTIL_H
|
||||
|
|
|
|||
|
|
@ -0,0 +1,237 @@
|
|||
/**
|
||||
* @file apply_test.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-12-19
|
||||
* @brief Test for apply.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "apply.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
#include <iomanip>
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llsd.h"
|
||||
#include "llsdutil.h"
|
||||
|
||||
// for ensure_equals
|
||||
std::ostream& operator<<(std::ostream& out, const std::vector<std::string>& stringvec)
|
||||
{
|
||||
const char* delim = "[";
|
||||
for (const auto& str : stringvec)
|
||||
{
|
||||
out << delim << std::quoted(str);
|
||||
delim = ", ";
|
||||
}
|
||||
return out << ']';
|
||||
}
|
||||
|
||||
// the above must be declared BEFORE ensure_equals(std::vector<std::string>)
|
||||
#include "../test/lltut.h"
|
||||
|
||||
/*****************************************************************************
|
||||
* TUT
|
||||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
namespace statics
|
||||
{
|
||||
/*------------------------------ data ------------------------------*/
|
||||
// Although we're using types from the LLSD namespace, we're not
|
||||
// constructing LLSD values, but rather instances of the C++ types
|
||||
// supported by LLSD.
|
||||
static LLSD::Boolean b{true};
|
||||
static LLSD::Integer i{17};
|
||||
static LLSD::Real f{3.14};
|
||||
static LLSD::String s{ "hello" };
|
||||
static LLSD::UUID uu{ "baadf00d-dead-beef-baad-feedb0ef" };
|
||||
static LLSD::Date dt{ "2022-12-19" };
|
||||
static LLSD::URI uri{ "http://secondlife.com" };
|
||||
static LLSD::Binary bin{ 0x01, 0x02, 0x03, 0x04, 0x05 };
|
||||
|
||||
static std::vector<LLSD::String> quick
|
||||
{
|
||||
"The", "quick", "brown", "fox", "etc."
|
||||
};
|
||||
|
||||
static std::array<int, 5> fibs
|
||||
{
|
||||
0, 1, 1, 2, 3
|
||||
};
|
||||
|
||||
// ensure that apply() actually reaches the target method --
|
||||
// lack of ensure_equals() failure could be due to no-op apply()
|
||||
bool called{ false };
|
||||
// capture calls from collect()
|
||||
std::vector<std::string> collected;
|
||||
|
||||
/*------------------------- test functions -------------------------*/
|
||||
void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s,
|
||||
const LLSD::UUID& uu, const LLSD::Date& dt,
|
||||
const LLSD::URI& uri, const LLSD::Binary& bin)
|
||||
{
|
||||
called = true;
|
||||
ensure_equals( "b mismatch", b, statics::b);
|
||||
ensure_equals( "i mismatch", i, statics::i);
|
||||
ensure_equals( "f mismatch", f, statics::f);
|
||||
ensure_equals( "s mismatch", s, statics::s);
|
||||
ensure_equals( "uu mismatch", uu, statics::uu);
|
||||
ensure_equals( "dt mismatch", dt, statics::dt);
|
||||
ensure_equals("uri mismatch", uri, statics::uri);
|
||||
ensure_equals("bin mismatch", bin, statics::bin);
|
||||
}
|
||||
|
||||
void strings(std::string s0, std::string s1, std::string s2, std::string s3, std::string s4)
|
||||
{
|
||||
called = true;
|
||||
ensure_equals("s0 mismatch", s0, statics::quick[0]);
|
||||
ensure_equals("s1 mismatch", s1, statics::quick[1]);
|
||||
ensure_equals("s2 mismatch", s2, statics::quick[2]);
|
||||
ensure_equals("s3 mismatch", s3, statics::quick[3]);
|
||||
ensure_equals("s4 mismatch", s4, statics::quick[4]);
|
||||
}
|
||||
|
||||
void ints(int i0, int i1, int i2, int i3, int i4)
|
||||
{
|
||||
called = true;
|
||||
ensure_equals("i0 mismatch", i0, statics::fibs[0]);
|
||||
ensure_equals("i1 mismatch", i1, statics::fibs[1]);
|
||||
ensure_equals("i2 mismatch", i2, statics::fibs[2]);
|
||||
ensure_equals("i3 mismatch", i3, statics::fibs[3]);
|
||||
ensure_equals("i4 mismatch", i4, statics::fibs[4]);
|
||||
}
|
||||
|
||||
void sdfunc(const LLSD& sd)
|
||||
{
|
||||
called = true;
|
||||
ensure_equals("sd mismatch", sd.asInteger(), statics::i);
|
||||
}
|
||||
|
||||
void intfunc(int i)
|
||||
{
|
||||
called = true;
|
||||
ensure_equals("i mismatch", i, statics::i);
|
||||
}
|
||||
|
||||
void voidfunc()
|
||||
{
|
||||
called = true;
|
||||
}
|
||||
|
||||
// recursion tail
|
||||
void collect()
|
||||
{
|
||||
called = true;
|
||||
}
|
||||
|
||||
// collect(arbitrary)
|
||||
template <typename... ARGS>
|
||||
void collect(const std::string& first, ARGS&&... rest)
|
||||
{
|
||||
statics::collected.push_back(first);
|
||||
collect(std::forward<ARGS>(rest)...);
|
||||
}
|
||||
} // namespace statics
|
||||
|
||||
struct apply_data
|
||||
{
|
||||
apply_data()
|
||||
{
|
||||
// reset called before each test
|
||||
statics::called = false;
|
||||
statics::collected.clear();
|
||||
}
|
||||
};
|
||||
typedef test_group<apply_data> apply_group;
|
||||
typedef apply_group::object object;
|
||||
apply_group applygrp("apply");
|
||||
|
||||
template<> template<>
|
||||
void object::test<1>()
|
||||
{
|
||||
set_test_name("apply(tuple)");
|
||||
LL::apply(statics::various,
|
||||
std::make_tuple(statics::b, statics::i, statics::f, statics::s,
|
||||
statics::uu, statics::dt, statics::uri, statics::bin));
|
||||
ensure("apply(tuple) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<2>()
|
||||
{
|
||||
set_test_name("apply(array)");
|
||||
LL::apply(statics::ints, statics::fibs);
|
||||
ensure("apply(array) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<3>()
|
||||
{
|
||||
set_test_name("apply(vector)");
|
||||
LL::apply(statics::strings, statics::quick);
|
||||
ensure("apply(vector) failed", statics::called);
|
||||
}
|
||||
|
||||
// The various apply(LLSD) tests exercise only the success cases because
|
||||
// the failure cases trigger assert() fail, which is hard to catch.
|
||||
template<> template<>
|
||||
void object::test<4>()
|
||||
{
|
||||
set_test_name("apply(LLSD())");
|
||||
LL::apply(statics::voidfunc, LLSD());
|
||||
ensure("apply(LLSD()) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<5>()
|
||||
{
|
||||
set_test_name("apply(fn(int), LLSD scalar)");
|
||||
LL::apply(statics::intfunc, LLSD(statics::i));
|
||||
ensure("apply(fn(int), LLSD scalar) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<6>()
|
||||
{
|
||||
set_test_name("apply(fn(LLSD), LLSD scalar)");
|
||||
// This test verifies that LLSDParam<LLSD> doesn't send the compiler
|
||||
// into infinite recursion when the target is itself LLSD.
|
||||
LL::apply(statics::sdfunc, LLSD(statics::i));
|
||||
ensure("apply(fn(LLSD), LLSD scalar) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<7>()
|
||||
{
|
||||
set_test_name("apply(LLSD array)");
|
||||
LL::apply(statics::various,
|
||||
llsd::array(statics::b, statics::i, statics::f, statics::s,
|
||||
statics::uu, statics::dt, statics::uri, statics::bin));
|
||||
ensure("apply(LLSD array) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<8>()
|
||||
{
|
||||
set_test_name("VAPPLY()");
|
||||
// Make a std::array<std::string> from statics::quick. We can't call a
|
||||
// variadic function with a data structure of dynamic length.
|
||||
std::array<std::string, 5> strray;
|
||||
for (size_t i = 0; i < strray.size(); ++i)
|
||||
strray[i] = statics::quick[i];
|
||||
// This doesn't work: the compiler doesn't know which overload of
|
||||
// collect() to pass to LL::apply().
|
||||
// LL::apply(statics::collect, strray);
|
||||
// That's what VAPPLY() is for.
|
||||
VAPPLY(statics::collect, strray);
|
||||
ensure("VAPPLY() failed", statics::called);
|
||||
ensure_equals("collected mismatch", statics::collected, statics::quick);
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* @file lazyeventapi_test.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-06-18
|
||||
* @brief Test for lazyeventapi.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "lazyeventapi.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "llevents.h"
|
||||
#include "llsdutil.h"
|
||||
|
||||
// observable side effect, solely for testing
|
||||
static LLSD data;
|
||||
|
||||
// LLEventAPI listener subclass
|
||||
class MyListener: public LLEventAPI
|
||||
{
|
||||
public:
|
||||
// need this trivial forwarding constructor
|
||||
// (of course do any other initialization your subclass requires)
|
||||
MyListener(const LL::LazyEventAPIParams& params):
|
||||
LLEventAPI(params)
|
||||
{}
|
||||
|
||||
// example operation, registered by LazyEventAPI subclass below
|
||||
void set_data(const LLSD& event)
|
||||
{
|
||||
data = event["data"];
|
||||
}
|
||||
};
|
||||
|
||||
// LazyEventAPI registrar subclass
|
||||
class MyRegistrar: public LL::LazyEventAPI<MyListener>
|
||||
{
|
||||
using super = LL::LazyEventAPI<MyListener>;
|
||||
using super::listener;
|
||||
public:
|
||||
// LazyEventAPI subclass initializes like a classic LLEventAPI subclass
|
||||
// constructor, with API name and desc plus add() calls for the defined
|
||||
// operations
|
||||
MyRegistrar():
|
||||
super("Test", "This is a test LLEventAPI")
|
||||
{
|
||||
add("set", "This is a set operation", &listener::set_data);
|
||||
}
|
||||
};
|
||||
// Normally we'd declare a static instance of MyRegistrar -- but because we
|
||||
// want to test both with and without, defer declaration to individual test
|
||||
// methods.
|
||||
|
||||
/*****************************************************************************
|
||||
* TUT
|
||||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
struct lazyeventapi_data
|
||||
{
|
||||
lazyeventapi_data()
|
||||
{
|
||||
// before every test, reset 'data'
|
||||
data.clear();
|
||||
}
|
||||
~lazyeventapi_data()
|
||||
{
|
||||
// after every test, reset LLEventPumps
|
||||
LLEventPumps::deleteSingleton();
|
||||
}
|
||||
};
|
||||
typedef test_group<lazyeventapi_data> lazyeventapi_group;
|
||||
typedef lazyeventapi_group::object object;
|
||||
lazyeventapi_group lazyeventapigrp("lazyeventapi");
|
||||
|
||||
template<> template<>
|
||||
void object::test<1>()
|
||||
{
|
||||
set_test_name("LazyEventAPI");
|
||||
// this is where the magic (should) happen
|
||||
// 'register' still a keyword until C++17
|
||||
MyRegistrar regster;
|
||||
LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "hey"));
|
||||
ensure_equals("failed to set data", data.asString(), "hey");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<2>()
|
||||
{
|
||||
set_test_name("No LazyEventAPI");
|
||||
// Because the MyRegistrar declaration in test<1>() is local, because
|
||||
// it has been destroyed, we fully expect NOT to reach a MyListener
|
||||
// instance with this post.
|
||||
LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "moot"));
|
||||
ensure("accidentally set data", ! data.isDefined());
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<3>()
|
||||
{
|
||||
set_test_name("LazyEventAPI metadata");
|
||||
MyRegistrar regster;
|
||||
// Of course we have 'regster' in hand; we don't need to search for
|
||||
// it. But this next test verifies that we can find (all) LazyEventAPI
|
||||
// instances using LazyEventAPIBase::instance_snapshot. Normally we
|
||||
// wouldn't search; normally we'd just look at each instance in the
|
||||
// loop body.
|
||||
const MyRegistrar* found = nullptr;
|
||||
for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot())
|
||||
if ((found = dynamic_cast<const MyRegistrar*>(®istrar)))
|
||||
break;
|
||||
ensure("Failed to find MyRegistrar via LLInstanceTracker", found);
|
||||
|
||||
ensure_equals("wrong API name", found->getName(), "Test");
|
||||
ensure_contains("wrong API desc", found->getDesc(), "test LLEventAPI");
|
||||
ensure_equals("wrong API field", found->getDispatchKey(), "op");
|
||||
// Normally we'd just iterate over *found. But for test purposes,
|
||||
// actually capture the range of NameDesc pairs in a vector.
|
||||
std::vector<LL::LazyEventAPIBase::NameDesc> ops{ found->begin(), found->end() };
|
||||
ensure_equals("failed to find operations", ops.size(), 1);
|
||||
ensure_equals("wrong operation name", ops[0].first, "set");
|
||||
ensure_contains("wrong operation desc", ops[0].second, "set operation");
|
||||
LLSD metadata{ found->getMetadata(ops[0].first) };
|
||||
ensure_equals("bad metadata name", metadata["name"].asString(), ops[0].first);
|
||||
ensure_equals("bad metadata desc", metadata["desc"].asString(), ops[0].second);
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
@ -18,9 +18,12 @@
|
|||
// external library headers
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "lleventfilter.h"
|
||||
#include "llsd.h"
|
||||
#include "llsdutil.h"
|
||||
#include "llevents.h"
|
||||
#include "stringize.h"
|
||||
#include "StringVec.h"
|
||||
#include "tests/wrapllerrs.h"
|
||||
#include "../test/catch_and_store_what_in.h"
|
||||
#include "../test/debug.h"
|
||||
|
|
@ -32,8 +35,6 @@
|
|||
#include <boost/bind.hpp>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/range.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#define foreach BOOST_FOREACH
|
||||
|
||||
#include <boost/lambda/lambda.hpp>
|
||||
|
||||
|
|
@ -205,7 +206,7 @@ struct Vars
|
|||
void methodnb(NPARAMSb)
|
||||
{
|
||||
std::ostringstream vbin;
|
||||
foreach(U8 byte, bin)
|
||||
for (U8 byte: bin)
|
||||
{
|
||||
vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte);
|
||||
}
|
||||
|
|
@ -315,6 +316,31 @@ void freenb(NPARAMSb)
|
|||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
void ensure_has(const std::string& outer, const std::string& inner)
|
||||
{
|
||||
ensure(stringize("'", outer, "' does not contain '", inner, "'"),
|
||||
outer.find(inner) != std::string::npos);
|
||||
}
|
||||
|
||||
template <typename CALLABLE>
|
||||
std::string call_exc(CALLABLE&& func, const std::string& exc_frag)
|
||||
{
|
||||
std::string what =
|
||||
catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
|
||||
ensure_has(what, exc_frag);
|
||||
return what;
|
||||
}
|
||||
|
||||
template <typename CALLABLE>
|
||||
void call_logerr(CALLABLE&& func, const std::string& frag)
|
||||
{
|
||||
CaptureLog capture;
|
||||
// the error should be logged; we just need to stop the exception
|
||||
// propagating
|
||||
catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
|
||||
capture.messageWith(frag);
|
||||
}
|
||||
|
||||
struct lleventdispatcher_data
|
||||
{
|
||||
Debug debug{"test"};
|
||||
|
|
@ -397,9 +423,9 @@ namespace tut
|
|||
work.add(name, desc, &Dispatcher::cmethod1, required);
|
||||
// Non-subclass method with/out required params
|
||||
addf("method1", "method1", &v);
|
||||
work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1));
|
||||
work.add(name, desc, [this](const LLSD& args){ return v.method1(args); });
|
||||
addf("method1_req", "method1", &v);
|
||||
work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1), required);
|
||||
work.add(name, desc, [this](const LLSD& args){ return v.method1(args); }, required);
|
||||
|
||||
/*--------------- Arbitrary params, array style ----------------*/
|
||||
|
||||
|
|
@ -461,7 +487,7 @@ namespace tut
|
|||
debug("dft_array_full:\n",
|
||||
dft_array_full);
|
||||
// Partial defaults arrays.
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
LLSD::Integer partition(std::min(partial_offset, dft_array_full[a].size()));
|
||||
dft_array_partial[a] =
|
||||
|
|
@ -471,7 +497,7 @@ namespace tut
|
|||
debug("dft_array_partial:\n",
|
||||
dft_array_partial);
|
||||
|
||||
foreach(LLSD::String a, ab)
|
||||
for(LLSD::String a: ab)
|
||||
{
|
||||
// Generate full defaults maps by zipping (params, dft_array_full).
|
||||
dft_map_full[a] = zipmap(params[a], dft_array_full[a]);
|
||||
|
|
@ -583,6 +609,7 @@ namespace tut
|
|||
|
||||
void addf(const std::string& n, const std::string& d, Vars* v)
|
||||
{
|
||||
debug("addf('", n, "', '", d, "')");
|
||||
// This method is to capture in our own DescMap the name and
|
||||
// description of every registered function, for metadata query
|
||||
// testing.
|
||||
|
|
@ -598,19 +625,14 @@ namespace tut
|
|||
{
|
||||
// Copy descs to a temp map of same type.
|
||||
DescMap forgotten(descs.begin(), descs.end());
|
||||
// LLEventDispatcher intentionally provides only const_iterator:
|
||||
// since dereferencing that iterator generates values on the fly,
|
||||
// it's meaningless to have a modifiable iterator. But since our
|
||||
// 'work' object isn't const, by default BOOST_FOREACH() wants to
|
||||
// use non-const iterators. Persuade it to use the const_iterator.
|
||||
foreach(LLEventDispatcher::NameDesc nd, const_cast<const Dispatcher&>(work))
|
||||
for (LLEventDispatcher::NameDesc nd: work)
|
||||
{
|
||||
DescMap::iterator found = forgotten.find(nd.first);
|
||||
ensure(STRINGIZE("LLEventDispatcher records function '" << nd.first
|
||||
<< "' we didn't enter"),
|
||||
ensure(stringize("LLEventDispatcher records function '", nd.first,
|
||||
"' we didn't enter"),
|
||||
found != forgotten.end());
|
||||
ensure_equals(STRINGIZE("LLEventDispatcher desc '" << nd.second <<
|
||||
"' doesn't match what we entered: '" << found->second << "'"),
|
||||
ensure_equals(stringize("LLEventDispatcher desc '", nd.second,
|
||||
"' doesn't match what we entered: '", found->second, "'"),
|
||||
nd.second, found->second);
|
||||
// found in our map the name from LLEventDispatcher, good, erase
|
||||
// our map entry
|
||||
|
|
@ -621,41 +643,49 @@ namespace tut
|
|||
std::ostringstream out;
|
||||
out << "LLEventDispatcher failed to report";
|
||||
const char* delim = ": ";
|
||||
foreach(const DescMap::value_type& fme, forgotten)
|
||||
for (const DescMap::value_type& fme: forgotten)
|
||||
{
|
||||
out << delim << fme.first;
|
||||
delim = ", ";
|
||||
}
|
||||
ensure(out.str(), false);
|
||||
throw failure(out.str());
|
||||
}
|
||||
}
|
||||
|
||||
Vars* varsfor(const std::string& name)
|
||||
{
|
||||
VarsMap::const_iterator found = funcvars.find(name);
|
||||
ensure(STRINGIZE("No Vars* for " << name), found != funcvars.end());
|
||||
ensure(STRINGIZE("NULL Vars* for " << name), found->second);
|
||||
ensure(stringize("No Vars* for ", name), found != funcvars.end());
|
||||
ensure(stringize("NULL Vars* for ", name), found->second);
|
||||
return found->second;
|
||||
}
|
||||
|
||||
void ensure_has(const std::string& outer, const std::string& inner)
|
||||
std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
|
||||
{
|
||||
ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(),
|
||||
outer.find(inner) != std::string::npos);
|
||||
return tut::call_exc(
|
||||
[this, func, args]()
|
||||
{
|
||||
if (func.empty())
|
||||
{
|
||||
work(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
work(func, args);
|
||||
}
|
||||
},
|
||||
exc_frag);
|
||||
}
|
||||
|
||||
void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
|
||||
void call_logerr(const std::string& func, const LLSD& args, const std::string& frag)
|
||||
{
|
||||
std::string threw = catch_what<std::runtime_error>([this, &func, &args](){
|
||||
work(func, args);
|
||||
});
|
||||
ensure_has(threw, exc_frag);
|
||||
tut::call_logerr([this, func, args](){ work(func, args); }, frag);
|
||||
}
|
||||
|
||||
LLSD getMetadata(const std::string& name)
|
||||
{
|
||||
LLSD meta(work.getMetadata(name));
|
||||
ensure(STRINGIZE("No metadata for " << name), meta.isDefined());
|
||||
ensure(stringize("No metadata for ", name), meta.isDefined());
|
||||
return meta;
|
||||
}
|
||||
|
||||
|
|
@ -724,7 +754,7 @@ namespace tut
|
|||
set_test_name("map-style registration with non-array params");
|
||||
// Pass "param names" as scalar or as map
|
||||
LLSD attempts(llsd::array(17, LLSDMap("pi", 3.14)("two", 2)));
|
||||
foreach(LLSD ae, inArray(attempts))
|
||||
for (LLSD ae: inArray(attempts))
|
||||
{
|
||||
std::string threw = catch_what<std::exception>([this, &ae](){
|
||||
work.add("freena_err", "freena", freena, ae);
|
||||
|
|
@ -799,7 +829,7 @@ namespace tut
|
|||
{
|
||||
set_test_name("query Callables with/out required params");
|
||||
LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1"));
|
||||
foreach(LLSD nm, inArray(names))
|
||||
for (LLSD nm: inArray(names))
|
||||
{
|
||||
LLSD metadata(getMetadata(nm));
|
||||
ensure_equals("name mismatch", metadata["name"], nm);
|
||||
|
|
@ -828,19 +858,19 @@ namespace tut
|
|||
(5, llsd::array("freena_array", "smethodna_array", "methodna_array")),
|
||||
llsd::array
|
||||
(5, llsd::array("freenb_array", "smethodnb_array", "methodnb_array"))));
|
||||
foreach(LLSD ae, inArray(expected))
|
||||
for (LLSD ae: inArray(expected))
|
||||
{
|
||||
LLSD::Integer arity(ae[0].asInteger());
|
||||
LLSD names(ae[1]);
|
||||
LLSD req(LLSD::emptyArray());
|
||||
if (arity)
|
||||
req[arity - 1] = LLSD();
|
||||
foreach(LLSD nm, inArray(names))
|
||||
for (LLSD nm: inArray(names))
|
||||
{
|
||||
LLSD metadata(getMetadata(nm));
|
||||
ensure_equals("name mismatch", metadata["name"], nm);
|
||||
ensure_equals(metadata["desc"].asString(), descs[nm]);
|
||||
ensure_equals(STRINGIZE("mismatched required for " << nm.asString()),
|
||||
ensure_equals(stringize("mismatched required for ", nm.asString()),
|
||||
metadata["required"], req);
|
||||
ensure("should not have optional", metadata["optional"].isUndefined());
|
||||
}
|
||||
|
|
@ -854,7 +884,7 @@ namespace tut
|
|||
// - (Free function | non-static method), map style, no params (ergo
|
||||
// no defaults)
|
||||
LLSD names(llsd::array("free0_map", "smethod0_map", "method0_map"));
|
||||
foreach(LLSD nm, inArray(names))
|
||||
for (LLSD nm: inArray(names))
|
||||
{
|
||||
LLSD metadata(getMetadata(nm));
|
||||
ensure_equals("name mismatch", metadata["name"], nm);
|
||||
|
|
@ -884,7 +914,7 @@ namespace tut
|
|||
llsd::array("smethodnb_map_adft", "smethodnb_map_mdft"),
|
||||
llsd::array("methodna_map_adft", "methodna_map_mdft"),
|
||||
llsd::array("methodnb_map_adft", "methodnb_map_mdft")));
|
||||
foreach(LLSD eq, inArray(equivalences))
|
||||
for (LLSD eq: inArray(equivalences))
|
||||
{
|
||||
LLSD adft(eq[0]);
|
||||
LLSD mdft(eq[1]);
|
||||
|
|
@ -898,8 +928,8 @@ namespace tut
|
|||
ensure_equals("mdft name", mdft, mmeta["name"]);
|
||||
ameta.erase("name");
|
||||
mmeta.erase("name");
|
||||
ensure_equals(STRINGIZE("metadata for " << adft.asString()
|
||||
<< " vs. " << mdft.asString()),
|
||||
ensure_equals(stringize("metadata for ", adft.asString(),
|
||||
" vs. ", mdft.asString()),
|
||||
ameta, mmeta);
|
||||
}
|
||||
}
|
||||
|
|
@ -915,7 +945,7 @@ namespace tut
|
|||
// params are required. Also maps containing left requirements for
|
||||
// partial defaults arrays. Also defaults maps from defaults arrays.
|
||||
LLSD allreq, leftreq, rightdft;
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
// The map in which all params are required uses params[a] as
|
||||
// keys, with all isUndefined() as values. We can accomplish that
|
||||
|
|
@ -943,9 +973,9 @@ namespace tut
|
|||
// Generate maps containing parameter names not provided by the
|
||||
// dft_map_partial maps.
|
||||
LLSD skipreq(allreq);
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
foreach(const MapEntry& me, inMap(dft_map_partial[a]))
|
||||
for (const MapEntry& me: inMap(dft_map_partial[a]))
|
||||
{
|
||||
skipreq[a].erase(me.first);
|
||||
}
|
||||
|
|
@ -990,7 +1020,7 @@ namespace tut
|
|||
(llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"),
|
||||
llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional
|
||||
|
||||
foreach(LLSD grp, inArray(groups))
|
||||
for (LLSD grp: inArray(groups))
|
||||
{
|
||||
// Internal structure of each group in 'groups':
|
||||
LLSD names(grp[0]);
|
||||
|
|
@ -1003,14 +1033,14 @@ namespace tut
|
|||
optional);
|
||||
|
||||
// Loop through 'names'
|
||||
foreach(LLSD nm, inArray(names))
|
||||
for (LLSD nm: inArray(names))
|
||||
{
|
||||
LLSD metadata(getMetadata(nm));
|
||||
ensure_equals("name mismatch", metadata["name"], nm);
|
||||
ensure_equals(nm.asString(), metadata["desc"].asString(), descs[nm]);
|
||||
ensure_equals(STRINGIZE(nm << " required mismatch"),
|
||||
ensure_equals(stringize(nm, " required mismatch"),
|
||||
metadata["required"], required);
|
||||
ensure_equals(STRINGIZE(nm << " optional mismatch"),
|
||||
ensure_equals(stringize(nm, " optional mismatch"),
|
||||
metadata["optional"], optional);
|
||||
}
|
||||
}
|
||||
|
|
@ -1031,13 +1061,7 @@ namespace tut
|
|||
{
|
||||
set_test_name("call with bad name");
|
||||
call_exc("freek", LLSD(), "not found");
|
||||
// We don't have a comparable helper function for the one-arg
|
||||
// operator() method, and it's not worth building one just for this
|
||||
// case. Write it out.
|
||||
std::string threw = catch_what<std::runtime_error>([this](){
|
||||
work(LLSDMap("op", "freek"));
|
||||
});
|
||||
ensure_has(threw, "bad");
|
||||
std::string threw = call_exc("", LLSDMap("op", "freek"), "bad");
|
||||
ensure_has(threw, "op");
|
||||
ensure_has(threw, "freek");
|
||||
}
|
||||
|
|
@ -1079,7 +1103,7 @@ namespace tut
|
|||
// LLSD value matching 'required' according to llsd_matches() rules.
|
||||
LLSD matching(LLSDMap("d", 3.14)("array", llsd::array("answer", true, answer)));
|
||||
// Okay, walk through 'tests'.
|
||||
foreach(const CallablesTriple& tr, tests)
|
||||
for (const CallablesTriple& tr: tests)
|
||||
{
|
||||
// Should be able to pass 'answer' to Callables registered
|
||||
// without 'required'.
|
||||
|
|
@ -1087,7 +1111,7 @@ namespace tut
|
|||
ensure_equals("answer mismatch", tr.llsd, answer);
|
||||
// Should NOT be able to pass 'answer' to Callables registered
|
||||
// with 'required'.
|
||||
call_exc(tr.name_req, answer, "bad request");
|
||||
call_logerr(tr.name_req, answer, "bad request");
|
||||
// But SHOULD be able to pass 'matching' to Callables registered
|
||||
// with 'required'.
|
||||
work(tr.name_req, matching);
|
||||
|
|
@ -1101,17 +1125,20 @@ namespace tut
|
|||
set_test_name("passing wrong args to (map | array)-style registrations");
|
||||
|
||||
// Pass scalar/map to array-style functions, scalar/array to map-style
|
||||
// functions. As that validation happens well before we engage the
|
||||
// argument magic, it seems pointless to repeat this with every
|
||||
// variation: (free function | non-static method), (no | arbitrary)
|
||||
// args. We should only need to engage it for one map-style
|
||||
// registration and one array-style registration.
|
||||
std::string array_exc("needs an args array");
|
||||
call_exc("free0_array", 17, array_exc);
|
||||
call_exc("free0_array", LLSDMap("pi", 3.14), array_exc);
|
||||
// functions. It seems pointless to repeat this with every variation:
|
||||
// (free function | non-static method), (no | arbitrary) args. We
|
||||
// should only need to engage it for one map-style registration and
|
||||
// one array-style registration.
|
||||
// Now that LLEventDispatcher has been extended to treat an LLSD
|
||||
// scalar as a single-entry array, the error we expect in this case is
|
||||
// that apply() is trying to pass that non-empty array to a nullary
|
||||
// function.
|
||||
call_logerr("free0_array", 17, "LL::apply");
|
||||
// similarly, apply() doesn't accept an LLSD Map
|
||||
call_logerr("free0_array", LLSDMap("pi", 3.14), "unsupported");
|
||||
|
||||
std::string map_exc("needs a map");
|
||||
call_exc("free0_map", 17, map_exc);
|
||||
call_logerr("free0_map", 17, map_exc);
|
||||
// Passing an array to a map-style function works now! No longer an
|
||||
// error case!
|
||||
// call_exc("free0_map", llsd::array("a", "b"), map_exc);
|
||||
|
|
@ -1125,7 +1152,7 @@ namespace tut
|
|||
("free0_array", "free0_map",
|
||||
"smethod0_array", "smethod0_map",
|
||||
"method0_array", "method0_map"));
|
||||
foreach(LLSD name, inArray(names))
|
||||
for (LLSD name: inArray(names))
|
||||
{
|
||||
// Look up the Vars instance for this function.
|
||||
Vars* vars(varsfor(name));
|
||||
|
|
@ -1150,15 +1177,21 @@ namespace tut
|
|||
template<> template<>
|
||||
void object::test<19>()
|
||||
{
|
||||
set_test_name("call array-style functions with too-short arrays");
|
||||
// Could have two different too-short arrays, one for *na and one for
|
||||
// *nb, but since they both take 5 params...
|
||||
set_test_name("call array-style functions with wrong-length arrays");
|
||||
// Could have different wrong-length arrays for *na and for *nb, but
|
||||
// since they both take 5 params...
|
||||
LLSD tooshort(llsd::array("this", "array", "too", "short"));
|
||||
foreach(const LLSD& funcsab, inArray(array_funcs))
|
||||
LLSD toolong (llsd::array("this", "array", "is", "one", "too", "long"));
|
||||
LLSD badargs (llsd::array(tooshort, toolong));
|
||||
for (const LLSD& toosomething: inArray(badargs))
|
||||
{
|
||||
foreach(const llsd::MapEntry& e, inMap(funcsab))
|
||||
for (const LLSD& funcsab: inArray(array_funcs))
|
||||
{
|
||||
call_exc(e.second, tooshort, "requires more arguments");
|
||||
for (const llsd::MapEntry& e: inMap(funcsab))
|
||||
{
|
||||
// apply() complains about wrong number of array entries
|
||||
call_logerr(e.second, toosomething, "LL::apply");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1166,7 +1199,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)
|
||||
{
|
||||
|
|
@ -1178,40 +1211,25 @@ namespace tut
|
|||
LLDate("2011-02-03T15:07:00Z"),
|
||||
LLURI("http://secondlife.com"),
|
||||
binary)));
|
||||
LLSD argsplus(args);
|
||||
argsplus["a"].append("bogus");
|
||||
argsplus["b"].append("bogus");
|
||||
LLSD expect;
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
expect[a] = zipmap(params[a], args[a]);
|
||||
}
|
||||
// Adjust expect["a"]["cp"] for special Vars::cp treatment.
|
||||
expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'";
|
||||
expect["a"]["cp"] = stringize("'", expect["a"]["cp"].asString(), "'");
|
||||
debug("expect: ", expect);
|
||||
|
||||
// Use substantially the same logic for args and argsplus
|
||||
LLSD argsarrays(llsd::array(args, argsplus));
|
||||
// So i==0 selects 'args', i==1 selects argsplus
|
||||
for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i)
|
||||
for (const LLSD& funcsab: inArray(array_funcs))
|
||||
{
|
||||
foreach(const LLSD& funcsab, inArray(array_funcs))
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
foreach(LLSD::String a, ab)
|
||||
{
|
||||
// Reset the Vars instance before each call
|
||||
Vars* vars(varsfor(funcsab[a]));
|
||||
*vars = Vars();
|
||||
work(funcsab[a], argsarrays[i][a]);
|
||||
ensure_llsd(STRINGIZE(funcsab[a].asString() <<
|
||||
": expect[\"" << a << "\"] mismatch"),
|
||||
vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
|
||||
|
||||
// TODO: in the i==1 or argsplus case, intercept LL_WARNS
|
||||
// output? Even without that, using argsplus verifies that
|
||||
// passing too many args isn't fatal; it works -- but
|
||||
// would be nice to notice the warning too.
|
||||
}
|
||||
// Reset the Vars instance before each call
|
||||
Vars* vars(varsfor(funcsab[a]));
|
||||
*vars = Vars();
|
||||
work(funcsab[a], args[a]);
|
||||
ensure_llsd(stringize(funcsab[a].asString(), ": expect[\"", a, "\"] mismatch"),
|
||||
vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1239,7 +1257,7 @@ namespace tut
|
|||
("a", llsd::array(false, 255, 98.6, 1024.5, "pointer"))
|
||||
("b", llsd::array("object", LLUUID::generateNewID(), LLDate::now(), LLURI("http://wiki.lindenlab.com/wiki"), LLSD::Binary(boost::begin(binary), boost::end(binary)))));
|
||||
LLSD array_overfull(array_full);
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
array_overfull[a].append("bogus");
|
||||
}
|
||||
|
|
@ -1253,7 +1271,7 @@ namespace tut
|
|||
ensure_not_equals("UUID collision",
|
||||
array_full["b"][1].asUUID(), dft_array_full["b"][1].asUUID());
|
||||
LLSD map_full, map_overfull;
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
map_full[a] = zipmap(params[a], array_full[a]);
|
||||
map_overfull[a] = map_full[a];
|
||||
|
|
@ -1294,21 +1312,360 @@ namespace tut
|
|||
"freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft")));
|
||||
// Treat (full | overfull) (array | map) the same.
|
||||
LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull));
|
||||
foreach(const LLSD& args, inArray(argssets))
|
||||
for (const LLSD& args: inArray(argssets))
|
||||
{
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
foreach(LLSD::String name, inArray(names[a]))
|
||||
for (LLSD::String name: inArray(names[a]))
|
||||
{
|
||||
// Reset the Vars instance
|
||||
Vars* vars(varsfor(name));
|
||||
*vars = Vars();
|
||||
work(name, args[a]);
|
||||
ensure_llsd(STRINGIZE(name << ": expect[\"" << a << "\"] mismatch"),
|
||||
ensure_llsd(stringize(name, ": expect[\"", a, "\"] mismatch"),
|
||||
vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits
|
||||
// intercept LL_WARNS for the two overfull cases?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DispatchResult: public LLDispatchListener
|
||||
{
|
||||
using DR = DispatchResult;
|
||||
|
||||
DispatchResult(): LLDispatchListener("results", "op")
|
||||
{
|
||||
add("strfunc", "return string", &DR::strfunc);
|
||||
add("voidfunc", "void function", &DR::voidfunc);
|
||||
add("emptyfunc", "return empty LLSD", &DR::emptyfunc);
|
||||
add("intfunc", "return Integer LLSD", &DR::intfunc);
|
||||
add("llsdfunc", "return passed LLSD", &DR::llsdfunc);
|
||||
add("mapfunc", "return map LLSD", &DR::mapfunc);
|
||||
add("arrayfunc", "return array LLSD", &DR::arrayfunc);
|
||||
}
|
||||
|
||||
std::string strfunc(const std::string& str) const { return "got " + str; }
|
||||
void voidfunc() const {}
|
||||
LLSD emptyfunc() const { return {}; }
|
||||
int intfunc(int i) const { return -i; }
|
||||
LLSD llsdfunc(const LLSD& event) const
|
||||
{
|
||||
LLSD result{ event };
|
||||
result["with"] = "string";
|
||||
return result;
|
||||
}
|
||||
LLSD mapfunc(int i, const std::string& str) const
|
||||
{
|
||||
return llsd::map("i", intfunc(i), "str", strfunc(str));
|
||||
}
|
||||
LLSD arrayfunc(int i, const std::string& str) const
|
||||
{
|
||||
return llsd::array(intfunc(i), strfunc(str));
|
||||
}
|
||||
};
|
||||
|
||||
template<> template<>
|
||||
void object::test<23>()
|
||||
{
|
||||
set_test_name("string result");
|
||||
DispatchResult service;
|
||||
LLSD result{ service("strfunc", "a string") };
|
||||
ensure_equals("strfunc() mismatch", result.asString(), "got 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", -17) };
|
||||
ensure_equals("intfunc() mismatch", result.asInteger(), 17);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<26>()
|
||||
{
|
||||
set_test_name("LLSD echo");
|
||||
DispatchResult service;
|
||||
LLSD result{ service("llsdfunc", llsd::map("op", "llsdfunc", "reqid", 17)) };
|
||||
ensure_equals("llsdfunc() mismatch", result,
|
||||
llsd::map("op", "llsdfunc", "reqid", 17, "with", "string"));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<27>()
|
||||
{
|
||||
set_test_name("map LLSD result");
|
||||
DispatchResult service;
|
||||
LLSD result{ service("mapfunc", llsd::array(-12, "value")) };
|
||||
ensure_equals("mapfunc() mismatch", result, llsd::map("i", 12, "str", "got value"));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<28>()
|
||||
{
|
||||
set_test_name("array LLSD result");
|
||||
DispatchResult service;
|
||||
LLSD result{ service("arrayfunc", llsd::array(-8, "word")) };
|
||||
ensure_equals("arrayfunc() mismatch", result, llsd::array(8, "got word"));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<29>()
|
||||
{
|
||||
set_test_name("listener error, no reply");
|
||||
DispatchResult service;
|
||||
tut::call_exc(
|
||||
[&service]()
|
||||
{ service.post(llsd::map("op", "nosuchfunc", "reqid", 17)); },
|
||||
"nosuchfunc");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<30>()
|
||||
{
|
||||
set_test_name("listener error with reply");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map("op", "nosuchfunc", "reqid", 17, "reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure("no reply", reply.isDefined());
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
ensure_has(reply["error"].asString(), "nosuchfunc");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<31>()
|
||||
{
|
||||
set_test_name("listener call to void function");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
result.set("non-empty");
|
||||
for (const auto& func: StringVec{ "voidfunc", "emptyfunc" })
|
||||
{
|
||||
service.post(llsd::map(
|
||||
"op", func,
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
ensure_equals("reply from " + func, result.get().asString(), "non-empty");
|
||||
}
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<32>()
|
||||
{
|
||||
set_test_name("listener call to string function");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", "strfunc",
|
||||
"args", llsd::array("a string"),
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
ensure_equals("bad reply from strfunc", reply["data"].asString(), "got a string");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<33>()
|
||||
{
|
||||
set_test_name("listener call to map function");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", "mapfunc",
|
||||
"args", llsd::array(-7, "value"),
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
ensure_equals("bad i from mapfunc", reply["i"].asInteger(), 7);
|
||||
ensure_equals("bad str from mapfunc", reply["str"], "got value");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<34>()
|
||||
{
|
||||
set_test_name("batched map success");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", llsd::map(
|
||||
"strfunc", "some string",
|
||||
"intfunc", 2,
|
||||
"voidfunc", LLSD(),
|
||||
"arrayfunc", llsd::array(-5, "other string")),
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
reply.erase("reqid");
|
||||
ensure_equals(
|
||||
"bad map batch",
|
||||
reply,
|
||||
llsd::map(
|
||||
"strfunc", "got some string",
|
||||
"intfunc", -2,
|
||||
"voidfunc", LLSD(),
|
||||
"arrayfunc", llsd::array(5, "got other string")));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<35>()
|
||||
{
|
||||
set_test_name("batched map error");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", llsd::map(
|
||||
"badfunc", 34, // !
|
||||
"strfunc", "some string",
|
||||
"intfunc", 2,
|
||||
"missing", LLSD(), // !
|
||||
"voidfunc", LLSD(),
|
||||
"arrayfunc", llsd::array(-5, "other string")),
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
reply.erase("reqid");
|
||||
auto error{ reply["error"].asString() };
|
||||
reply.erase("error");
|
||||
ensure_has(error, "badfunc");
|
||||
ensure_has(error, "missing");
|
||||
ensure_equals(
|
||||
"bad partial batch",
|
||||
reply,
|
||||
llsd::map(
|
||||
"strfunc", "got some string",
|
||||
"intfunc", -2,
|
||||
"voidfunc", LLSD(),
|
||||
"arrayfunc", llsd::array(5, "got other string")));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<36>()
|
||||
{
|
||||
set_test_name("batched map exception");
|
||||
DispatchResult service;
|
||||
auto error = tut::call_exc(
|
||||
[&service]()
|
||||
{
|
||||
service.post(llsd::map(
|
||||
"op", llsd::map(
|
||||
"badfunc", 34, // !
|
||||
"strfunc", "some string",
|
||||
"intfunc", 2,
|
||||
"missing", LLSD(), // !
|
||||
"voidfunc", LLSD(),
|
||||
"arrayfunc", llsd::array(-5, "other string")),
|
||||
"reqid", 17));
|
||||
// no "reply"
|
||||
},
|
||||
"badfunc");
|
||||
ensure_has(error, "missing");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<37>()
|
||||
{
|
||||
set_test_name("batched array success");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", llsd::array(
|
||||
llsd::array("strfunc", "some string"),
|
||||
llsd::array("intfunc", 2),
|
||||
"arrayfunc",
|
||||
"voidfunc"),
|
||||
"args", llsd::array(
|
||||
LLSD(),
|
||||
LLSD(),
|
||||
llsd::array(-5, "other string")),
|
||||
// args array deliberately short, since the default
|
||||
// [3] is undefined, which should work for voidfunc
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
reply.erase("reqid");
|
||||
ensure_equals(
|
||||
"bad array batch",
|
||||
reply,
|
||||
llsd::map(
|
||||
"data", llsd::array(
|
||||
"got some string",
|
||||
-2,
|
||||
llsd::array(5, "got other string"),
|
||||
LLSD())));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<38>()
|
||||
{
|
||||
set_test_name("batched array error");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", llsd::array(
|
||||
llsd::array("strfunc", "some string"),
|
||||
llsd::array("intfunc", 2, "whoops"), // bad form
|
||||
"arrayfunc",
|
||||
"voidfunc"),
|
||||
"args", llsd::array(
|
||||
LLSD(),
|
||||
LLSD(),
|
||||
llsd::array(-5, "other string")),
|
||||
// args array deliberately short, since the default
|
||||
// [3] is undefined, which should work for voidfunc
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
reply.erase("reqid");
|
||||
auto error{ reply["error"] };
|
||||
reply.erase("error");
|
||||
ensure_has(error, "[1]");
|
||||
ensure_has(error, "unsupported");
|
||||
ensure_equals("bad array batch", reply,
|
||||
llsd::map("data", llsd::array("got some string")));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<39>()
|
||||
{
|
||||
set_test_name("batched array exception");
|
||||
DispatchResult service;
|
||||
auto error = tut::call_exc(
|
||||
[&service]()
|
||||
{
|
||||
service.post(llsd::map(
|
||||
"op", llsd::array(
|
||||
llsd::array("strfunc", "some string"),
|
||||
llsd::array("intfunc", 2, "whoops"), // bad form
|
||||
"arrayfunc",
|
||||
"voidfunc"),
|
||||
"args", llsd::array(
|
||||
LLSD(),
|
||||
LLSD(),
|
||||
llsd::array(-5, "other string")),
|
||||
// args array deliberately short, since the default
|
||||
// [3] is undefined, which should work for voidfunc
|
||||
"reqid", 17));
|
||||
// no "reply"
|
||||
},
|
||||
"[1]");
|
||||
ensure_has(error, "unsupported");
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
// std headers
|
||||
#include <functional>
|
||||
// external library headers
|
||||
//#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include <boost/phoenix/core/argument.hpp>
|
||||
// other Linden headers
|
||||
|
|
|
|||
|
|
@ -226,6 +226,11 @@ public:
|
|||
return boost::dynamic_pointer_cast<CaptureLogRecorder>(mRecorder)->streamto(out);
|
||||
}
|
||||
|
||||
friend inline std::ostream& operator<<(std::ostream& out, const CaptureLog& self)
|
||||
{
|
||||
return self.streamto(out);
|
||||
}
|
||||
|
||||
private:
|
||||
LLError::FatalFunction mFatalFunction;
|
||||
LLError::SettingsStoragePtr mOldSettings;
|
||||
|
|
|
|||
|
|
@ -76,6 +76,13 @@ if (WINDOWS)
|
|||
LINK_FLAGS "/NODEFAULTLIB:LIBCMT"
|
||||
LINK_FLAGS_DEBUG "/NODEFAULTLIB:\"LIBCMT;LIBCMTD;MSVCRT\""
|
||||
)
|
||||
elseif (DARWIN)
|
||||
# Support our "@executable_path/../Resources" load path for our test
|
||||
# executable. This SHOULD properly be "$<TARGET_FILE_DIR:lltest>/Resources",
|
||||
# but the CMake $<TARGET_FILE_DIR> generator expression isn't evaluated by
|
||||
# CREATE_LINK, so fudge it.
|
||||
file(CREATE_LINK "../sharedlibs/Release/Resources" "${CMAKE_BINARY_DIR}/test/Resources"
|
||||
SYMBOLIC)
|
||||
endif (WINDOWS)
|
||||
|
||||
set(TEST_EXE $<TARGET_FILE:lltest>)
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ public:
|
|||
break;
|
||||
case tut::test_result::ex:
|
||||
++mFailedTests;
|
||||
out << "exception: " << tr.exception_typeid;
|
||||
out << "exception: " << LLError::Log::demangle(tr.exception_typeid.c_str());
|
||||
break;
|
||||
case tut::test_result::warn:
|
||||
++mFailedTests;
|
||||
|
|
|
|||
Loading…
Reference in New Issue