diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 1cd0c2526f6..849c10d62e5 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -254,7 +254,7 @@ jobs:
runs-on: windows
steps:
- name: Sign and package Windows viewer
- uses: secondlife/viewer-build-util/sign-pkg-windows@main
+ uses: secondlife/viewer-build-util/sign-pkg-windows@v1
with:
vault_uri: "${{ secrets.AZURE_KEY_VAULT_URI }}"
cert_name: "${{ secrets.AZURE_CERT_NAME }}"
@@ -286,7 +286,7 @@ jobs:
[[ -n "$USERNAME" && -n "$PASSWORD" && -n "$TEAM_ID" ]]
- name: Sign and package Mac viewer
- uses: secondlife/viewer-build-util/sign-pkg-mac@main
+ uses: secondlife/viewer-build-util/sign-pkg-mac@v1
with:
channel: ${{ needs.build.outputs.viewer_channel }}
imagename: ${{ needs.build.outputs.imagename }}
@@ -302,7 +302,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Post Windows symbols
- uses: secondlife/viewer-build-util/post-bugsplat-windows@main
+ uses: secondlife/viewer-build-util/post-bugsplat-windows@v1
with:
username: ${{ secrets.BUGSPLAT_USER }}
password: ${{ secrets.BUGSPLAT_PASS }}
@@ -315,7 +315,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Post Mac symbols
- uses: secondlife/viewer-build-util/post-bugsplat-mac@main
+ uses: secondlife/viewer-build-util/post-bugsplat-mac@v1
with:
username: ${{ secrets.BUGSPLAT_USER }}
password: ${{ secrets.BUGSPLAT_PASS }}
@@ -330,29 +330,29 @@ jobs:
steps:
- uses: actions/download-artifact@v3
with:
- path: artifacts
+ name: Windows-installer
- - name: Reshuffle artifact files
- uses: secondlife/viewer-build-util/release-artifacts@main
+ - uses: actions/download-artifact@v3
+ with:
+ name: macOS-installer
+
+ - uses: actions/download-artifact@v3
with:
- input-path: artifacts
- output-path: assets
- # The *-app artifacts are for use only by the signing and
- # packaging steps. Once we've generated signed installers, we no
- # longer need them, and we CERTAINLY don't want to publish
- # thousands of individual files as separate URLs.
- exclude: |-
- Windows-app
- macOS-app
- # Use just "Windows" or "macOS" prefix because these are the only
- # artifacts in which we expect files from both platforms with
- # colliding names (e.g. autobuild-package.xml). release-artifacts
- # normally resolves collisions by prepending the artifact name, so
- # when we anticipate collisions, it's good to keep the prefix
- # short and sweet.
- prefix: |-
- Windows-metadata=Windows
- macOS-metadata=macOS
+ name: Windows-metadata
+
+ - name: Rename windows metadata
+ run: |
+ mv autobuild-package.xml Windows-autobuild-package.xml
+ mv newview/viewer_version.txt Windows-viewer_version.txt
+
+ - uses: actions/download-artifact@v3
+ with:
+ name: macOS-metadata
+
+ - name: Rename macOS metadata
+ run: |
+ mv autobuild-package.xml macOS-autobuild-package.xml
+ mv newview/viewer_version.txt macOS-viewer_version.txt
# forked from softprops/action-gh-release
- uses: secondlife-3p/action-gh-release@v1
@@ -364,4 +364,8 @@ jobs:
generate_release_notes: true
# the only reason we generate a GH release is to post build products
fail_on_unmatched_files: true
- files: "assets/*"
+ files: |
+ *.dmg
+ *.exe
+ *-autobuild-package.xml
+ *-viewer_version.txt
diff --git a/.gitignore b/.gitignore
index fc75db76bba..371b8383c0c 100755
--- a/.gitignore
+++ b/.gitignore
@@ -89,3 +89,4 @@ web/locale.*
web/secondlife.com.*
.env
+.vscode
diff --git a/autobuild.xml b/autobuild.xml
index 37371746d56..0e5a5a2a258 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -2563,11 +2563,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
name
darwin64
@@ -2577,11 +2577,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
name
linux64
@@ -2591,11 +2591,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
archive
name
windows64
@@ -2608,7 +2608,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
copyright
Copyright (c) 2000-2012, Linden Research, Inc.
version
- 3.0.cc7ea1e
+ 3.0-08bf5ee
name
viewer-manager
description
diff --git a/doc/contributions.txt b/doc/contributions.txt
index af8b259c749..f830e19e0d8 100755
--- a/doc/contributions.txt
+++ b/doc/contributions.txt
@@ -240,6 +240,7 @@ Ansariel Hiller
SL-18432
SL-19140
SL-4126
+ SL-20224
Aralara Rajal
Arare Chantilly
CHUIBUG-191
@@ -928,6 +929,8 @@ LSL Scientist
Lamorna Proctor
Lares Carter
Larry Pixel
+Lars Næsbye Christensen
+ SL-20054
Laurent Bechir
Leal Choche
Lenae Munz
diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt
index 205ce402a0b..500ffa3e8bb 100644
--- a/indra/CMakeLists.txt
+++ b/indra/CMakeLists.txt
@@ -29,15 +29,6 @@ else()
set( USE_AUTOBUILD_3P ON )
endif()
-# The viewer code base can now be successfully compiled with -std=c++14. But
-# turning that on in the generic viewer-build-variables/variables file would
-# potentially require tweaking each of our ~50 third-party library builds.
-# Until we decide to set -std=c++14 in viewer-build-variables/variables, set
-# it locally here: we want to at least prevent inadvertently reintroducing
-# viewer code that would fail with C++14.
-set(CMAKE_CXX_STANDARD 17)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
include(Variables)
include(BuildVersion)
diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake
index a3db02372d8..7938d4f54b7 100644
--- a/indra/cmake/Copy3rdPartyLibs.cmake
+++ b/indra/cmake/Copy3rdPartyLibs.cmake
@@ -86,7 +86,8 @@ if(WINDOWS)
endif (USE_BUGSPLAT)
if (TARGET ll::fmodstudio)
- set(debug_files ${debug_files} fmodL.dll)
+ # fmodL is included for logging, only one should be picked by manifest
+ set(release_files ${release_files} fmodL.dll)
set(release_files ${release_files} fmod.dll)
endif ()
diff --git a/indra/cmake/Python.cmake b/indra/cmake/Python.cmake
index f9259f6c2b7..da5d2ef22c2 100644
--- a/indra/cmake/Python.cmake
+++ b/indra/cmake/Python.cmake
@@ -13,7 +13,7 @@ elseif (WINDOWS)
foreach(hive HKEY_CURRENT_USER HKEY_LOCAL_MACHINE)
# prefer more recent Python versions to older ones, if multiple versions
# are installed
- foreach(pyver 3.11 3.10 3.9 3.8 3.7)
+ foreach(pyver 3.12 3.11 3.10 3.9 3.8 3.7)
list(APPEND regpaths "[${hive}\\SOFTWARE\\Python\\PythonCore\\${pyver}\\InstallPath]")
endforeach()
endforeach()
@@ -40,7 +40,7 @@ elseif (WINDOWS)
${regpaths}
${pymaybe}
)
- include(FindPythonInterp)
+ find_package(Python3 COMPONENTS Interpreter)
else()
find_program(python python3)
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index fa9c6eaa79e..5f4ed2fffaa 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -16,8 +16,10 @@ include(Tracy)
set(llcommon_SOURCE_FILES
+ apply.cpp
commoncontrol.cpp
indra_constants.cpp
+ lazyeventapi.cpp
llallocator.cpp
llallocator_heap_profile.cpp
llapp.cpp
@@ -115,12 +117,16 @@ set(llcommon_SOURCE_FILES
set(llcommon_HEADER_FILES
CMakeLists.txt
+ always_return.h
+ apply.h
chrono.h
classic_callback.h
commoncontrol.h
ctype_workaround.h
fix_macros.h
+ function_types.h
indra_constants.h
+ lazyeventapi.h
linden_common.h
llalignedarray.h
llallocator.h
@@ -292,9 +298,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}")
diff --git a/indra/llcommon/always_return.h b/indra/llcommon/always_return.h
new file mode 100644
index 00000000000..6b9f1fdeafa
--- /dev/null
+++ b/indra/llcommon/always_return.h
@@ -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 // std::enable_if, std::is_convertible
+
+namespace LL
+{
+
+#if __cpp_lib_is_invocable >= 201703L // C++17
+ template
+ using invoke_result = std::invoke_result;
+#else // C++14
+ template
+ using invoke_result = std::result_of;
+#endif // C++14
+
+ /**
+ * AlwaysReturn()(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
+ 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 ::type,
+ DESIRED
+ >::value,
+ bool
+ >::type=true>
+ DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
+ {
+ // discard whatever callable(args) returns
+ std::forward(callable)(std::forward(args)...);
+ return mDefault;
+ }
+
+ // callable returns a type convertible to DESIRED
+ template ::type,
+ DESIRED
+ >::value,
+ bool
+ >::type=true>
+ DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
+ {
+ return { std::forward(callable)(std::forward(args)...) };
+ }
+
+ private:
+ DESIRED mDefault;
+ };
+
+ /**
+ * always_return(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
+ DESIRED always_return(CALLABLE&& callable, ARGS&&... args)
+ {
+ return AlwaysReturn()(std::forward(callable),
+ std::forward(args)...);
+ }
+
+ /**
+ * make_always_return(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
+ auto make_always_return(CALLABLE&& callable, const DESIRED& dft=DESIRED())
+ {
+ return
+ [dft, callable = std::forward(callable)]
+ (auto&&... args)
+ {
+ return AlwaysReturn(dft)(callable,
+ std::forward(args)...);
+ };
+ }
+
+} // namespace LL
+
+#endif /* ! defined(LL_ALWAYS_RETURN_H) */
diff --git a/indra/llcommon/apply.cpp b/indra/llcommon/apply.cpp
new file mode 100644
index 00000000000..417e23d3b48
--- /dev/null
+++ b/indra/llcommon/apply.cpp
@@ -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))")));
+ }
+}
diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h
index 7c58d63bc03..cf6161ed501 100644
--- a/indra/llcommon/apply.h
+++ b/indra/llcommon/apply.h
@@ -12,8 +12,11 @@
#if ! defined(LL_APPLY_H)
#define LL_APPLY_H
+#include "llexception.h"
#include
+#include // std::mem_fn()
#include
+#include // 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::type>::value,
+ int>::type = 0 >
+auto invoke(Fn&& f, Args&&... args)
+{
+ return std::mem_fn(std::forward(f))(std::forward(args)...);
+}
+
+template::type>::value,
+ int>::type = 0 >
+auto invoke(Fn&& f, Args&&... args)
+{
+ return std::forward(f)(std::forward(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
+auto apply(CALLABLE&& func, const std::tuple& args)
+{
+ return std::apply(std::forward(func), args);
+}
#else // C++14
// Derived from https://stackoverflow.com/a/20441189
// and https://en.cppreference.com/w/cpp/utility/apply
-template
-auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence)
+template
+auto apply_impl(CALLABLE&& func, const std::tuple& args, std::index_sequence)
{
+ // We accept const std::tuple& so a caller can construct an tuple on the
+ // fly. But std::get(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&>(args) };
+
// call func(unpacked args)
- return std::forward(func)(std::move(std::get(args))...);
+ return invoke(std::forward(func),
+ std::forward(std::get(non_const_args))...);
}
template
@@ -81,6 +131,8 @@ auto apply(CALLABLE&& func, const std::tuple& args)
std::index_sequence_for{});
}
+#endif // C++14
+
// per https://stackoverflow.com/a/57510428/5533635
template
auto apply(CALLABLE&& func, const std::array& args)
@@ -88,28 +140,92 @@ auto apply(CALLABLE&& func, const std::array& args)
return apply(std::forward(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::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(sfx_args)...)));
+ };
+}
+
+template::type>::value,
+ int>::type = 0 >
+auto bind_front(Fn&& f, Args&&... args)
+{
+ return bind_front(std::mem_fn(std::forward(f)), std::forward(args)...);
+}
+
+#endif // C++20 with std::bind_front()
+
+/*****************************************************************************
+* apply(function, std::vector)
+*****************************************************************************/
// per https://stackoverflow.com/a/28411055/5533635
template
auto apply_impl(CALLABLE&& func, const std::vector& args, std::index_sequence)
{
+ return apply(std::forward(func),
+ std::make_tuple(args[I]...));
+}
+
+// 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
+auto apply_n(CALLABLE&& func, const std::vector& args)
+{
+ apply_validate_size(args.size(), ARITY);
return apply_impl(std::forward(func),
- std::make_tuple(std::forward(args[I])...),
- I...);
+ args,
+ std::make_index_sequence());
}
-// this goes beyond C++17 std::apply()
+/**
+ * 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
auto apply(CALLABLE&& func, const std::vector& args)
{
+ // infer arity from the definition of func
constexpr auto arity = boost::function_traits::arity;
- assert(args.size() == arity);
- return apply_impl(std::forward(func),
- args,
- std::make_index_sequence());
+ // now that we have a compile-time arity, apply_n() works
+ return apply_n(std::forward(func), args);
}
-#endif // C++14
-
} // namespace LL
#endif /* ! defined(LL_APPLY_H) */
diff --git a/indra/llcommon/function_types.h b/indra/llcommon/function_types.h
new file mode 100644
index 00000000000..3f42f6d6404
--- /dev/null
+++ b/indra/llcommon/function_types.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
+#include
+#include
+
+namespace LL
+{
+
+ template
+ struct function_arity_impl
+ {
+ static constexpr auto value = boost::function_types::function_arity::value;
+ };
+
+ template
+ struct function_arity_impl>
+ {
+ static constexpr auto value = function_arity_impl::value;
+ };
+
+ template
+ struct function_arity_impl>
+ {
+ static constexpr auto value = function_arity_impl::value;
+ };
+
+ template
+ struct function_arity
+ {
+ static constexpr auto value = function_arity_impl::type>::value;
+ };
+
+} // namespace LL
+
+#endif /* ! defined(LL_FUNCTION_TYPES_H) */
diff --git a/indra/llcommon/lazyeventapi.cpp b/indra/llcommon/lazyeventapi.cpp
new file mode 100644
index 00000000000..028af9f33f3
--- /dev/null
+++ b/indra/llcommon/lazyeventapi.cpp
@@ -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 // 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);
+}
diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h
new file mode 100644
index 00000000000..e36831270b0
--- /dev/null
+++ b/indra/llcommon/lazyeventapi.h
@@ -0,0 +1,205 @@
+/**
+ * @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
+#include
+#include
+#include // std::pair
+#include
+
+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 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
+ {
+ 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
+ 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.
+
+ // apply() can't accept a template per se; it needs a particular
+ // specialization. Specialize out here to work around a clang bug:
+ // https://github.com/llvm/llvm-project/issues/41999
+ auto func{ &LazyEventAPIBase::add_trampoline
+ };
+ // We can't bind an unexpanded parameter pack into a lambda --
+ // shame really. Instead, capture all our args as a std::tuple and
+ // then, in the lambda, use apply() to pass to add_trampoline().
+ auto args{ std::make_tuple(name, desc, std::forward(rest)...) };
+
+ mParams.init.connect_extended(
+ [func, args]
+ (const boost::signals2::connection& conn, LLEventAPI* instance)
+ {
+ // we only need this connection once
+ conn.disconnect();
+ // apply() expects a tuple specifying ALL the arguments,
+ // so prepend instance.
+ apply(func, std::tuple_cat(std::make_tuple(instance), args));
+ });
+ }
+
+ // 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;
+
+ private:
+ // metadata that might be queried by LLLeapListener
+ std::vector 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
+ static
+ void add_trampoline(LLEventAPI* instance, ARGS&&... args)
+ {
+ instance->add(std::forward(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
+ * (const LL::LazyEventAPIParams& params)
+ * and forward that reference to (the protected)
+ * LLEventAPI(const LL::LazyEventAPIParams&) constructor.
+ *
+ * Then derive your listener registrar from
+ * LazyEventAPI. 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 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) */
diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp
index e49f72722b9..575c5242192 100644
--- a/indra/llcommon/llapr.cpp
+++ b/indra/llcommon/llapr.cpp
@@ -38,6 +38,12 @@ const S32 FULL_VOLATILE_APR_POOL = 1024 ; //number of references to LLVolatileAP
bool gAPRInitialized = false;
+int abortfunc(int retcode)
+{
+ LL_WARNS("APR") << "Allocation failure in apr pool with code " << (S32)retcode << LL_ENDL;
+ return 0;
+}
+
void ll_init_apr()
{
// Initialize APR and create the global pool
@@ -45,7 +51,7 @@ void ll_init_apr()
if (!gAPRPoolp)
{
- apr_pool_create(&gAPRPoolp, NULL);
+ apr_pool_create_ex(&gAPRPoolp, NULL, abortfunc, NULL);
}
if(!LLAPRFile::sAPRFilePoolp)
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index cfaf3415e76..3ab97b557f0 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -278,6 +278,7 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl
catch (std::bad_alloc&)
{
// Out of memory on stack allocation?
+ printActiveCoroutines();
LL_ERRS("LLCoros") << "Bad memory allocation in LLCoros::launch(" << prefix << ")!" << LL_ENDL;
}
diff --git a/indra/llcommon/lleventapi.cpp b/indra/llcommon/lleventapi.cpp
index ff5459c1eb8..3d46ef10347 100644
--- a/indra/llcommon/lleventapi.cpp
+++ b/indra/llcommon/lleventapi.cpp
@@ -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()
{
}
diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h
index 5991fe8fd59..25f6becd8ba 100644
--- a/indra/llcommon/lleventapi.h
+++ b/indra/llcommon/lleventapi.h
@@ -35,6 +35,11 @@
#include "llinstancetracker.h"
#include
+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 @@ class LL_COMMON_API LLEventAPI: public LLDispatchListener,
/// 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
- 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 @@ class LL_COMMON_API LLEventAPI: public LLDispatchListener,
* @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;
};
diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp
index cd0ab6bc298..da96de18f7e 100644
--- a/indra/llcommon/lleventdispatcher.cpp
+++ b/indra/llcommon/lleventdispatcher.cpp
@@ -40,70 +40,12 @@
// other Linden headers
#include "llevents.h"
#include "llerror.h"
+#include "llexception.h"
#include "llsdutil.h"
#include "stringize.h"
+#include // std::quoted()
#include // 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
+ [[noreturn]] 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 @@ class LL_COMMON_API LLSDArgsMapper
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
+[[noreturn]] void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const
{
+ _parent->callFail
+ (_function, std::forward(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;
+ 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()
+ 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())
+ {
+ callFail("LLEventDispatcher has no args key");
+ }
+ if ((! event.has(argskey)))
+ {
+ 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;
- }
+ return try_call(std::string(), name, event);
}
-/// Extract the @a key value from the incoming @a event, and call the
-/// callable whose name is specified by that map @a key. If no such
-/// callable exists, die with LL_ERRS.
-void LLEventDispatcher::operator()(const LLSD& event) const
+bool LLEventDispatcher::try_call(const std::string& name, 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))
+ try
{
- LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey
- << " value '" << name << "'" << LL_ENDL;
+ 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
{
- return try_call(event[mKey], event);
+ try
+ {
+ try_call(mKey, event[mKey], event);
+ return true;
+ }
+ catch (const DispatchMissing&)
+ {
+ return false;
+ }
}
-bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
+LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name,
+ const LLSD& event) const
{
+ if (name.empty())
+ {
+ if (key.empty())
+ {
+ callFail("attempting to call with no name");
+ }
+ else
+ {
+ callFail("no ", key);
+ }
+ }
+
DispatchMap::const_iterator found = mDispatch.find(name);
if (found == mDispatch.end())
{
- return false;
+ // 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(std::quoted(name), " not found");
+ }
+ else
+ {
+ callFail("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
+//static
+[[noreturn]] void LLEventDispatcher::sCallFail(ARGS&&... args)
+{
+ auto error = stringize(std::forward(args)...);
+ LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
+ LLTHROW(EXCEPTION(error));
+}
+
+template
+[[noreturn]] void LLEventDispatcher::callFail(ARGS&&... args) const
+{
+ // Describe this instance in addition to the error itself.
+ sCallFail(*this, ": ", std::forward(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;
+}
+
+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();
}
-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::string LLEventDispatcher::getState() const
{
+ // default value of fiber_specific_ptr is nullptr, and ~SetState() reverts
+ // to that; infer empty string
+ if (! mState.get())
+ return {};
+ else
+ return *mState;
}
-bool LLDispatchListener::process(const LLSD& event)
+bool LLEventDispatcher::setState(SetState&, const std::string& state) const
{
- (*this)(event);
+ // 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(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(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);
+}
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h
index 9e1244ef5bf..a82bc7a69b4 100644
--- a/indra/llcommon/lleventdispatcher.h
+++ b/indra/llcommon/lleventdispatcher.h
@@ -27,55 +27,26 @@
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
- *
- * The invoker machinery that constructs a boost::fusion argument list for use
- * with boost::fusion::invoke() is derived from
- * http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp
- * whose license information is copied below:
- *
- * "(C) Copyright Tobias Schwinger
- *
- * Use modification and distribution are subject to the boost Software License,
- * Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt)."
*/
#if ! defined(LL_LLEVENTDISPATCHER_H)
#define LL_LLEVENTDISPATCHER_H
-// nil is too generic a term to be allowed to be a global macro. In
-// particular, boost::fusion defines a 'class nil' (properly encapsulated in a
-// namespace) that a global 'nil' macro breaks badly.
-#if defined(nil)
-// Capture the value of the macro 'nil', hoping int is an appropriate type.
-static const auto nil_(nil);
-// Now forget the macro.
-#undef nil
-// Finally, reintroduce 'nil' as a properly-scoped alias for the previously-
-// defined const 'nil_'. Make it static since otherwise it produces duplicate-
-// symbol link errors later.
-static const auto& nil(nil_);
-#endif
-
-#include
-#include
-#include
-#include
-#include
-#include
+#include
+#include
#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include // until C++17, when we get std::is_invocable
+#include
+#include // std::function
+#include // std::unique_ptr
+#include
#include
+#include
+#include // std::pair
+#include "always_return.h"
+#include "function_types.h" // LL::function_arity
#include "llevents.h"
+#include "llptrto.h"
#include "llsdutil.h"
class LLSD;
@@ -89,15 +60,27 @@ class LLSD;
class LL_COMMON_API LLEventDispatcher
{
public:
+ /**
+ * Pass description and the LLSD key used by try_call(const LLSD&) and
+ * operator()(const LLSD&) to extract the name of the registered callable
+ * to invoke.
+ */
LLEventDispatcher(const std::string& desc, const std::string& key);
+ /**
+ * Pass description, the LLSD key used by try_call(const LLSD&) and
+ * operator()(const LLSD&) to extract the name of the registered callable
+ * to invoke, and the LLSD key used by try_call(const LLSD&) and
+ * operator()(const LLSD&) to extract arguments LLSD.
+ */
+ LLEventDispatcher(const std::string& desc, const std::string& key,
+ const std::string& argskey);
virtual ~LLEventDispatcher();
/// @name Register functions accepting(const LLSD&)
//@{
- /// Accept any C++ callable with the right signature, typically a
- /// boost::bind() expression
- typedef boost::function Callable;
+ /// Accept any C++ callable with the right signature
+ typedef std::function Callable;
/**
* Register a @a callable by @a name. The passed @a callable accepts a
@@ -109,27 +92,54 @@ class LL_COMMON_API LLEventDispatcher
void add(const std::string& name,
const std::string& desc,
const Callable& callable,
- const LLSD& required=LLSD());
+ const LLSD& required=LLSD())
+ {
+ addLLSD(name, desc, callable, required);
+ }
- /**
- * The case of a free function (or static method) accepting(const LLSD&)
- * could also be intercepted by the arbitrary-args overload below. Ensure
- * that it's directed to the Callable overload above instead.
- */
+ template ::value
+ >::type>
void add(const std::string& name,
const std::string& desc,
- void (*f)(const LLSD&),
+ CALLABLE&& callable,
const LLSD& required=LLSD())
{
- add(name, desc, Callable(f), required);
+ addLLSD(
+ name,
+ desc,
+ Callable(LL::make_always_return(std::forward(callable))),
+ required);
}
/**
* Special case: a subclass of this class can pass an unbound member
* function pointer (of an LLEventDispatcher subclass) without explicitly
- * specifying the boost::bind() expression. The passed @a method
+ * specifying a std::bind() expression. The passed @a method
* accepts a single LLSD value, presumably containing other parameters.
*/
+ template
+ void add(const std::string& name,
+ const std::string& desc,
+ R (CLASS::*method)(const LLSD&),
+ const LLSD& required=LLSD())
+ {
+ addMethod(name, desc, method, required);
+ }
+
+ /// Overload for both const and non-const methods. The passed @a method
+ /// accepts a single LLSD value, presumably containing other parameters.
+ template
+ void add(const std::string& name,
+ const std::string& desc,
+ R (CLASS::*method)(const LLSD&) const,
+ const LLSD& required=LLSD())
+ {
+ addMethod(name, desc, method, required);
+ }
+
+ // because the compiler can't match a method returning void to the above
template
void add(const std::string& name,
const std::string& desc,
@@ -150,6 +160,128 @@ class LL_COMMON_API LLEventDispatcher
addMethod(name, desc, method, required);
}
+ // non-const nullary method
+ template
+ void add(const std::string& name,
+ const std::string& desc,
+ R (CLASS::*method)())
+ {
+ addVMethod(name, desc, method);
+ }
+
+ // const nullary method
+ template
+ void add(const std::string& name,
+ const std::string& desc,
+ R (CLASS::*method)() const)
+ {
+ addVMethod(name, desc, method);
+ }
+
+ // non-const nullary method returning void
+ template
+ void add(const std::string& name,
+ const std::string& desc,
+ void (CLASS::*method)())
+ {
+ addVMethod(name, desc, method);
+ }
+
+ // const nullary method returning void
+ template
+ void add(const std::string& name,
+ const std::string& desc,
+ void (CLASS::*method)() const)
+ {
+ addVMethod(name, desc, method);
+ }
+
+ // non-const unary method (but method accepting LLSD should use the other add())
+ // enable_if usage per https://stackoverflow.com/a/39913395/5533635
+ template ::type, LLSD>::value
+ >::type>
+ void add(const std::string& name,
+ const std::string& desc,
+ R (CLASS::*method)(ARG))
+ {
+ addVMethod(name, desc, method);
+ }
+
+ // const unary method (but method accepting LLSD should use the other add())
+ template ::type, LLSD>::value
+ >::type>
+ void add(const std::string& name,
+ const std::string& desc,
+ R (CLASS::*method)(ARG) const)
+ {
+ addVMethod(name, desc, method);
+ }
+
+ // non-const unary method returning void
+ // enable_if usage per https://stackoverflow.com/a/39913395/5533635
+ template ::type, LLSD>::value
+ >::type>
+ void add(const std::string& name,
+ const std::string& desc,
+ void (CLASS::*method)(ARG))
+ {
+ addVMethod(name, desc, method);
+ }
+
+ // const unary method returning void
+ template ::type, LLSD>::value
+ >::type>
+ void add(const std::string& name,
+ const std::string& desc,
+ void (CLASS::*method)(ARG) const)
+ {
+ addVMethod(name, desc, method);
+ }
+
+ // non-const binary (or more) method
+ template
+ void add(const std::string& name,
+ const std::string& desc,
+ R (CLASS::*method)(ARG0, ARG1, ARGS...))
+ {
+ addVMethod(name, desc, method);
+ }
+
+ // const binary (or more) method
+ template
+ void add(const std::string& name,
+ const std::string& desc,
+ R (CLASS::*method)(ARG0, ARG1, ARGS...) const)
+ {
+ addVMethod(name, desc, method);
+ }
+
+ // non-const binary (or more) method returning void
+ template
+ void add(const std::string& name,
+ const std::string& desc,
+ void (CLASS::*method)(ARG0, ARG1, ARGS...))
+ {
+ addVMethod(name, desc, method);
+ }
+
+ // const binary (or more) method returning void
+ template
+ void add(const std::string& name,
+ const std::string& desc,
+ void (CLASS::*method)(ARG0, ARG1, ARGS...) const)
+ {
+ addVMethod(name, desc, method);
+ }
+
//@}
/// @name Register functions with arbitrary param lists
@@ -159,51 +291,43 @@ class LL_COMMON_API LLEventDispatcher
* Register a free function with arbitrary parameters. (This also works
* for static class methods.)
*
- * @note This supports functions with up to about 6 parameters -- after
- * that you start getting dismaying compile errors in which
- * boost::fusion::joint_view is mentioned a surprising number of times.
- *
* When calling this name, pass an LLSD::Array. Each entry in turn will be
* converted to the corresponding parameter type using LLSDParam.
*/
- template
- typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin
- >::type add(const std::string& name,
- const std::string& desc,
- Function f);
+ template ()
+ >::type>
+ void add(const std::string& name,
+ const std::string& desc,
+ CALLABLE&& f)
+ {
+ addV(name, desc, f);
+ }
/**
* Register a nonstatic class method with arbitrary parameters.
*
- * @note This supports functions with up to about 6 parameters -- after
- * that you start getting dismaying compile errors in which
- * boost::fusion::joint_view is mentioned a surprising number of times.
- *
* To cover cases such as a method on an LLSingleton we don't yet want to
* instantiate, instead of directly storing an instance pointer, accept a
* nullary callable returning a pointer/reference to the desired class
- * instance. If you already have an instance in hand,
- * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr)
- * produce suitable callables.
+ * instance.
*
* When calling this name, pass an LLSD::Array. Each entry in turn will be
* converted to the corresponding parameter type using LLSDParam.
*/
- template
- typename boost::enable_if< boost::function_types::is_member_function_pointer
- >::type add(const std::string& name,
- const std::string& desc,
- Method f,
- const InstanceGetter& getter);
+ template::value &&
+ ! std::is_convertible::value
+ >::type>
+ void add(const std::string& name, const std::string& desc, Method f,
+ const InstanceGetter& getter);
/**
* Register a free function with arbitrary parameters. (This also works
* for static class methods.)
*
- * @note This supports functions with up to about 6 parameters -- after
- * that you start getting dismaying compile errors in which
- * boost::fusion::joint_view is mentioned a surprising number of times.
- *
* Pass an LLSD::Array of parameter names, and optionally another
* LLSD::Array of default parameter values, a la LLSDArgsMapper.
*
@@ -211,21 +335,17 @@ class LL_COMMON_API LLEventDispatcher
* an LLSD::Array using LLSDArgsMapper and then convert each entry in turn
* to the corresponding parameter type using LLSDParam.
*/
- template
- typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin
- >::type add(const std::string& name,
- const std::string& desc,
- Function f,
- const LLSD& params,
- const LLSD& defaults=LLSD());
+ template::value &&
+ ! boost::hof::is_invocable::value
+ >::type>
+ void add(const std::string& name, const std::string& desc, Function f,
+ const LLSD& params, const LLSD& defaults=LLSD());
/**
* Register a nonstatic class method with arbitrary parameters.
*
- * @note This supports functions with up to about 6 parameters -- after
- * that you start getting dismaying compile errors in which
- * boost::fusion::joint_view is mentioned a surprising number of times.
- *
* To cover cases such as a method on an LLSingleton we don't yet want to
* instantiate, instead of directly storing an instance pointer, accept a
* nullary callable returning a pointer/reference to the desired class
@@ -233,6 +353,8 @@ class LL_COMMON_API LLEventDispatcher
* boost::lambda::var(instance) or boost::lambda::constant(instance_ptr)
* produce suitable callables.
*
+ * TODO: variant accepting a method of the containing class, no getter.
+ *
* Pass an LLSD::Array of parameter names, and optionally another
* LLSD::Array of default parameter values, a la LLSDArgsMapper.
*
@@ -240,42 +362,96 @@ class LL_COMMON_API LLEventDispatcher
* an LLSD::Array using LLSDArgsMapper and then convert each entry in turn
* to the corresponding parameter type using LLSDParam.
*/
- template
- typename boost::enable_if< boost::function_types::is_member_function_pointer
- >::type add(const std::string& name,
- const std::string& desc,
- Method f,
- const InstanceGetter& getter,
- const LLSD& params,
- const LLSD& defaults=LLSD());
+ template::value &&
+ ! std::is_convertible::value
+ >::type>
+ void add(const std::string& name, const std::string& desc, Method f,
+ const InstanceGetter& getter, const LLSD& params,
+ const LLSD& defaults=LLSD());
//@}
/// Unregister a callable
bool remove(const std::string& name);
- /// Call a registered callable with an explicitly-specified name. If no
- /// such callable exists, die with LL_ERRS. If the @a event fails to match
- /// the @a required prototype specified at add() time, die with LL_ERRS.
- void operator()(const std::string& name, const LLSD& event) const;
+ /// Exception if an attempted call fails for any reason
+ struct DispatchError: public LLException
+ {
+ DispatchError(const std::string& what): LLException(what) {}
+ };
+
+ /// Specific exception for an attempt to call a nonexistent name
+ struct DispatchMissing: public DispatchError
+ {
+ DispatchMissing(const std::string& what): DispatchError(what) {}
+ };
+
+ /**
+ * Call a registered callable with an explicitly-specified name,
+ * converting its return value to LLSD (undefined for a void callable).
+ * It is an error if no such callable exists. It is an error if the @a
+ * event fails to match the @a required prototype specified at add()
+ * time.
+ *
+ * @a event must be an LLSD array for a callable registered to accept its
+ * arguments from such an array. It must be an LLSD map for a callable
+ * registered to accept its arguments from such a map.
+ */
+ LLSD operator()(const std::string& name, const LLSD& event) const;
- /// Call a registered callable with an explicitly-specified name and
- /// return true. If no such callable exists, return
- /// false. If the @a event fails to match the @a required
- /// prototype specified at add() time, die with LL_ERRS.
+ /**
+ * Call a registered callable with an explicitly-specified name and
+ * return true. If no such callable exists, return
+ * false. It is an error if the @a event fails to match the @a
+ * required prototype specified at add() time.
+ *
+ * @a event must be an LLSD array for a callable registered to accept its
+ * arguments from such an array. It must be an LLSD map for a callable
+ * registered to accept its arguments from such a map.
+ */
bool try_call(const std::string& name, const LLSD& event) const;
- /// Extract the @a key value from the incoming @a event, and call the
- /// callable whose name is specified by that map @a key. If no such
- /// callable exists, die with LL_ERRS. If the @a event fails to match the
- /// @a required prototype specified at add() time, die with LL_ERRS.
- void operator()(const LLSD& event) const;
-
- /// Extract the @a key value from the incoming @a event, call the callable
- /// whose name is specified by that map @a key and return true.
- /// If no such callable exists, return false. If the @a event
- /// fails to match the @a required prototype specified at add() time, die
- /// with LL_ERRS.
+ /**
+ * Extract the @a key specified to our constructor from the incoming LLSD
+ * map @a event, and call the callable whose name is specified by that @a
+ * key's value, converting its return value to LLSD (undefined for a void
+ * callable). It is an error if no such callable exists. It is an error if
+ * the @a event fails to match the @a required prototype specified at
+ * add() time.
+ *
+ * For a (non-nullary) callable registered to accept its arguments from an
+ * LLSD array, the @a event map must contain the key @a argskey specified to
+ * our constructor. The value of the @a argskey key must be an LLSD array
+ * containing the arguments to pass to the callable named by @a key.
+ *
+ * For a callable registered to accept its arguments from an LLSD map, if
+ * the @a event map contains the key @a argskey specified our constructor,
+ * extract the value of the @a argskey key and use it as the arguments map.
+ * If @a event contains no @a argskey key, use the whole @a event as the
+ * arguments map.
+ */
+ LLSD operator()(const LLSD& event) const;
+
+ /**
+ * Extract the @a key specified to our constructor from the incoming LLSD
+ * map @a event, call the callable whose name is specified by that @a
+ * key's value and return true. If no such callable exists,
+ * return false. It is an error if the @a event fails to match
+ * the @a required prototype specified at add() time.
+ *
+ * For a (non-nullary) callable registered to accept its arguments from an
+ * LLSD array, the @a event map must contain the key @a argskey specified to
+ * our constructor. The value of the @a argskey key must be an LLSD array
+ * containing the arguments to pass to the callable named by @a key.
+ *
+ * For a callable registered to accept its arguments from an LLSD map, if
+ * the @a event map contains the key @a argskey specified our constructor,
+ * extract the value of the @a argskey key and use it as the arguments map.
+ * If @a event contains no @a argskey key, use the whole @a event as the
+ * arguments map.
+ */
bool try_call(const LLSD& event) const;
/// @name Iterate over defined names
@@ -285,22 +461,26 @@ class LL_COMMON_API LLEventDispatcher
private:
struct DispatchEntry
{
- DispatchEntry(const std::string& desc);
+ DispatchEntry(LLEventDispatcher* parent, const std::string& desc);
virtual ~DispatchEntry() {} // suppress MSVC warning, sigh
+ // store a plain dumb back-pointer because the parent
+ // LLEventDispatcher manages the lifespan of each DispatchEntry
+ // subclass instance -- not the other way around
+ LLEventDispatcher* mParent;
std::string mDesc;
- virtual void call(const std::string& desc, const LLSD& event) const = 0;
- virtual LLSD addMetadata(LLSD) const = 0;
+ virtual LLSD call(const std::string& desc, const LLSD& event,
+ bool fromMap, const std::string& argskey) const = 0;
+ virtual LLSD getMetadata() const = 0;
+
+ template
+ [[noreturn]] void callFail(ARGS&&... args) const
+ {
+ mParent->callFail(std::forward(args)...);
+ }
};
- // Tried using boost::ptr_map, but ptr_map<>
- // wants its value type to be "clonable," even just to dereference an
- // iterator. I don't want to clone entries -- if I have to copy an entry
- // around, I want it to continue pointing to the same DispatchEntry
- // subclass object. However, I definitely want DispatchMap to destroy
- // DispatchEntry if no references are outstanding at the time an entry is
- // removed. This looks like a job for boost::shared_ptr.
- typedef std::map > DispatchMap;
+ typedef std::map > DispatchMap;
public:
/// We want the flexibility to redefine what data we store per name,
@@ -323,11 +503,57 @@ class LL_COMMON_API LLEventDispatcher
/// Retrieve the LLSD key we use for one-arg operator() method
std::string getDispatchKey() const { return mKey; }
+ /// Retrieve the LLSD key we use for non-map arguments
+ std::string getArgsKey() const { return mArgskey; }
+
+ /// description of this instance's leaf class and description
+ friend std::ostream& operator<<(std::ostream&, const LLEventDispatcher&);
private:
- template
+ void addLLSD(const std::string& name,
+ const std::string& desc,
+ const Callable& callable,
+ const LLSD& required);
+
+ template ::value,
+ bool
+ >::type=true>
void addMethod(const std::string& name, const std::string& desc,
const METHOD& method, const LLSD& required)
+ {
+ // Why two overloaded addMethod() methods, discriminated with
+ // std::is_base_of? It might seem simpler to use dynamic_cast and test
+ // for nullptr. The trouble is that it doesn't work for LazyEventAPI
+ // deferred registration: we get nullptr even for a method of an
+ // LLEventAPI subclass.
+ CLASS* downcast = static_cast(this);
+ add(name,
+ desc,
+ Callable(LL::make_always_return(
+ [downcast, method]
+ (const LLSD& args)
+ {
+ return (downcast->*method)(args);
+ })),
+ required);
+ }
+
+ template ::value,
+ bool
+ >::type=true>
+ void addMethod(const std::string& name, const std::string& desc,
+ const METHOD&, const LLSD&)
+ {
+ addFail(name, typeid(CLASS).name());
+ }
+
+ template
+ void addVMethod(const std::string& name, const std::string& desc,
+ const METHOD& method)
{
CLASS* downcast = dynamic_cast(this);
if (! downcast)
@@ -336,38 +562,85 @@ class LL_COMMON_API LLEventDispatcher
}
else
{
- add(name, desc, boost::bind(method, downcast, _1), required);
+ // add() arbitrary method plus InstanceGetter, where the
+ // InstanceGetter in this case returns 'this'. We don't need to
+ // worry about binding 'this' because, once this LLEventDispatcher
+ // is destroyed, the DispatchEntry goes away too.
+ add(name, desc, method, [downcast](){ return downcast; });
}
}
- void addFail(const std::string& name, const std::string& classname) const;
- std::string mDesc, mKey;
+ template
+ void addV(const std::string& name, const std::string& desc, Function f);
+
+ void addFail(const std::string& name, const char* classname) const;
+ LLSD try_call(const std::string& key, const std::string& name,
+ const LLSD& event) const;
+
+protected:
+ // raise specified EXCEPTION with specified stringize(ARGS)
+ template
+ [[noreturn]] void callFail(ARGS&&... args) const;
+ template
+ [[noreturn]] static
+ void sCallFail(ARGS&&... args);
+
+ // Manage transient state, e.g. which registered callable we're attempting
+ // to call, for error reporting
+ class SetState
+ {
+ public:
+ template
+ SetState(const LLEventDispatcher* self, ARGS&&... args):
+ mSelf(self)
+ {
+ mSet = mSelf->setState(*this, stringize(std::forward(args)...));
+ }
+ // RAII class: forbid both copy and move
+ SetState(const SetState&) = delete;
+ SetState(SetState&&) = delete;
+ SetState& operator=(const SetState&) = delete;
+ SetState& operator=(SetState&&) = delete;
+ virtual ~SetState()
+ {
+ // if we're the ones who succeeded in setting state, clear it
+ if (mSet)
+ {
+ mSelf->setState(*this, {});
+ }
+ }
+
+ private:
+ const LLEventDispatcher* mSelf;
+ bool mSet;
+ };
+
+private:
+ std::string mDesc, mKey, mArgskey;
DispatchMap mDispatch;
+ // transient state: must be fiber_specific since multiple threads and/or
+ // multiple fibers may be calling concurrently. Make it mutable so we can
+ // use SetState even within const methods.
+ mutable boost::fibers::fiber_specific_ptr mState;
+
+ std::string getState() const;
+ // setState() requires SetState& because only the SetState class should
+ // call it. Make it const so we can use SetState even within const methods.
+ bool setState(SetState&, const std::string& state) const;
static NameDesc makeNameDesc(const DispatchMap::value_type& item)
{
return NameDesc(item.first, item.second->mDesc);
}
+ class LLSDArgsMapper;
struct LLSDDispatchEntry;
struct ParamsDispatchEntry;
struct ArrayParamsDispatchEntry;
struct MapParamsDispatchEntry;
- // Step 2 of parameter analysis. Instantiating invoker
- // implicitly sets its From and To parameters to the (compile time) begin
- // and end iterators over that function's parameter types.
- template< typename Function
- , class From = typename boost::mpl::begin< boost::function_types::parameter_types >::type
- , class To = typename boost::mpl::end< boost::function_types::parameter_types >::type
- >
- struct invoker;
-
- // deliver LLSD arguments one at a time
- typedef boost::function args_source;
- // obtain args from an args_source to build param list and call target
- // function
- typedef boost::function invoker_function;
+ // call target function with args from LLSD array
+ typedef std::function invoker_function;
template
invoker_function make_invoker(Function f);
@@ -387,101 +660,38 @@ class LL_COMMON_API LLEventDispatcher
/*****************************************************************************
* LLEventDispatcher template implementation details
*****************************************************************************/
-// Step 3 of parameter analysis, the recursive case.
-template
-struct LLEventDispatcher::invoker
-{
- template
- struct remove_cv_ref
- : boost::remove_cv< typename boost::remove_reference::type >
- { };
-
- // apply() accepts an arbitrary boost::fusion sequence as args. It
- // examines the next parameter type in the parameter-types sequence
- // bounded by From and To, obtains the next LLSD object from the passed
- // args_source and constructs an LLSDParam of appropriate type to try
- // to convert the value. It then recurs with the next parameter-types
- // iterator, passing the args sequence thus far.
- template
- static inline
- void apply(Function func, const args_source& argsrc, Args const & args)
- {
- typedef typename boost::mpl::deref::type arg_type;
- typedef typename boost::mpl::next::type next_iter_type;
- typedef typename remove_cv_ref::type plain_arg_type;
-
- invoker::apply
- ( func, argsrc, boost::fusion::push_back(args, LLSDParam(argsrc())));
- }
-
- // Special treatment for instance (first) parameter of a non-static member
- // function. Accept the instance-getter callable, calling that to produce
- // the first args value. Since we know we're at the top of the recursion
- // chain, we need not also require a partial args sequence from our caller.
- template
- static inline
- void method_apply(Function func, const args_source& argsrc, const InstanceGetter& getter)
- {
- typedef typename boost::mpl::next::type next_iter_type;
-
- // Instead of grabbing the first item from argsrc and making an
- // LLSDParam of it, call getter() and pass that as the instance param.
- invoker::apply
- ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), boost::ref(getter())));
- }
-};
-
-// Step 4 of parameter analysis, the leaf case. When the general
-// invoker logic has advanced From until it matches To,
-// the compiler will pick this template specialization.
-template
-struct LLEventDispatcher::invoker
-{
- // the argument list is complete, now call the function
- template
- static inline
- void apply(Function func, const args_source&, Args const & args)
- {
- boost::fusion::invoke(func, args);
- }
-};
-
-template
-typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type
-LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f)
+template
+void LLEventDispatcher::addV(const std::string& name, const std::string& desc, Function f)
{
- // Construct an invoker_function, a callable accepting const args_source&.
+ // Construct an invoker_function, a callable accepting const LLSD&.
// Add to DispatchMap an ArrayParamsDispatchEntry that will handle the
// caller's LLSD::Array.
addArrayParamsDispatchEntry(name, desc, make_invoker(f),
- boost::function_types::function_arity::value);
+ LL::function_arity::value);
}
-template
-typename boost::enable_if< boost::function_types::is_member_function_pointer >::type
-LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f,
- const InstanceGetter& getter)
+template
+void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f,
+ const InstanceGetter& getter)
{
// Subtract 1 from the compile-time arity because the getter takes care of
// the first parameter. We only need (arity - 1) additional arguments.
addArrayParamsDispatchEntry(name, desc, make_invoker(f, getter),
- boost::function_types::function_arity::value - 1);
+ LL::function_arity::value - 1);
}
-template