Merge branch 'DRTVWR-587-maint-V' into signal/simple-release
commit
5416d94d07
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -2471,11 +2471,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>d8bc8720846cfa31e23e7e1008e32ba6ad4a8322</string>
|
||||
<string>30e4b77c86baae7885af692a1f9e9601f2e9d442</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<key>url</key>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.cc7ea1e/viewer_manager-3.0.cc7ea1e-darwin64-cc7ea1e.tar.zst</string>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.73fef89/viewer_manager-3.0.73fef89-darwin64-73fef89.tar.zst</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
|
|
@ -2485,11 +2485,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>228fae4ee0ce12b9d1d1b0a8ebb0bdf58ee521eb</string>
|
||||
<string>cb838349fc908d87e5bdebb44942bf7558b9f0ee</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<key>url</key>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.cc7ea1e/viewer_manager-3.0.cc7ea1e-linux64-cc7ea1e.tar.zst</string>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.73fef89/viewer_manager-3.0.73fef89-linux64-73fef89.tar.zst</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>linux64</string>
|
||||
|
|
@ -2499,11 +2499,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>ca6999b64d96d45952fe872b381db9b2abc0248c</string>
|
||||
<string>0830850ea21b48f9da7b933df85a3330570f8918</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<key>url</key>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.cc7ea1e/viewer_manager-3.0.cc7ea1e-windows64-cc7ea1e.tar.zst</string>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.73fef89/viewer_manager-3.0.73fef89-windows64-73fef89.tar.zst</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
|
|
@ -2516,7 +2516,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>copyright</key>
|
||||
<string>Copyright (c) 2000-2012, Linden Research, Inc.</string>
|
||||
<key>version</key>
|
||||
<string>3.0.cc7ea1e</string>
|
||||
<string>3.0-73fef89</string>
|
||||
<key>name</key>
|
||||
<string>viewer-manager</string>
|
||||
<key>description</key>
|
||||
|
|
|
|||
|
|
@ -239,6 +239,7 @@ Ansariel Hiller
|
|||
SL-18432
|
||||
SL-19140
|
||||
SL-4126
|
||||
SL-20224
|
||||
Aralara Rajal
|
||||
Arare Chantilly
|
||||
CHUIBUG-191
|
||||
|
|
@ -927,6 +928,8 @@ LSL Scientist
|
|||
Lamorna Proctor
|
||||
Lares Carter
|
||||
Larry Pixel
|
||||
Lars Næsbye Christensen
|
||||
SL-20054
|
||||
Laurent Bechir
|
||||
Leal Choche
|
||||
Lenae Munz
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1050,7 +1050,6 @@ BOOL LLAvatarAppearance::loadSkeletonNode ()
|
|||
mRoot->addChild(mMeshLOD[MESH_ID_UPPER_BODY]);
|
||||
mRoot->addChild(mMeshLOD[MESH_ID_LOWER_BODY]);
|
||||
mRoot->addChild(mMeshLOD[MESH_ID_SKIRT]);
|
||||
mRoot->addChild(mMeshLOD[MESH_ID_HEAD]);
|
||||
|
||||
LLAvatarJoint *skull = (LLAvatarJoint*)mRoot->findJoint("mSkull");
|
||||
if (skull)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -288,9 +294,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,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 <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.
|
||||
|
||||
// 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
|
||||
<const std::string&, const std::string&, ARGS...> };
|
||||
// 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<ARGS>(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<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) */
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
[[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 @@ 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>
|
||||
[[noreturn]] 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
[[noreturn]] 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>
|
||||
[[noreturn]] 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.
|
||||
|
|
|
|||
|
|
@ -268,6 +268,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.
|
||||
|
|
@ -325,13 +364,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
|
||||
|
|
|
|||
|
|
@ -340,11 +340,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -529,6 +529,7 @@ LLProcess::LLProcess(const LLSDOrParams& params):
|
|||
// preserve existing semantics, we promise that mAttached defaults to the
|
||||
// same setting as mAutokill.
|
||||
mAttached(params.attached.isProvided()? params.attached : params.autokill),
|
||||
mPool(NULL),
|
||||
mPipes(NSLOTS)
|
||||
{
|
||||
// Hmm, when you construct a ptr_vector with a size, it merely reserves
|
||||
|
|
@ -549,8 +550,14 @@ LLProcess::LLProcess(const LLSDOrParams& params):
|
|||
|
||||
mPostend = params.postend;
|
||||
|
||||
apr_pool_create(&mPool, gAPRPoolp);
|
||||
if (!mPool)
|
||||
{
|
||||
LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool")));
|
||||
}
|
||||
|
||||
apr_procattr_t *procattr = NULL;
|
||||
chkapr(apr_procattr_create(&procattr, gAPRPoolp));
|
||||
chkapr(apr_procattr_create(&procattr, mPool));
|
||||
|
||||
// IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to
|
||||
// constrain the set of handles passed to the child process. Before we
|
||||
|
|
@ -689,14 +696,14 @@ LLProcess::LLProcess(const LLSDOrParams& params):
|
|||
// one. Hand-expand chkapr() macro so we can fill in the actual command
|
||||
// string instead of the variable names.
|
||||
if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr,
|
||||
gAPRPoolp)))
|
||||
mPool)))
|
||||
{
|
||||
LLTHROW(LLProcessError(STRINGIZE(params << " failed")));
|
||||
}
|
||||
|
||||
// arrange to call status_callback()
|
||||
apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in,
|
||||
gAPRPoolp);
|
||||
mPool);
|
||||
// and make sure we poll it once per "mainloop" tick
|
||||
sProcessListener.addPoll(*this);
|
||||
mStatus.mState = RUNNING;
|
||||
|
|
@ -815,6 +822,12 @@ LLProcess::~LLProcess()
|
|||
{
|
||||
kill("destructor");
|
||||
}
|
||||
|
||||
if (mPool)
|
||||
{
|
||||
apr_pool_destroy(mPool);
|
||||
mPool = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool LLProcess::kill(const std::string& who)
|
||||
|
|
|
|||
|
|
@ -568,6 +568,7 @@ private:
|
|||
// explicitly want this ptr_vector to be able to store NULLs
|
||||
typedef boost::ptr_vector< boost::nullable<BasePipe> > PipeVector;
|
||||
PipeVector mPipes;
|
||||
apr_pool_t* mPool;
|
||||
};
|
||||
|
||||
/// for logging
|
||||
|
|
|
|||
|
|
@ -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) */
|
||||
|
|
|
|||
|
|
@ -1046,3 +1046,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,14 @@
|
|||
#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 <memory> // std::shared_ptr
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
// U32
|
||||
LL_COMMON_API LLSD ll_sd_from_U32(const U32);
|
||||
|
|
@ -298,6 +304,11 @@ LLSD map(Ts&&... vs)
|
|||
/*****************************************************************************
|
||||
* LLSDParam
|
||||
*****************************************************************************/
|
||||
struct LLSDParamBase
|
||||
{
|
||||
virtual ~LLSDParamBase() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* LLSDParam is a customization point for passing LLSD values to function
|
||||
* parameters of more or less arbitrary type. LLSD provides a small set of
|
||||
|
|
@ -315,7 +326,7 @@ LLSD map(Ts&&... vs)
|
|||
* @endcode
|
||||
*/
|
||||
template <typename T>
|
||||
class LLSDParam
|
||||
class LLSDParam: public LLSDParamBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
|
|
@ -323,13 +334,66 @@ public:
|
|||
* value for later retrieval
|
||||
*/
|
||||
LLSDParam(const LLSD& value):
|
||||
_value(value)
|
||||
value_(value)
|
||||
{}
|
||||
|
||||
operator T() const { return _value; }
|
||||
operator T() const { return value_; }
|
||||
|
||||
private:
|
||||
T _value;
|
||||
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>: public LLSDParamBase
|
||||
{
|
||||
private:
|
||||
LLSD value_;
|
||||
// LLSDParam<LLSD>::operator T() works by instantiating an LLSDParam<T> on
|
||||
// demand. Returning that engages LLSDParam<T>::operator T(), producing
|
||||
// the desired result. But LLSDParam<const char*> owns a std::string whose
|
||||
// c_str() is returned by its operator const char*(). If we return a temp
|
||||
// LLSDParam<const char*>, the compiler can destroy it right away, as soon
|
||||
// as we've called operator const char*(). That's a problem! That
|
||||
// invalidates the const char* we've just passed to the subject function.
|
||||
// This LLSDParam<LLSD> is presumably guaranteed to survive until the
|
||||
// subject function has returned, so we must ensure that any constructed
|
||||
// LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting
|
||||
// each LLSDParam<T> on the heap and capturing a smart pointer in a vector
|
||||
// works. We would have liked to use std::unique_ptr, but vector entries
|
||||
// must be copyable.
|
||||
// (Alternatively we could assume that every instance of LLSDParam<LLSD>
|
||||
// will be asked for at most ONE conversion. We could store a scalar
|
||||
// std::unique_ptr and, when constructing an new LLSDParam<T>, assert that
|
||||
// the unique_ptr is empty. But some future change in usage patterns, and
|
||||
// consequent failure of that assertion, would be very mysterious. Instead
|
||||
// of explaining how to fix it, just fix it now.)
|
||||
mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_;
|
||||
|
||||
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
|
||||
{
|
||||
// capture 'ptr' with the specific subclass type because converters_
|
||||
// only stores LLSDParamBase pointers
|
||||
auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) };
|
||||
// keep the new converter alive until we ourselves are destroyed
|
||||
converters_.push_back(ptr);
|
||||
return *ptr;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -346,17 +410,17 @@ private:
|
|||
*/
|
||||
#define LLSDParam_for(T, AS) \
|
||||
template <> \
|
||||
class LLSDParam<T> \
|
||||
class LLSDParam<T>: public LLSDParamBase \
|
||||
{ \
|
||||
public: \
|
||||
LLSDParam(const LLSD& value): \
|
||||
_value((T)value.AS()) \
|
||||
value_((T)value.AS()) \
|
||||
{} \
|
||||
\
|
||||
operator T() const { return _value; } \
|
||||
operator T() const { return value_; } \
|
||||
\
|
||||
private: \
|
||||
T _value; \
|
||||
T value_; \
|
||||
}
|
||||
|
||||
LLSDParam_for(float, asReal);
|
||||
|
|
@ -372,31 +436,31 @@ LLSDParam_for(LLSD::Binary, asBinary);
|
|||
* safely pass an LLSDParam<const char*>(yourLLSD).
|
||||
*/
|
||||
template <>
|
||||
class LLSDParam<const char*>
|
||||
class LLSDParam<const char*>: public LLSDParamBase
|
||||
{
|
||||
private:
|
||||
// The difference here is that we store a std::string rather than a const
|
||||
// char*. It's important that the LLSDParam object own the std::string.
|
||||
std::string _value;
|
||||
std::string value_;
|
||||
// We don't bother storing the incoming LLSD object, but we do have to
|
||||
// distinguish whether _value is an empty string because the LLSD object
|
||||
// distinguish whether value_ is an empty string because the LLSD object
|
||||
// contains an empty string or because it's isUndefined().
|
||||
bool _undefined;
|
||||
bool undefined_;
|
||||
|
||||
public:
|
||||
LLSDParam(const LLSD& value):
|
||||
_value(value),
|
||||
_undefined(value.isUndefined())
|
||||
value_(value),
|
||||
undefined_(value.isUndefined())
|
||||
{}
|
||||
|
||||
// The const char* we retrieve is for storage owned by our _value member.
|
||||
// The const char* we retrieve is for storage owned by our value_ member.
|
||||
// That's how we guarantee that the const char* is valid for the lifetime
|
||||
// of this LLSDParam object. Constructing your LLSDParam in the argument
|
||||
// list should ensure that the LLSDParam object will persist for the
|
||||
// duration of the function call.
|
||||
operator const char*() const
|
||||
{
|
||||
if (_undefined)
|
||||
if (undefined_)
|
||||
{
|
||||
// By default, an isUndefined() LLSD object's asString() method
|
||||
// will produce an empty string. But for a function accepting
|
||||
|
|
@ -406,7 +470,7 @@ public:
|
|||
// case, though, no LLSD value could pass NULL.
|
||||
return NULL;
|
||||
}
|
||||
return _value.c_str();
|
||||
return value_.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -555,4 +619,56 @@ struct hash<LLSD>
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
||||
/*****************************************************************************
|
||||
* apply(function, LLSD array)
|
||||
*****************************************************************************/
|
||||
// validate incoming LLSD blob, and return an LLSD array suitable to pass to
|
||||
// the function of interest
|
||||
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,240 @@
|
|||
/**
|
||||
* @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"
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// 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>
|
||||
|
||||
|
|
@ -177,6 +178,7 @@ struct Vars
|
|||
/*-------- Arbitrary-params (non-const, const, static) methods ---------*/
|
||||
void methodna(NPARAMSa)
|
||||
{
|
||||
DEBUG;
|
||||
// Because our const char* param cp might be NULL, and because we
|
||||
// intend to capture the value in a std::string, have to distinguish
|
||||
// between the NULL value and any non-NULL value. Use a convention
|
||||
|
|
@ -188,7 +190,7 @@ struct Vars
|
|||
else
|
||||
vcp = std::string("'") + cp + "'";
|
||||
|
||||
debug()("methodna(", b,
|
||||
this->debug()("methodna(", b,
|
||||
", ", i,
|
||||
", ", f,
|
||||
", ", d,
|
||||
|
|
@ -205,7 +207,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);
|
||||
}
|
||||
|
|
@ -226,7 +228,8 @@ struct Vars
|
|||
|
||||
void cmethodna(NPARAMSa) const
|
||||
{
|
||||
debug()('c', NONL);
|
||||
DEBUG;
|
||||
this->debug()('c', NONL);
|
||||
const_cast<Vars*>(this)->methodna(NARGSa);
|
||||
}
|
||||
|
||||
|
|
@ -315,6 +318,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 +425,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 +489,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 +499,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 +611,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 +627,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 +645,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 +756,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 +831,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 +860,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 +886,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 +916,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 +930,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 +947,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 +975,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 +1022,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 +1035,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 +1063,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 +1105,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 +1113,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 +1127,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 +1154,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 +1179,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 +1201,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 +1213,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 +1259,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 +1273,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 +1314,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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ void LLCocoaPlugin::processEvents()
|
|||
{
|
||||
// Some plugins (webkit at least) will want an event loop. This qualifies.
|
||||
NSEvent * event;
|
||||
event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
|
||||
event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
|
||||
[NSApp sendEvent: event];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ public:
|
|||
virtual void draw();
|
||||
virtual void deleteAllChildren();
|
||||
|
||||
void stopAutoScollining() {mNeedsScroll = false;}
|
||||
void scrollToShowSelection();
|
||||
void scrollToShowItem(LLFolderViewItem* item, const LLRect& constraint_rect);
|
||||
void setScrollContainer( LLScrollContainer* parent ) { mScrollContainer = parent; }
|
||||
|
|
|
|||
|
|
@ -164,7 +164,8 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
|
|||
mHighlightColor(p.highlight_color()),
|
||||
mPreeditBgColor(p.preedit_bg_color()),
|
||||
mGLFont(p.font),
|
||||
mContextMenuHandle()
|
||||
mContextMenuHandle(),
|
||||
mShowContextMenu(true)
|
||||
{
|
||||
llassert( mMaxLengthBytes > 0 );
|
||||
|
||||
|
|
@ -825,7 +826,7 @@ BOOL LLLineEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
|
|||
BOOL LLLineEditor::handleRightMouseDown(S32 x, S32 y, MASK mask)
|
||||
{
|
||||
setFocus(TRUE);
|
||||
if (!LLUICtrl::handleRightMouseDown(x, y, mask))
|
||||
if (!LLUICtrl::handleRightMouseDown(x, y, mask) && getShowContextMenu())
|
||||
{
|
||||
showContextMenu(x, y);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -286,7 +286,10 @@ public:
|
|||
void setBgImage(LLPointer<LLUIImage> image) { mBgImage = image; }
|
||||
void setBgImageFocused(LLPointer<LLUIImage> image) { mBgImageFocused = image; }
|
||||
|
||||
private:
|
||||
void setShowContextMenu(bool show) { mShowContextMenu = show; }
|
||||
bool getShowContextMenu() const { return mShowContextMenu; }
|
||||
|
||||
private:
|
||||
// private helper methods
|
||||
|
||||
void pasteHelper(bool is_primary);
|
||||
|
|
@ -405,6 +408,8 @@ protected:
|
|||
|
||||
LLHandle<LLContextMenu> mContextMenuHandle;
|
||||
|
||||
bool mShowContextMenu;
|
||||
|
||||
private:
|
||||
// Instances that by default point to the statics but can be overidden in XML.
|
||||
LLPointer<LLUIImage> mBgImage;
|
||||
|
|
|
|||
|
|
@ -2149,14 +2149,19 @@ void LLTabContainer::commitHoveredButton(S32 x, S32 y)
|
|||
{
|
||||
if (!getTabsHidden() && hasMouseCapture())
|
||||
{
|
||||
for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
|
||||
for (tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
|
||||
{
|
||||
LLTabTuple* tuple = *iter;
|
||||
S32 local_x = x - tuple->mButton->getRect().mLeft;
|
||||
S32 local_y = y - tuple->mButton->getRect().mBottom;
|
||||
if (tuple->mButton->pointInView(local_x, local_y) && tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible())
|
||||
LLButton* button = (*iter)->mButton;
|
||||
LLPanel* panel = (*iter)->mTabPanel;
|
||||
if (button->getEnabled() && button->getVisible() && !panel->getVisible())
|
||||
{
|
||||
tuple->mButton->onCommit();
|
||||
S32 local_x = x - button->getRect().mLeft;
|
||||
S32 local_y = y - button->getRect().mBottom;
|
||||
if (button->pointInView(local_x, local_y))
|
||||
{
|
||||
button->onCommit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1550,7 +1550,13 @@ S32 LLTextBase::getLeftOffset(S32 width)
|
|||
case LLFontGL::HCENTER:
|
||||
return mHPad + llmax(0, (mVisibleTextRect.getWidth() - width - mHPad) / 2);
|
||||
case LLFontGL::RIGHT:
|
||||
return mVisibleTextRect.getWidth() - width;
|
||||
{
|
||||
// Font's rendering rounds string size, if value gets rounded
|
||||
// down last symbol might not have enough space to render,
|
||||
// compensate by adding an extra pixel as padding
|
||||
const S32 right_padding = 1;
|
||||
return llmax(mHPad, mVisibleTextRect.getWidth() - width - right_padding);
|
||||
}
|
||||
default:
|
||||
return mHPad;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@
|
|||
|
||||
#include "llavatarnamecache.h"
|
||||
#include "llcachename.h"
|
||||
#include "llkeyboard.h"
|
||||
#include "llregex.h"
|
||||
#include "llscrolllistctrl.h" // for LLUrlEntryKeybinding file parsing
|
||||
#include "lltrans.h"
|
||||
#include "lluicolortable.h"
|
||||
#include "message.h"
|
||||
|
|
@ -1609,3 +1611,122 @@ std::string LLUrlEntryIPv6::getUrl(const std::string &string) const
|
|||
{
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// LLUrlEntryKeybinding Displays currently assigned key
|
||||
//
|
||||
LLUrlEntryKeybinding::LLUrlEntryKeybinding()
|
||||
: LLUrlEntryBase()
|
||||
, pHandler(NULL)
|
||||
{
|
||||
mPattern = boost::regex(APP_HEADER_REGEX "/keybinding/\\w+(\\?mode=\\w+)?$",
|
||||
boost::regex::perl | boost::regex::icase);
|
||||
mMenuName = "menu_url_experience.xml";
|
||||
|
||||
initLocalization();
|
||||
}
|
||||
|
||||
std::string LLUrlEntryKeybinding::getLabel(const std::string& url, const LLUrlLabelCallback& cb)
|
||||
{
|
||||
std::string control = getControlName(url);
|
||||
|
||||
std::map<std::string, LLLocalizationData>::iterator iter = mLocalizations.find(control);
|
||||
|
||||
std::string keybind;
|
||||
if (pHandler)
|
||||
{
|
||||
keybind = pHandler->getKeyBindingAsString(getMode(url), control);
|
||||
}
|
||||
|
||||
if (iter != mLocalizations.end())
|
||||
{
|
||||
return iter->second.mLocalization + ": " + keybind;
|
||||
}
|
||||
|
||||
return control + ": " + keybind;
|
||||
}
|
||||
|
||||
std::string LLUrlEntryKeybinding::getTooltip(const std::string& url) const
|
||||
{
|
||||
std::string control = getControlName(url);
|
||||
|
||||
std::map<std::string, LLLocalizationData>::const_iterator iter = mLocalizations.find(control);
|
||||
if (iter != mLocalizations.end())
|
||||
{
|
||||
return iter->second.mTooltip;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
std::string LLUrlEntryKeybinding::getControlName(const std::string& url) const
|
||||
{
|
||||
std::string search = "/keybinding/";
|
||||
size_t pos_start = url.find(search);
|
||||
if (pos_start == std::string::npos)
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
pos_start += search.size();
|
||||
|
||||
size_t pos_end = url.find("?mode=");
|
||||
if (pos_end == std::string::npos)
|
||||
{
|
||||
pos_end = url.size();
|
||||
}
|
||||
return url.substr(pos_start, pos_end - pos_start);
|
||||
}
|
||||
|
||||
std::string LLUrlEntryKeybinding::getMode(const std::string& url) const
|
||||
{
|
||||
std::string search = "?mode=";
|
||||
size_t pos_start = url.find(search);
|
||||
if (pos_start == std::string::npos)
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
pos_start += search.size();
|
||||
return url.substr(pos_start, url.size() - pos_start);
|
||||
}
|
||||
|
||||
void LLUrlEntryKeybinding::initLocalization()
|
||||
{
|
||||
initLocalizationFromFile("control_table_contents_movement.xml");
|
||||
initLocalizationFromFile("control_table_contents_camera.xml");
|
||||
initLocalizationFromFile("control_table_contents_editing.xml");
|
||||
initLocalizationFromFile("control_table_contents_media.xml");
|
||||
}
|
||||
|
||||
void LLUrlEntryKeybinding::initLocalizationFromFile(const std::string& filename)
|
||||
{
|
||||
LLXMLNodePtr xmlNode;
|
||||
LLScrollListCtrl::Contents contents;
|
||||
if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode))
|
||||
{
|
||||
LL_WARNS() << "Failed to load " << filename << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
LLXUIParser parser;
|
||||
parser.readXUI(xmlNode, contents, filename);
|
||||
|
||||
if (!contents.validateBlock())
|
||||
{
|
||||
LL_WARNS() << "Failed to validate " << filename << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
|
||||
for (LLInitParam::ParamIterator<LLScrollListItem::Params>::const_iterator row_it = contents.rows.begin();
|
||||
row_it != contents.rows.end();
|
||||
++row_it)
|
||||
{
|
||||
std::string control = row_it->value.getValue().asString();
|
||||
if (!control.empty() && control != "menu_separator")
|
||||
{
|
||||
mLocalizations[control] =
|
||||
LLLocalizationData(
|
||||
row_it->columns.begin()->value.getValue().asString(),
|
||||
row_it->columns.begin()->tool_tip.getValue()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -550,4 +550,37 @@ public:
|
|||
std::string mHostPath;
|
||||
};
|
||||
|
||||
class LLKeyBindingToStringHandler;
|
||||
|
||||
///
|
||||
/// LLUrlEntryKeybinding A way to access keybindings and show currently used one in text.
|
||||
/// secondlife:///app/keybinding/control_name
|
||||
class LLUrlEntryKeybinding: public LLUrlEntryBase
|
||||
{
|
||||
public:
|
||||
LLUrlEntryKeybinding();
|
||||
/*virtual*/ std::string getLabel(const std::string& url, const LLUrlLabelCallback& cb);
|
||||
/*virtual*/ std::string getTooltip(const std::string& url) const;
|
||||
void setHandler(LLKeyBindingToStringHandler* handler) {pHandler = handler;}
|
||||
private:
|
||||
std::string getControlName(const std::string& url) const;
|
||||
std::string getMode(const std::string& url) const;
|
||||
void initLocalization();
|
||||
void initLocalizationFromFile(const std::string& filename);
|
||||
|
||||
struct LLLocalizationData
|
||||
{
|
||||
LLLocalizationData() {}
|
||||
LLLocalizationData(const std::string& localization, const std::string& tooltip)
|
||||
: mLocalization(localization)
|
||||
, mTooltip(tooltip)
|
||||
{}
|
||||
std::string mLocalization;
|
||||
std::string mTooltip;
|
||||
};
|
||||
|
||||
std::map<std::string, LLLocalizationData> mLocalizations;
|
||||
LLKeyBindingToStringHandler* pHandler;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ LLUrlRegistry::LLUrlRegistry()
|
|||
registerUrl(new LLUrlEntryPlace());
|
||||
registerUrl(new LLUrlEntryInventory());
|
||||
registerUrl(new LLUrlEntryExperienceProfile());
|
||||
mUrlEntryKeybinding = new LLUrlEntryKeybinding();
|
||||
registerUrl(mUrlEntryKeybinding);
|
||||
//LLUrlEntrySL and LLUrlEntrySLLabel have more common pattern,
|
||||
//so it should be registered in the end of list
|
||||
registerUrl(new LLUrlEntrySL());
|
||||
|
|
@ -307,3 +309,9 @@ bool LLUrlRegistry::isUrl(const LLWString &text)
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LLUrlRegistry::setKeybindingHandler(LLKeyBindingToStringHandler* handler)
|
||||
{
|
||||
LLUrlEntryKeybinding *entry = (LLUrlEntryKeybinding*)mUrlEntryKeybinding;
|
||||
entry->setHandler(handler);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class LLKeyBindingToStringHandler;
|
||||
|
||||
/// This default callback for findUrl() simply ignores any label updates
|
||||
void LLUrlRegistryNullCallback(const std::string &url,
|
||||
const std::string &label,
|
||||
|
|
@ -88,6 +90,9 @@ public:
|
|||
bool isUrl(const std::string &text);
|
||||
bool isUrl(const LLWString &text);
|
||||
|
||||
// Set handler for url registry to be capable of parsing and populating keybindings
|
||||
void setKeybindingHandler(LLKeyBindingToStringHandler* handler);
|
||||
|
||||
private:
|
||||
std::vector<LLUrlEntryBase *> mUrlEntry;
|
||||
LLUrlEntryBase* mUrlEntryTrusted;
|
||||
|
|
@ -96,6 +101,7 @@ private:
|
|||
LLUrlEntryBase* mUrlEntryHTTPLabel;
|
||||
LLUrlEntryBase* mUrlEntrySLLabel;
|
||||
LLUrlEntryBase* mUrlEntryNoLink;
|
||||
LLUrlEntryBase* mUrlEntryKeybinding;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -366,6 +366,45 @@ std::string LLKeyboard::stringFromKey(KEY key, bool translate)
|
|||
return res;
|
||||
}
|
||||
|
||||
//static
|
||||
std::string LLKeyboard::stringFromMouse(EMouseClickType click, bool translate)
|
||||
{
|
||||
std::string res;
|
||||
switch (click)
|
||||
{
|
||||
case CLICK_LEFT:
|
||||
res = "LMB";
|
||||
break;
|
||||
case CLICK_MIDDLE:
|
||||
res = "MMB";
|
||||
break;
|
||||
case CLICK_RIGHT:
|
||||
res = "RMB";
|
||||
break;
|
||||
case CLICK_BUTTON4:
|
||||
res = "MB4";
|
||||
break;
|
||||
case CLICK_BUTTON5:
|
||||
res = "MB5";
|
||||
break;
|
||||
case CLICK_DOUBLELEFT:
|
||||
res = "Double LMB";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (translate && !res.empty())
|
||||
{
|
||||
LLKeyStringTranslatorFunc* trans = gKeyboard->mStringTranslator;
|
||||
if (trans != NULL)
|
||||
{
|
||||
res = trans(res.c_str());
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
//static
|
||||
std::string LLKeyboard::stringFromAccelerator(MASK accel_mask)
|
||||
{
|
||||
|
|
@ -433,6 +472,18 @@ std::string LLKeyboard::stringFromAccelerator( MASK accel_mask, KEY key )
|
|||
return res;
|
||||
}
|
||||
|
||||
//static
|
||||
std::string LLKeyboard::stringFromAccelerator(MASK accel_mask, EMouseClickType click)
|
||||
{
|
||||
std::string res;
|
||||
if (CLICK_NONE == click)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
res.append(stringFromAccelerator(accel_mask));
|
||||
res.append(stringFromMouse(click));
|
||||
return res;
|
||||
}
|
||||
|
||||
//static
|
||||
BOOL LLKeyboard::maskFromString(const std::string& str, MASK *mask)
|
||||
|
|
|
|||
|
|
@ -96,8 +96,10 @@ public:
|
|||
static BOOL maskFromString(const std::string& str, MASK *mask); // False on failure
|
||||
static BOOL keyFromString(const std::string& str, KEY *key); // False on failure
|
||||
static std::string stringFromKey(KEY key, bool translate = true);
|
||||
static std::string stringFromMouse(EMouseClickType click, bool translate = true);
|
||||
static std::string stringFromAccelerator( MASK accel_mask ); // separated for convinience, returns with "+": "Shift+" or "Shift+Alt+"...
|
||||
static std::string stringFromAccelerator( MASK accel_mask, KEY key );
|
||||
static std::string stringFromAccelerator(MASK accel_mask, EMouseClickType click);
|
||||
|
||||
void setCallbacks(LLWindowCallbacks *cbs) { mCallbacks = cbs; }
|
||||
F32 getKeyElapsedTime( KEY key ); // Returns time in seconds since key was pressed.
|
||||
|
|
@ -130,6 +132,13 @@ protected:
|
|||
static std::map<std::string,KEY> sNamesToKeys;
|
||||
};
|
||||
|
||||
// Interface to get key from assigned command
|
||||
class LLKeyBindingToStringHandler
|
||||
{
|
||||
public:
|
||||
virtual std::string getKeyBindingAsString(const std::string& mode, const std::string& control) const = 0;
|
||||
};
|
||||
|
||||
extern LLKeyboard *gKeyboard;
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1740,7 +1740,7 @@ if (WINDOWS)
|
|||
if (TARGET ll::fmodstudio)
|
||||
list(APPEND COPY_INPUT_DEPENDENCIES
|
||||
${SHARED_LIB_STAGING_DIR}/fmod.dll
|
||||
${SHARED_LIB_STAGING_DIR}/Debug/fmodL.dll
|
||||
${SHARED_LIB_STAGING_DIR}/fmodL.dll
|
||||
)
|
||||
endif ()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
|
||||
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
|
||||
|
||||
|
||||
Bitstream Vera Fonts Copyright
|
||||
------------------------------
|
||||
|
||||
|
|
@ -46,7 +47,7 @@ Foundation, and Bitstream Inc., shall not be used in advertising or
|
|||
otherwise to promote the sale, use or other dealings in this Font Software
|
||||
without prior written authorization from the Gnome Foundation or Bitstream
|
||||
Inc., respectively. For further information, contact: fonts at gnome dot
|
||||
org.
|
||||
org.
|
||||
|
||||
Arev Fonts Copyright
|
||||
------------------------------
|
||||
|
|
@ -96,4 +97,91 @@ dealings in this Font Software without prior written authorization
|
|||
from Tavmjong Bah. For further information, contact: tavmjong @ free
|
||||
. fr.
|
||||
|
||||
$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
|
||||
TeX Gyre DJV Math
|
||||
-----------------
|
||||
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
|
||||
|
||||
Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski
|
||||
(on behalf of TeX users groups) are in public domain.
|
||||
|
||||
Letters imported from Euler Fraktur from AMSfonts are (c) American
|
||||
Mathematical Society (see below).
|
||||
Bitstream Vera Fonts Copyright
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera
|
||||
is a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of the fonts accompanying this license (“Fonts”) and associated
|
||||
documentation
|
||||
files (the “Font Software”), to reproduce and distribute the Font Software,
|
||||
including without limitation the rights to use, copy, merge, publish,
|
||||
distribute,
|
||||
and/or sell copies of the Font Software, and to permit persons to whom
|
||||
the Font Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice
|
||||
shall be
|
||||
included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular
|
||||
the designs of glyphs or characters in the Fonts may be modified and
|
||||
additional
|
||||
glyphs or characters may be added to the Fonts, only if the fonts are
|
||||
renamed
|
||||
to names not containing either the words “Bitstream” or the word “Vera”.
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or
|
||||
Font Software
|
||||
that has been modified and is distributed under the “Bitstream Vera”
|
||||
names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but
|
||||
no copy
|
||||
of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
|
||||
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
|
||||
FOUNDATION
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL,
|
||||
SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN
|
||||
ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
|
||||
INABILITY TO USE
|
||||
THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Except as contained in this notice, the names of GNOME, the GNOME
|
||||
Foundation,
|
||||
and Bitstream Inc., shall not be used in advertising or otherwise to promote
|
||||
the sale, use or other dealings in this Font Software without prior written
|
||||
authorization from the GNOME Foundation or Bitstream Inc., respectively.
|
||||
For further information, contact: fonts at gnome dot org.
|
||||
|
||||
AMSFonts (v. 2.2) copyright
|
||||
|
||||
The PostScript Type 1 implementation of the AMSFonts produced by and
|
||||
previously distributed by Blue Sky Research and Y&Y, Inc. are now freely
|
||||
available for general use. This has been accomplished through the
|
||||
cooperation
|
||||
of a consortium of scientific publishers with Blue Sky Research and Y&Y.
|
||||
Members of this consortium include:
|
||||
|
||||
Elsevier Science IBM Corporation Society for Industrial and Applied
|
||||
Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS)
|
||||
|
||||
In order to assure the authenticity of these fonts, copyright will be
|
||||
held by
|
||||
the American Mathematical Society. This is not meant to restrict in any way
|
||||
the legitimate use of the fonts, such as (but not limited to) electronic
|
||||
distribution of documents containing these fonts, inclusion of these fonts
|
||||
into other public domain or commercial font collections or computer
|
||||
applications, use of the outline data to create derivative fonts and/or
|
||||
faces, etc. However, the AMS does require that the AMS copyright notice be
|
||||
removed from any derivative versions of the fonts which have been altered in
|
||||
any way. In addition, to ensure the fidelity of TeX documents using Computer
|
||||
Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces,
|
||||
has requested that any alterations which yield different font metrics be
|
||||
given a different name.
|
||||
|
||||
$Id$
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 61 KiB |
|
|
@ -487,7 +487,11 @@ void LLAgent::init()
|
|||
|
||||
// *Note: this is where LLViewerCamera::getInstance() used to be constructed.
|
||||
|
||||
setFlying( gSavedSettings.getBOOL("FlyingAtExit") );
|
||||
bool is_flying = gSavedSettings.getBOOL("FlyingAtExit");
|
||||
if(is_flying)
|
||||
{
|
||||
setFlying(is_flying);
|
||||
}
|
||||
|
||||
*mEffectColor = LLUIColorTable::instance().getColor("EffectColor");
|
||||
|
||||
|
|
@ -2633,12 +2637,6 @@ void LLAgent::setStartPosition( U32 location_id )
|
|||
if (!requestPostCapability("HomeLocation", body,
|
||||
boost::bind(&LLAgent::setStartPositionSuccess, this, _1)))
|
||||
LL_WARNS() << "Unable to post to HomeLocation capability." << LL_ENDL;
|
||||
|
||||
const U32 HOME_INDEX = 1;
|
||||
if( HOME_INDEX == location_id )
|
||||
{
|
||||
setHomePosRegion( mRegionp->getHandle(), getPositionAgent() );
|
||||
}
|
||||
}
|
||||
|
||||
void LLAgent::setStartPositionSuccess(const LLSD &result)
|
||||
|
|
|
|||
|
|
@ -100,6 +100,12 @@ const F32 GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME = 0.5f;
|
|||
|
||||
const F32 OBJECT_EXTENTS_PADDING = 0.5f;
|
||||
|
||||
static bool isDisableCameraConstraints()
|
||||
{
|
||||
static LLCachedControl<bool> sDisableCameraConstraints(gSavedSettings, "DisableCameraConstraints", false);
|
||||
return sDisableCameraConstraints;
|
||||
}
|
||||
|
||||
// The agent instance.
|
||||
LLAgentCamera gAgentCamera;
|
||||
|
||||
|
|
@ -570,9 +576,9 @@ BOOL LLAgentCamera::calcCameraMinDistance(F32 &obj_min_distance)
|
|||
{
|
||||
BOOL soft_limit = FALSE; // is the bounding box to be treated literally (volumes) or as an approximation (avatars)
|
||||
|
||||
if (!mFocusObject || mFocusObject->isDead() ||
|
||||
if (!mFocusObject || mFocusObject->isDead() ||
|
||||
mFocusObject->isMesh() ||
|
||||
gSavedSettings.getBOOL("DisableCameraConstraints"))
|
||||
isDisableCameraConstraints())
|
||||
{
|
||||
obj_min_distance = 0.f;
|
||||
return TRUE;
|
||||
|
|
@ -742,39 +748,44 @@ F32 LLAgentCamera::getCameraZoomFraction(bool get_third_person)
|
|||
// already [0,1]
|
||||
return mHUDTargetZoom;
|
||||
}
|
||||
else if (get_third_person || (mFocusOnAvatar && cameraThirdPerson()))
|
||||
|
||||
if (isDisableCameraConstraints())
|
||||
{
|
||||
return mCameraZoomFraction;
|
||||
}
|
||||
|
||||
if (get_third_person || (mFocusOnAvatar && cameraThirdPerson()))
|
||||
{
|
||||
return clamp_rescale(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION, 1.f, 0.f);
|
||||
}
|
||||
else if (cameraCustomizeAvatar())
|
||||
|
||||
if (cameraCustomizeAvatar())
|
||||
{
|
||||
F32 distance = (F32)mCameraFocusOffsetTarget.magVec();
|
||||
return clamp_rescale(distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM, 1.f, 0.f );
|
||||
}
|
||||
else
|
||||
{
|
||||
F32 min_zoom;
|
||||
F32 max_zoom = getCameraMaxZoomDistance();
|
||||
|
||||
F32 distance = (F32)mCameraFocusOffsetTarget.magVec();
|
||||
if (mFocusObject.notNull())
|
||||
F32 min_zoom;
|
||||
F32 max_zoom = getCameraMaxZoomDistance();
|
||||
|
||||
F32 distance = (F32)mCameraFocusOffsetTarget.magVec();
|
||||
if (mFocusObject.notNull())
|
||||
{
|
||||
if (mFocusObject->isAvatar())
|
||||
{
|
||||
if (mFocusObject->isAvatar())
|
||||
{
|
||||
min_zoom = AVATAR_MIN_ZOOM;
|
||||
}
|
||||
else
|
||||
{
|
||||
min_zoom = OBJECT_MIN_ZOOM;
|
||||
}
|
||||
min_zoom = AVATAR_MIN_ZOOM;
|
||||
}
|
||||
else
|
||||
{
|
||||
min_zoom = LAND_MIN_ZOOM;
|
||||
min_zoom = OBJECT_MIN_ZOOM;
|
||||
}
|
||||
|
||||
return clamp_rescale(distance, min_zoom, max_zoom, 1.f, 0.f);
|
||||
}
|
||||
else
|
||||
{
|
||||
min_zoom = LAND_MIN_ZOOM;
|
||||
}
|
||||
|
||||
return clamp_rescale(distance, min_zoom, max_zoom, 1.f, 0.f);
|
||||
}
|
||||
|
||||
void LLAgentCamera::setCameraZoomFraction(F32 fraction)
|
||||
|
|
@ -787,6 +798,10 @@ void LLAgentCamera::setCameraZoomFraction(F32 fraction)
|
|||
{
|
||||
mHUDTargetZoom = fraction;
|
||||
}
|
||||
else if (isDisableCameraConstraints())
|
||||
{
|
||||
mCameraZoomFraction = fraction;
|
||||
}
|
||||
else if (mFocusOnAvatar && cameraThirdPerson())
|
||||
{
|
||||
mCameraZoomFraction = rescale(fraction, 0.f, 1.f, MAX_ZOOM_FRACTION, MIN_ZOOM_FRACTION);
|
||||
|
|
@ -821,6 +836,7 @@ void LLAgentCamera::setCameraZoomFraction(F32 fraction)
|
|||
camera_offset_dir.normalize();
|
||||
mCameraFocusOffsetTarget = camera_offset_dir * rescale(fraction, 0.f, 1.f, max_zoom, min_zoom);
|
||||
}
|
||||
|
||||
startCameraAnimation();
|
||||
}
|
||||
|
||||
|
|
@ -925,51 +941,42 @@ void LLAgentCamera::cameraZoomIn(const F32 fraction)
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
LLVector3d camera_offset_unit(mCameraFocusOffsetTarget);
|
||||
F32 min_zoom = LAND_MIN_ZOOM;
|
||||
LLVector3d camera_offset_unit(mCameraFocusOffsetTarget);
|
||||
F32 current_distance = (F32)camera_offset_unit.normalize();
|
||||
F32 new_distance = current_distance * fraction;
|
||||
|
||||
// Don't move through focus point
|
||||
if (mFocusObject)
|
||||
// Unless camera is unlocked
|
||||
if (!isDisableCameraConstraints())
|
||||
{
|
||||
LLVector3 camera_offset_dir((F32)camera_offset_unit.mdV[VX], (F32)camera_offset_unit.mdV[VY], (F32)camera_offset_unit.mdV[VZ]);
|
||||
F32 min_zoom = LAND_MIN_ZOOM;
|
||||
|
||||
if (mFocusObject->isAvatar())
|
||||
// Don't move through focus point
|
||||
if (mFocusObject)
|
||||
{
|
||||
calcCameraMinDistance(min_zoom);
|
||||
LLVector3 camera_offset_dir((F32)camera_offset_unit.mdV[VX], (F32)camera_offset_unit.mdV[VY], (F32)camera_offset_unit.mdV[VZ]);
|
||||
|
||||
if (mFocusObject->isAvatar())
|
||||
{
|
||||
calcCameraMinDistance(min_zoom);
|
||||
}
|
||||
else
|
||||
{
|
||||
min_zoom = OBJECT_MIN_ZOOM;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
new_distance = llmax(new_distance, min_zoom);
|
||||
|
||||
F32 max_distance = getCameraMaxZoomDistance();
|
||||
max_distance = llmin(max_distance, current_distance * 4.f); //Scaled max relative to current distance. MAINT-3154
|
||||
new_distance = llmin(new_distance, max_distance);
|
||||
|
||||
if (cameraCustomizeAvatar())
|
||||
{
|
||||
min_zoom = OBJECT_MIN_ZOOM;
|
||||
new_distance = llclamp(new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM);
|
||||
}
|
||||
}
|
||||
|
||||
new_distance = llmax(new_distance, min_zoom);
|
||||
|
||||
F32 max_distance = getCameraMaxZoomDistance();
|
||||
|
||||
max_distance = llmin(max_distance, current_distance * 4.f); //Scaled max relative to current distance. MAINT-3154
|
||||
|
||||
if (new_distance > max_distance)
|
||||
{
|
||||
new_distance = max_distance;
|
||||
|
||||
/*
|
||||
// Unless camera is unlocked
|
||||
if (!LLViewerCamera::sDisableCameraConstraints)
|
||||
{
|
||||
return;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if(cameraCustomizeAvatar())
|
||||
{
|
||||
new_distance = llclamp( new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM );
|
||||
}
|
||||
|
||||
mCameraFocusOffsetTarget = new_distance * camera_offset_unit;
|
||||
}
|
||||
|
||||
|
|
@ -990,53 +997,52 @@ void LLAgentCamera::cameraOrbitIn(const F32 meters)
|
|||
changeCameraToMouselook(FALSE);
|
||||
}
|
||||
|
||||
mCameraZoomFraction = llclamp(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION);
|
||||
if (!isDisableCameraConstraints())
|
||||
{
|
||||
mCameraZoomFraction = llclamp(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LLVector3d camera_offset_unit(mCameraFocusOffsetTarget);
|
||||
F32 current_distance = (F32)camera_offset_unit.normalize();
|
||||
F32 new_distance = current_distance - meters;
|
||||
F32 min_zoom = LAND_MIN_ZOOM;
|
||||
|
||||
// Don't move through focus point
|
||||
if (mFocusObject.notNull())
|
||||
|
||||
// Unless camera is unlocked
|
||||
if (!isDisableCameraConstraints())
|
||||
{
|
||||
if (mFocusObject->isAvatar())
|
||||
F32 min_zoom = LAND_MIN_ZOOM;
|
||||
|
||||
// Don't move through focus point
|
||||
if (mFocusObject.notNull())
|
||||
{
|
||||
min_zoom = AVATAR_MIN_ZOOM;
|
||||
if (mFocusObject->isAvatar())
|
||||
{
|
||||
min_zoom = AVATAR_MIN_ZOOM;
|
||||
}
|
||||
else
|
||||
{
|
||||
min_zoom = OBJECT_MIN_ZOOM;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
new_distance = llmax(new_distance, min_zoom);
|
||||
|
||||
F32 max_distance = getCameraMaxZoomDistance();
|
||||
new_distance = llmin(new_distance, max_distance);
|
||||
|
||||
if (CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode())
|
||||
{
|
||||
min_zoom = OBJECT_MIN_ZOOM;
|
||||
new_distance = llclamp(new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM);
|
||||
}
|
||||
}
|
||||
|
||||
new_distance = llmax(new_distance, min_zoom);
|
||||
|
||||
F32 max_distance = getCameraMaxZoomDistance();
|
||||
|
||||
if (new_distance > max_distance)
|
||||
{
|
||||
// Unless camera is unlocked
|
||||
if (!gSavedSettings.getBOOL("DisableCameraConstraints"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if( CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode() )
|
||||
{
|
||||
new_distance = llclamp( new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM );
|
||||
}
|
||||
|
||||
// Compute new camera offset
|
||||
mCameraFocusOffsetTarget = new_distance * camera_offset_unit;
|
||||
cameraZoomIn(1.f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// cameraPanIn()
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
@ -1835,7 +1841,8 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(BOOL *hit_limit)
|
|||
local_camera_offset = gAgent.getFrameAgent().rotateToAbsolute( local_camera_offset );
|
||||
}
|
||||
|
||||
if (!mCameraCollidePlane.isExactlyZero() && (!isAgentAvatarValid() || !gAgentAvatarp->isSitting()))
|
||||
if (!isDisableCameraConstraints() && !mCameraCollidePlane.isExactlyZero() &&
|
||||
(!isAgentAvatarValid() || !gAgentAvatarp->isSitting()))
|
||||
{
|
||||
LLVector3 plane_normal;
|
||||
plane_normal.setVec(mCameraCollidePlane.mV);
|
||||
|
|
@ -1954,7 +1961,7 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(BOOL *hit_limit)
|
|||
camera_position_global = focusPosGlobal + mCameraFocusOffset;
|
||||
}
|
||||
|
||||
if (!gSavedSettings.getBOOL("DisableCameraConstraints") && !gAgent.isGodlike())
|
||||
if (!isDisableCameraConstraints() && !gAgent.isGodlike())
|
||||
{
|
||||
LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromPosGlobal(camera_position_global);
|
||||
bool constrain = true;
|
||||
|
|
@ -1989,16 +1996,14 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(BOOL *hit_limit)
|
|||
|
||||
// Don't let camera go underground
|
||||
F32 camera_min_off_ground = getCameraMinOffGround();
|
||||
|
||||
camera_land_height = LLWorld::getInstance()->resolveLandHeightGlobal(camera_position_global);
|
||||
|
||||
if (camera_position_global.mdV[VZ] < camera_land_height + camera_min_off_ground)
|
||||
F32 minZ = llmax(F_ALMOST_ZERO, camera_land_height + camera_min_off_ground);
|
||||
if (camera_position_global.mdV[VZ] < minZ)
|
||||
{
|
||||
camera_position_global.mdV[VZ] = camera_land_height + camera_min_off_ground;
|
||||
camera_position_global.mdV[VZ] = minZ;
|
||||
isConstrained = TRUE;
|
||||
}
|
||||
|
||||
|
||||
if (hit_limit)
|
||||
{
|
||||
*hit_limit = isConstrained;
|
||||
|
|
@ -2118,17 +2123,13 @@ F32 LLAgentCamera::getCameraMinOffGround()
|
|||
{
|
||||
return 0.f;
|
||||
}
|
||||
else
|
||||
|
||||
if (isDisableCameraConstraints())
|
||||
{
|
||||
if (gSavedSettings.getBOOL("DisableCameraConstraints"))
|
||||
{
|
||||
return -1000.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.5f;
|
||||
}
|
||||
return -1000.f;
|
||||
}
|
||||
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2776,9 +2776,23 @@ void LLAppearanceMgr::wearInventoryCategory(LLInventoryCategory* category, bool
|
|||
else
|
||||
{
|
||||
selfStartPhase("wear_inventory_category_fetch");
|
||||
callAfterCategoryFetch(category->getUUID(),boost::bind(&LLAppearanceMgr::wearCategoryFinal,
|
||||
&LLAppearanceMgr::instance(),
|
||||
category->getUUID(), copy, append));
|
||||
if (AISAPI::isAvailable() && category->getPreferredType() == LLFolderType::FT_OUTFIT)
|
||||
{
|
||||
// for reliability just fetch it whole, linked items included
|
||||
LLUUID cat_id = category->getUUID();
|
||||
LLInventoryModelBackgroundFetch::getInstance()->fetchFolderAndLinks(
|
||||
cat_id,
|
||||
[cat_id, copy, append]
|
||||
{
|
||||
LLAppearanceMgr::instance().wearCategoryFinal(cat_id, copy, append);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
callAfterCategoryFetch(category->getUUID(), boost::bind(&LLAppearanceMgr::wearCategoryFinal,
|
||||
&LLAppearanceMgr::instance(),
|
||||
category->getUUID(), copy, append));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2787,7 +2801,7 @@ S32 LLAppearanceMgr::getActiveCopyOperations() const
|
|||
return LLCallAfterInventoryCopyMgr::getInstanceCount();
|
||||
}
|
||||
|
||||
void LLAppearanceMgr::wearCategoryFinal(LLUUID& cat_id, bool copy_items, bool append)
|
||||
void LLAppearanceMgr::wearCategoryFinal(const LLUUID& cat_id, bool copy_items, bool append)
|
||||
{
|
||||
LL_INFOS("Avatar") << self_av_string() << "starting" << LL_ENDL;
|
||||
|
||||
|
|
@ -4571,30 +4585,20 @@ protected:
|
|||
|
||||
void callAfterCOFFetch(nullary_func_t cb)
|
||||
{
|
||||
LLUUID cat_id = LLAppearanceMgr::instance().getCOF();
|
||||
LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
|
||||
|
||||
if (AISAPI::isAvailable())
|
||||
{
|
||||
// Mark cof (update timer) so that background fetch won't request it
|
||||
cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE);
|
||||
// For reliability assume that we have no relevant cache, so
|
||||
// fetch cof along with items cof's links point to.
|
||||
AISAPI::FetchCOF([cb](const LLUUID& id)
|
||||
{
|
||||
cb();
|
||||
LLUUID cat_id = LLAppearanceMgr::instance().getCOF();
|
||||
LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
|
||||
if (cat)
|
||||
{
|
||||
cat->setFetching(LLViewerInventoryCategory::FETCH_NONE);
|
||||
}
|
||||
});
|
||||
LLInventoryModelBackgroundFetch::getInstance()->fetchCOF(cb);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_INFOS() << "AIS API v3 not available, using callAfterCategoryFetch" << LL_ENDL;
|
||||
// startup should have marked folder as fetching, remove that
|
||||
LLUUID cat_id = LLAppearanceMgr::instance().getCOF();
|
||||
LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
|
||||
|
||||
// Special case, startup should have marked cof as FETCH_RECURSIVE
|
||||
// to prevent dupplicate request, remove that
|
||||
cat->setFetching(LLViewerInventoryCategory::FETCH_NONE);
|
||||
callAfterCategoryFetch(cat_id, cb);
|
||||
}
|
||||
|
|
@ -4616,30 +4620,16 @@ void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb)
|
|||
|
||||
void callAfterCategoryLinksFetch(const LLUUID &cat_id, nullary_func_t cb)
|
||||
{
|
||||
LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id);
|
||||
if (AISAPI::isAvailable())
|
||||
{
|
||||
// Mark folder (update timer) so that background fetch won't request it
|
||||
cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE);
|
||||
// Assume that we have no relevant cache. Fetch folder, and items folder's links point to.
|
||||
AISAPI::FetchCategoryLinks(cat_id,
|
||||
[cb, cat_id](const LLUUID &id)
|
||||
{
|
||||
cb();
|
||||
LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id);
|
||||
if (cat)
|
||||
{
|
||||
cat->setFetching(LLViewerInventoryCategory::FETCH_NONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS() << "AIS API v3 not available, can't use AISAPI::FetchCOF" << LL_ENDL;
|
||||
// startup should have marked folder as fetching, remove that
|
||||
cat->setFetching(LLViewerInventoryCategory::FETCH_NONE);
|
||||
callAfterCategoryFetch(cat_id, cb);
|
||||
}
|
||||
LLInventoryModelBackgroundFetch::getInstance()->fetchFolderAndLinks(cat_id, cb);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS() << "AIS API v3 not available, can't use AISAPI::FetchCOF" << LL_ENDL;
|
||||
callAfterCategoryFetch(cat_id, cb);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public:
|
|||
void updateCOF(const LLUUID& category, bool append = false);
|
||||
void wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append);
|
||||
void wearInventoryCategoryOnAvatar(LLInventoryCategory* category, bool append);
|
||||
void wearCategoryFinal(LLUUID& cat_id, bool copy_items, bool append);
|
||||
void wearCategoryFinal(const LLUUID& cat_id, bool copy_items, bool append);
|
||||
void wearOutfitByName(const std::string& name);
|
||||
void changeOutfit(bool proceed, const LLUUID& category, bool append);
|
||||
void replaceCurrentOutfit(const LLUUID& new_outfit);
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@
|
|||
#include "llhudeffecttrail.h"
|
||||
#include "llvectorperfoptions.h"
|
||||
#include "llslurl.h"
|
||||
#include "llurlregistry.h"
|
||||
#include "llwatchdog.h"
|
||||
|
||||
// Included so that constants/settings might be initialized
|
||||
|
|
@ -4295,6 +4296,7 @@ void LLAppViewer::loadKeyBindings()
|
|||
LL_ERRS("InitInfo") << "Unable to open default key bindings from " << key_bindings_file << LL_ENDL;
|
||||
}
|
||||
}
|
||||
LLUrlRegistry::instance().setKeybindingHandler(&gViewerInput);
|
||||
}
|
||||
|
||||
void LLAppViewer::purgeCache()
|
||||
|
|
|
|||
|
|
@ -247,6 +247,11 @@ void LLAvatarIconCtrl::setValue(const LLSD& value)
|
|||
app->addObserver(mAvatarId, this);
|
||||
app->sendAvatarPropertiesRequest(mAvatarId);
|
||||
}
|
||||
else if (gAgentID == mAvatarId)
|
||||
{
|
||||
// Always track any changes to our own icon id
|
||||
app->addObserver(mAvatarId, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -79,8 +79,14 @@ void LLAvatarRenderInfoAccountant::avatarRenderInfoGetCoro(std::string url, U64
|
|||
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
|
||||
httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("AvatarRenderInfoAccountant", httpPolicy));
|
||||
LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
|
||||
LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
|
||||
|
||||
LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
|
||||
// Going to request each 15 seconds either way, so don't wait
|
||||
// too long and don't repeat
|
||||
httpOpts->setRetries(0);
|
||||
httpOpts->setTimeout(SECS_BETWEEN_REGION_REQUEST);
|
||||
|
||||
LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts);
|
||||
|
||||
LLWorld *world_inst = LLWorld::getInstance();
|
||||
if (!world_inst)
|
||||
|
|
@ -190,6 +196,11 @@ void LLAvatarRenderInfoAccountant::avatarRenderInfoReportCoro(std::string url, U
|
|||
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
|
||||
httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("AvatarRenderInfoAccountant", httpPolicy));
|
||||
LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
|
||||
LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
|
||||
|
||||
// Going to request each 60+ seconds, timeout is 30s.
|
||||
// Don't repeat too often, will be sending newer data soon
|
||||
httpOpts->setRetries(1);
|
||||
|
||||
LLWorld *world_inst = LLWorld::getInstance();
|
||||
if (!world_inst)
|
||||
|
|
@ -256,7 +267,7 @@ void LLAvatarRenderInfoAccountant::avatarRenderInfoReportCoro(std::string url, U
|
|||
|
||||
regionp = NULL;
|
||||
world_inst = NULL;
|
||||
LLSD result = httpAdapter->postAndSuspend(httpRequest, url, report);
|
||||
LLSD result = httpAdapter->postAndSuspend(httpRequest, url, report, httpOpts);
|
||||
|
||||
world_inst = LLWorld::getInstance();
|
||||
if (!world_inst)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class LLConversationItem;
|
|||
class LLConversationItemSession;
|
||||
class LLConversationItemParticipant;
|
||||
|
||||
typedef std::map<LLUUID, LLConversationItem*> conversations_items_map;
|
||||
typedef std::map<LLUUID, LLPointer<LLConversationItem> > conversations_items_map;
|
||||
typedef std::map<LLUUID, LLFolderViewItem*> conversations_widgets_map;
|
||||
|
||||
typedef std::vector<std::string> menuentry_vec_t;
|
||||
|
|
|
|||
|
|
@ -763,19 +763,6 @@ void LLDrawable::movePartition()
|
|||
if (part)
|
||||
{
|
||||
part->move(this, getSpatialGroup());
|
||||
|
||||
// SL-18251 "On-screen animesh characters using pelvis offset animations
|
||||
// disappear when root goes off-screen"
|
||||
//
|
||||
// Update extents of the root node when Control Avatar changes it's bounds
|
||||
if (mRenderType == LLPipeline::RENDER_TYPE_CONTROL_AV && isRoot())
|
||||
{
|
||||
LLControlAvatar* controlAvatar = dynamic_cast<LLControlAvatar*>(getVObj().get());
|
||||
if (controlAvatar && controlAvatar->mControlAVBridge)
|
||||
{
|
||||
((LLSpatialGroup*)controlAvatar->mControlAVBridge->mOctree->getListener(0))->setState(LLViewerOctreeGroup::DIRTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -183,8 +183,8 @@ void LLDrawPoolAvatar::beginDeferredPass(S32 pass)
|
|||
is_deferred_render = true;
|
||||
|
||||
if (LLPipeline::sImpostorRender)
|
||||
{ //impostor pass does not have rigid or impostor rendering
|
||||
pass += 2;
|
||||
{ //impostor pass does not have impostor rendering
|
||||
++pass;
|
||||
}
|
||||
|
||||
switch (pass)
|
||||
|
|
@ -210,7 +210,7 @@ void LLDrawPoolAvatar::endDeferredPass(S32 pass)
|
|||
|
||||
if (LLPipeline::sImpostorRender)
|
||||
{
|
||||
pass += 2;
|
||||
++pass;
|
||||
}
|
||||
|
||||
switch (pass)
|
||||
|
|
@ -431,7 +431,7 @@ void LLDrawPoolAvatar::render(S32 pass)
|
|||
LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
|
||||
if (LLPipeline::sImpostorRender)
|
||||
{
|
||||
renderAvatars(NULL, pass+2);
|
||||
renderAvatars(NULL, ++pass);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -446,7 +446,7 @@ void LLDrawPoolAvatar::beginRenderPass(S32 pass)
|
|||
|
||||
if (LLPipeline::sImpostorRender)
|
||||
{ //impostor render does not have impostors or rigid rendering
|
||||
pass += 2;
|
||||
++pass;
|
||||
}
|
||||
|
||||
switch (pass)
|
||||
|
|
@ -474,7 +474,7 @@ void LLDrawPoolAvatar::endRenderPass(S32 pass)
|
|||
|
||||
if (LLPipeline::sImpostorRender)
|
||||
{
|
||||
pass += 2;
|
||||
++pass;
|
||||
}
|
||||
|
||||
switch (pass)
|
||||
|
|
|
|||
|
|
@ -155,6 +155,20 @@ void LLFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const
|
|||
LLFloaterIMSessionTab::addToHost(new_session_id);
|
||||
}
|
||||
|
||||
|
||||
LLConversationItem* LLFloaterIMContainer::getSessionModel(const LLUUID& session_id)
|
||||
{
|
||||
conversations_items_map::iterator iter = mConversationsItems.find(session_id);
|
||||
if (iter == mConversationsItems.end())
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
return iter->second.get();
|
||||
}
|
||||
}
|
||||
|
||||
void LLFloaterIMContainer::sessionRemoved(const LLUUID& session_id)
|
||||
{
|
||||
removeConversationListItem(session_id);
|
||||
|
|
@ -608,7 +622,8 @@ void LLFloaterIMContainer::handleConversationModelEvent(const LLSD& event)
|
|||
}
|
||||
else if (type == "add_participant")
|
||||
{
|
||||
LLConversationItemSession* session_model = dynamic_cast<LLConversationItemSession*>(mConversationsItems[session_id]);
|
||||
LLConversationItem* item = getSessionModel(session_id);
|
||||
LLConversationItemSession* session_model = dynamic_cast<LLConversationItemSession*>(item);
|
||||
LLConversationItemParticipant* participant_model = (session_model ? session_model->findParticipant(participant_id) : NULL);
|
||||
LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(session_id);
|
||||
if (!participant_view && session_model && participant_model)
|
||||
|
|
@ -1749,10 +1764,9 @@ BOOL LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool
|
|||
|
||||
void LLFloaterIMContainer::setTimeNow(const LLUUID& session_id, const LLUUID& participant_id)
|
||||
{
|
||||
LLConversationItemSession* item = dynamic_cast<LLConversationItemSession*>(get_ptr_in_map(mConversationsItems,session_id));
|
||||
LLConversationItemSession* item = dynamic_cast<LLConversationItemSession*>(getSessionModel(session_id));
|
||||
if (item)
|
||||
{
|
||||
item->setTimeNow(participant_id);
|
||||
mConversationViewModel.requestSortAll();
|
||||
mConversationsRoot->arrangeAll();
|
||||
}
|
||||
|
|
@ -1761,7 +1775,7 @@ void LLFloaterIMContainer::setTimeNow(const LLUUID& session_id, const LLUUID& pa
|
|||
void LLFloaterIMContainer::setNearbyDistances()
|
||||
{
|
||||
// Get the nearby chat session: that's the one with uuid nul
|
||||
LLConversationItemSession* item = dynamic_cast<LLConversationItemSession*>(get_ptr_in_map(mConversationsItems,LLUUID()));
|
||||
LLConversationItemSession* item = dynamic_cast<LLConversationItemSession*>(getSessionModel(LLUUID()));
|
||||
if (item)
|
||||
{
|
||||
// Get the positions of the nearby avatars and their ids
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ public:
|
|||
LLConversationViewModel& getRootViewModel() { return mConversationViewModel; }
|
||||
LLUUID getSelectedSession() { return mSelectedSession; }
|
||||
void setSelectedSession(LLUUID sessionID) { mSelectedSession = sessionID; }
|
||||
LLConversationItem* getSessionModel(const LLUUID& session_id) { return get_ptr_in_map(mConversationsItems,session_id); }
|
||||
LLConversationItem* getSessionModel(const LLUUID& session_id);
|
||||
LLConversationSort& getSortOrder() { return mConversationViewModel.getSorter(); }
|
||||
|
||||
// Handling of lists of participants is public so to be common with llfloatersessiontab
|
||||
|
|
|
|||
|
|
@ -251,11 +251,49 @@ void fractionFromDecimal(F32 decimal_val, S32& numerator, S32& denominator)
|
|||
}
|
||||
}
|
||||
}
|
||||
// static
|
||||
std::string LLFloaterPreference::sSkin = "";
|
||||
|
||||
// handle secondlife:///app/worldmap/{NAME}/{COORDS} URLs
|
||||
// Also see LLUrlEntryKeybinding, the value of this command type
|
||||
// is ability to show up to date value in chat
|
||||
class LLKeybindingHandler: public LLCommandHandler
|
||||
{
|
||||
public:
|
||||
// requires trusted browser to trigger
|
||||
LLKeybindingHandler(): LLCommandHandler("keybinding", UNTRUSTED_CLICK_ONLY)
|
||||
{
|
||||
}
|
||||
|
||||
bool handle(const LLSD& params, const LLSD& query_map,
|
||||
const std::string& grid, LLMediaCtrl* web)
|
||||
{
|
||||
if (params.size() < 1) return false;
|
||||
|
||||
LLFloaterPreference* prefsfloater = dynamic_cast<LLFloaterPreference*>
|
||||
(LLFloaterReg::showInstance("preferences"));
|
||||
|
||||
if (prefsfloater)
|
||||
{
|
||||
// find 'controls' panel and bring it the front
|
||||
LLTabContainer* tabcontainer = prefsfloater->getChild<LLTabContainer>("pref core");
|
||||
LLPanel* panel = prefsfloater->getChild<LLPanel>("controls");
|
||||
if (tabcontainer && panel)
|
||||
{
|
||||
tabcontainer->selectTabPanel(panel);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
LLKeybindingHandler gKeybindHandler;
|
||||
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// LLFloaterPreference
|
||||
|
||||
// static
|
||||
std::string LLFloaterPreference::sSkin = "";
|
||||
|
||||
LLFloaterPreference::LLFloaterPreference(const LLSD& key)
|
||||
: LLFloater(key),
|
||||
mGotPersonalInfo(false),
|
||||
|
|
|
|||
|
|
@ -175,10 +175,9 @@ void LLFloaterURLEntry::onBtnOK( void* userdata )
|
|||
// We assume that an empty scheme is an http url, as this is how we will treat it.
|
||||
if(scheme == "")
|
||||
{
|
||||
scheme = "http";
|
||||
scheme = "https";
|
||||
}
|
||||
|
||||
// Discover the MIME type only for "http" scheme.
|
||||
if(!media_url.empty() &&
|
||||
(scheme == "http" || scheme == "https"))
|
||||
{
|
||||
|
|
@ -204,13 +203,18 @@ void LLFloaterURLEntry::getMediaTypeCoro(std::string url, LLHandle<LLFloater> pa
|
|||
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
|
||||
httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMediaTypeCoro", httpPolicy));
|
||||
LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
|
||||
LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders);
|
||||
LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions);
|
||||
|
||||
httpOpts->setFollowRedirects(true);
|
||||
httpOpts->setHeadersOnly(true);
|
||||
|
||||
httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*");
|
||||
httpHeaders->append(HTTP_OUT_HEADER_COOKIE, "");
|
||||
|
||||
LL_INFOS("HttpCoroutineAdapter", "genericPostCoro") << "Generic POST for " << url << LL_ENDL;
|
||||
|
||||
LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts);
|
||||
LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts, httpHeaders);
|
||||
|
||||
LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
|
||||
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
|
||||
|
|
@ -226,12 +230,6 @@ void LLFloaterURLEntry::getMediaTypeCoro(std::string url, LLHandle<LLFloater> pa
|
|||
// which have no mime type set.
|
||||
std::string resolvedMimeType = LLMIMETypes::getDefaultMimeType();
|
||||
|
||||
if (!status)
|
||||
{
|
||||
floaterUrlEntry->headerFetchComplete(status.getType(), resolvedMimeType);
|
||||
return;
|
||||
}
|
||||
|
||||
LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS];
|
||||
|
||||
if (resultHeaders.has(HTTP_IN_HEADER_CONTENT_TYPE))
|
||||
|
|
|
|||
|
|
@ -121,10 +121,27 @@ static const F32 ZOOM_MAX = 128.f;
|
|||
class LLWorldMapHandler : public LLCommandHandler
|
||||
{
|
||||
public:
|
||||
// requires trusted browser to trigger
|
||||
LLWorldMapHandler() : LLCommandHandler("worldmap", UNTRUSTED_CLICK_ONLY ) { }
|
||||
|
||||
bool handle(const LLSD& params,
|
||||
LLWorldMapHandler() : LLCommandHandler("worldmap", UNTRUSTED_THROTTLE)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool canHandleUntrusted(
|
||||
const LLSD& params,
|
||||
const LLSD& query_map,
|
||||
LLMediaCtrl* web,
|
||||
const std::string& nav_type)
|
||||
{
|
||||
if (nav_type == NAV_TYPE_CLICKED
|
||||
|| nav_type == NAV_TYPE_EXTERNAL)
|
||||
{
|
||||
// NAV_TYPE_EXTERNAL will be throttled
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool handle(const LLSD& params,
|
||||
const LLSD& query_map,
|
||||
const std::string& grid,
|
||||
LLMediaCtrl* web)
|
||||
|
|
@ -160,12 +177,32 @@ LLWorldMapHandler gWorldMapHandler;
|
|||
class LLMapTrackAvatarHandler : public LLCommandHandler
|
||||
{
|
||||
public:
|
||||
// requires trusted browser to trigger
|
||||
LLMapTrackAvatarHandler() : LLCommandHandler("maptrackavatar", UNTRUSTED_CLICK_ONLY)
|
||||
LLMapTrackAvatarHandler() : LLCommandHandler("maptrackavatar", UNTRUSTED_THROTTLE)
|
||||
{
|
||||
}
|
||||
|
||||
bool handle(const LLSD& params,
|
||||
|
||||
virtual bool canHandleUntrusted(
|
||||
const LLSD& params,
|
||||
const LLSD& query_map,
|
||||
LLMediaCtrl* web,
|
||||
const std::string& nav_type)
|
||||
{
|
||||
if (params.size() < 1)
|
||||
{
|
||||
return true; // don't block, will fail later
|
||||
}
|
||||
|
||||
if (nav_type == NAV_TYPE_CLICKED
|
||||
|| nav_type == NAV_TYPE_EXTERNAL)
|
||||
{
|
||||
// NAV_TYPE_EXTERNAL will be throttled
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool handle(const LLSD& params,
|
||||
const LLSD& query_map,
|
||||
const std::string& grid,
|
||||
LLMediaCtrl* web)
|
||||
|
|
|
|||
|
|
@ -549,20 +549,7 @@ void LLFriendCardsManager::syncFriendsFolder()
|
|||
// Create own calling card if it was not found in Friends/All folder
|
||||
if (!collector.isAgentCallingCardFound())
|
||||
{
|
||||
LLAvatarName av_name;
|
||||
LLAvatarNameCache::get( gAgentID, &av_name );
|
||||
|
||||
create_inventory_item(gAgentID,
|
||||
gAgent.getSessionID(),
|
||||
calling_cards_folder_id,
|
||||
LLTransactionID::tnull,
|
||||
av_name.getCompleteName(),
|
||||
gAgentID.asString(),
|
||||
LLAssetType::AT_CALLINGCARD,
|
||||
LLInventoryType::IT_CALLINGCARD,
|
||||
NO_INV_SUBTYPE,
|
||||
PERM_MOVE | PERM_TRANSFER,
|
||||
NULL);
|
||||
create_inventory_callingcard(gAgentID, calling_cards_folder_id);
|
||||
}
|
||||
|
||||
// All folders created and updated.
|
||||
|
|
|
|||
|
|
@ -65,7 +65,8 @@ public:
|
|||
return true; // don't block, will fail later
|
||||
}
|
||||
|
||||
if (nav_type == NAV_TYPE_CLICKED)
|
||||
if (nav_type == NAV_TYPE_CLICKED
|
||||
|| nav_type == NAV_TYPE_EXTERNAL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4320,7 +4320,8 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items
|
|||
|| is_recent_panel
|
||||
|| !trash
|
||||
|| trash->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN
|
||||
|| trash->getDescendentCount() == LLViewerInventoryCategory::VERSION_UNKNOWN)
|
||||
|| trash->getDescendentCount() == LLViewerInventoryCategory::VERSION_UNKNOWN
|
||||
|| gAgentAvatarp->hasAttachmentsInTrash())
|
||||
{
|
||||
disabled_items.push_back(std::string("Empty Trash"));
|
||||
}
|
||||
|
|
@ -6474,6 +6475,7 @@ void LLGestureBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
|
|||
{
|
||||
items.push_back(std::string("Activate"));
|
||||
}
|
||||
items.push_back(std::string("PlayGesture"));
|
||||
}
|
||||
addLinkReplaceMenuOption(items, disabled_items);
|
||||
hide_context_entries(menu, items, disabled_items);
|
||||
|
|
|
|||
|
|
@ -357,6 +357,7 @@ void LLInventoryModelBackgroundFetch::scheduleFolderFetch(const LLUUID& cat_id,
|
|||
if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != cat_id)
|
||||
{
|
||||
mBackgroundFetchActive = true;
|
||||
mFolderFetchActive = true;
|
||||
|
||||
// Specific folder requests go to front of queue.
|
||||
mFetchFolderQueue.push_front(FetchQueueInfo(cat_id, forced ? FT_FORCED : FT_DEFAULT));
|
||||
|
|
@ -375,6 +376,61 @@ void LLInventoryModelBackgroundFetch::scheduleItemFetch(const LLUUID& item_id, b
|
|||
}
|
||||
}
|
||||
|
||||
void LLInventoryModelBackgroundFetch::fetchFolderAndLinks(const LLUUID& cat_id, nullary_func_t callback)
|
||||
{
|
||||
LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
|
||||
if (cat)
|
||||
{
|
||||
// Mark folder (update timer) so that background fetch won't request it
|
||||
cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE);
|
||||
}
|
||||
incrFetchFolderCount(1);
|
||||
mExpectedFolderIds.push_back(cat_id);
|
||||
|
||||
// Assume that we have no relevant cache. Fetch folder, and items folder's links point to.
|
||||
AISAPI::FetchCategoryLinks(cat_id,
|
||||
[callback, cat_id](const LLUUID& id)
|
||||
{
|
||||
callback();
|
||||
if (id.isNull())
|
||||
{
|
||||
LL_WARNS() << "Failed to fetch category links " << cat_id << LL_ENDL;
|
||||
}
|
||||
LLInventoryModelBackgroundFetch::getInstance()->onAISFolderCalback(cat_id, id, FT_DEFAULT);
|
||||
});
|
||||
|
||||
// start idle loop to track completion
|
||||
mBackgroundFetchActive = true;
|
||||
mFolderFetchActive = true;
|
||||
gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL);
|
||||
}
|
||||
|
||||
void LLInventoryModelBackgroundFetch::fetchCOF(nullary_func_t callback)
|
||||
{
|
||||
LLUUID cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT);
|
||||
LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
|
||||
if (cat)
|
||||
{
|
||||
// Mark cof (update timer) so that background fetch won't request it
|
||||
cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE);
|
||||
}
|
||||
incrFetchFolderCount(1);
|
||||
mExpectedFolderIds.push_back(cat_id);
|
||||
// For reliability assume that we have no relevant cache, so
|
||||
// fetch cof along with items cof's links point to.
|
||||
AISAPI::FetchCOF([callback](const LLUUID& id)
|
||||
{
|
||||
callback();
|
||||
LLUUID cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT);
|
||||
LLInventoryModelBackgroundFetch::getInstance()->onAISFolderCalback(cat_id, id, FT_DEFAULT);
|
||||
});
|
||||
|
||||
// start idle loop to track completion
|
||||
mBackgroundFetchActive = true;
|
||||
mFolderFetchActive = true;
|
||||
gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL);
|
||||
}
|
||||
|
||||
void LLInventoryModelBackgroundFetch::findLostItems()
|
||||
{
|
||||
mBackgroundFetchActive = true;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,13 @@ public:
|
|||
void scheduleFolderFetch(const LLUUID& cat_id, bool forced = false);
|
||||
void scheduleItemFetch(const LLUUID& item_id, bool forced = false);
|
||||
|
||||
typedef boost::function<void()> nullary_func_t;
|
||||
// AIS3 only, Fetches folder and everithing links inside the folder point to
|
||||
// Intended for outfits
|
||||
void fetchFolderAndLinks(const LLUUID& cat_id, nullary_func_t callback);
|
||||
// AIS3 only
|
||||
void fetchCOF(nullary_func_t callback);
|
||||
|
||||
BOOL folderFetchActive() const;
|
||||
bool isEverythingFetched() const; // completing the fetch once per session should be sufficient
|
||||
|
||||
|
|
|
|||
|
|
@ -337,52 +337,49 @@ void LLInventoryFetchItemsObserver::startFetch()
|
|||
{
|
||||
for (requests_by_folders_t::value_type &folder : requests)
|
||||
{
|
||||
if (folder.second.size() > MAX_INDIVIDUAL_ITEM_REQUESTS)
|
||||
LLViewerInventoryCategory* cat = gInventory.getCategory(folder.first);
|
||||
if (cat)
|
||||
{
|
||||
// requesting one by one will take a while
|
||||
// do whole folder
|
||||
LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
LLViewerInventoryCategory* cat = gInventory.getCategory(folder.first);
|
||||
if (cat)
|
||||
if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN)
|
||||
{
|
||||
if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN)
|
||||
{
|
||||
// start fetching whole folder since it's not ready either way
|
||||
cat->fetch();
|
||||
}
|
||||
else if (cat->getViewerDescendentCount() <= folder.second.size()
|
||||
|| cat->getDescendentCount() <= folder.second.size())
|
||||
{
|
||||
// Start fetching whole folder since we need all items
|
||||
LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true);
|
||||
// start fetching whole folder since it's not ready either way
|
||||
cat->fetch();
|
||||
}
|
||||
else if (folder.second.size() > MAX_INDIVIDUAL_ITEM_REQUESTS)
|
||||
{
|
||||
// requesting one by one will take a while
|
||||
// do whole folder
|
||||
LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true);
|
||||
}
|
||||
else if (cat->getViewerDescendentCount() <= folder.second.size()
|
||||
|| cat->getDescendentCount() <= folder.second.size())
|
||||
{
|
||||
// Start fetching whole folder since we need all items
|
||||
LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// get items one by one
|
||||
for (LLUUID &item_id : folder.second)
|
||||
{
|
||||
LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Isn't supposed to happen? We should have all folders
|
||||
// and if item exists, folder is supposed to exist as well.
|
||||
llassert(false);
|
||||
LL_WARNS("Inventory") << "Missing folder: " << folder.first << " fetching items individually" << LL_ENDL;
|
||||
|
||||
// get items one by one
|
||||
for (LLUUID &item_id : folder.second)
|
||||
for (LLUUID& item_id : folder.second)
|
||||
{
|
||||
LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Isn't supposed to happen? We should have all folders
|
||||
// and if item exists, folder is supposed to exist as well.
|
||||
llassert(false);
|
||||
LL_WARNS("Inventory") << "Missing folder: " << folder.first << " fetching items individually" << LL_ENDL;
|
||||
|
||||
// get items one by one
|
||||
for (LLUUID& item_id : folder.second)
|
||||
{
|
||||
LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -421,10 +418,22 @@ void LLInventoryFetchDescendentsObserver::changed(U32 mask)
|
|||
}
|
||||
++it;
|
||||
}
|
||||
if (mIncomplete.empty())
|
||||
{
|
||||
done();
|
||||
}
|
||||
|
||||
if (mIncomplete.empty())
|
||||
{
|
||||
done();
|
||||
}
|
||||
else
|
||||
{
|
||||
LLInventoryModelBackgroundFetch* fetcher = LLInventoryModelBackgroundFetch::getInstance();
|
||||
if (fetcher->isEverythingFetched()
|
||||
&& !fetcher->folderFetchActive())
|
||||
{
|
||||
// If fetcher is done with folders yet we are waiting, fetch either
|
||||
// failed or version is somehow stuck at -1
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLInventoryFetchDescendentsObserver::startFetch()
|
||||
|
|
@ -435,12 +444,8 @@ void LLInventoryFetchDescendentsObserver::startFetch()
|
|||
if (!cat) continue;
|
||||
if (!isCategoryComplete(cat))
|
||||
{
|
||||
// CHECK IT: isCategoryComplete() checks both version and descendant count but
|
||||
// fetch() only works for Unknown version and doesn't care about descentants,
|
||||
// as result fetch won't start and folder will potentially get stuck as
|
||||
// incomplete in observer.
|
||||
// Likely either both should use only version or both should check descendants.
|
||||
cat->fetch(); //blindly fetch it without seeing if anything else is fetching it.
|
||||
//blindly fetch it without seeing if anything else is fetching it.
|
||||
LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(*it, true);
|
||||
mIncomplete.push_back(*it); //Add to list of things being downloaded for this observer.
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1449,45 +1449,6 @@ void LLInventoryPanel::onFocusReceived()
|
|||
// inventory now handles cut/copy/paste/delete
|
||||
LLEditMenuHandler::gEditMenuHandler = mFolderRoot.get();
|
||||
|
||||
// Tab support, when tabbing into this view, select first item
|
||||
// (ideally needs to account for scroll)
|
||||
bool select_first = mSelectThisID.isNull() && mFolderRoot.get() && mFolderRoot.get()->getSelectedCount() == 0;
|
||||
|
||||
if (select_first)
|
||||
{
|
||||
LLFolderViewFolder::folders_t::const_iterator folders_it = mFolderRoot.get()->getFoldersBegin();
|
||||
LLFolderViewFolder::folders_t::const_iterator folders_end = mFolderRoot.get()->getFoldersEnd();
|
||||
|
||||
for (; folders_it != folders_end; ++folders_it)
|
||||
{
|
||||
const LLFolderViewFolder* folder_view = *folders_it;
|
||||
if (folder_view->getVisible())
|
||||
{
|
||||
const LLFolderViewModelItemInventory* modelp = static_cast<const LLFolderViewModelItemInventory*>(folder_view->getViewModelItem());
|
||||
setSelectionByID(modelp->getUUID(), TRUE);
|
||||
select_first = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (select_first)
|
||||
{
|
||||
LLFolderViewFolder::items_t::const_iterator items_it = mFolderRoot.get()->getItemsBegin();
|
||||
LLFolderViewFolder::items_t::const_iterator items_end = mFolderRoot.get()->getItemsEnd();
|
||||
|
||||
for (; items_it != items_end; ++items_it)
|
||||
{
|
||||
const LLFolderViewItem* item_view = *items_it;
|
||||
if (item_view->getVisible())
|
||||
{
|
||||
const LLFolderViewModelItemInventory* modelp = static_cast<const LLFolderViewModelItemInventory*>(item_view->getViewModelItem());
|
||||
setSelectionByID(modelp->getUUID(), TRUE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LLPanel::onFocusReceived();
|
||||
}
|
||||
|
||||
|
|
@ -2254,6 +2215,53 @@ void LLInventorySingleFolderPanel::initFromParams(const Params& p)
|
|||
LLPanel::initFromParams(mParams);
|
||||
}
|
||||
|
||||
void LLInventorySingleFolderPanel::onFocusReceived()
|
||||
{
|
||||
// Tab support, when tabbing into this view, select first item
|
||||
// (ideally needs to account for scroll)
|
||||
bool select_first = mSelectThisID.isNull() && mFolderRoot.get() && mFolderRoot.get()->getSelectedCount() == 0;
|
||||
|
||||
if (select_first)
|
||||
{
|
||||
LLFolderViewFolder::folders_t::const_iterator folders_it = mFolderRoot.get()->getFoldersBegin();
|
||||
LLFolderViewFolder::folders_t::const_iterator folders_end = mFolderRoot.get()->getFoldersEnd();
|
||||
|
||||
for (; folders_it != folders_end; ++folders_it)
|
||||
{
|
||||
const LLFolderViewFolder* folder_view = *folders_it;
|
||||
if (folder_view->getVisible())
|
||||
{
|
||||
const LLFolderViewModelItemInventory* modelp = static_cast<const LLFolderViewModelItemInventory*>(folder_view->getViewModelItem());
|
||||
setSelectionByID(modelp->getUUID(), TRUE);
|
||||
// quick and dirty fix: don't scroll on switching focus
|
||||
// todo: better 'tab' support, one that would work for LLInventoryPanel
|
||||
mFolderRoot.get()->stopAutoScollining();
|
||||
select_first = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (select_first)
|
||||
{
|
||||
LLFolderViewFolder::items_t::const_iterator items_it = mFolderRoot.get()->getItemsBegin();
|
||||
LLFolderViewFolder::items_t::const_iterator items_end = mFolderRoot.get()->getItemsEnd();
|
||||
|
||||
for (; items_it != items_end; ++items_it)
|
||||
{
|
||||
const LLFolderViewItem* item_view = *items_it;
|
||||
if (item_view->getVisible())
|
||||
{
|
||||
const LLFolderViewModelItemInventory* modelp = static_cast<const LLFolderViewModelItemInventory*>(item_view->getViewModelItem());
|
||||
setSelectionByID(modelp->getUUID(), TRUE);
|
||||
mFolderRoot.get()->stopAutoScollining();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
LLInventoryPanel::onFocusReceived();
|
||||
}
|
||||
|
||||
void LLInventorySingleFolderPanel::initFolderRoot(const LLUUID& start_folder_id)
|
||||
{
|
||||
if(mRootInited) return;
|
||||
|
|
|
|||
|
|
@ -404,6 +404,8 @@ public:
|
|||
{};
|
||||
|
||||
void initFromParams(const Params& p);
|
||||
void onFocusReceived() override;
|
||||
|
||||
bool isSelectionRemovable() { return false; }
|
||||
|
||||
void initFolderRoot(const LLUUID& start_folder_id = LLUUID::null);
|
||||
|
|
|
|||
|
|
@ -74,40 +74,6 @@ std::string string_from_mask(MASK mask)
|
|||
return res;
|
||||
}
|
||||
|
||||
std::string string_from_mouse(EMouseClickType click, bool translate)
|
||||
{
|
||||
std::string res;
|
||||
switch (click)
|
||||
{
|
||||
case CLICK_LEFT:
|
||||
res = "LMB";
|
||||
break;
|
||||
case CLICK_MIDDLE:
|
||||
res = "MMB";
|
||||
break;
|
||||
case CLICK_RIGHT:
|
||||
res = "RMB";
|
||||
break;
|
||||
case CLICK_BUTTON4:
|
||||
res = "MB4";
|
||||
break;
|
||||
case CLICK_BUTTON5:
|
||||
res = "MB5";
|
||||
break;
|
||||
case CLICK_DOUBLELEFT:
|
||||
res = "Double LMB";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (translate && !res.empty())
|
||||
{
|
||||
res = LLTrans::getString(res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// LLKeyConflictHandler
|
||||
|
||||
S32 LLKeyConflictHandler::sTemporaryFileUseCount = 0;
|
||||
|
|
@ -270,7 +236,7 @@ std::string LLKeyConflictHandler::getStringFromKeyData(const LLKeyData& keydata)
|
|||
result = LLKeyboard::stringFromAccelerator(keydata.mMask);
|
||||
}
|
||||
|
||||
result += string_from_mouse(keydata.mMouse, true);
|
||||
result += LLKeyboard::stringFromMouse(keydata.mMouse);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -545,7 +511,7 @@ void LLKeyConflictHandler::saveToSettings(bool temporary)
|
|||
{
|
||||
// set() because 'optional', for compatibility purposes
|
||||
// just copy old keys.xml and rename to key_bindings.xml, it should work
|
||||
binding.mouse.set(string_from_mouse(data.mMouse, false), true);
|
||||
binding.mouse.set(LLKeyboard::stringFromMouse(data.mMouse, false), true);
|
||||
}
|
||||
binding.command = iter->first;
|
||||
mode.bindings.add(binding);
|
||||
|
|
|
|||
|
|
@ -391,7 +391,9 @@ LLLocationInputCtrl::LLLocationInputCtrl(const LLLocationInputCtrl::Params& p)
|
|||
LL_WARNS() << "Error loading navigation bar context menu" << LL_ENDL;
|
||||
|
||||
}
|
||||
getTextEntry()->setRightMouseUpCallback(boost::bind(&LLLocationInputCtrl::onTextEditorRightClicked,this,_2,_3,_4));
|
||||
//don't show default context menu
|
||||
getTextEntry()->setShowContextMenu(false);
|
||||
getTextEntry()->setRightMouseDownCallback(boost::bind(&LLLocationInputCtrl::onTextEditorRightClicked, this, _2, _3, _4));
|
||||
updateWidgetlayout();
|
||||
|
||||
// Connecting signal for updating location on "Show Coordinates" setting change.
|
||||
|
|
|
|||
|
|
@ -1077,7 +1077,7 @@ void LLGroupMoneyDetailsTabEventHandler::processReply(LLMessageSystem* msg,
|
|||
msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_CurrentInterval, current_interval );
|
||||
msg->getStringFast(_PREHASH_MoneyData, _PREHASH_StartDate, start_date);
|
||||
|
||||
std::string time_str = LLTrans::getString("GroupMoneyDate");
|
||||
std::string time_str = LLTrans::getString("GroupMoneyStartDate");
|
||||
LLSD substitution;
|
||||
|
||||
// We don't do time zone corrections of the calculated number of seconds
|
||||
|
|
@ -1232,7 +1232,7 @@ void LLGroupMoneySalesTabEventHandler::processReply(LLMessageSystem* msg,
|
|||
// Start with the date.
|
||||
if (text == mImplementationp->mLoadingText)
|
||||
{
|
||||
std::string time_str = LLTrans::getString("GroupMoneyDate");
|
||||
std::string time_str = LLTrans::getString("GroupMoneyStartDate");
|
||||
LLSD substitution;
|
||||
|
||||
// We don't do time zone corrections of the calculated number of seconds
|
||||
|
|
|
|||
|
|
@ -277,9 +277,9 @@ void request_avatar_properties_coro(std::string cap_url, LLUUID agent_id)
|
|||
//TODO: changes take two minutes to propagate!
|
||||
// Add some storage that holds updated data for two minutes
|
||||
// for new instances to reuse the data
|
||||
// Profile data is only relevant to won avatar, but notes
|
||||
// are for everybody
|
||||
void put_avatar_properties_coro(std::string cap_url, LLUUID agent_id, LLSD data)
|
||||
// Profile data is only relevant to own avatar, but notes
|
||||
// are for everybody (no onger an issue?)
|
||||
void put_avatar_properties_coro(std::string cap_url, LLUUID agent_id, LLSD data, std::function<void(bool)> callback)
|
||||
{
|
||||
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
|
||||
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
|
||||
|
|
@ -300,10 +300,16 @@ void put_avatar_properties_coro(std::string cap_url, LLUUID agent_id, LLSD data)
|
|||
if (!status)
|
||||
{
|
||||
LL_WARNS("AvatarProperties") << "Failed to put agent information " << data << " for id " << agent_id << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_DEBUGS("AvatarProperties") << "Agent id: " << agent_id << " Data: " << data << " Result: " << httpResults << LL_ENDL;
|
||||
}
|
||||
|
||||
LL_DEBUGS("AvatarProperties") << "Agent id: " << agent_id << " Data: " << data << " Result: " << httpResults << LL_ENDL;
|
||||
if (callback)
|
||||
{
|
||||
callback(status);
|
||||
}
|
||||
}
|
||||
|
||||
LLUUID post_profile_image(std::string cap_url, const LLSD &first_data, std::string path_to_image, LLHandle<LLPanel> *handle)
|
||||
|
|
@ -448,6 +454,13 @@ void post_profile_image_coro(std::string cap_url, EProfileImageType type, std::s
|
|||
}
|
||||
}
|
||||
|
||||
if (type == PROFILE_IMAGE_SL && result.notNull())
|
||||
{
|
||||
LLAvatarIconIDCache::getInstance()->add(gAgentID, result);
|
||||
// Should trigger callbacks in icon controls
|
||||
LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
LLFile::remove(path_to_image);
|
||||
delete handle;
|
||||
|
|
@ -499,7 +512,8 @@ public:
|
|||
return true; // don't block, will fail later
|
||||
}
|
||||
|
||||
if (nav_type == NAV_TYPE_CLICKED)
|
||||
if (nav_type == NAV_TYPE_CLICKED
|
||||
|| nav_type == NAV_TYPE_EXTERNAL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1832,7 +1846,7 @@ void LLPanelProfileSecondLife::onShowInSearchCallback()
|
|||
LLSD data;
|
||||
data["allow_publish"] = mAllowPublish;
|
||||
LLCoros::instance().launch("putAgentUserInfoCoro",
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), data));
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), data, nullptr));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1847,7 +1861,7 @@ void LLPanelProfileSecondLife::onSaveDescriptionChanges()
|
|||
if (!cap_url.empty())
|
||||
{
|
||||
LLCoros::instance().launch("putAgentUserInfoCoro",
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("sl_about_text", mDescriptionText)));
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("sl_about_text", mDescriptionText), nullptr));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1994,10 +2008,19 @@ void LLPanelProfileSecondLife::onCommitProfileImage(const LLUUID& id)
|
|||
std::string cap_url = gAgent.getRegionCapability(PROFILE_PROPERTIES_CAP);
|
||||
if (!cap_url.empty())
|
||||
{
|
||||
std::function<void(bool)> callback = [id](bool result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
LLAvatarIconIDCache::getInstance()->add(gAgentID, id);
|
||||
// Should trigger callbacks in icon controls
|
||||
LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID);
|
||||
}
|
||||
};
|
||||
LLSD params;
|
||||
params["sl_image_id"] = id;
|
||||
LLCoros::instance().launch("putAgentUserInfoCoro",
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), params));
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), params, callback));
|
||||
|
||||
mImageId = id;
|
||||
if (mImageId == LLUUID::null)
|
||||
|
|
@ -2334,7 +2357,7 @@ void LLPanelProfileFirstLife::onCommitPhoto(const LLUUID& id)
|
|||
LLSD params;
|
||||
params["fl_image_id"] = id;
|
||||
LLCoros::instance().launch("putAgentUserInfoCoro",
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), params));
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), params, nullptr));
|
||||
|
||||
mImageId = id;
|
||||
if (mImageId.notNull())
|
||||
|
|
@ -2378,7 +2401,7 @@ void LLPanelProfileFirstLife::onSaveDescriptionChanges()
|
|||
if (!cap_url.empty())
|
||||
{
|
||||
LLCoros::instance().launch("putAgentUserInfoCoro",
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("fl_about_text", mCurrentDescription)));
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("fl_about_text", mCurrentDescription), nullptr));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -2521,7 +2544,7 @@ void LLPanelProfileNotes::onSaveNotesChanges()
|
|||
if (!cap_url.empty())
|
||||
{
|
||||
LLCoros::instance().launch("putAgentUserInfoCoro",
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("notes", mCurrentNotes)));
|
||||
boost::bind(put_avatar_properties_coro, cap_url, getAvatarId(), LLSD().with("notes", mCurrentNotes), nullptr));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -93,7 +93,8 @@ public:
|
|||
return true; // don't block, will fail later
|
||||
}
|
||||
|
||||
if (nav_type == NAV_TYPE_CLICKED)
|
||||
if (nav_type == NAV_TYPE_CLICKED
|
||||
|| nav_type == NAV_TYPE_EXTERNAL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@ public:
|
|||
return true; // don't block, will fail later
|
||||
}
|
||||
|
||||
if (nav_type == NAV_TYPE_CLICKED)
|
||||
if (nav_type == NAV_TYPE_CLICKED
|
||||
|| nav_type == NAV_TYPE_EXTERNAL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -847,6 +847,43 @@ void LLSpatialGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* c
|
|||
assert_states_valid(this);
|
||||
}
|
||||
|
||||
//virtual
|
||||
void LLSpatialGroup::rebound()
|
||||
{
|
||||
if (!isDirty())
|
||||
return;
|
||||
|
||||
super::rebound();
|
||||
|
||||
if (mSpatialPartition->mDrawableType == LLPipeline::RENDER_TYPE_CONTROL_AV)
|
||||
{
|
||||
llassert(mSpatialPartition->mPartitionType == LLViewerRegion::PARTITION_CONTROL_AV);
|
||||
|
||||
LLSpatialBridge* bridge = getSpatialPartition()->asBridge();
|
||||
if (bridge &&
|
||||
bridge->mDrawable &&
|
||||
bridge->mDrawable->getVObj() &&
|
||||
bridge->mDrawable->getVObj()->isRoot())
|
||||
{
|
||||
LLControlAvatar* controlAvatar = bridge->mDrawable->getVObj()->getControlAvatar();
|
||||
if (controlAvatar &&
|
||||
controlAvatar->mDrawable &&
|
||||
controlAvatar->mControlAVBridge)
|
||||
{
|
||||
llassert(controlAvatar->mControlAVBridge->mOctree);
|
||||
|
||||
LLSpatialGroup* root = (LLSpatialGroup*)controlAvatar->mControlAVBridge->mOctree->getListener(0);
|
||||
if (this == root)
|
||||
{
|
||||
const LLVector4a* addingExtents = controlAvatar->mDrawable->getSpatialExtents();
|
||||
const LLXformMatrix* currentTransform = bridge->mDrawable->getXform();
|
||||
expandExtents(addingExtents, *currentTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLSpatialGroup::destroyGL(bool keep_occlusion)
|
||||
{
|
||||
setState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::IMAGE_DIRTY);
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ public:
|
|||
LL_ALIGN_PREFIX(64)
|
||||
class LLSpatialGroup : public LLOcclusionCullingGroup
|
||||
{
|
||||
using super = LLOcclusionCullingGroup;
|
||||
friend class LLSpatialPartition;
|
||||
friend class LLOctreeStateCheck;
|
||||
public:
|
||||
|
|
@ -322,6 +323,9 @@ public:
|
|||
virtual void handleDestruction(const TreeNode* node);
|
||||
virtual void handleChildAddition(const OctreeNode* parent, OctreeNode* child);
|
||||
|
||||
// LLViewerOctreeGroup
|
||||
virtual void rebound();
|
||||
|
||||
public:
|
||||
LL_ALIGN_16(LLVector4a mViewAngle);
|
||||
LL_ALIGN_16(LLVector4a mLastUpdateViewAngle);
|
||||
|
|
|
|||
|
|
@ -2748,6 +2748,7 @@ void register_viewer_callbacks(LLMessageSystem* msg)
|
|||
msg->setHandlerFunc("InitiateDownload", process_initiate_download);
|
||||
msg->setHandlerFunc("LandStatReply", LLFloaterTopObjects::handle_land_reply);
|
||||
msg->setHandlerFunc("GenericMessage", process_generic_message);
|
||||
msg->setHandlerFunc("GenericStreamingMessage", process_generic_streaming_message);
|
||||
msg->setHandlerFunc("LargeGenericMessage", process_large_generic_message);
|
||||
|
||||
msg->setHandlerFuncFast(_PREHASH_FeatureDisabled, process_feature_disabled_message);
|
||||
|
|
@ -2877,8 +2878,7 @@ void LLStartUp::loadInitialOutfit( const std::string& outfit_folder_name,
|
|||
// Need to fetch cof contents before we can wear.
|
||||
if (do_copy)
|
||||
{
|
||||
callAfterCategoryFetch(LLAppearanceMgr::instance().getCOF(),
|
||||
boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append));
|
||||
callAfterCOFFetch(boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -193,7 +193,11 @@ public:
|
|||
|
||||
std::string fl_name = params[0].asString();
|
||||
|
||||
if (nav_type == NAV_TYPE_CLICKED)
|
||||
// External browsers explicitly ask user about opening links
|
||||
// so treat "external" same as "clicked" in this case,
|
||||
// despite it being treated as untrusted.
|
||||
if (nav_type == NAV_TYPE_CLICKED
|
||||
|| nav_type == NAV_TYPE_EXTERNAL)
|
||||
{
|
||||
const std::list<std::string> blacklist_clicked = {
|
||||
"camera_presets",
|
||||
|
|
|
|||
|
|
@ -92,6 +92,11 @@ void process_generic_message(LLMessageSystem* msg, void**)
|
|||
}
|
||||
}
|
||||
|
||||
void process_generic_streaming_message(LLMessageSystem* msg, void**)
|
||||
{
|
||||
// placeholder to suppress packet loss reports and log spam (SL-20473)
|
||||
}
|
||||
|
||||
void process_large_generic_message(LLMessageSystem* msg, void**)
|
||||
{
|
||||
LLUUID agent_id;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ void send_generic_message(const std::string& method,
|
|||
const LLUUID& invoice = LLUUID::null);
|
||||
|
||||
void process_generic_message(LLMessageSystem* msg, void**);
|
||||
void process_generic_streaming_message(LLMessageSystem* msg, void**);
|
||||
void process_large_generic_message(LLMessageSystem* msg, void**);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -991,34 +991,50 @@ LLViewerInput::LLViewerInput()
|
|||
}
|
||||
}
|
||||
|
||||
// static
|
||||
BOOL LLViewerInput::modeFromString(const std::string& string, S32 *mode)
|
||||
LLViewerInput::~LLViewerInput()
|
||||
{
|
||||
if (string == "FIRST_PERSON")
|
||||
|
||||
}
|
||||
|
||||
// static
|
||||
bool LLViewerInput::modeFromString(const std::string& string, S32 *mode)
|
||||
{
|
||||
if (string.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string cmp_string = string;
|
||||
LLStringUtil::toLower(cmp_string);
|
||||
if (cmp_string == "first_person")
|
||||
{
|
||||
*mode = MODE_FIRST_PERSON;
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
else if (string == "THIRD_PERSON")
|
||||
else if (cmp_string == "third_person")
|
||||
{
|
||||
*mode = MODE_THIRD_PERSON;
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
else if (string == "EDIT_AVATAR")
|
||||
else if (cmp_string == "edit_avatar")
|
||||
{
|
||||
*mode = MODE_EDIT_AVATAR;
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
else if (string == "SITTING")
|
||||
else if (cmp_string == "sitting")
|
||||
{
|
||||
*mode = MODE_SITTING;
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
*mode = MODE_THIRD_PERSON;
|
||||
return FALSE;
|
||||
return true;
|
||||
}
|
||||
|
||||
S32 val = atoi(string.c_str());
|
||||
if (val >= 0 && val < MODE_COUNT)
|
||||
{
|
||||
*mode = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
@ -1222,6 +1238,7 @@ BOOL LLViewerInput::bindKey(const S32 mode, const KEY key, const MASK mask, cons
|
|||
bind.mKey = key;
|
||||
bind.mMask = mask;
|
||||
bind.mFunction = function;
|
||||
bind.mFunctionName = function_name;
|
||||
|
||||
if (result->mIsGlobal)
|
||||
{
|
||||
|
|
@ -1303,6 +1320,7 @@ BOOL LLViewerInput::bindMouse(const S32 mode, const EMouseClickType mouse, const
|
|||
bind.mMouse = mouse;
|
||||
bind.mMask = mask;
|
||||
bind.mFunction = function;
|
||||
bind.mFunctionName = function_name;
|
||||
|
||||
if (result->mIsGlobal)
|
||||
{
|
||||
|
|
@ -1801,3 +1819,49 @@ bool LLViewerInput::isMouseBindUsed(const EMouseClickType mouse, const MASK mask
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string LLViewerInput::getKeyBindingAsString(const std::string& mode, const std::string& control) const
|
||||
{
|
||||
S32 keyboard_mode;
|
||||
if (!modeFromString(mode, &keyboard_mode))
|
||||
{
|
||||
keyboard_mode = getMode();
|
||||
}
|
||||
|
||||
std::string res;
|
||||
bool needs_separator = false;
|
||||
|
||||
// keybindings are sorted from having most mask to no mask (from restrictive to less restrictive),
|
||||
// but it's visually better to present this data in reverse
|
||||
std::vector<LLKeyboardBinding>::const_reverse_iterator iter_key = mKeyBindings[keyboard_mode].rbegin();
|
||||
while (iter_key != mKeyBindings[keyboard_mode].rend())
|
||||
{
|
||||
if (iter_key->mFunctionName == control)
|
||||
{
|
||||
if (needs_separator)
|
||||
{
|
||||
res.append(" | ");
|
||||
}
|
||||
res.append(LLKeyboard::stringFromAccelerator(iter_key->mMask, iter_key->mKey));
|
||||
needs_separator = true;
|
||||
}
|
||||
iter_key++;
|
||||
}
|
||||
|
||||
std::vector<LLMouseBinding>::const_reverse_iterator iter_mouse = mMouseBindings[keyboard_mode].rbegin();
|
||||
while (iter_mouse != mMouseBindings[keyboard_mode].rend())
|
||||
{
|
||||
if (iter_mouse->mFunctionName == control)
|
||||
{
|
||||
if (needs_separator)
|
||||
{
|
||||
res.append(" | ");
|
||||
}
|
||||
res.append(LLKeyboard::stringFromAccelerator(iter_mouse->mMask, iter_mouse->mMouse));
|
||||
needs_separator = true;
|
||||
}
|
||||
iter_mouse++;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,12 +28,13 @@
|
|||
#define LL_LLVIEWERINPUT_H
|
||||
|
||||
#include "llkeyboard.h" // For EKeystate
|
||||
#include "llinitparam.h"
|
||||
|
||||
const S32 MAX_KEY_BINDINGS = 128; // was 60
|
||||
const S32 keybindings_xml_version = 1;
|
||||
const std::string script_mouse_handler_name = "script_trigger_lbutton";
|
||||
|
||||
class LLWindow;
|
||||
|
||||
class LLNamedFunction
|
||||
{
|
||||
public:
|
||||
|
|
@ -51,6 +52,7 @@ public:
|
|||
MASK mMask;
|
||||
|
||||
LLKeyFunc mFunction;
|
||||
std::string mFunctionName;
|
||||
};
|
||||
|
||||
class LLMouseBinding
|
||||
|
|
@ -60,6 +62,7 @@ public:
|
|||
MASK mMask;
|
||||
|
||||
LLKeyFunc mFunction;
|
||||
std::string mFunctionName;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -72,11 +75,7 @@ typedef enum e_keyboard_mode
|
|||
MODE_COUNT
|
||||
} EKeyboardMode;
|
||||
|
||||
class LLWindow;
|
||||
|
||||
void bind_keyboard_functions();
|
||||
|
||||
class LLViewerInput
|
||||
class LLViewerInput : public LLKeyBindingToStringHandler
|
||||
{
|
||||
public:
|
||||
struct KeyBinding : public LLInitParam::Block<KeyBinding>
|
||||
|
|
@ -107,6 +106,7 @@ public:
|
|||
};
|
||||
|
||||
LLViewerInput();
|
||||
virtual ~LLViewerInput();
|
||||
|
||||
BOOL handleKey(KEY key, MASK mask, BOOL repeated);
|
||||
BOOL handleKeyUp(KEY key, MASK mask);
|
||||
|
|
@ -121,7 +121,7 @@ public:
|
|||
S32 loadBindingsXML(const std::string& filename); // returns number bound, 0 on error
|
||||
EKeyboardMode getMode() const;
|
||||
|
||||
static BOOL modeFromString(const std::string& string, S32 *mode); // False on failure
|
||||
static bool modeFromString(const std::string& string, S32 *mode); // False on failure
|
||||
static BOOL mouseFromString(const std::string& string, EMouseClickType *mode);// False on failure
|
||||
|
||||
bool scanKey(KEY key,
|
||||
|
|
@ -136,6 +136,9 @@ public:
|
|||
bool isMouseBindUsed(const EMouseClickType mouse, const MASK mask, const S32 mode) const;
|
||||
bool isLMouseHandlingDefault(const S32 mode) const { return mLMouseDefaultHandling[mode]; }
|
||||
|
||||
// inherited from LLKeyBindingToStringHandler
|
||||
virtual std::string getKeyBindingAsString(const std::string& mode, const std::string& control) const override;
|
||||
|
||||
private:
|
||||
bool scanKey(const std::vector<LLKeyboardBinding> &binding,
|
||||
S32 binding_count,
|
||||
|
|
|
|||
|
|
@ -231,9 +231,29 @@ LLLocalizedInventoryItemsDictionary::LLLocalizedInventoryItemsDictionary()
|
|||
class LLInventoryHandler : public LLCommandHandler
|
||||
{
|
||||
public:
|
||||
// requires trusted browser to trigger
|
||||
LLInventoryHandler() : LLCommandHandler("inventory", UNTRUSTED_CLICK_ONLY) { }
|
||||
|
||||
LLInventoryHandler() : LLCommandHandler("inventory", UNTRUSTED_THROTTLE) { }
|
||||
|
||||
virtual bool canHandleUntrusted(
|
||||
const LLSD& params,
|
||||
const LLSD& query_map,
|
||||
LLMediaCtrl* web,
|
||||
const std::string& nav_type)
|
||||
{
|
||||
if (params.size() < 1)
|
||||
{
|
||||
return true; // don't block, will fail later
|
||||
}
|
||||
|
||||
if (nav_type == NAV_TYPE_CLICKED
|
||||
|| nav_type == NAV_TYPE_EXTERNAL)
|
||||
{
|
||||
// NAV_TYPE_EXTERNAL will be throttled
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool handle(const LLSD& params,
|
||||
const LLSD& query_map,
|
||||
const std::string& grid,
|
||||
|
|
@ -1135,14 +1155,29 @@ void create_inventory_item(
|
|||
gAgent.sendReliableMessage();
|
||||
}
|
||||
|
||||
void create_inventory_callingcard_callback(LLPointer<LLInventoryCallback> cb,
|
||||
const LLUUID &parent,
|
||||
const LLUUID &avatar_id,
|
||||
const LLAvatarName &av_name)
|
||||
{
|
||||
std::string item_desc = avatar_id.asString();
|
||||
create_inventory_item(gAgent.getID(),
|
||||
gAgent.getSessionID(),
|
||||
parent,
|
||||
LLTransactionID::tnull,
|
||||
av_name.getUserName(),
|
||||
item_desc,
|
||||
LLAssetType::AT_CALLINGCARD,
|
||||
LLInventoryType::IT_CALLINGCARD,
|
||||
NO_INV_SUBTYPE,
|
||||
PERM_MOVE | PERM_TRANSFER,
|
||||
cb);
|
||||
}
|
||||
|
||||
void create_inventory_callingcard(const LLUUID& avatar_id, const LLUUID& parent /*= LLUUID::null*/, LLPointer<LLInventoryCallback> cb/*=NULL*/)
|
||||
{
|
||||
std::string item_desc = avatar_id.asString();
|
||||
LLAvatarName av_name;
|
||||
LLAvatarNameCache::get(avatar_id, &av_name);
|
||||
create_inventory_item(gAgent.getID(), gAgent.getSessionID(),
|
||||
parent, LLTransactionID::tnull, av_name.getUserName(), item_desc, LLAssetType::AT_CALLINGCARD,
|
||||
LLInventoryType::IT_CALLINGCARD, NO_INV_SUBTYPE, PERM_MOVE | PERM_TRANSFER, cb);
|
||||
LLAvatarNameCache::get(avatar_id, boost::bind(&create_inventory_callingcard_callback, cb, parent, _1, _2));
|
||||
}
|
||||
|
||||
void create_inventory_wearable(const LLUUID& agent_id, const LLUUID& session_id,
|
||||
|
|
|
|||
|
|
@ -3473,6 +3473,12 @@ void LLViewerObject::doInventoryCallback()
|
|||
|
||||
void LLViewerObject::removeInventory(const LLUUID& item_id)
|
||||
{
|
||||
// close associated floater properties
|
||||
LLSD params;
|
||||
params["id"] = item_id;
|
||||
params["object"] = mID;
|
||||
LLFloaterReg::hideInstance("item_properties", params);
|
||||
|
||||
LLMessageSystem* msg = gMessageSystem;
|
||||
msg->newMessageFast(_PREHASH_RemoveTaskInventory);
|
||||
msg->nextBlockFast(_PREHASH_AgentData);
|
||||
|
|
|
|||
|
|
@ -5230,11 +5230,6 @@ U32 LLVOAvatar::renderRigid()
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!mIsBuilt)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool should_alpha_mask = shouldAlphaMask();
|
||||
LLGLState test(GL_ALPHA_TEST, should_alpha_mask);
|
||||
|
|
|
|||
|
|
@ -960,7 +960,7 @@ void LLVOAvatarSelf::updateRegion(LLViewerRegion *regionp)
|
|||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// draw tractor beam when editing objects
|
||||
// draw tractor (selection) beam when editing objects
|
||||
//--------------------------------------------------------------------
|
||||
//virtual
|
||||
void LLVOAvatarSelf::idleUpdateTractorBeam()
|
||||
|
|
@ -1247,6 +1247,27 @@ BOOL LLVOAvatarSelf::detachObject(LLViewerObject *viewer_object)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
bool LLVOAvatarSelf::hasAttachmentsInTrash()
|
||||
{
|
||||
const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH);
|
||||
|
||||
for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter)
|
||||
{
|
||||
LLViewerJointAttachment *attachment = iter->second;
|
||||
for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
|
||||
attachment_iter != attachment->mAttachedObjects.end();
|
||||
++attachment_iter)
|
||||
{
|
||||
LLViewerObject *attached_object = attachment_iter->get();
|
||||
if (attached_object && gInventory.isObjectDescendentOf(attached_object->getAttachmentItemID(), trash_id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
BOOL LLVOAvatarSelf::detachAttachmentIntoInventory(const LLUUID &item_id)
|
||||
{
|
||||
|
|
@ -2800,12 +2821,14 @@ BOOL LLVOAvatarSelf::needsRenderBeam()
|
|||
LLTool *tool = LLToolMgr::getInstance()->getCurrentTool();
|
||||
|
||||
BOOL is_touching_or_grabbing = (tool == LLToolGrab::getInstance() && LLToolGrab::getInstance()->isEditing());
|
||||
if (LLToolGrab::getInstance()->getEditingObject() &&
|
||||
LLToolGrab::getInstance()->getEditingObject()->isAttachment())
|
||||
{
|
||||
// don't render selection beam on hud objects
|
||||
is_touching_or_grabbing = FALSE;
|
||||
}
|
||||
LLViewerObject* objp = LLToolGrab::getInstance()->getEditingObject();
|
||||
if (objp // might need to be "!objp ||" instead of "objp &&".
|
||||
&& (objp->isAttachment() || objp->isAvatar()))
|
||||
{
|
||||
// don't render grab tool's selection beam on hud objects,
|
||||
// attachments or avatars
|
||||
is_touching_or_grabbing = FALSE;
|
||||
}
|
||||
return is_touching_or_grabbing || (getAttachmentState() & AGENT_STATE_EDITING && LLSelectMgr::getInstance()->shouldShowSelection());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -289,6 +289,8 @@ public:
|
|||
/*virtual*/ BOOL detachObject(LLViewerObject *viewer_object);
|
||||
static BOOL detachAttachmentIntoInventory(const LLUUID& item_id);
|
||||
|
||||
bool hasAttachmentsInTrash();
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// HUDs
|
||||
//--------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1086,6 +1086,8 @@ void LLVOCache::initCache(ELLPath location, U32 size, U32 cache_version)
|
|||
|
||||
readCacheHeader();
|
||||
|
||||
LL_INFOS() << "Viewer Object Cache Versions - expected: " << cache_version << " found: " << mMetaInfo.mVersion << LL_ENDL;
|
||||
|
||||
if( mMetaInfo.mVersion != cache_version
|
||||
|| mMetaInfo.mAddressSize != expected_address)
|
||||
{
|
||||
|
|
@ -1096,7 +1098,8 @@ void LLVOCache::initCache(ELLPath location, U32 size, U32 cache_version)
|
|||
clearCacheInMemory();
|
||||
}
|
||||
else //delete the current cache if the format does not match.
|
||||
{
|
||||
{
|
||||
LL_INFOS() << "Viewer Object Cache Versions unmatched. clearing cache." << LL_ENDL;
|
||||
removeCache();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,20 +9,17 @@
|
|||
<text name="ItemcountText">
|
||||
Genstande:
|
||||
</text>
|
||||
<filter_editor label="Filter" name="inventory search editor"/>
|
||||
<tab_container name="inventory filter tabs">
|
||||
<inventory_panel label="Alle ting" name="All Items"/>
|
||||
<recent_inventory_panel label="Nye ting" name="Recent Items"/>
|
||||
</tab_container>
|
||||
<layout_stack name="bottom_panel">
|
||||
<layout_panel name="options_gear_btn_panel">
|
||||
<button name="options_gear_btn" tool_tip="Vis yderligere valg"/>
|
||||
</layout_panel>
|
||||
<layout_panel name="add_btn_panel">
|
||||
<button name="add_btn" tool_tip="Tilføj ny genstand"/>
|
||||
</layout_panel>
|
||||
<layout_panel name="trash_btn_panel">
|
||||
<dnd_button name="trash_btn" tool_tip="Fjern valgte genstand"/>
|
||||
</layout_panel>
|
||||
</layout_stack>
|
||||
<layout_stack name="top_stack">
|
||||
<layout_panel name="filter_layout_panel">
|
||||
<filter_editor label="Filter" name="inventory search editor"/>
|
||||
<button name="options_gear_btn" tool_tip="Vis yderligere valg"/>
|
||||
<button name="add_btn" tool_tip="Tilføj ny genstand"/>
|
||||
</layout_panel>
|
||||
</layout_stack>
|
||||
<panel name="default_inventory_panel">
|
||||
<tab_container name="inventory filter tabs">
|
||||
<inventory_panel label="Alle ting" name="All Items"/>
|
||||
<recent_inventory_panel label="Nye ting" name="Recent Items"/>
|
||||
</tab_container>
|
||||
</panel>
|
||||
</panel>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue