summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/CMakeLists.txt3
-rw-r--r--indra/llcommon/llcond.h52
-rw-r--r--indra/llcommon/llpreprocessor.h4
-rw-r--r--indra/llcommon/llsingleton.h14
-rw-r--r--indra/llcommon/llstring.cpp104
-rw-r--r--indra/llcommon/llstring.h225
-rw-r--r--indra/llcommon/llthreadsafequeue.h48
-rw-r--r--indra/llcommon/stdtypes.h7
-rw-r--r--indra/llcommon/stringize.h117
-rw-r--r--indra/llcommon/tests/threadsafeschedule_test.cpp4
-rw-r--r--indra/llcommon/tests/workqueue_test.cpp84
-rw-r--r--indra/llcommon/threadpool.cpp88
-rw-r--r--indra/llcommon/threadpool.h71
-rw-r--r--indra/llcommon/threadsafeschedule.h34
-rw-r--r--indra/llcommon/timing.cpp25
-rw-r--r--indra/llcommon/workqueue.cpp36
-rw-r--r--indra/llcommon/workqueue.h381
-rw-r--r--indra/llrender/llimagegl.cpp94
-rw-r--r--indra/llrender/llimagegl.h29
-rw-r--r--indra/llui/CMakeLists.txt6
-rw-r--r--indra/llwindow/llwindowwin32.cpp599
-rw-r--r--indra/llwindow/llwindowwin32.h49
-rw-r--r--indra/newview/CMakeLists.txt3
-rw-r--r--indra/newview/app_settings/settings.xml25
-rw-r--r--indra/newview/llappviewer.cpp68
-rw-r--r--indra/newview/llmainlooprepeater.cpp88
-rw-r--r--indra/newview/llmainlooprepeater.h64
-rw-r--r--indra/newview/llstartup.cpp22
-rw-r--r--indra/newview/llstartup.h8
-rw-r--r--indra/newview/llviewercontrol.cpp2
-rw-r--r--indra/newview/llviewertexture.cpp34
-rw-r--r--indra/newview/llviewertexture.h4
32 files changed, 1406 insertions, 986 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 9defa6b6c1..782f656406 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -119,8 +119,8 @@ set(llcommon_SOURCE_FILES
lluriparser.cpp
lluuid.cpp
llworkerthread.cpp
- timing.cpp
u64.cpp
+ threadpool.cpp
workqueue.cpp
StackWalker.cpp
)
@@ -256,6 +256,7 @@ set(llcommon_HEADER_FILES
lockstatic.h
stdtypes.h
stringize.h
+ threadpool.h
threadsafeschedule.h
timer.h
tuple.h
diff --git a/indra/llcommon/llcond.h b/indra/llcommon/llcond.h
index c08acb66a1..da6e6affe1 100644
--- a/indra/llcommon/llcond.h
+++ b/indra/llcommon/llcond.h
@@ -67,9 +67,11 @@ public:
LLCond(const LLCond&) = delete;
LLCond& operator=(const LLCond&) = delete;
- /// get() returns the stored DATA by value -- so to use get(), DATA must
- /// be copyable. The only way to get a non-const reference -- to modify
- /// the stored DATA -- is via update_one() or update_all().
+ /**
+ * get() returns the stored DATA by value -- so to use get(), DATA must
+ * be copyable. The only way to get a non-const reference -- to modify
+ * the stored DATA -- is via update_one() or update_all().
+ */
value_type get()
{
LockType lk(mMutex);
@@ -77,6 +79,19 @@ public:
}
/**
+ * get(functor) returns whatever the functor returns. It allows us to peek
+ * at the stored DATA without copying the whole thing. The functor must
+ * accept a const reference to DATA. If you want to modify DATA, call
+ * update_one() or update_all() instead.
+ */
+ template <typename FUNC>
+ auto get(FUNC&& func)
+ {
+ LockType lk(mMutex);
+ return std::forward<FUNC>(func)(const_data());
+ }
+
+ /**
* Pass update_one() an invocable accepting non-const (DATA&). The
* invocable will presumably modify the referenced DATA. update_one()
* will lock the mutex, call the invocable and then call notify_one() on
@@ -86,11 +101,11 @@ public:
* update_one() when DATA is a struct or class.
*/
template <typename MODIFY>
- void update_one(MODIFY modify)
+ void update_one(MODIFY&& modify)
{
{ // scope of lock can/should end before notify_one()
LockType lk(mMutex);
- modify(mData);
+ std::forward<MODIFY>(modify)(mData);
}
mCond.notify_one();
}
@@ -105,11 +120,11 @@ public:
* update_all() when DATA is a struct or class.
*/
template <typename MODIFY>
- void update_all(MODIFY modify)
+ void update_all(MODIFY&& modify)
{
{ // scope of lock can/should end before notify_all()
LockType lk(mMutex);
- modify(mData);
+ std::forward<MODIFY>(modify)(mData);
}
mCond.notify_all();
}
@@ -122,7 +137,7 @@ public:
* wait() on the condition_variable.
*/
template <typename Pred>
- void wait(Pred pred)
+ void wait(Pred&& pred)
{
LockType lk(mMutex);
// We must iterate explicitly since the predicate accepted by
@@ -133,7 +148,7 @@ public:
// But what if they instead pass a predicate accepting non-const
// (DATA&)? Such a predicate could modify mData, which would be Bad.
// Forbid that.
- while (! pred(const_cast<const value_type&>(mData)))
+ while (! std::forward<Pred>(pred)(const_data()))
{
mCond.wait(lk);
}
@@ -150,7 +165,7 @@ public:
* returning true.
*/
template <typename Rep, typename Period, typename Pred>
- bool wait_for(const std::chrono::duration<Rep, Period>& timeout_duration, Pred pred)
+ bool wait_for(const std::chrono::duration<Rep, Period>& timeout_duration, Pred&& pred)
{
// Instead of replicating wait_until() logic, convert duration to
// time_point and just call wait_until().
@@ -159,7 +174,8 @@ public:
// wrong! We'd keep pushing the timeout time farther and farther into
// the future. This way, we establish a definite timeout time and
// stick to it.
- return wait_until(std::chrono::steady_clock::now() + timeout_duration, pred);
+ return wait_until(std::chrono::steady_clock::now() + timeout_duration,
+ std::forward<Pred>(pred));
}
/**
@@ -169,9 +185,9 @@ public:
* generic wait_for() method.
*/
template <typename Pred>
- bool wait_for(F32Milliseconds timeout_duration, Pred pred)
+ bool wait_for(F32Milliseconds timeout_duration, Pred&& pred)
{
- return wait_for(convert(timeout_duration), pred);
+ return wait_for(convert(timeout_duration), std::forward<Pred>(pred));
}
protected:
@@ -189,6 +205,10 @@ protected:
}
private:
+ // It's important to pass a const ref to certain user-specified functors
+ // that aren't supposed to be able to modify mData.
+ const value_type& const_data() const { return mData; }
+
/**
* Pass wait_until() a chrono::time_point, indicating the time at which we
* should stop waiting, and a predicate accepting (const DATA&), returning
@@ -209,21 +229,21 @@ private:
* honoring a fixed timeout.
*/
template <typename Clock, typename Duration, typename Pred>
- bool wait_until(const std::chrono::time_point<Clock, Duration>& timeout_time, Pred pred)
+ bool wait_until(const std::chrono::time_point<Clock, Duration>& timeout_time, Pred&& pred)
{
LockType lk(mMutex);
// We advise the caller to pass a predicate accepting (const DATA&).
// But what if they instead pass a predicate accepting non-const
// (DATA&)? Such a predicate could modify mData, which would be Bad.
// Forbid that.
- while (! pred(const_cast<const value_type&>(mData)))
+ while (! std::forward<Pred>(pred)(const_data()))
{
if (cv_status::timeout == mCond.wait_until(lk, timeout_time))
{
// It's possible that wait_until() timed out AND the predicate
// became true more or less simultaneously. Even though
// wait_until() timed out, check the predicate one more time.
- return pred(const_cast<const value_type&>(mData));
+ return std::forward<Pred>(pred)(const_data());
}
}
return true;
diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h
index b17a8e761a..dc586b0008 100644
--- a/indra/llcommon/llpreprocessor.h
+++ b/indra/llcommon/llpreprocessor.h
@@ -171,7 +171,9 @@
#define LL_DLLIMPORT
#endif // LL_WINDOWS
-#if ! defined(LL_WINDOWS)
+#if __clang__ || ! defined(LL_WINDOWS)
+// Only on Windows, and only with the Microsoft compiler (vs. clang) is
+// wchar_t potentially not a distinct type.
#define LL_WCHAR_T_NATIVE 1
#else // LL_WINDOWS
// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros
diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h
index f85f961287..6042c0906c 100644
--- a/indra/llcommon/llsingleton.h
+++ b/indra/llcommon/llsingleton.h
@@ -847,14 +847,13 @@ template<class T>
class LLSimpleton
{
public:
- static T* sInstance;
-
- static void createInstance()
- {
+ template <typename... ARGS>
+ static void createInstance(ARGS&&... args)
+ {
llassert(sInstance == nullptr);
- sInstance = new T();
+ sInstance = new T(std::forward<ARGS>(args)...);
}
-
+
static inline T* getInstance() { return sInstance; }
static inline T& instance() { return *getInstance(); }
static inline bool instanceExists() { return sInstance != nullptr; }
@@ -864,6 +863,9 @@ public:
delete sInstance;
sInstance = nullptr;
}
+
+private:
+ static T* sInstance;
};
template <class T>
diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp
index f6f9f97809..bdea1e76ea 100644
--- a/indra/llcommon/llstring.cpp
+++ b/indra/llcommon/llstring.cpp
@@ -212,7 +212,7 @@ S32 utf16chars_to_wchar(const U16* inchars, llwchar* outchar)
return inchars - base;
}
-llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len)
+llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len)
{
llutf16string out;
@@ -234,27 +234,19 @@ llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len)
return out;
}
-llutf16string wstring_to_utf16str(const LLWString &utf32str)
+llutf16string utf8str_to_utf16str( const char* utf8str, size_t len )
{
- const S32 len = (S32)utf32str.length();
- return wstring_to_utf16str(utf32str, len);
-}
-
-llutf16string utf8str_to_utf16str ( const std::string& utf8str )
-{
- LLWString wstr = utf8str_to_wstring ( utf8str );
+ LLWString wstr = utf8str_to_wstring ( utf8str, len );
return wstring_to_utf16str ( wstr );
}
-
-LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len)
+LLWString utf16str_to_wstring(const U16* utf16str, size_t len)
{
LLWString wout;
- if((len <= 0) || utf16str.empty()) return wout;
+ if (len == 0) return wout;
S32 i = 0;
- // craziness to make gcc happy (llutf16string.c_str() is tweaked on linux):
- const U16* chars16 = &(*(utf16str.begin()));
+ const U16* chars16 = utf16str;
while (i < len)
{
llwchar cur_char;
@@ -264,12 +256,6 @@ LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len)
return wout;
}
-LLWString utf16str_to_wstring(const llutf16string &utf16str)
-{
- const S32 len = (S32)utf16str.length();
- return utf16str_to_wstring(utf16str, len);
-}
-
// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string.
S32 utf16str_wstring_length(const llutf16string &utf16str, const S32 utf16_len)
{
@@ -389,8 +375,7 @@ S32 wstring_utf8_length(const LLWString& wstr)
return len;
}
-
-LLWString utf8str_to_wstring(const std::string& utf8str, S32 len)
+LLWString utf8str_to_wstring(const char* utf8str, size_t len)
{
LLWString wout;
@@ -478,13 +463,7 @@ LLWString utf8str_to_wstring(const std::string& utf8str, S32 len)
return wout;
}
-LLWString utf8str_to_wstring(const std::string& utf8str)
-{
- const S32 len = (S32)utf8str.length();
- return utf8str_to_wstring(utf8str, len);
-}
-
-std::string wstring_to_utf8str(const LLWString& utf32str, S32 len)
+std::string wstring_to_utf8str(const llwchar* utf32str, size_t len)
{
std::string out;
@@ -500,20 +479,9 @@ std::string wstring_to_utf8str(const LLWString& utf32str, S32 len)
return out;
}
-std::string wstring_to_utf8str(const LLWString& utf32str)
-{
- const S32 len = (S32)utf32str.length();
- return wstring_to_utf8str(utf32str, len);
-}
-
-std::string utf16str_to_utf8str(const llutf16string& utf16str)
+std::string utf16str_to_utf8str(const U16* utf16str, size_t len)
{
- return wstring_to_utf8str(utf16str_to_wstring(utf16str));
-}
-
-std::string utf16str_to_utf8str(const llutf16string& utf16str, S32 len)
-{
- return wstring_to_utf8str(utf16str_to_wstring(utf16str, len), len);
+ return wstring_to_utf8str(utf16str_to_wstring(utf16str, len));
}
std::string utf8str_trim(const std::string& utf8str)
@@ -654,17 +622,16 @@ std::string utf8str_removeCRLF(const std::string& utf8str)
}
#if LL_WINDOWS
-std::string ll_convert_wide_to_string(const wchar_t* in)
+unsigned int ll_wstring_default_code_page()
{
- return ll_convert_wide_to_string(in, CP_UTF8);
+ return CP_UTF8;
}
-std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page)
+std::string ll_convert_wide_to_string(const wchar_t* in, size_t len_in, unsigned int code_page)
{
std::string out;
if(in)
{
- int len_in = wcslen(in);
int len_out = WideCharToMultiByte(
code_page,
0,
@@ -696,12 +663,7 @@ std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page)
return out;
}
-std::wstring ll_convert_string_to_wide(const std::string& in)
-{
- return ll_convert_string_to_wide(in, CP_UTF8);
-}
-
-std::wstring ll_convert_string_to_wide(const std::string& in, unsigned int code_page)
+std::wstring ll_convert_string_to_wide(const char* in, size_t len, unsigned int code_page)
{
// From review:
// We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input,
@@ -713,10 +675,10 @@ std::wstring ll_convert_string_to_wide(const std::string& in, unsigned int code_
// reserve an output buffer that will be destroyed on exit, with a place
// to put NULL terminator
- std::vector<wchar_t> w_out(in.length() + 1);
+ std::vector<wchar_t> w_out(len + 1);
memset(&w_out[0], 0, w_out.size());
- int real_output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(),
+ int real_output_str_len = MultiByteToWideChar(code_page, 0, in, len,
&w_out[0], w_out.size() - 1);
//looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858.
@@ -726,30 +688,32 @@ std::wstring ll_convert_string_to_wide(const std::string& in, unsigned int code_
return {&w_out[0]};
}
-LLWString ll_convert_wide_to_wstring(const std::wstring& in)
+LLWString ll_convert_wide_to_wstring(const wchar_t* in, size_t len)
{
- // This function, like its converse, is a placeholder, encapsulating a
- // guilty little hack: the only "official" way nat has found to convert
- // between std::wstring (16 bits on Windows) and LLWString (UTF-32) is
- // by using iconv, which we've avoided so far. It kinda sorta works to
- // just copy individual characters...
- // The point is that if/when we DO introduce some more official way to
- // perform such conversions, we should only have to call it here.
- return { in.begin(), in.end() };
+ // Whether or not std::wstring and llutf16string are distinct types, they
+ // both hold UTF-16LE characters. (See header file comments.) Pretend this
+ // wchar_t* sequence is really a U16* sequence and use the conversion we
+ // define above.
+ return utf16str_to_wstring(reinterpret_cast<const U16*>(in), len);
}
-std::wstring ll_convert_wstring_to_wide(const LLWString& in)
+std::wstring ll_convert_wstring_to_wide(const llwchar* in, size_t len)
{
- // See comments in ll_convert_wide_to_wstring()
- return { in.begin(), in.end() };
+ // first, convert to llutf16string, for which we have a real implementation
+ auto utf16str{ wstring_to_utf16str(in, len) };
+ // then, because each U16 char must be UTF-16LE encoded, pretend the U16*
+ // string pointer is a wchar_t* and instantiate a std::wstring of the same
+ // length.
+ return { reinterpret_cast<const wchar_t*>(utf16str.c_str()), utf16str.length() };
}
std::string ll_convert_string_to_utf8_string(const std::string& in)
{
- auto w_mesg = ll_convert_string_to_wide(in, CP_ACP);
- std::string out_utf8(ll_convert_wide_to_string(w_mesg.c_str(), CP_UTF8));
-
- return out_utf8;
+ // If you pass code_page, you must also pass length, otherwise the code
+ // page parameter will be mistaken for length.
+ auto w_mesg = ll_convert_string_to_wide(in, in.length(), CP_ACP);
+ // CP_UTF8 is default -- see ll_wstring_default_code_page() above.
+ return ll_convert_wide_to_string(w_mesg);
}
namespace
diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h
index 4263122f36..d94f549480 100644
--- a/indra/llcommon/llstring.h
+++ b/indra/llcommon/llstring.h
@@ -27,9 +27,11 @@
#ifndef LL_LLSTRING_H
#define LL_LLSTRING_H
+#include <boost/call_traits.hpp>
#include <boost/optional/optional.hpp>
#include <string>
#include <cstdio>
+#include <cwchar> // std::wcslen()
//#include <locale>
#include <iomanip>
#include <algorithm>
@@ -527,14 +529,71 @@ struct ll_convert_impl<T, T>
T operator()(const T& in) const { return in; }
};
+// simple construction from char*
+template<typename T>
+struct ll_convert_impl<T, const typename T::value_type*>
+{
+ T operator()(const typename T::value_type* in) const { return { in }; }
+};
+
// specialize ll_convert_impl<TO, FROM> to return EXPR
#define ll_convert_alias(TO, FROM, EXPR) \
template<> \
struct ll_convert_impl<TO, FROM> \
{ \
- TO operator()(const FROM& in) const { return EXPR; } \
+ /* param_type optimally passes both char* and string */ \
+ TO operator()(typename boost::call_traits<FROM>::param_type in) const { return EXPR; } \
+}
+
+// If all we're doing is copying characters, pass this to ll_convert_alias as
+// EXPR. Since it expands into the 'return EXPR' slot in the ll_convert_impl
+// specialization above, it implies TO{ in.begin(), in.end() }.
+#define LL_CONVERT_COPY_CHARS { in.begin(), in.end() }
+
+// Generic name for strlen() / wcslen() - the default implementation should
+// (!) work with U16 and llwchar, but we don't intend to engage it.
+template <typename CHARTYPE>
+size_t ll_convert_length(const CHARTYPE* zstr)
+{
+ const CHARTYPE* zp;
+ // classic C string scan
+ for (zp = zstr; *zp; ++zp)
+ ;
+ return (zp - zstr);
}
+// specialize where we have a library function; may use intrinsic operations
+template <>
+inline size_t ll_convert_length<wchar_t>(const wchar_t* zstr) { return std::wcslen(zstr); }
+template <>
+inline size_t ll_convert_length<char> (const char* zstr) { return std::strlen(zstr); }
+
+// ll_convert_forms() is short for a bunch of boilerplate. It defines
+// longname(const char*, len), longname(const char*), longname(const string&)
+// and longname(const string&, len) so calls written pre-ll_convert() will
+// work. Most of these overloads will be unified once we turn on C++17 and can
+// use std::string_view.
+// It also uses aliasmacro to ensure that both ll_convert<OUTSTR>(const char*)
+// and ll_convert<OUTSTR>(const string&) will work.
+#define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \
+LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \
+inline auto longname(const INSTR& in, size_t len) \
+{ \
+ return longname(in.c_str(), len); \
+} \
+inline auto longname(const INSTR::value_type* in) \
+{ \
+ return longname(in, ll_convert_length(in)); \
+} \
+inline auto longname(const INSTR& in) \
+{ \
+ return longname(in.c_str(), in.length()); \
+} \
+/* string param */ \
+aliasmacro(OUTSTR, INSTR, longname(in)); \
+/* char* param */ \
+aliasmacro(OUTSTR, const INSTR::value_type*, longname(in))
+
// Make the incoming string a utf8 string. Replaces any unknown glyph
// with the UNKNOWN_CHARACTER. Once any unknown glyph is found, the rest
// of the data may not be recovered.
@@ -571,63 +630,47 @@ LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw);
// LL_WCHAR_T_NATIVE.
typedef std::basic_string<U16> llutf16string;
-#if ! defined(LL_WCHAR_T_NATIVE)
-// wchar_t is identical to U16, and std::wstring is identical to llutf16string.
-// Defining an ll_convert alias involving llutf16string would collide with the
-// comparable preferred alias involving std::wstring. (In this scenario, if
-// you pass llutf16string, it will engage the std::wstring specialization.)
-#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing
-#else // defined(LL_WCHAR_T_NATIVE)
-// wchar_t is a distinct native type, so llutf16string is also a distinct
-// type, and there IS a point to converting separately to/from llutf16string.
-// (But why? Windows APIs are still defined in terms of wchar_t, and
-// in this scenario llutf16string won't work for them!)
-#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
+// Considering wchar_t, llwchar and U16, there are three relevant cases:
+#if LLWCHAR_IS_WCHAR_T // every which way but Windows
+// llwchar is identical to wchar_t, LLWString is identical to std::wstring.
+// U16 is distinct, llutf16string is distinct (though pretty useless).
+// Given conversions to/from LLWString and to/from llutf16string, conversions
+// involving std::wstring would collide.
+#define ll_convert_wstr_alias(TO, FROM, EXPR) // nothing
+// but we can define conversions involving llutf16string without collisions
+#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
+
+#elif defined(LL_WCHAR_T_NATIVE) // Windows, either clang or MS /Zc:wchar_t
+// llwchar (32-bit), wchar_t (16-bit) and U16 are all different types.
+// Conversions to/from LLWString, to/from std::wstring and to/from llutf16string
+// can all be defined.
+#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
+#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
+
+#else // ! LL_WCHAR_T_NATIVE: Windows with MS /Zc:wchar_t-
+// wchar_t is identical to U16, std::wstring is identical to llutf16string.
+// Given conversions to/from LLWString and to/from std::wstring, conversions
+// involving llutf16string would collide.
+#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing
+// but we can define conversions involving std::wstring without collisions
+#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
+#endif
+
+ll_convert_forms(ll_convert_u16_alias, LLWString, llutf16string, utf16str_to_wstring);
+ll_convert_forms(ll_convert_u16_alias, llutf16string, LLWString, wstring_to_utf16str);
+ll_convert_forms(ll_convert_u16_alias, llutf16string, std::string, utf8str_to_utf16str);
+ll_convert_forms(ll_convert_alias, LLWString, std::string, utf8str_to_wstring);
-#if LL_WINDOWS
-// LL_WCHAR_T_NATIVE is defined on non-Windows systems because, in fact,
-// wchar_t is native. Everywhere but Windows, we use it for llwchar (see
-// stdtypes.h). That makes LLWString identical to std::wstring, so these
-// aliases for std::wstring would collide with those for LLWString. Only
-// define on Windows, where converting between std::wstring and llutf16string
-// means copying chars.
-ll_convert_alias(llutf16string, std::wstring, llutf16string(in.begin(), in.end()));
-ll_convert_alias(std::wstring, llutf16string, std::wstring(in.begin(), in.end()));
-#endif // LL_WINDOWS
-#endif // defined(LL_WCHAR_T_NATIVE)
-
-LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len);
-LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str);
-ll_convert_u16_alias(LLWString, llutf16string, utf16str_to_wstring(in));
-
-LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len);
-LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str);
-ll_convert_u16_alias(llutf16string, LLWString, wstring_to_utf16str(in));
-
-LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str, S32 len);
-LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str );
-ll_convert_u16_alias(llutf16string, std::string, utf8str_to_utf16str(in));
-
-LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str, S32 len);
-LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str);
// Same function, better name. JC
inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); }
-// best name of all
-ll_convert_alias(LLWString, std::string, utf8string_to_wstring(in));
-//
LL_COMMON_API S32 wchar_to_utf8chars(llwchar inchar, char* outchars);
-LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str, S32 len);
-LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str);
-ll_convert_alias(std::string, LLWString, wstring_to_utf8str(in));
-LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len);
-LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str);
-ll_convert_u16_alias(std::string, llutf16string, utf16str_to_utf8str(in));
+ll_convert_forms(ll_convert_alias, std::string, LLWString, wstring_to_utf8str);
+ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_utf8str);
-#if LL_WINDOWS
+// an older alias for utf16str_to_utf8str(llutf16string)
inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);}
-#endif
// Length of this UTF32 string in bytes when transformed to UTF8
LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr);
@@ -701,42 +744,48 @@ LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str);
//@{
/**
- * @brief Convert a wide string to std::string
+ * @brief Convert a wide string to/from std::string
+ * Convert a Windows wide string to/from our LLWString
*
* This replaces the unsafe W2A macro from ATL.
*/
-LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page);
-LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in); // default CP_UTF8
-inline std::string ll_convert_wide_to_string(const std::wstring& in, unsigned int code_page)
-{
- return ll_convert_wide_to_string(in.c_str(), code_page);
-}
-inline std::string ll_convert_wide_to_string(const std::wstring& in)
-{
- return ll_convert_wide_to_string(in.c_str());
-}
-ll_convert_alias(std::string, std::wstring, ll_convert_wide_to_string(in));
-
-/**
- * Converts a string to wide string.
- */
-LL_COMMON_API std::wstring ll_convert_string_to_wide(const std::string& in,
- unsigned int code_page);
-LL_COMMON_API std::wstring ll_convert_string_to_wide(const std::string& in);
- // default CP_UTF8
-ll_convert_alias(std::wstring, std::string, ll_convert_string_to_wide(in));
-
-/**
- * Convert a Windows wide string to our LLWString
- */
-LL_COMMON_API LLWString ll_convert_wide_to_wstring(const std::wstring& in);
-ll_convert_alias(LLWString, std::wstring, ll_convert_wide_to_wstring(in));
-
-/**
- * Convert LLWString to Windows wide string
- */
-LL_COMMON_API std::wstring ll_convert_wstring_to_wide(const LLWString& in);
-ll_convert_alias(std::wstring, LLWString, ll_convert_wstring_to_wide(in));
+// Avoid requiring this header to #include the Windows header file declaring
+// our actual default code_page by delegating this function to our .cpp file.
+LL_COMMON_API unsigned int ll_wstring_default_code_page();
+
+// This is like ll_convert_forms(), with the added complexity of a code page
+// parameter that may or may not be passed.
+#define ll_convert_cp_forms(aliasmacro, OUTSTR, INSTR, longname) \
+/* declare the only nontrivial implementation (in .cpp file) */ \
+LL_COMMON_API OUTSTR longname( \
+ const INSTR::value_type* in, \
+ size_t len, \
+ unsigned int code_page=ll_wstring_default_code_page()); \
+/* if passed only a char pointer, scan for nul terminator */ \
+inline auto longname(const INSTR::value_type* in) \
+{ \
+ return longname(in, ll_convert_length(in)); \
+} \
+/* if passed string and length, extract its char pointer */ \
+inline auto longname( \
+ const INSTR& in, \
+ size_t len, \
+ unsigned int code_page=ll_wstring_default_code_page()) \
+{ \
+ return longname(in.c_str(), len, code_page); \
+} \
+/* if passed only a string object, no scan, pass known length */ \
+inline auto longname(const INSTR& in) \
+{ \
+ return longname(in.c_str(), in.length()); \
+} \
+aliasmacro(OUTSTR, INSTR, longname(in)); \
+aliasmacro(OUTSTR, const INSTR::value_type*, longname(in))
+
+ll_convert_cp_forms(ll_convert_wstr_alias, std::string, std::wstring, ll_convert_wide_to_string);
+ll_convert_cp_forms(ll_convert_wstr_alias, std::wstring, std::string, ll_convert_string_to_wide);
+ ll_convert_forms(ll_convert_wstr_alias, LLWString, std::wstring, ll_convert_wide_to_wstring);
+ ll_convert_forms(ll_convert_wstr_alias, std::wstring, LLWString, ll_convert_wstring_to_wide);
/**
* Converts incoming string into utf8 string
@@ -1937,4 +1986,14 @@ void LLStringUtilBase<T>::truncate(string_type& string, size_type count)
string.resize(count < cur_size ? count : cur_size);
}
+// The good thing about *declaration* macros, vs. usage macros, is that now
+// we're done with them: we don't need them to bleed into the consuming source
+// file.
+#undef ll_convert_alias
+#undef ll_convert_u16_alias
+#undef ll_convert_wstr_alias
+#undef LL_CONVERT_COPY_CHARS
+#undef ll_convert_forms
+#undef ll_convert_cp_forms
+
#endif // LL_STRING_H
diff --git a/indra/llcommon/llthreadsafequeue.h b/indra/llcommon/llthreadsafequeue.h
index 06e8d8f609..2806506550 100644
--- a/indra/llcommon/llthreadsafequeue.h
+++ b/indra/llcommon/llthreadsafequeue.h
@@ -85,8 +85,8 @@ public:
LLThreadSafeQueue(U32 capacity = 1024);
virtual ~LLThreadSafeQueue() {}
- // Add an element to the queue (will block if the queue has
- // reached capacity).
+ // Add an element to the queue (will block if the queue has reached
+ // capacity).
//
// This call will raise an interrupt error if the queue is closed while
// the caller is blocked.
@@ -95,6 +95,11 @@ public:
// legacy name
void pushFront(ElementT const & element) { return push(element); }
+ // Add an element to the queue (will block if the queue has reached
+ // capacity). Return false if the queue is closed before push is possible.
+ template <typename T>
+ bool pushIfOpen(T&& element);
+
// Try to add an element to the queue without blocking. Returns
// true only if the element was actually added.
template <typename T>
@@ -270,6 +275,7 @@ template <typename ElementT, typename QueueT>
template <typename CALLABLE>
bool LLThreadSafeQueue<ElementT, QueueT>::tryLock(CALLABLE&& callable)
{
+ LL_PROFILE_ZONE_SCOPED;
lock_t lock1(mLock, std::defer_lock);
if (!lock1.try_lock())
return false;
@@ -286,6 +292,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::tryLockUntil(
const std::chrono::time_point<Clock, Duration>& until,
CALLABLE&& callable)
{
+ LL_PROFILE_ZONE_SCOPED;
lock_t lock1(mLock, std::defer_lock);
if (!lock1.try_lock_until(until))
return false;
@@ -299,6 +306,7 @@ template <typename ElementT, typename QueueT>
template <typename T>
bool LLThreadSafeQueue<ElementT, QueueT>::push_(lock_t& lock, T&& element)
{
+ LL_PROFILE_ZONE_SCOPED;
if (mStorage.size() >= mCapacity)
return false;
@@ -311,9 +319,10 @@ bool LLThreadSafeQueue<ElementT, QueueT>::push_(lock_t& lock, T&& element)
template <typename ElementT, typename QueueT>
-template<typename T>
-void LLThreadSafeQueue<ElementT, QueueT>::push(T&& element)
+template <typename T>
+bool LLThreadSafeQueue<ElementT, QueueT>::pushIfOpen(T&& element)
{
+ LL_PROFILE_ZONE_SCOPED;
lock_t lock1(mLock);
while (true)
{
@@ -321,12 +330,10 @@ void LLThreadSafeQueue<ElementT, QueueT>::push(T&& element)
// drained or not: the moment either end calls close(), further push()
// operations will fail.
if (mClosed)
- {
- LLTHROW(LLThreadSafeQueueInterrupt());
- }
+ return false;
if (push_(lock1, std::forward<T>(element)))
- return;
+ return true;
// Storage Full. Wait for signal.
mCapacityCond.wait(lock1);
@@ -334,10 +341,23 @@ void LLThreadSafeQueue<ElementT, QueueT>::push(T&& element)
}
+template <typename ElementT, typename QueueT>
+template<typename T>
+void LLThreadSafeQueue<ElementT, QueueT>::push(T&& element)
+{
+ LL_PROFILE_ZONE_SCOPED;
+ if (! pushIfOpen(std::forward<T>(element)))
+ {
+ LLTHROW(LLThreadSafeQueueInterrupt());
+ }
+}
+
+
template<typename ElementT, typename QueueT>
template<typename T>
bool LLThreadSafeQueue<ElementT, QueueT>::tryPush(T&& element)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryLock(
[this, element=std::move(element)](lock_t& lock)
{
@@ -354,6 +374,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::tryPushFor(
const std::chrono::duration<Rep, Period>& timeout,
T&& element)
{
+ LL_PROFILE_ZONE_SCOPED;
// Convert duration to time_point: passing the same timeout duration to
// each of multiple calls is wrong.
return tryPushUntil(std::chrono::steady_clock::now() + timeout,
@@ -367,6 +388,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::tryPushUntil(
const std::chrono::time_point<Clock, Duration>& until,
T&& element)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryLockUntil(
until,
[this, until, element=std::move(element)](lock_t& lock)
@@ -399,6 +421,7 @@ template <typename ElementT, typename QueueT>
typename LLThreadSafeQueue<ElementT, QueueT>::pop_result
LLThreadSafeQueue<ElementT, QueueT>::pop_(lock_t& lock, ElementT& element)
{
+ LL_PROFILE_ZONE_SCOPED;
// If mStorage is empty, there's no head element.
if (mStorage.empty())
return mClosed? DONE : EMPTY;
@@ -420,6 +443,7 @@ LLThreadSafeQueue<ElementT, QueueT>::pop_(lock_t& lock, ElementT& element)
template<typename ElementT, typename QueueT>
ElementT LLThreadSafeQueue<ElementT, QueueT>::pop(void)
{
+ LL_PROFILE_ZONE_SCOPED;
lock_t lock1(mLock);
ElementT value;
while (true)
@@ -448,6 +472,7 @@ ElementT LLThreadSafeQueue<ElementT, QueueT>::pop(void)
template<typename ElementT, typename QueueT>
bool LLThreadSafeQueue<ElementT, QueueT>::tryPop(ElementT & element)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryLock(
[this, &element](lock_t& lock)
{
@@ -465,6 +490,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::tryPopFor(
const std::chrono::duration<Rep, Period>& timeout,
ElementT& element)
{
+ LL_PROFILE_ZONE_SCOPED;
// Convert duration to time_point: passing the same timeout duration to
// each of multiple calls is wrong.
return tryPopUntil(std::chrono::steady_clock::now() + timeout, element);
@@ -477,6 +503,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::tryPopUntil(
const std::chrono::time_point<Clock, Duration>& until,
ElementT& element)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryLockUntil(
until,
[this, until, &element](lock_t& lock)
@@ -496,6 +523,7 @@ LLThreadSafeQueue<ElementT, QueueT>::tryPopUntil_(
const std::chrono::time_point<Clock, Duration>& until,
ElementT& element)
{
+ LL_PROFILE_ZONE_SCOPED;
while (true)
{
pop_result popped = pop_(lock, element);
@@ -522,6 +550,7 @@ LLThreadSafeQueue<ElementT, QueueT>::tryPopUntil_(
template<typename ElementT, typename QueueT>
size_t LLThreadSafeQueue<ElementT, QueueT>::size(void)
{
+ LL_PROFILE_ZONE_SCOPED;
lock_t lock(mLock);
return mStorage.size();
}
@@ -530,6 +559,7 @@ size_t LLThreadSafeQueue<ElementT, QueueT>::size(void)
template<typename ElementT, typename QueueT>
void LLThreadSafeQueue<ElementT, QueueT>::close()
{
+ LL_PROFILE_ZONE_SCOPED;
lock_t lock(mLock);
mClosed = true;
lock.unlock();
@@ -543,6 +573,7 @@ void LLThreadSafeQueue<ElementT, QueueT>::close()
template<typename ElementT, typename QueueT>
bool LLThreadSafeQueue<ElementT, QueueT>::isClosed()
{
+ LL_PROFILE_ZONE_SCOPED;
lock_t lock(mLock);
return mClosed;
}
@@ -551,6 +582,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::isClosed()
template<typename ElementT, typename QueueT>
bool LLThreadSafeQueue<ElementT, QueueT>::done()
{
+ LL_PROFILE_ZONE_SCOPED;
lock_t lock(mLock);
return mClosed && mStorage.empty();
}
diff --git a/indra/llcommon/stdtypes.h b/indra/llcommon/stdtypes.h
index 887f6ab733..b07805b628 100644
--- a/indra/llcommon/stdtypes.h
+++ b/indra/llcommon/stdtypes.h
@@ -42,10 +42,17 @@ typedef unsigned int U32;
// Windows wchar_t is 16-bit, whichever way /Zc:wchar_t is set. In effect,
// Windows wchar_t is always a typedef, either for unsigned short or __wchar_t.
// (__wchar_t, available either way, is Microsoft's native 2-byte wchar_t type.)
+// The version of clang available with VS 2019 also defines wchar_t as __wchar_t
+// which is also 16 bits.
// In any case, llwchar should be a UTF-32 type.
typedef U32 llwchar;
#else
typedef wchar_t llwchar;
+// What we'd actually want is a simple module-scope 'if constexpr' to test
+// std::is_same<wchar_t, llwchar>::value and use that to define, or not
+// define, string conversion specializations. Since we don't have that, we'll
+// have to rely on #if instead. Sorry, Dr. Stroustrup.
+#define LLWCHAR_IS_WCHAR_T 1
#endif
#if LL_WINDOWS
diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h
index 38dd198ad3..12df693910 100644
--- a/indra/llcommon/stringize.h
+++ b/indra/llcommon/stringize.h
@@ -31,58 +31,109 @@
#include <sstream>
#include <llstring.h>
+#include <boost/call_traits.hpp>
/**
- * gstringize(item) encapsulates an idiom we use constantly, using
- * operator<<(std::ostringstream&, TYPE) followed by std::ostringstream::str()
- * or their wstring equivalents
- * to render a string expressing some item.
+ * stream_to(std::ostream&, items, ...) streams each item in the parameter list
+ * to the passed std::ostream using the insertion operator <<. This can be
+ * used, for instance, to make a simple print() function, e.g.:
+ *
+ * @code
+ * template <typename... Items>
+ * void print(Items&&... items)
+ * {
+ * stream_to(std::cout, std::forward<Items>(items)...);
+ * }
+ * @endcode
*/
-template <typename CHARTYPE, typename T>
-std::basic_string<CHARTYPE> gstringize(const T& item)
+// recursion tail
+template <typename CHARTYPE>
+void stream_to(std::basic_ostream<CHARTYPE>& out) {}
+// stream one or more items
+template <typename CHARTYPE, typename T, typename... Items>
+void stream_to(std::basic_ostream<CHARTYPE>& out, T&& item, Items&&... items)
{
- std::basic_ostringstream<CHARTYPE> out;
- out << item;
- return out.str();
+ out << std::forward<T>(item);
+ stream_to(out, std::forward<Items>(items)...);
}
+// why we use function overloads, not function template specializations:
+// http://www.gotw.ca/publications/mill17.htm
+
/**
- *partial specialization of stringize for handling wstring
- *TODO: we should have similar specializations for wchar_t[] but not until it is needed.
+ * gstringize(item, ...) encapsulates an idiom we use constantly, using
+ * operator<<(std::ostringstream&, TYPE) followed by std::ostringstream::str()
+ * or their wstring equivalents to render a string expressing one or more items.
*/
-inline std::string stringize(const std::wstring& item)
+// two or more args - the case of a single argument is handled separately
+template <typename CHARTYPE, typename T0, typename T1, typename... Items>
+auto gstringize(T0&& item0, T1&& item1, Items&&... items)
{
- return wstring_to_utf8str(item);
+ std::basic_ostringstream<CHARTYPE> out;
+ stream_to(out, std::forward<T0>(item0), std::forward<T1>(item1),
+ std::forward<Items>(items)...);
+ return out.str();
}
-/**
- * Specialization of gstringize for std::string return types
- */
-template <typename T>
-std::string stringize(const T& item)
+// generic single argument: stream to out, as above
+template <typename CHARTYPE, typename T>
+struct gstringize_impl
{
- return gstringize<char>(item);
+ auto operator()(typename boost::call_traits<T>::param_type arg)
+ {
+ std::basic_ostringstream<CHARTYPE> out;
+ out << arg;
+ return out.str();
+ }
+};
+
+// partially specialize for a single STRING argument -
+// note that ll_convert<T>(T) already handles the trivial case
+template <typename OUTCHAR, typename INCHAR>
+struct gstringize_impl<OUTCHAR, std::basic_string<INCHAR>>
+{
+ auto operator()(const std::basic_string<INCHAR>& arg)
+ {
+ return ll_convert<std::basic_string<OUTCHAR>>(arg);
+ }
+};
+
+// partially specialize for a single CHARTYPE* argument -
+// since it's not a basic_string and we do want to optimize this common case
+template <typename OUTCHAR, typename INCHAR>
+struct gstringize_impl<OUTCHAR, INCHAR*>
+{
+ auto operator()(const INCHAR* arg)
+ {
+ return ll_convert<std::basic_string<OUTCHAR>>(arg);
+ }
+};
+
+// gstringize(single argument)
+template <typename CHARTYPE, typename T>
+auto gstringize(T&& item)
+{
+ // use decay<T> so we don't require separate specializations
+ // for T, const T, T&, const T& ...
+ return gstringize_impl<CHARTYPE, std::decay_t<T>>()(std::forward<T>(item));
}
/**
- * Specialization for generating wstring from string.
- * Both a convenience function and saves a miniscule amount of overhead.
+ * Specialization of gstringize for std::string return types
*/
-inline std::wstring wstringize(const std::string& item)
+template <typename... Items>
+auto stringize(Items&&... items)
{
- // utf8str_to_wstring() returns LLWString, which isn't necessarily the
- // same as std::wstring
- LLWString s(utf8str_to_wstring(item));
- return std::wstring(s.begin(), s.end());
+ return gstringize<char>(std::forward<Items>(items)...);
}
/**
* Specialization of gstringize for std::wstring return types
*/
-template <typename T>
-std::wstring wstringize(const T& item)
+template <typename... Items>
+auto wstringize(Items&&... items)
{
- return gstringize<wchar_t>(item);
+ return gstringize<wchar_t>(std::forward<Items>(items)...);
}
/**
@@ -146,11 +197,9 @@ void destringize_f(std::basic_string<CHARTYPE> const & str, Functor const & f)
* std::istringstream in(str);
* in >> item1 >> item2 >> item3 ... ;
* @endcode
- * @NOTE - once we get generic lambdas, we shouldn't need DEWSTRINGIZE() any
- * more since DESTRINGIZE() should do the right thing with a std::wstring. But
- * until then, the lambda we pass must accept the right std::basic_istream.
*/
-#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](std::istream& in){in >> EXPRESSION;}))
-#define DEWSTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](std::wistream& in){in >> EXPRESSION;}))
+#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](auto& in){in >> EXPRESSION;}))
+// legacy name, just use DESTRINGIZE() going forward
+#define DEWSTRINGIZE(STR, EXPRESSION) DESTRINGIZE(STR, EXPRESSION)
#endif /* ! defined(LL_STRINGIZE_H) */
diff --git a/indra/llcommon/tests/threadsafeschedule_test.cpp b/indra/llcommon/tests/threadsafeschedule_test.cpp
index af67b9f492..c421cc7b1c 100644
--- a/indra/llcommon/tests/threadsafeschedule_test.cpp
+++ b/indra/llcommon/tests/threadsafeschedule_test.cpp
@@ -46,11 +46,11 @@ namespace tut
// the real time required for each push() call. Explicitly increment
// the timestamp for each one -- but since we're passing explicit
// timestamps, make the queue reorder them.
- queue.push(Queue::TimeTuple(Queue::Clock::now() + 20ms, "ghi"));
+ queue.push(Queue::TimeTuple(Queue::Clock::now() + 200ms, "ghi"));
// Given the various push() overloads, you have to match the type
// exactly: conversions are ambiguous.
queue.push("abc"s);
- queue.push(Queue::Clock::now() + 10ms, "def");
+ queue.push(Queue::Clock::now() + 100ms, "def");
queue.close();
auto entry = queue.pop();
ensure_equals("failed to pop first", std::get<0>(entry), "abc"s);
diff --git a/indra/llcommon/tests/workqueue_test.cpp b/indra/llcommon/tests/workqueue_test.cpp
index d5405400fd..1d73f7aa0d 100644
--- a/indra/llcommon/tests/workqueue_test.cpp
+++ b/indra/llcommon/tests/workqueue_test.cpp
@@ -20,7 +20,10 @@
// external library headers
// other Linden headers
#include "../test/lltut.h"
+#include "../test/catch_and_store_what_in.h"
#include "llcond.h"
+#include "llcoros.h"
+#include "lleventcoro.h"
#include "llstring.h"
#include "stringize.h"
@@ -96,9 +99,15 @@ namespace tut
return (++count < 3);
});
// no convenient way to close() our queue while we've got a
- // postEvery() running, so run until we think we should have exhausted
- // the iterations
- queue.runFor(10*interval);
+ // postEvery() running, so run until we have exhausted the iterations
+ // or we time out waiting
+ for (auto finish = start + 10*interval;
+ WorkQueue::TimePoint::clock::now() < finish &&
+ data.get([](const Shared& data){ return data.size(); }) < 3; )
+ {
+ queue.runPending();
+ std::this_thread::sleep_for(interval/10);
+ }
// Take a copy of the captured deque.
Shared result = data.get();
ensure_equals("called wrong number of times", result.size(), 3);
@@ -138,7 +147,8 @@ namespace tut
[](){ return 17; },
// Note that a postTo() *callback* can safely bind a reference to
// a variable on the invoking thread, because the callback is run
- // on the invoking thread.
+ // on the invoking thread. (Of course the bound variable must
+ // survive until the callback is called.)
[&result](int i){ result = i; });
// this should post the callback to main
qptr->runOne();
@@ -156,4 +166,70 @@ namespace tut
main.runPending();
ensure_equals("failed to run string callback", alpha, "abc");
}
+
+ template<> template<>
+ void object::test<5>()
+ {
+ set_test_name("postTo with void return");
+ WorkQueue main("main");
+ auto qptr = WorkQueue::getInstance("queue");
+ std::string observe;
+ main.postTo(
+ qptr,
+ // The ONLY reason we can get away with binding a reference to
+ // 'observe' in our work callable is because we're directly
+ // calling qptr->runOne() on this same thread. It would be a
+ // mistake to do that if some other thread were servicing 'queue'.
+ [&observe](){ observe = "queue"; },
+ [&observe](){ observe.append(";main"); });
+ qptr->runOne();
+ main.runOne();
+ ensure_equals("failed to run both lambdas", observe, "queue;main");
+ }
+
+ template<> template<>
+ void object::test<6>()
+ {
+ set_test_name("waitForResult");
+ std::string stored;
+ // Try to call waitForResult() on this thread's main coroutine. It
+ // should throw because the main coroutine must service the queue.
+ auto what{ catch_what<WorkQueue::Error>(
+ [this, &stored](){ stored = queue.waitForResult(
+ [](){ return "should throw"; }); }) };
+ ensure("lambda should not have run", stored.empty());
+ ensure_not("waitForResult() should have thrown", what.empty());
+ ensure(STRINGIZE("should mention waitForResult: " << what),
+ what.find("waitForResult") != std::string::npos);
+
+ // Call waitForResult() on a coroutine, with a string result.
+ LLCoros::instance().launch(
+ "waitForResult string",
+ [this, &stored]()
+ { stored = queue.waitForResult(
+ [](){ return "string result"; }); });
+ llcoro::suspend();
+ // Nothing will have happened yet because, even if the coroutine did
+ // run immediately, all it did was to queue the inner lambda on
+ // 'queue'. Service it.
+ queue.runOne();
+ llcoro::suspend();
+ ensure_equals("bad waitForResult return", stored, "string result");
+
+ // Call waitForResult() on a coroutine, with a void callable.
+ stored.clear();
+ bool done = false;
+ LLCoros::instance().launch(
+ "waitForResult void",
+ [this, &stored, &done]()
+ {
+ queue.waitForResult([&stored](){ stored = "ran"; });
+ done = true;
+ });
+ llcoro::suspend();
+ queue.runOne();
+ llcoro::suspend();
+ ensure_equals("didn't run coroutine", stored, "ran");
+ ensure("void waitForResult() didn't return", done);
+ }
} // namespace tut
diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp
new file mode 100644
index 0000000000..ba914035e2
--- /dev/null
+++ b/indra/llcommon/threadpool.cpp
@@ -0,0 +1,88 @@
+/**
+ * @file threadpool.cpp
+ * @author Nat Goodspeed
+ * @date 2021-10-21
+ * @brief Implementation for threadpool.
+ *
+ * $LicenseInfo:firstyear=2021&license=viewerlgpl$
+ * Copyright (c) 2021, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "threadpool.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "llerror.h"
+#include "llevents.h"
+#include "stringize.h"
+
+LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity):
+ mQueue(name, capacity),
+ mName("ThreadPool:" + name),
+ mThreadCount(threads)
+{}
+
+void LL::ThreadPool::start()
+{
+ for (size_t i = 0; i < mThreadCount; ++i)
+ {
+ std::string tname{ stringize(mName, ':', (i+1), '/', mThreadCount) };
+ mThreads.emplace_back(tname, [this, tname]()
+ {
+ LL_PROFILER_SET_THREAD_NAME(tname.c_str());
+ run(tname);
+ });
+ }
+ // Listen on "LLApp", and when the app is shutting down, close the queue
+ // and join the workers.
+ LLEventPumps::instance().obtain("LLApp").listen(
+ mName,
+ [this](const LLSD& stat)
+ {
+ std::string status(stat["status"]);
+ if (status != "running")
+ {
+ // viewer is starting shutdown -- proclaim the end is nigh!
+ LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL;
+ close();
+ }
+ return false;
+ });
+}
+
+LL::ThreadPool::~ThreadPool()
+{
+ close();
+}
+
+void LL::ThreadPool::close()
+{
+ if (! mQueue.isClosed())
+ {
+ LL_DEBUGS("ThreadPool") << mName << " closing queue and joining threads" << LL_ENDL;
+ mQueue.close();
+ for (auto& pair: mThreads)
+ {
+ LL_DEBUGS("ThreadPool") << mName << " waiting on thread " << pair.first << LL_ENDL;
+ pair.second.join();
+ }
+ LL_DEBUGS("ThreadPool") << mName << " shutdown complete" << LL_ENDL;
+ }
+}
+
+void LL::ThreadPool::run(const std::string& name)
+{
+ LL_DEBUGS("ThreadPool") << name << " starting" << LL_ENDL;
+ run();
+ LL_DEBUGS("ThreadPool") << name << " stopping" << LL_ENDL;
+}
+
+void LL::ThreadPool::run()
+{
+ mQueue.runUntilClose();
+}
diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h
new file mode 100644
index 0000000000..b79c9b9090
--- /dev/null
+++ b/indra/llcommon/threadpool.h
@@ -0,0 +1,71 @@
+/**
+ * @file threadpool.h
+ * @author Nat Goodspeed
+ * @date 2021-10-21
+ * @brief ThreadPool configures a WorkQueue along with a pool of threads to
+ * service it.
+ *
+ * $LicenseInfo:firstyear=2021&license=viewerlgpl$
+ * Copyright (c) 2021, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_THREADPOOL_H)
+#define LL_THREADPOOL_H
+
+#include "workqueue.h"
+#include <string>
+#include <thread>
+#include <utility> // std::pair
+#include <vector>
+
+namespace LL
+{
+
+ class ThreadPool
+ {
+ public:
+ /**
+ * Pass ThreadPool a string name. This can be used to look up the
+ * relevant WorkQueue.
+ */
+ ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024);
+ virtual ~ThreadPool();
+
+ /**
+ * Launch the ThreadPool. Until this call, a constructed ThreadPool
+ * launches no threads. That permits coders to derive from ThreadPool,
+ * or store it as a member of some other class, but refrain from
+ * launching it until all other construction is complete.
+ */
+ void start();
+
+ /**
+ * ThreadPool listens for application shutdown messages on the "LLApp"
+ * LLEventPump. Call close() to shut down this ThreadPool early.
+ */
+ void close();
+
+ std::string getName() const { return mName; }
+ size_t getWidth() const { return mThreads.size(); }
+ /// obtain a non-const reference to the WorkQueue to post work to it
+ WorkQueue& getQueue() { return mQueue; }
+
+ /**
+ * Override run() if you need special processing. The default run()
+ * implementation simply calls WorkQueue::runUntilClose().
+ */
+ virtual void run();
+
+ private:
+ void run(const std::string& name);
+
+ WorkQueue mQueue;
+ std::string mName;
+ size_t mThreadCount;
+ std::vector<std::pair<std::string, std::thread>> mThreads;
+ };
+
+} // namespace LL
+
+#endif /* ! defined(LL_THREADPOOL_H) */
diff --git a/indra/llcommon/threadsafeschedule.h b/indra/llcommon/threadsafeschedule.h
index c8ad23532b..601681d550 100644
--- a/indra/llcommon/threadsafeschedule.h
+++ b/indra/llcommon/threadsafeschedule.h
@@ -98,12 +98,14 @@ namespace LL
// we could minimize redundancy by breaking out a common base class...
void push(const DataTuple& tuple)
{
+ LL_PROFILE_ZONE_SCOPED;
push(tuple_cons(Clock::now(), tuple));
}
/// individually pass each component of the TimeTuple
void push(const TimePoint& time, Args&&... args)
{
+ LL_PROFILE_ZONE_SCOPED;
push(TimeTuple(time, std::forward<Args>(args)...));
}
@@ -114,6 +116,7 @@ namespace LL
// and call that overload.
void push(Args&&... args)
{
+ LL_PROFILE_ZONE_SCOPED;
push(Clock::now(), std::forward<Args>(args)...);
}
@@ -124,18 +127,21 @@ namespace LL
/// DataTuple with implicit now
bool tryPush(const DataTuple& tuple)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryPush(tuple_cons(Clock::now(), tuple));
}
/// individually pass components
bool tryPush(const TimePoint& time, Args&&... args)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryPush(TimeTuple(time, std::forward<Args>(args)...));
}
/// individually pass components with implicit now
bool tryPush(Args&&... args)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryPush(Clock::now(), std::forward<Args>(args)...);
}
@@ -148,6 +154,7 @@ namespace LL
bool tryPushFor(const std::chrono::duration<Rep, Period>& timeout,
const DataTuple& tuple)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryPushFor(timeout, tuple_cons(Clock::now(), tuple));
}
@@ -156,6 +163,7 @@ namespace LL
bool tryPushFor(const std::chrono::duration<Rep, Period>& timeout,
const TimePoint& time, Args&&... args)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryPushFor(TimeTuple(time, std::forward<Args>(args)...));
}
@@ -164,6 +172,7 @@ namespace LL
bool tryPushFor(const std::chrono::duration<Rep, Period>& timeout,
Args&&... args)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryPushFor(Clock::now(), std::forward<Args>(args)...);
}
@@ -176,6 +185,7 @@ namespace LL
bool tryPushUntil(const std::chrono::time_point<Clock, Duration>& until,
const DataTuple& tuple)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryPushUntil(until, tuple_cons(Clock::now(), tuple));
}
@@ -184,6 +194,7 @@ namespace LL
bool tryPushUntil(const std::chrono::time_point<Clock, Duration>& until,
const TimePoint& time, Args&&... args)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryPushUntil(until, TimeTuple(time, std::forward<Args>(args)...));
}
@@ -192,6 +203,7 @@ namespace LL
bool tryPushUntil(const std::chrono::time_point<Clock, Duration>& until,
Args&&... args)
{
+ LL_PROFILE_ZONE_SCOPED;
return tryPushUntil(until, Clock::now(), std::forward<Args>(args)...);
}
@@ -209,12 +221,14 @@ namespace LL
// haven't yet jumped through those hoops.
DataTuple pop()
{
+ LL_PROFILE_ZONE_SCOPED;
return tuple_cdr(popWithTime());
}
/// pop TimeTuple by value
TimeTuple popWithTime()
{
+ LL_PROFILE_ZONE_SCOPED;
lock_t lock(super::mLock);
// We can't just sit around waiting forever, given that there may
// be items in the queue that are not yet ready but will *become*
@@ -254,6 +268,7 @@ namespace LL
/// tryPop(DataTuple&)
bool tryPop(DataTuple& tuple)
{
+ LL_PROFILE_ZONE_SCOPED;
TimeTuple tt;
if (! super::tryPop(tt))
return false;
@@ -264,6 +279,7 @@ namespace LL
/// for when Args has exactly one type
bool tryPop(typename std::tuple_element<1, TimeTuple>::type& value)
{
+ LL_PROFILE_ZONE_SCOPED;
TimeTuple tt;
if (! super::tryPop(tt))
return false;
@@ -275,6 +291,7 @@ namespace LL
template <typename Rep, typename Period, typename Tuple>
bool tryPopFor(const std::chrono::duration<Rep, Period>& timeout, Tuple& tuple)
{
+ LL_PROFILE_ZONE_SCOPED;
// It's important to use OUR tryPopUntil() implementation, rather
// than delegating immediately to our base class.
return tryPopUntil(Clock::now() + timeout, tuple);
@@ -285,6 +302,7 @@ namespace LL
bool tryPopUntil(const std::chrono::time_point<Clock, Duration>& until,
TimeTuple& tuple)
{
+ LL_PROFILE_ZONE_SCOPED;
// super::tryPopUntil() wakes up when an item becomes available or
// we hit 'until', whichever comes first. Thing is, the current
// head of the queue could become ready sooner than either of
@@ -304,20 +322,25 @@ namespace LL
pop_result tryPopUntil_(lock_t& lock, const TimePoint& until, TimeTuple& tuple)
{
+ LL_PROFILE_ZONE_SCOPED;
TimePoint adjusted = until;
if (! super::mStorage.empty())
{
+ LL_PROFILE_ZONE_NAMED("tpu - adjust");
// use whichever is earlier: the head item's timestamp, or
// the caller's limit
adjusted = min(std::get<0>(super::mStorage.front()), adjusted);
}
// now delegate to base-class tryPopUntil_()
pop_result popped;
- while ((popped = pop_result(super::tryPopUntil_(lock, adjusted, tuple))) == WAITING)
{
- // If super::tryPopUntil_() returns WAITING, it means there's
- // a head item, but it's not yet time. But it's worth looping
- // back to recheck.
+ LL_PROFILE_ZONE_NAMED("tpu - super");
+ while ((popped = pop_result(super::tryPopUntil_(lock, adjusted, tuple))) == WAITING)
+ {
+ // If super::tryPopUntil_() returns WAITING, it means there's
+ // a head item, but it's not yet time. But it's worth looping
+ // back to recheck.
+ }
}
return popped;
}
@@ -327,6 +350,7 @@ namespace LL
bool tryPopUntil(const std::chrono::time_point<Clock, Duration>& until,
DataTuple& tuple)
{
+ LL_PROFILE_ZONE_SCOPED;
TimeTuple tt;
if (! tryPopUntil(until, tt))
return false;
@@ -339,6 +363,7 @@ namespace LL
bool tryPopUntil(const std::chrono::time_point<Clock, Duration>& until,
typename std::tuple_element<1, TimeTuple>::type& value)
{
+ LL_PROFILE_ZONE_SCOPED;
TimeTuple tt;
if (! tryPopUntil(until, tt))
return false;
@@ -362,6 +387,7 @@ namespace LL
// considering whether to deliver the current head element
bool canPop(const TimeTuple& head) const override
{
+ LL_PROFILE_ZONE_SCOPED;
// an item with a future timestamp isn't yet ready to pop
// (should we add some slop for overhead?)
return std::get<0>(head) <= Clock::now();
diff --git a/indra/llcommon/timing.cpp b/indra/llcommon/timing.cpp
deleted file mode 100644
index c2dc695ef3..0000000000
--- a/indra/llcommon/timing.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * @file timing.cpp
- * @brief This file will be deprecated in the future.
- *
- * $LicenseInfo:firstyear=2000&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp
index b32357e832..c74dada2e4 100644
--- a/indra/llcommon/workqueue.cpp
+++ b/indra/llcommon/workqueue.cpp
@@ -26,8 +26,9 @@
using Mutex = LLCoros::Mutex;
using Lock = LLCoros::LockType;
-LL::WorkQueue::WorkQueue(const std::string& name):
- super(makeName(name))
+LL::WorkQueue::WorkQueue(const std::string& name, size_t capacity):
+ super(makeName(name)),
+ mQueue(capacity)
{
// TODO: register for "LLApp" events so we can implicitly close() on
// viewer shutdown.
@@ -38,12 +39,28 @@ void LL::WorkQueue::close()
mQueue.close();
}
+size_t LL::WorkQueue::size()
+{
+ return mQueue.size();
+}
+
+bool LL::WorkQueue::isClosed()
+{
+ return mQueue.isClosed();
+}
+
+bool LL::WorkQueue::done()
+{
+ return mQueue.done();
+}
+
void LL::WorkQueue::runUntilClose()
{
try
{
for (;;)
{
+ LL_PROFILE_ZONE_SCOPED;
callWork(mQueue.pop());
}
}
@@ -74,10 +91,11 @@ bool LL::WorkQueue::runOne()
bool LL::WorkQueue::runUntil(const TimePoint& until)
{
+ LL_PROFILE_ZONE_SCOPED;
// Should we subtract some slop to allow for typical Work execution time?
// How much slop?
- Work work;
- while (TimePoint::clock::now() < until && mQueue.tryPopUntil(until, work))
+ // runUntil() is simply a time-bounded runPending().
+ for (Work work; TimePoint::clock::now() < until && mQueue.tryPop(work); )
{
callWork(work);
}
@@ -128,3 +146,13 @@ void LL::WorkQueue::error(const std::string& msg)
{
LL_ERRS("WorkQueue") << msg << LL_ENDL;
}
+
+void LL::WorkQueue::checkCoroutine(const std::string& method)
+{
+ // By convention, the default coroutine on each thread has an empty name
+ // string. See also LLCoros::logname().
+ if (LLCoros::getName().empty())
+ {
+ LLTHROW(Error("Do not call " + method + " from a thread's default coroutine"));
+ }
+}
diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h
index 5ec790da79..96574a18b9 100644
--- a/indra/llcommon/workqueue.h
+++ b/indra/llcommon/workqueue.h
@@ -12,14 +12,14 @@
#if ! defined(LL_WORKQUEUE_H)
#define LL_WORKQUEUE_H
+#include "llcoros.h"
+#include "llexception.h"
#include "llinstancetracker.h"
#include "threadsafeschedule.h"
#include <chrono>
+#include <exception> // std::current_exception
#include <functional> // std::function
-#include <queue>
#include <string>
-#include <utility> // std::pair
-#include <vector>
namespace LL
{
@@ -45,11 +45,16 @@ namespace LL
using TimedWork = Queue::TimeTuple;
using Closed = Queue::Closed;
+ struct Error: public LLException
+ {
+ Error(const std::string& what): LLException(what) {}
+ };
+
/**
* You may omit the WorkQueue name, in which case a unique name is
* synthesized; for practical purposes that makes it anonymous.
*/
- WorkQueue(const std::string& name = std::string());
+ WorkQueue(const std::string& name = std::string(), size_t capacity=1024);
/**
* Since the point of WorkQueue is to pass work to some other worker
@@ -59,15 +64,36 @@ namespace LL
*/
void close();
+ /**
+ * WorkQueue supports multiple producers and multiple consumers. In
+ * the general case it's misleading to test size(), since any other
+ * thread might change it the nanosecond the lock is released. On that
+ * basis, some might argue against publishing a size() method at all.
+ *
+ * But there are two specific cases in which a test based on size()
+ * might be reasonable:
+ *
+ * * If you're the only producer, noticing that size() == 0 is
+ * meaningful.
+ * * If you're the only consumer, noticing that size() > 0 is
+ * meaningful.
+ */
+ size_t size();
+ /// producer end: are we prevented from pushing any additional items?
+ bool isClosed();
+ /// consumer end: are we done, is the queue entirely drained?
+ bool done();
+
/*---------------------- fire and forget API -----------------------*/
/// fire-and-forget, but at a particular (future?) time
template <typename CALLABLE>
void post(const TimePoint& time, CALLABLE&& callable)
{
- // Defer reifying an arbitrary CALLABLE until we hit this method.
- // All other methods should accept CALLABLEs of arbitrary type to
- // avoid multiple levels of std::function indirection.
+ // Defer reifying an arbitrary CALLABLE until we hit this or
+ // postIfOpen(). All other methods should accept CALLABLEs of
+ // arbitrary type to avoid multiple levels of std::function
+ // indirection.
mQueue.push(TimedWork(time, std::move(callable)));
}
@@ -83,6 +109,47 @@ namespace LL
}
/**
+ * post work for a particular time, unless the queue is closed before
+ * we can post
+ */
+ template <typename CALLABLE>
+ bool postIfOpen(const TimePoint& time, CALLABLE&& callable)
+ {
+ // Defer reifying an arbitrary CALLABLE until we hit this or
+ // post(). All other methods should accept CALLABLEs of arbitrary
+ // type to avoid multiple levels of std::function indirection.
+ return mQueue.pushIfOpen(TimedWork(time, std::move(callable)));
+ }
+
+ /**
+ * post work, unless the queue is closed before we can post
+ */
+ template <typename CALLABLE>
+ bool postIfOpen(CALLABLE&& callable)
+ {
+ return postIfOpen(TimePoint::clock::now(), std::move(callable));
+ }
+
+ /**
+ * Post work to be run at a specified time to another WorkQueue, which
+ * may or may not still exist and be open. Return true if we were able
+ * to post.
+ */
+ template <typename CALLABLE>
+ static bool postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable);
+
+ /**
+ * Post work to another WorkQueue, which may or may not still exist
+ * and be open. Return true if we were able to post.
+ */
+ template <typename CALLABLE>
+ static bool postMaybe(weak_t target, CALLABLE&& callable)
+ {
+ return postMaybe(target, TimePoint::clock::now(),
+ std::forward<CALLABLE>(callable));
+ }
+
+ /**
* Launch a callable returning bool that will trigger repeatedly at
* specified interval, until the callable returns false.
*
@@ -115,63 +182,8 @@ namespace LL
// Studio compile errors that seem utterly unrelated to this source
// code.
template <typename CALLABLE, typename FOLLOWUP>
- bool postTo(WorkQueue::weak_t target,
- const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback)
- {
- // We're being asked to post to the WorkQueue at target.
- // target is a weak_ptr: have to lock it to check it.
- auto tptr = target.lock();
- if (! tptr)
- // can't post() if the target WorkQueue has been destroyed
- return false;
-
- // Here we believe target WorkQueue still exists. Post to it a
- // lambda that packages our callable, our callback and a weak_ptr
- // to this originating WorkQueue.
- tptr->post(
- time,
- [reply = super::getWeak(),
- callable = std::move(callable),
- callback = std::move(callback)]
- ()
- {
- // Call the callable in any case -- but to minimize
- // copying the result, immediately bind it into a reply
- // lambda. The reply lambda also binds the original
- // callback, so that when we, the originating WorkQueue,
- // finally receive and process the reply lambda, we'll
- // call the bound callback with the bound result -- on the
- // same thread that originally called postTo().
- auto rlambda =
- [result = callable(),
- callback = std::move(callback)]
- ()
- { callback(std::move(result)); };
- // Check if this originating WorkQueue still exists.
- // Remember, the outer lambda is now running on a thread
- // servicing the target WorkQueue, and real time has
- // elapsed since postTo()'s tptr->post() call.
- // reply is a weak_ptr: have to lock it to check it.
- auto rptr = reply.lock();
- if (rptr)
- {
- // Only post reply lambda if the originating WorkQueue
- // still exists. If not -- who would we tell? Log it?
- try
- {
- rptr->post(std::move(rlambda));
- }
- catch (const Closed&)
- {
- // Originating WorkQueue might still exist, but
- // might be Closed. Same thing: just discard the
- // callback.
- }
- }
- });
- // looks like we were able to post()
- return true;
- }
+ bool postTo(weak_t target,
+ const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback);
/**
* Post work to another WorkQueue, requesting a specific callback to
@@ -181,10 +193,36 @@ namespace LL
* inaccessible.
*/
template <typename CALLABLE, typename FOLLOWUP>
- bool postTo(WorkQueue::weak_t target,
- CALLABLE&& callable, FOLLOWUP&& callback)
+ bool postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback)
{
- return postTo(target, TimePoint::clock::now(), std::move(callable), std::move(callback));
+ return postTo(target, TimePoint::clock::now(),
+ std::move(callable), std::move(callback));
+ }
+
+ /**
+ * Post work to another WorkQueue to be run at a specified time,
+ * blocking the calling coroutine until then, returning the result to
+ * caller on completion.
+ *
+ * In general, we assume that each thread's default coroutine is busy
+ * servicing its WorkQueue or whatever. To try to prevent mistakes, we
+ * forbid calling waitForResult() from a thread's default coroutine.
+ */
+ template <typename CALLABLE>
+ auto waitForResult(const TimePoint& time, CALLABLE&& callable);
+
+ /**
+ * Post work to another WorkQueue, blocking the calling coroutine
+ * until then, returning the result to caller on completion.
+ *
+ * In general, we assume that each thread's default coroutine is busy
+ * servicing its WorkQueue or whatever. To try to prevent mistakes, we
+ * forbid calling waitForResult() from a thread's default coroutine.
+ */
+ template <typename CALLABLE>
+ auto waitForResult(CALLABLE&& callable)
+ {
+ return waitForResult(TimePoint::clock::now(), std::move(callable));
}
/*--------------------------- worker API ---------------------------*/
@@ -222,6 +260,7 @@ namespace LL
template <typename Rep, typename Period>
bool runFor(const std::chrono::duration<Rep, Period>& timeslice)
{
+ LL_PROFILE_ZONE_SCOPED;
return runUntil(TimePoint::clock::now() + timeslice);
}
@@ -232,6 +271,23 @@ namespace LL
bool runUntil(const TimePoint& until);
private:
+ template <typename CALLABLE, typename FOLLOWUP>
+ static auto makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback);
+ /// general case: arbitrary C++ return type
+ template <typename CALLABLE, typename FOLLOWUP, typename RETURNTYPE>
+ struct MakeReplyLambda;
+ /// specialize for CALLABLE returning void
+ template <typename CALLABLE, typename FOLLOWUP>
+ struct MakeReplyLambda<CALLABLE, FOLLOWUP, void>;
+
+ /// general case: arbitrary C++ return type
+ template <typename CALLABLE, typename RETURNTYPE>
+ struct WaitForResult;
+ /// specialize for CALLABLE returning void
+ template <typename CALLABLE>
+ struct WaitForResult<CALLABLE, void>;
+
+ static void checkCoroutine(const std::string& method);
static void error(const std::string& msg);
static std::string makeName(const std::string& name);
void callWork(const Queue::DataTuple& work);
@@ -253,8 +309,8 @@ namespace LL
{
public:
// bind the desired data
- BackJack(WorkQueue::weak_t target,
- const WorkQueue::TimePoint& start,
+ BackJack(weak_t target,
+ const TimePoint& start,
const std::chrono::duration<Rep, Period>& interval,
CALLABLE&& callable):
mTarget(target),
@@ -301,8 +357,8 @@ namespace LL
}
private:
- WorkQueue::weak_t mTarget;
- WorkQueue::TimePoint mStart;
+ weak_t mTarget;
+ TimePoint mStart;
std::chrono::duration<Rep, Period> mInterval;
CALLABLE mCallable;
};
@@ -330,6 +386,189 @@ namespace LL
getWeak(), TimePoint::clock::now(), interval, std::move(callable)));
}
+ /// general case: arbitrary C++ return type
+ template <typename CALLABLE, typename FOLLOWUP, typename RETURNTYPE>
+ struct WorkQueue::MakeReplyLambda
+ {
+ auto operator()(CALLABLE&& callable, FOLLOWUP&& callback)
+ {
+ // Call the callable in any case -- but to minimize
+ // copying the result, immediately bind it into the reply
+ // lambda. The reply lambda also binds the original
+ // callback, so that when we, the originating WorkQueue,
+ // finally receive and process the reply lambda, we'll
+ // call the bound callback with the bound result -- on the
+ // same thread that originally called postTo().
+ return
+ [result = std::forward<CALLABLE>(callable)(),
+ callback = std::move(callback)]
+ ()
+ { callback(std::move(result)); };
+ }
+ };
+
+ /// specialize for CALLABLE returning void
+ template <typename CALLABLE, typename FOLLOWUP>
+ struct WorkQueue::MakeReplyLambda<CALLABLE, FOLLOWUP, void>
+ {
+ auto operator()(CALLABLE&& callable, FOLLOWUP&& callback)
+ {
+ // Call the callable, which produces no result.
+ std::forward<CALLABLE>(callable)();
+ // Our completion callback is simply the caller's callback.
+ return std::move(callback);
+ }
+ };
+
+ template <typename CALLABLE, typename FOLLOWUP>
+ auto WorkQueue::makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback)
+ {
+ return MakeReplyLambda<CALLABLE, FOLLOWUP,
+ decltype(std::forward<CALLABLE>(callable)())>()
+ (std::move(callable), std::move(callback));
+ }
+
+ template <typename CALLABLE, typename FOLLOWUP>
+ bool WorkQueue::postTo(weak_t target,
+ const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback)
+ {
+ LL_PROFILE_ZONE_SCOPED;
+ // We're being asked to post to the WorkQueue at target.
+ // target is a weak_ptr: have to lock it to check it.
+ auto tptr = target.lock();
+ if (! tptr)
+ // can't post() if the target WorkQueue has been destroyed
+ return false;
+
+ // Here we believe target WorkQueue still exists. Post to it a
+ // lambda that packages our callable, our callback and a weak_ptr
+ // to this originating WorkQueue.
+ tptr->post(
+ time,
+ [reply = super::getWeak(),
+ callable = std::move(callable),
+ callback = std::move(callback)]
+ ()
+ {
+ // Use postMaybe() below in case this originating WorkQueue
+ // has been closed or destroyed. Remember, the outer lambda is
+ // now running on a thread servicing the target WorkQueue, and
+ // real time has elapsed since postTo()'s tptr->post() call.
+ try
+ {
+ // Make a reply lambda to repost to THIS WorkQueue.
+ // Delegate to makeReplyLambda() so we can partially
+ // specialize on void return.
+ postMaybe(reply, makeReplyLambda(std::move(callable), std::move(callback)));
+ }
+ catch (...)
+ {
+ // Either variant of makeReplyLambda() is responsible for
+ // calling the caller's callable. If that throws, return
+ // the exception to the originating thread.
+ postMaybe(
+ reply,
+ // Bind the current exception to transport back to the
+ // originating WorkQueue. Once there, rethrow it.
+ [exc = std::current_exception()](){ std::rethrow_exception(exc); });
+ }
+ });
+
+ // looks like we were able to post()
+ return true;
+ }
+
+ template <typename CALLABLE>
+ bool WorkQueue::postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable)
+ {
+ LL_PROFILE_ZONE_SCOPED;
+ // target is a weak_ptr: have to lock it to check it
+ auto tptr = target.lock();
+ if (tptr)
+ {
+ try
+ {
+ tptr->post(time, std::forward<CALLABLE>(callable));
+ // we were able to post()
+ return true;
+ }
+ catch (const Closed&)
+ {
+ // target WorkQueue still exists, but is Closed
+ }
+ }
+ // either target no longer exists, or its WorkQueue is Closed
+ return false;
+ }
+
+ /// general case: arbitrary C++ return type
+ template <typename CALLABLE, typename RETURNTYPE>
+ struct WorkQueue::WaitForResult
+ {
+ auto operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable)
+ {
+ LLCoros::Promise<RETURNTYPE> promise;
+ self->post(
+ time,
+ // We dare to bind a reference to Promise because it's
+ // specifically designed for cross-thread communication.
+ [&promise, callable = std::move(callable)]()
+ {
+ try
+ {
+ // call the caller's callable and trigger promise with result
+ promise.set_value(callable());
+ }
+ catch (...)
+ {
+ promise.set_exception(std::current_exception());
+ }
+ });
+ auto future{ LLCoros::getFuture(promise) };
+ // now, on the calling thread, wait for that result
+ LLCoros::TempStatus st("waiting for WorkQueue::waitForResult()");
+ return future.get();
+ }
+ };
+
+ /// specialize for CALLABLE returning void
+ template <typename CALLABLE>
+ struct WorkQueue::WaitForResult<CALLABLE, void>
+ {
+ void operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable)
+ {
+ LLCoros::Promise<void> promise;
+ self->post(
+ time,
+ // &promise is designed for cross-thread access
+ [&promise, callable = std::move(callable)]()
+ {
+ try
+ {
+ callable();
+ promise.set_value();
+ }
+ catch (...)
+ {
+ promise.set_exception(std::current_exception());
+ }
+ });
+ auto future{ LLCoros::getFuture(promise) };
+ // block until set_value()
+ LLCoros::TempStatus st("waiting for void WorkQueue::waitForResult()");
+ future.get();
+ }
+ };
+
+ template <typename CALLABLE>
+ auto WorkQueue::waitForResult(const TimePoint& time, CALLABLE&& callable)
+ {
+ checkCoroutine("waitForResult()");
+ // derive callable's return type so we can specialize for void
+ return WaitForResult<CALLABLE, decltype(std::forward<CALLABLE>(callable)())>()
+ (this, time, std::forward<CALLABLE>(callable));
+ }
+
} // namespace LL
#endif /* ! defined(LL_WORKQUEUE_H) */
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index cbc5392882..894eb8c773 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -172,31 +172,19 @@ BOOL is_little_endian()
return (*c == 0x78) ;
}
-LLImageGLThread* LLImageGLThread::sInstance = nullptr;
-
//static
void LLImageGL::initClass(LLWindow* window, S32 num_catagories, BOOL skip_analyze_alpha /* = false */)
{
LL_PROFILE_ZONE_SCOPED;
sSkipAnalyzeAlpha = skip_analyze_alpha;
- LLImageGLThread::sInstance = new LLImageGLThread(window);
- LLImageGLThread::sInstance->start();
-}
-
-//static
-void LLImageGL::updateClass()
-{
- LL_PROFILE_ZONE_SCOPED;
- LLImageGLThread::sInstance->executeCallbacks();
+ LLImageGLThread::createInstance(window);
}
//static
void LLImageGL::cleanupClass()
{
LL_PROFILE_ZONE_SCOPED;
- LLImageGLThread::sInstance->mFunctionQueue.close();
- delete LLImageGLThread::sInstance;
- LLImageGLThread::sInstance = nullptr;
+ LLImageGLThread::deleteSingleton();
}
//static
@@ -504,6 +492,9 @@ void LLImageGL::init(BOOL usemipmaps)
#endif
mCategory = -1;
+
+ // Sometimes we have to post work for the main thread.
+ mMainQueue = LL::WorkQueue::getInstance("mainloop");
}
void LLImageGL::cleanup()
@@ -1536,8 +1527,7 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
}
//if we're on the image loading thread, be sure to delete old_texname and update mTexName on the main thread
- if (LLImageGLThread::sInstance != nullptr &&
- LLThread::currentID() == LLImageGLThread::sInstance->getID())
+ if (! on_main_thread())
{
{
LL_PROFILE_ZONE_NAMED("cglt - sync");
@@ -1554,7 +1544,9 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
}
ref();
- LLImageGLThread::sInstance->postCallback([=]()
+ LL::WorkQueue::postMaybe(
+ mMainQueue,
+ [=]()
{
LL_PROFILE_ZONE_NAMED("cglt - delete callback");
if (old_texname != 0)
@@ -2259,73 +2251,27 @@ void LLImageGL::resetCurTexSizebar()
*/
LLImageGLThread::LLImageGLThread(LLWindow* window)
- : LLThread("LLImageGL"), mWindow(window)
+ // We want exactly one thread, but a very large capacity: we never want
+ // anyone, especially inner-loop render code, to have to block on post()
+ // because we're full.
+ : ThreadPool("LLImageGL", 1, 1024*1024)
+ , mWindow(window)
{
+ LL_PROFILE_ZONE_SCOPED;
mFinished = false;
mContext = mWindow->createSharedContext();
-}
-
-// post a function to be executed on the LLImageGL background thread
-
-bool LLImageGLThread::post(const std::function<void()>& func)
-{
- try
- {
- mFunctionQueue.post(func);
- }
- catch (LLThreadSafeQueueInterrupt e)
- {
- return false;
- }
-
- return true;
-}
-
-//post a callback to be executed on the main thread
-
-bool LLImageGLThread::postCallback(const std::function<void()>& callback)
-{
- try
- {
- if (!mCallbackQueue.tryPost(callback))
- {
- mPendingCallbackQ.push(callback);
- }
- }
- catch (LLThreadSafeQueueInterrupt e)
- {
- //thread is closing, drop request
- return false;
- }
-
- return true;
-}
-
-void LLImageGLThread::executeCallbacks()
-{
- LL_PROFILE_ZONE_SCOPED;
- //executed from main thread
- mCallbackQueue.runPending();
-
- while (!mPendingCallbackQ.empty())
- {
- if (mCallbackQueue.tryPost(mPendingCallbackQ.front()))
- {
- mPendingCallbackQ.pop();
- }
- else
- {
- break;
- }
- }
+ ThreadPool::start();
}
void LLImageGLThread::run()
{
+ LL_PROFILE_ZONE_SCOPED;
+ // We must perform setup on this thread before actually servicing our
+ // WorkQueue, likewise cleanup afterwards.
mWindow->makeContextCurrent(mContext);
gGL.init();
- mFunctionQueue.runUntilClose();
+ ThreadPool::run();
gGL.shutdown();
mWindow->destroySharedContext(mContext);
}
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index 8264e4a5f2..ae773bb362 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -37,6 +37,7 @@
#include "llunits.h"
#include "llthreadsafequeue.h"
#include "llrender.h"
+#include "threadpool.h"
#include "workqueue.h"
class LLTextureAtlas ;
@@ -198,6 +199,7 @@ private:
void freePickMask();
LLPointer<LLImageRaw> mSaveData; // used for destroyGL/restoreGL
+ LL::WorkQueue::weak_t mMainQueue;
U8* mPickMask; //downsampled bitmap approximation of alpha channel. NULL if no alpha channel
U16 mPickMaskWidth;
U16 mPickMaskHeight;
@@ -271,7 +273,6 @@ public:
public:
static void initClass(LLWindow* window, S32 num_catagories, BOOL skip_analyze_alpha = false);
- static void updateClass();
static void cleanupClass() ;
private:
@@ -307,34 +308,24 @@ public:
};
-class LLImageGLThread : public LLThread
+class LLImageGLThread : public LLSimpleton<LLImageGLThread>, LL::ThreadPool
{
public:
LLImageGLThread(LLWindow* window);
// post a function to be executed on the LLImageGL background thread
- bool post(const std::function<void()>& func);
-
- //post a callback to be executed on the main thread
- bool postCallback(const std::function<void()>& callback);
-
- void executeCallbacks();
+ template <typename CALLABLE>
+ bool post(CALLABLE&& func)
+ {
+ return getQueue().postIfOpen(std::forward<CALLABLE>(func));
+ }
void run() override;
- // Work Queue for background thread
- LL::WorkQueue mFunctionQueue;
-
- // Work Queue for main thread (run from updateClass)
- LL::WorkQueue mCallbackQueue;
-
+private:
LLWindow* mWindow;
- void* mContext;
+ void* mContext = nullptr;
LLAtomicBool mFinished;
-
- std::queue<std::function<void()>> mPendingCallbackQ;
-
- static LLImageGLThread* sInstance;
};
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index f781ff4110..55c1655d7b 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -308,6 +308,10 @@ if(LL_TESTS)
${BOOST_FIBER_LIBRARY} ${BOOST_CONTEXT_LIBRARY} ${BOOST_SYSTEM_LIBRARY}
${WINDOWS_LIBRARIES})
if(NOT LINUX)
- LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "${test_libs}")
+ if(WINDOWS)
+ LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "imm32;${test_libs}")
+ else(WINDOWS)
+ LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "${test_libs}")
+ endif(WINDOWS)
endif(NOT LINUX)
endif(LL_TESTS)
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index 485d332068..062dd02903 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -46,6 +46,7 @@
#include "llsdutil.h"
#include "llglslshader.h"
#include "llthreadsafequeue.h"
+#include "stringize.h"
// System includes
#include <commdlg.h>
@@ -55,6 +56,10 @@
#include <shellapi.h>
#include <fstream>
#include <Imm.h>
+#include <iomanip>
+#include <future>
+#include <sstream>
+#include <utility> // std::pair
// Require DirectInput version 8
#define DIRECTINPUT_VERSION 0x0800
@@ -78,6 +83,10 @@ const F32 ICON_FLASH_TIME = 0.5f;
#define USER_DEFAULT_SCREEN_DPI 96 // Win7
#endif
+// Claim a couple unused GetMessage() message IDs
+const UINT WM_DUMMY_(WM_USER + 0x0017);
+const UINT WM_POST_FUNCTION_(WM_USER + 0x0018);
+
extern BOOL gDebugWindowProc;
static std::thread::id sWindowThreadId;
@@ -174,23 +183,19 @@ DWORD LLWindowWin32::sWinIMESentenceMode = IME_SMODE_AUTOMATIC;
LLCoordWindow LLWindowWin32::sWinIMEWindowPosition(-1,-1);
// The following class LLWinImm delegates Windows IMM APIs.
-// We need this because some language versions of Windows,
-// e.g., US version of Windows XP, doesn't install IMM32.DLL
-// as a default, and we can't link against imm32.lib statically.
-// I believe DLL loading of this type is best suited to do
-// in a static initialization of a class. What I'm not sure is
-// whether it follows the Linden Conding Standard...
-// See http://wiki.secondlife.com/wiki/Coding_standards#Static_Members
+// It was originally introduced to support US Windows XP, on which we needed
+// to dynamically load IMM32.DLL and use GetProcAddress to resolve its entry
+// points. Now that that's moot, we retain this wrapper only for hooks for
+// metrics.
class LLWinImm
{
public:
- static bool isAvailable() { return sTheInstance.mHImmDll != NULL; }
+ static bool isAvailable() { return true; }
public:
// Wrappers for IMM API.
static BOOL isIME(HKL hkl);
- static HWND getDefaultIMEWnd(HWND hwnd);
static HIMC getContext(HWND hwnd);
static BOOL releaseContext(HWND hwnd, HIMC himc);
static BOOL getOpenStatus(HIMC himc);
@@ -204,236 +209,96 @@ public:
static BOOL setCompositionFont(HIMC himc, LPLOGFONTW logfont);
static BOOL setCandidateWindow(HIMC himc, LPCANDIDATEFORM candidate_form);
static BOOL notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value);
-
-private:
- LLWinImm();
- ~LLWinImm();
-
-private:
- // Pointers to IMM API.
- BOOL (WINAPI *mImmIsIME)(HKL);
- HWND (WINAPI *mImmGetDefaultIMEWnd)(HWND);
- HIMC (WINAPI *mImmGetContext)(HWND);
- BOOL (WINAPI *mImmReleaseContext)(HWND, HIMC);
- BOOL (WINAPI *mImmGetOpenStatus)(HIMC);
- BOOL (WINAPI *mImmSetOpenStatus)(HIMC, BOOL);
- BOOL (WINAPI *mImmGetConversionStatus)(HIMC, LPDWORD, LPDWORD);
- BOOL (WINAPI *mImmSetConversionStatus)(HIMC, DWORD, DWORD);
- BOOL (WINAPI *mImmGetCompostitionWindow)(HIMC, LPCOMPOSITIONFORM);
- BOOL (WINAPI *mImmSetCompostitionWindow)(HIMC, LPCOMPOSITIONFORM);
- LONG (WINAPI *mImmGetCompositionString)(HIMC, DWORD, LPVOID, DWORD);
- BOOL (WINAPI *mImmSetCompositionString)(HIMC, DWORD, LPVOID, DWORD, LPVOID, DWORD);
- BOOL (WINAPI *mImmSetCompositionFont)(HIMC, LPLOGFONTW);
- BOOL (WINAPI *mImmSetCandidateWindow)(HIMC, LPCANDIDATEFORM);
- BOOL (WINAPI *mImmNotifyIME)(HIMC, DWORD, DWORD, DWORD);
-
-private:
- HMODULE mHImmDll;
- static LLWinImm sTheInstance;
};
-LLWinImm LLWinImm::sTheInstance;
-
-LLWinImm::LLWinImm() : mHImmDll(NULL)
-{
- // Check system metrics
- if ( !GetSystemMetrics( SM_IMMENABLED ) )
- return;
-
- mHImmDll = LoadLibraryA("Imm32");
- if (mHImmDll != NULL)
- {
- mImmIsIME = (BOOL (WINAPI *)(HKL)) GetProcAddress(mHImmDll, "ImmIsIME");
- mImmGetDefaultIMEWnd = (HWND (WINAPI *)(HWND)) GetProcAddress(mHImmDll, "ImmGetDefaultIMEWnd");
- mImmGetContext = (HIMC (WINAPI *)(HWND)) GetProcAddress(mHImmDll, "ImmGetContext");
- mImmReleaseContext = (BOOL (WINAPI *)(HWND, HIMC)) GetProcAddress(mHImmDll, "ImmReleaseContext");
- mImmGetOpenStatus = (BOOL (WINAPI *)(HIMC)) GetProcAddress(mHImmDll, "ImmGetOpenStatus");
- mImmSetOpenStatus = (BOOL (WINAPI *)(HIMC, BOOL)) GetProcAddress(mHImmDll, "ImmSetOpenStatus");
- mImmGetConversionStatus = (BOOL (WINAPI *)(HIMC, LPDWORD, LPDWORD)) GetProcAddress(mHImmDll, "ImmGetConversionStatus");
- mImmSetConversionStatus = (BOOL (WINAPI *)(HIMC, DWORD, DWORD)) GetProcAddress(mHImmDll, "ImmSetConversionStatus");
- mImmGetCompostitionWindow = (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM)) GetProcAddress(mHImmDll, "ImmGetCompositionWindow");
- mImmSetCompostitionWindow = (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM)) GetProcAddress(mHImmDll, "ImmSetCompositionWindow");
- mImmGetCompositionString= (LONG (WINAPI *)(HIMC, DWORD, LPVOID, DWORD)) GetProcAddress(mHImmDll, "ImmGetCompositionStringW");
- mImmSetCompositionString= (BOOL (WINAPI *)(HIMC, DWORD, LPVOID, DWORD, LPVOID, DWORD)) GetProcAddress(mHImmDll, "ImmSetCompositionStringW");
- mImmSetCompositionFont = (BOOL (WINAPI *)(HIMC, LPLOGFONTW)) GetProcAddress(mHImmDll, "ImmSetCompositionFontW");
- mImmSetCandidateWindow = (BOOL (WINAPI *)(HIMC, LPCANDIDATEFORM)) GetProcAddress(mHImmDll, "ImmSetCandidateWindow");
- mImmNotifyIME = (BOOL (WINAPI *)(HIMC, DWORD, DWORD, DWORD)) GetProcAddress(mHImmDll, "ImmNotifyIME");
-
- if (mImmIsIME == NULL ||
- mImmGetDefaultIMEWnd == NULL ||
- mImmGetContext == NULL ||
- mImmReleaseContext == NULL ||
- mImmGetOpenStatus == NULL ||
- mImmSetOpenStatus == NULL ||
- mImmGetConversionStatus == NULL ||
- mImmSetConversionStatus == NULL ||
- mImmGetCompostitionWindow == NULL ||
- mImmSetCompostitionWindow == NULL ||
- mImmGetCompositionString == NULL ||
- mImmSetCompositionString == NULL ||
- mImmSetCompositionFont == NULL ||
- mImmSetCandidateWindow == NULL ||
- mImmNotifyIME == NULL)
- {
- // If any of the above API entires are not found, we can't use IMM API.
- // So, turn off the IMM support. We should log some warning message in
- // the case, since it is very unusual; these APIs are available from
- // the beginning, and all versions of IMM32.DLL should have them all.
- // Unfortunately, this code may be executed before initialization of
- // the logging channel (LL_WARNS()), and we can't do it here... Yes, this
- // is one of disadvantages to use static constraction to DLL loading.
- FreeLibrary(mHImmDll);
- mHImmDll = NULL;
-
- // If we unload the library, make sure all the function pointers are cleared
- mImmIsIME = NULL;
- mImmGetDefaultIMEWnd = NULL;
- mImmGetContext = NULL;
- mImmReleaseContext = NULL;
- mImmGetOpenStatus = NULL;
- mImmSetOpenStatus = NULL;
- mImmGetConversionStatus = NULL;
- mImmSetConversionStatus = NULL;
- mImmGetCompostitionWindow = NULL;
- mImmSetCompostitionWindow = NULL;
- mImmGetCompositionString = NULL;
- mImmSetCompositionString = NULL;
- mImmSetCompositionFont = NULL;
- mImmSetCandidateWindow = NULL;
- mImmNotifyIME = NULL;
- }
- }
-}
-
-
// static
BOOL LLWinImm::isIME(HKL hkl)
{
- if ( sTheInstance.mImmIsIME )
- return sTheInstance.mImmIsIME(hkl);
- return FALSE;
+ return ImmIsIME(hkl);
}
// static
HIMC LLWinImm::getContext(HWND hwnd)
{
- if ( sTheInstance.mImmGetContext )
- return sTheInstance.mImmGetContext(hwnd);
- return 0;
+ return ImmGetContext(hwnd);
}
//static
BOOL LLWinImm::releaseContext(HWND hwnd, HIMC himc)
{
- if ( sTheInstance.mImmIsIME )
- return sTheInstance.mImmReleaseContext(hwnd, himc);
- return FALSE;
+ return ImmReleaseContext(hwnd, himc);
}
// static
BOOL LLWinImm::getOpenStatus(HIMC himc)
{
- if ( sTheInstance.mImmGetOpenStatus )
- return sTheInstance.mImmGetOpenStatus(himc);
- return FALSE;
+ return ImmGetOpenStatus(himc);
}
// static
BOOL LLWinImm::setOpenStatus(HIMC himc, BOOL status)
{
- if ( sTheInstance.mImmSetOpenStatus )
- return sTheInstance.mImmSetOpenStatus(himc, status);
- return FALSE;
+ return ImmSetOpenStatus(himc, status);
}
// static
BOOL LLWinImm::getConversionStatus(HIMC himc, LPDWORD conversion, LPDWORD sentence)
{
- if ( sTheInstance.mImmGetConversionStatus )
- return sTheInstance.mImmGetConversionStatus(himc, conversion, sentence);
- return FALSE;
+ return ImmGetConversionStatus(himc, conversion, sentence);
}
// static
BOOL LLWinImm::setConversionStatus(HIMC himc, DWORD conversion, DWORD sentence)
{
- if ( sTheInstance.mImmSetConversionStatus )
- return sTheInstance.mImmSetConversionStatus(himc, conversion, sentence);
- return FALSE;
+ return ImmSetConversionStatus(himc, conversion, sentence);
}
// static
BOOL LLWinImm::getCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form)
{
- if ( sTheInstance.mImmGetCompostitionWindow )
- return sTheInstance.mImmGetCompostitionWindow(himc, form);
- return FALSE;
+ return ImmGetCompositionWindow(himc, form);
}
// static
BOOL LLWinImm::setCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form)
{
- if ( sTheInstance.mImmSetCompostitionWindow )
- return sTheInstance.mImmSetCompostitionWindow(himc, form);
- return FALSE;
+ return ImmSetCompositionWindow(himc, form);
}
// static
LONG LLWinImm::getCompositionString(HIMC himc, DWORD index, LPVOID data, DWORD length)
{
- if ( sTheInstance.mImmGetCompositionString )
- return sTheInstance.mImmGetCompositionString(himc, index, data, length);
- return FALSE;
+ return ImmGetCompositionString(himc, index, data, length);
}
// static
BOOL LLWinImm::setCompositionString(HIMC himc, DWORD index, LPVOID pComp, DWORD compLength, LPVOID pRead, DWORD readLength)
{
- if ( sTheInstance.mImmSetCompositionString )
- return sTheInstance.mImmSetCompositionString(himc, index, pComp, compLength, pRead, readLength);
- return FALSE;
+ return ImmSetCompositionString(himc, index, pComp, compLength, pRead, readLength);
}
// static
BOOL LLWinImm::setCompositionFont(HIMC himc, LPLOGFONTW pFont)
{
- if ( sTheInstance.mImmSetCompositionFont )
- return sTheInstance.mImmSetCompositionFont(himc, pFont);
- return FALSE;
+ return ImmSetCompositionFont(himc, pFont);
}
// static
BOOL LLWinImm::setCandidateWindow(HIMC himc, LPCANDIDATEFORM form)
{
- if ( sTheInstance.mImmSetCandidateWindow )
- return sTheInstance.mImmSetCandidateWindow(himc, form);
- return FALSE;
+ return ImmSetCandidateWindow(himc, form);
}
// static
BOOL LLWinImm::notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value)
{
- if ( sTheInstance.mImmNotifyIME )
- return sTheInstance.mImmNotifyIME(himc, action, index, value);
- return FALSE;
+ return ImmNotifyIME(himc, action, index, value);
}
-
-// ----------------------------------------------------------------------------------------
-LLWinImm::~LLWinImm()
-{
- if (mHImmDll != NULL)
- {
- FreeLibrary(mHImmDll);
- mHImmDll = NULL;
- }
-}
-
-
class LLMonitorInfo
{
public:
@@ -468,6 +333,70 @@ private:
static LLMonitorInfo sMonitorInfo;
+// Thread that owns the Window Handle
+// This whole struct is private to LLWindowWin32, which needs to mess with its
+// members, which is why it's a struct rather than a class. In effect, we make
+// the containing class a friend.
+struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
+{
+ static const int MAX_QUEUE_SIZE = 2048;
+
+ LLThreadSafeQueue<MSG> mMessageQueue;
+
+ LLWindowWin32Thread();
+
+ void run() override;
+
+ /// called by main thread to post work to this window thread
+ template <typename CALLABLE>
+ void post(CALLABLE&& func)
+ {
+ try
+ {
+ getQueue().post(std::forward<CALLABLE>(func));
+ }
+ catch (const LLThreadSafeQueueInterrupt&)
+ {
+ // Shutdown timing is tricky. The main thread can end up trying
+ // to post a cursor position after having closed the WorkQueue.
+ }
+ }
+
+ /**
+ * Like post(), Post() is a way of conveying a single work item to this
+ * thread. Its virtue is that it will definitely be executed "soon" rather
+ * than potentially waiting for the next frame: it uses PostMessage() to
+ * break us out of the window thread's blocked GetMessage() call. It's
+ * more expensive, though, not only from the Windows API latency of
+ * PostMessage() and GetMessage(), but also because it involves heap
+ * allocation and release.
+ *
+ * Require HWND from caller, even though we store an HWND locally.
+ * Otherwise, if our mWindowHandle was accessed from both threads, we'd
+ * have to protect it with a mutex.
+ */
+ template <typename CALLABLE>
+ void Post(HWND windowHandle, CALLABLE&& func)
+ {
+ // Move func to the heap. If we knew FuncType could fit into LPARAM,
+ // we could simply instantiate FuncType and pass it by value. But
+ // since we don't, we must put that on the heap as well as the
+ // internal heap allocation it likely requires to store func.
+ auto ptr = new FuncType(std::move(func));
+ WPARAM wparam{ 0xF1C };
+ LL_DEBUGS("Window") << "PostMessage(" << std::hex << windowHandle
+ << ", " << WM_POST_FUNCTION_
+ << ", " << wparam << std::dec << LL_ENDL;
+ PostMessage(windowHandle, WM_POST_FUNCTION_, wparam, LPARAM(ptr));
+ }
+
+ using FuncType = std::function<void()>;
+ // call GetMessage() and pull enqueue messages for later processing
+ void gatherInput();
+ HWND mWindowHandle = NULL;
+ HDC mhDC = 0;
+};
+
LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
const std::string& title, const std::string& name, S32 x, S32 y, S32 width,
@@ -479,8 +408,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
: LLWindow(callbacks, fullscreen, flags)
{
sMainThreadId = LLThread::currentID();
- mWindowThread = new LLWindowWin32Thread(this);
- mWindowThread->start();
+ mWindowThread = new LLWindowWin32Thread();
//MAINT-516 -- force a load of opengl32.dll just in case windows went sideways
LoadLibrary(L"opengl32.dll");
@@ -551,7 +479,6 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
// Make an instance of our window then define the window class
mhInstance = GetModuleHandle(NULL);
- mWndProc = NULL;
// Init Direct Input - needed for joystick / Spacemouse
@@ -875,7 +802,7 @@ void LLWindowWin32::restore()
// I'm turning off optimizations for this part to be sure code executes as intended
// (it is a straw, but I have no idea why else __try can get overruled)
#pragma optimize("", off)
-bool destroy_window_handler(HWND &hWnd)
+bool destroy_window_handler(HWND hWnd)
{
bool res;
__try
@@ -975,17 +902,13 @@ void LLWindowWin32::close()
// Something killed the window while we were busy destroying gl or handle somehow got broken
LL_WARNS("Window") << "Failed to destroy Window, invalid handle!" << LL_ENDL;
}
- mWindowHandle = NULL;
- mWindowThread->mFinished = true;
});
-
- while (!mWindowThread->isStopped())
- {
- //nudge window thread
- PostMessage(mWindowHandle, WM_USER + 0x0017, 0xB0B0, 0x1337);
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
- }
+ // Even though the above lambda might not yet have run, we've already
+ // bound mWindowHandle into it by value, which should suffice for the
+ // operations we're asking. That's the last time WE should touch it.
+ mWindowHandle = NULL;
+ mWindowThread->close();
}
BOOL LLWindowWin32::isValid()
@@ -1278,51 +1201,7 @@ BOOL LLWindowWin32::switchContext(BOOL fullscreen, const LLCoordScreen& size, BO
<< " Fullscreen: " << mFullscreen
<< LL_ENDL;
- auto oldHandle = mWindowHandle;
-
- //zero out mWindowHandle and mhDC before destroying window so window thread falls back to peekmessage
- mWindowHandle = 0;
- mhDC = 0;
-
- if (oldHandle && !destroy_window_handler(oldHandle))
- {
- LL_WARNS("Window") << "Failed to properly close window before recreating it!" << LL_ENDL;
- }
-
- mWindowHandle = NULL;
- mhDC = 0;
-
- mWindowThread->post(
- [this, window_rect, dw_ex_style, dw_style]()
- {
- mWindowHandle = CreateWindowEx(dw_ex_style,
- mWindowClassName,
- mWindowTitle,
- WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style,
- window_rect.left, // x pos
- window_rect.top, // y pos
- window_rect.right - window_rect.left, // width
- window_rect.bottom - window_rect.top, // height
- NULL,
- NULL,
- mhInstance,
- NULL);
-
- if (mWindowHandle)
- {
- mhDC = GetDC(mWindowHandle);
- }
- }
- );
-
- // HACK wait for above handle to become populated
- // TODO: use a future
- int count = 1024;
- while (!mhDC && count > 0)
- {
- Sleep(10);
- --count;
- }
+ recreateWindow(window_rect, dw_ex_style, dw_style);
if (mWindowHandle)
{
@@ -1650,48 +1529,7 @@ const S32 max_format = (S32)num_formats - 1;
mhDC = 0; // Zero The Device Context
}
- auto oldHandle = mWindowHandle;
- mWindowHandle = NULL;
- mhDC = 0;
-
- // Destroy The Window
- if (oldHandle && !destroy_window_handler(oldHandle))
- {
- LL_WARNS("Window") << "Failed to properly close window!" << LL_ENDL;
- }
-
- mWindowThread->post(
- [this, window_rect, dw_ex_style, dw_style]()
- {
- mWindowHandle = CreateWindowEx(dw_ex_style,
- mWindowClassName,
- mWindowTitle,
- WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style,
- window_rect.left, // x pos
- window_rect.top, // y pos
- window_rect.right - window_rect.left, // width
- window_rect.bottom - window_rect.top, // height
- NULL,
- NULL,
- mhInstance,
- NULL);
-
- if (mWindowHandle)
- {
- mhDC = GetDC(mWindowHandle);
- }
- }
- );
-
- // HACK wait for above handle to become populated
- // TODO: use a future
- int count = 1024;
- while (!mhDC && count > 0)
- {
- PostMessage(oldHandle, WM_USER + 8, 0x1717, 0x3b3b);
- Sleep(10);
- --count;
- }
+ recreateWindow(window_rect, dw_ex_style, dw_style);
if (mWindowHandle)
{
@@ -1828,6 +1666,99 @@ const S32 max_format = (S32)num_formats - 1;
return TRUE;
}
+void LLWindowWin32::recreateWindow(RECT window_rect, DWORD dw_ex_style, DWORD dw_style)
+{
+ auto oldHandle = mWindowHandle;
+
+ // zero out mWindowHandle and mhDC before destroying window so window
+ // thread falls back to peekmessage
+ mWindowHandle = 0;
+ mhDC = 0;
+
+ std::promise<std::pair<HWND, HDC>> promise;
+ // What follows must be done on the window thread.
+ auto window_work =
+ [self=mWindowThread,
+ oldHandle,
+ // bind CreateWindowEx() parameters by value instead of
+ // back-referencing LLWindowWin32 members
+ windowClassName=mWindowClassName,
+ windowTitle=mWindowTitle,
+ hInstance=mhInstance,
+ window_rect,
+ dw_ex_style,
+ dw_style,
+ &promise]
+ ()
+ {
+ LL_DEBUGS("Window") << "recreateWindow(): window_work entry" << LL_ENDL;
+ self->mWindowHandle = 0;
+ self->mhDC = 0;
+
+ // important to call DestroyWindow() from the window thread
+ if (oldHandle && !destroy_window_handler(oldHandle))
+ {
+ LL_WARNS("Window") << "Failed to properly close window before recreating it!"
+ << LL_ENDL;
+ }
+
+ auto handle = CreateWindowEx(dw_ex_style,
+ windowClassName,
+ windowTitle,
+ WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style,
+ window_rect.left, // x pos
+ window_rect.top, // y pos
+ window_rect.right - window_rect.left, // width
+ window_rect.bottom - window_rect.top, // height
+ NULL,
+ NULL,
+ hInstance,
+ NULL);
+
+ if (! handle)
+ {
+ // Failed to create window: clear the variables. This
+ // assignment is valid because we're running on mWindowThread.
+ self->mWindowHandle = NULL;
+ self->mhDC = 0;
+ }
+ else
+ {
+ // Update mWindowThread's own mWindowHandle and mhDC.
+ self->mWindowHandle = handle;
+ self->mhDC = GetDC(handle);
+ }
+
+ // It's important to wake up the future either way.
+ promise.set_value(std::make_pair(self->mWindowHandle, self->mhDC));
+ LL_DEBUGS("Window") << "recreateWindow(): window_work done" << LL_ENDL;
+ };
+ // But how we pass window_work to the window thread depends on whether we
+ // already have a window handle.
+ if (! oldHandle)
+ {
+ // Pass window_work using the WorkQueue: without an existing window
+ // handle, the window thread can't call GetMessage().
+ LL_DEBUGS("Window") << "posting window_work to WorkQueue" << LL_ENDL;
+ mWindowThread->post(window_work);
+ }
+ else
+ {
+ // Pass window_work using PostMessage(). We can still
+ // PostMessage(oldHandle) because oldHandle won't be destroyed until
+ // the window thread has retrieved and executed window_work.
+ LL_DEBUGS("Window") << "posting window_work to message queue" << LL_ENDL;
+ mWindowThread->Post(oldHandle, window_work);
+ }
+
+ auto future = promise.get_future();
+ // This blocks until mWindowThread processes CreateWindowEx() and calls
+ // promise.set_value().
+ auto pair = future.get();
+ mWindowHandle = pair.first;
+ mhDC = pair.second;
+}
+
void* LLWindowWin32::createSharedContext()
{
S32 attribs[] =
@@ -1839,7 +1770,7 @@ void* LLWindowWin32::createSharedContext()
0
};
- HGLRC rc = wglCreateContextAttribsARB(mhDC, mhRC, attribs);
+ HGLRC rc = 0;
bool done = false;
while (!done)
@@ -2181,14 +2112,10 @@ void LLWindowWin32::gatherInput()
}
- if (mWindowThread->mFunctionQueue.size() > 0)
+ if (mWindowThread->getQueue().size())
{
LL_PROFILE_ZONE_NAMED("gi - PostMessage");
- if (mWindowHandle)
- { // post a nonsense user message to wake up the Window Thread in case any functions are pending
- // and no windows events came through this frame
- PostMessage(mWindowHandle, WM_USER + 0x0017, 0xB0B0, 0x1337);
- }
+ kickWindowThread();
}
while (mWindowThread->mMessageQueue.tryPopBack(msg))
@@ -2262,6 +2189,23 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
ASSERT_WINDOW_THREAD();
LL_PROFILE_ZONE_SCOPED;
+ LL_DEBUGS("Window") << "mainWindowProc(" << std::hex << h_wnd
+ << ", " << u_msg
+ << ", " << w_param << ")" << std::dec << LL_ENDL;
+
+ if (u_msg == WM_POST_FUNCTION_)
+ {
+ LL_DEBUGS("Window") << "WM_POST_FUNCTION_" << LL_ENDL;
+ // from LLWindowWin32Thread::Post()
+ // Cast l_param back to the pointer to the heap FuncType
+ // allocated by Post(). Capture in unique_ptr so we'll delete
+ // once we're done with it.
+ std::unique_ptr<LLWindowWin32Thread::FuncType>
+ ptr(reinterpret_cast<LLWindowWin32Thread::FuncType*>(l_param));
+ (*ptr)();
+ return 0;
+ }
+
// Ignore clicks not originated in the client area, i.e. mouse-up events not preceded with a WM_LBUTTONDOWN.
// This helps prevent avatar walking after maximizing the window by double-clicking the title bar.
static bool sHandleLeftMouseUp = true;
@@ -2276,17 +2220,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
if (NULL != window_imp)
{
- // Has user provided their own window callback?
- if (NULL != window_imp->mWndProc)
- {
- LL_PROFILE_ZONE_NAMED("mwp - WndProc");
- if (!window_imp->mWndProc(h_wnd, u_msg, w_param, l_param))
- {
- // user has handled window message
- return 0;
- }
- }
-
// Juggle to make sure we can get negative positions for when
// mouse is outside window.
LLCoordWindow window_coord((S32)(S16)LOWORD(l_param), (S32)(S16)HIWORD(l_param));
@@ -4567,38 +4500,87 @@ std::vector<std::string> LLWindowWin32::getDynamicFallbackFontList()
#endif // LL_WINDOWS
-inline LLWindowWin32Thread::LLWindowWin32Thread(LLWindowWin32* window)
- : LLThread("Window Thread"),
- mWindow(window),
- mFunctionQueue(MAX_QUEUE_SIZE)
+inline LLWindowWin32::LLWindowWin32Thread::LLWindowWin32Thread()
+ : ThreadPool("Window Thread", 1, MAX_QUEUE_SIZE)
{
-
+ ThreadPool::start();
}
-inline void LLWindowWin32Thread::run()
+/**
+ * LogChange is to log changes in status while trying to avoid spamming the
+ * log with repeated messages, especially in a tight loop. It refuses to log
+ * a continuous run of identical messages, but logs every time the message
+ * changes. (It will happily spam when messages quickly bounce back and
+ * forth.)
+ */
+class LogChange
{
- sWindowThreadId = getID();
- while (!mFinished)
+public:
+ LogChange(const std::string& tag):
+ mTag(tag)
+ {}
+
+ template <typename... Items>
+ void always(Items&&... items)
{
- LL_PROFILE_ZONE_SCOPED;
+ // This odd construct ensures that the stringize() call is only
+ // executed if DEBUG logging is enabled for the passed tag.
+ LL_DEBUGS(mTag.c_str());
+ log(LL_CONT, stringize(std::forward<Items>(items)...));
+ LL_ENDL;
+ }
+ template <typename... Items>
+ void onChange(Items&&... items)
+ {
+ LL_DEBUGS(mTag.c_str());
+ auto str = stringize(std::forward<Items>(items)...);
+ if (str != mPrev)
+ {
+ log(LL_CONT, str);
+ }
+ LL_ENDL;
+ }
+
+private:
+ void log(std::ostream& out, const std::string& message)
+ {
+ mPrev = message;
+ out << message;
+ }
+ std::string mTag;
+ std::string mPrev;
+};
+
+void LLWindowWin32::LLWindowWin32Thread::run()
+{
+ sWindowThreadId = std::this_thread::get_id();
+ LogChange logger("Window");
+
+ while (! getQueue().done())
+ {
+ LL_PROFILE_ZONE_SCOPED;
- if (mWindow && mWindow->mWindowHandle != 0)
+ if (mWindowHandle != 0)
{
MSG msg;
BOOL status;
- if (mWindow->mhDC == 0)
+ if (mhDC == 0)
{
LL_PROFILE_ZONE_NAMED("w32t - PeekMessage");
- status = PeekMessage(&msg, mWindow->mWindowHandle, 0, 0, PM_REMOVE);
+ logger.onChange("PeekMessage(", std::hex, mWindowHandle, ")");
+ status = PeekMessage(&msg, mWindowHandle, 0, 0, PM_REMOVE);
}
else
{
LL_PROFILE_ZONE_NAMED("w32t - GetMessage");
- status = GetMessage(&msg, mWindow->mWindowHandle, 0, 0);
+ logger.always("GetMessage(", std::hex, mWindowHandle, ")");
+ status = GetMessage(&msg, mWindowHandle, 0, 0);
}
if (status > 0)
{
+ logger.always("got MSG (", std::hex, msg.hwnd, ", ", msg.message,
+ ", ", msg.wParam, ")");
TranslateMessage(&msg);
DispatchMessage(&msg);
@@ -4608,28 +4590,21 @@ inline void LLWindowWin32Thread::run()
{
LL_PROFILE_ZONE_NAMED("w32t - Function Queue");
+ logger.onChange("runPending()");
//process any pending functions
- std::function<void()> curFunc;
- while (mFunctionQueue.tryPopBack(curFunc))
- {
- curFunc();
- }
+ getQueue().runPending();
}
#if 0
{
LL_PROFILE_ZONE_NAMED("w32t - Sleep");
+ logger.always("sleep(1)");
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
#endif
}
}
-void LLWindowWin32Thread::post(const std::function<void()>& func)
-{
- mFunctionQueue.pushFront(func);
-}
-
void LLWindowWin32::post(const std::function<void()>& func)
{
mFunctionQueue.pushFront(func);
@@ -4640,3 +4615,19 @@ void LLWindowWin32::postMouseButtonEvent(const std::function<void()>& func)
mMouseQueue.pushFront(func);
}
+void LLWindowWin32::kickWindowThread(HWND windowHandle)
+{
+ if (! windowHandle)
+ windowHandle = mWindowHandle;
+ if (windowHandle)
+ {
+ // post a nonsense user message to wake up the Window Thread in
+ // case any functions are pending and no windows events came
+ // through this frame
+ WPARAM wparam{ 0xB0B0 };
+ LL_DEBUGS("Window") << "PostMessage(" << std::hex << windowHandle
+ << ", " << WM_DUMMY_
+ << ", " << wparam << ")" << std::dec << LL_ENDL;
+ PostMessage(windowHandle, WM_DUMMY_, wparam, 0x1337);
+ }
+}
diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h
index d082080807..b02815e990 100644
--- a/indra/llwindow/llwindowwin32.h
+++ b/indra/llwindow/llwindowwin32.h
@@ -36,44 +36,12 @@
#include "llthread.h"
#include "llthreadsafequeue.h"
#include "llmutex.h"
+#include "workqueue.h"
// Hack for async host by name
#define LL_WM_HOST_RESOLVED (WM_APP + 1)
typedef void (*LLW32MsgCallback)(const MSG &msg);
-class LLWindowWin32;
-
-// Thread that owns the Window Handle
-class LLWindowWin32Thread : public LLThread
-{
-public:
- class Message
- {
- public:
- LRESULT mMsg;
- };
-
- static const int MAX_QUEUE_SIZE = 2048;
-
- LLThreadSafeQueue<MSG> mMessageQueue;
- LLThreadSafeQueue<std::function<void()>> mFunctionQueue;
-
- bool mFinished = false;
-
- LLWindowWin32Thread(LLWindowWin32* window);
-
- void run() override;
-
- void post(const std::function<void()>& func);
-
-private:
-
- // call PeekMessage and pull enqueue messages for later processing
- void gatherInput();
- LLWindowWin32* mWindow = nullptr;
-
-};
-
class LLWindowWin32 : public LLWindow
{
public:
@@ -218,7 +186,6 @@ protected:
HGLRC mhRC = 0; // OpenGL rendering context
HDC mhDC = 0; // Windows Device context handle
HINSTANCE mhInstance; // handle to application instance
- WNDPROC mWndProc; // user-installable window proc
RECT mOldMouseClip; // Screen rect to which the mouse cursor was globally constrained before we changed it in clipMouse()
WPARAM mLastSizeWParam;
F32 mOverrideAspectRatio;
@@ -270,14 +237,16 @@ protected:
BOOL mMouseVanish;
- LLWindowWin32Thread* mWindowThread = nullptr;
- LLThreadSafeQueue<std::function<void()>> mFunctionQueue;
- LLThreadSafeQueue<std::function<void()>> mMouseQueue;
- void post(const std::function<void()>& func);
- void postMouseButtonEvent(const std::function<void()>& func);
+ struct LLWindowWin32Thread;
+ LLWindowWin32Thread* mWindowThread = nullptr;
+ LLThreadSafeQueue<std::function<void()>> mFunctionQueue;
+ LLThreadSafeQueue<std::function<void()>> mMouseQueue;
+ void post(const std::function<void()>& func);
+ void postMouseButtonEvent(const std::function<void()>& func);
+ void recreateWindow(RECT window_rect, DWORD dw_ex_style, DWORD dw_style);
+ void kickWindowThread(HWND windowHandle=0);
friend class LLWindowManager;
- friend class LLWindowWin32Thread;
};
class LLSplashScreenWin32 : public LLSplashScreen
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 9b636e5e5d..5f085bb9ad 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -393,7 +393,6 @@ set(viewer_SOURCE_FILES
llloginhandler.cpp
lllogininstance.cpp
llmachineid.cpp
- llmainlooprepeater.cpp
llmanip.cpp
llmaniprotate.cpp
llmanipscale.cpp
@@ -1032,7 +1031,6 @@ set(viewer_HEADER_FILES
llloginhandler.h
lllogininstance.h
llmachineid.h
- llmainlooprepeater.h
llmanip.h
llmaniprotate.h
llmanipscale.h
@@ -1604,6 +1602,7 @@ if (WINDOWS)
${WINDOWS_LIBRARIES}
comdlg32
dxguid
+ imm32
kernel32
odbc32
odbccp32
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 2d821b7451..058da4b66d 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -3871,6 +3871,17 @@
<key>Value</key>
<integer>1</integer>
</map>
+ <key>MainWorkTime</key>
+ <map>
+ <key>Comment</key>
+ <string>Max time per frame devoted to mainloop work queue (in milliseconds)</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.1</real>
+ </map>
<key>QueueInventoryFetchTimeout</key>
<map>
<key>Comment</key>
@@ -12667,6 +12678,20 @@
<key>Value</key>
<string />
</map>
+ <key>ThreadPoolSizes</key>
+ <map>
+ <key>Comment</key>
+ <string>Map of size overrides for specific thread pools.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>LLSD</string>
+ <key>Value</key>
+ <map>
+ <key>General</key>
+ <integer>4</integer>
+ </map>
+ </map>
<key>ThrottleBandwidthKBPS</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 13670b7f13..d6c6eb6a98 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -233,11 +233,12 @@
#include "llavatariconctrl.h"
#include "llgroupiconctrl.h"
#include "llviewerassetstats.h"
+#include "workqueue.h"
+using namespace LL;
// Include for security api initialization
#include "llsecapi.h"
#include "llmachineid.h"
-#include "llmainlooprepeater.h"
#include "llcleanup.h"
#include "llcoproceduremanager.h"
@@ -366,6 +367,10 @@ BOOL gLogoutInProgress = FALSE;
BOOL gSimulateMemLeak = FALSE;
+// We don't want anyone, especially threads working on the graphics pipeline,
+// to have to block due to this WorkQueue being full.
+WorkQueue gMainloopWork("mainloop", 1024*1024);
+
////////////////////////////////////////////////////////////
// Internal globals... that should be removed.
static std::string gArgs;
@@ -381,42 +386,6 @@ static std::string gLaunchFileOnQuit;
// Used on Win32 for other apps to identify our window (eg, win_setup)
const char* const VIEWER_WINDOW_CLASSNAME = "Second Life";
-//-- LLDeferredTaskList ------------------------------------------------------
-
-/**
- * A list of deferred tasks.
- *
- * We sometimes need to defer execution of some code until the viewer gets idle,
- * e.g. removing an inventory item from within notifyObservers() may not work out.
- *
- * Tasks added to this list will be executed in the next LLAppViewer::idle() iteration.
- * All tasks are executed only once.
- */
-class LLDeferredTaskList: public LLSingleton<LLDeferredTaskList>
-{
- LLSINGLETON_EMPTY_CTOR(LLDeferredTaskList);
- LOG_CLASS(LLDeferredTaskList);
-
- friend class LLAppViewer;
- typedef boost::signals2::signal<void()> signal_t;
-
- void addTask(const signal_t::slot_type& cb)
- {
- mSignal.connect(cb);
- }
-
- void run()
- {
- if (!mSignal.empty())
- {
- mSignal();
- mSignal.disconnect_all_slots();
- }
- }
-
- signal_t mSignal;
-};
-
//----------------------------------------------------------------------------
// List of entries from strings.xml to always replace
@@ -973,9 +942,6 @@ bool LLAppViewer::init()
}
LL_INFOS("InitInfo") << "Cache initialization is done." << LL_ENDL ;
- // Initialize the repeater service.
- LLMainLoopRepeater::instance().start();
-
// Initialize event recorder
LLViewerEventRecorder::createInstance();
@@ -2218,8 +2184,6 @@ bool LLAppViewer::cleanup()
SUBSYSTEM_CLEANUP(LLProxy);
LLCore::LLHttp::cleanup();
- LLMainLoopRepeater::instance().stop();
-
ll_close_fail_log();
LLError::LLCallStacks::cleanup();
@@ -4551,7 +4515,7 @@ bool LLAppViewer::initCache()
void LLAppViewer::addOnIdleCallback(const boost::function<void()>& cb)
{
- LLDeferredTaskList::instance().addTask(cb);
+ gMainloopWork.post(cb);
}
void LLAppViewer::loadKeyBindings()
@@ -4949,11 +4913,24 @@ void LLAppViewer::idle()
LLNotificationsUI::LLToast::updateClass();
LLSmoothInterpolation::updateInterpolants();
LLMortician::updateClass();
- LLImageGL::updateClass();
LLFilePickerThread::clearDead(); //calls LLFilePickerThread::notify()
LLDirPickerThread::clearDead();
F32 dt_raw = idle_timer.getElapsedTimeAndResetF32();
+ // Service the WorkQueue we use for replies from worker threads.
+ // Use function statics for the timeslice setting so we only have to fetch
+ // and convert MainWorkTime once.
+ static F32 MainWorkTimeRaw = gSavedSettings.getF32("MainWorkTime");
+ static F32Milliseconds MainWorkTimeMs(MainWorkTimeRaw);
+ // MainWorkTime is specified in fractional milliseconds, but std::chrono
+ // uses integer representations. What if we want less than a microsecond?
+ // Use nanoseconds. We're very sure we will never need to specify a
+ // MainWorkTime that would be larger than we could express in
+ // std::chrono::nanoseconds.
+ static std::chrono::nanoseconds MainWorkTimeNanoSec{
+ std::chrono::nanoseconds::rep(MainWorkTimeMs.value() * 1000000)};
+ gMainloopWork.runFor(MainWorkTimeNanoSec);
+
// Cap out-of-control frame times
// Too low because in menus, swapping, debugger, etc.
// Too high because idle called with no objects in view, etc.
@@ -5326,9 +5303,6 @@ void LLAppViewer::idle()
}
}
- // Execute deferred tasks.
- LLDeferredTaskList::instance().run();
-
// Handle shutdown process, for example,
// wait for floaters to close, send quit message,
// forcibly quit if it has taken too long
diff --git a/indra/newview/llmainlooprepeater.cpp b/indra/newview/llmainlooprepeater.cpp
deleted file mode 100644
index 6736e9a950..0000000000
--- a/indra/newview/llmainlooprepeater.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/**
- * @file llmachineid.cpp
- * @brief retrieves unique machine ids
- *
- * $LicenseInfo:firstyear=2009&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "llviewerprecompiledheaders.h"
-#include "llapr.h"
-#include "llevents.h"
-#include "llmainlooprepeater.h"
-
-
-
-// LLMainLoopRepeater
-//-----------------------------------------------------------------------------
-
-
-LLMainLoopRepeater::LLMainLoopRepeater(void):
- mQueue(0)
-{
- ; // No op.
-}
-
-
-void LLMainLoopRepeater::start(void)
-{
- if(mQueue != 0) return;
-
- mQueue = new LLThreadSafeQueue<LLSD>(1024);
- mMainLoopConnection = LLEventPumps::instance().
- obtain("mainloop").listen(LLEventPump::inventName(), boost::bind(&LLMainLoopRepeater::onMainLoop, this, _1));
- mRepeaterConnection = LLEventPumps::instance().
- obtain("mainlooprepeater").listen(LLEventPump::inventName(), boost::bind(&LLMainLoopRepeater::onMessage, this, _1));
-}
-
-
-void LLMainLoopRepeater::stop(void)
-{
- mMainLoopConnection.release();
- mRepeaterConnection.release();
-
- delete mQueue;
- mQueue = 0;
-}
-
-
-bool LLMainLoopRepeater::onMainLoop(LLSD const &)
-{
- LLSD message;
- while(mQueue->tryPopBack(message)) {
- std::string pump = message["pump"].asString();
- if(pump.length() == 0 ) continue; // No pump.
- LLEventPumps::instance().obtain(pump).post(message["payload"]);
- }
- return false;
-}
-
-
-bool LLMainLoopRepeater::onMessage(LLSD const & event)
-{
- try {
- mQueue->pushFront(event);
- } catch(LLThreadSafeQueueError & e) {
- LL_WARNS() << "could not repeat message (" << e.what() << ")" <<
- event.asString() << LL_ENDL;
- }
- return false;
-}
diff --git a/indra/newview/llmainlooprepeater.h b/indra/newview/llmainlooprepeater.h
deleted file mode 100644
index 2ec3a74e4a..0000000000
--- a/indra/newview/llmainlooprepeater.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * @file llmainlooprepeater.h
- * @brief a service for repeating messages on the main loop.
- *
- * $LicenseInfo:firstyear=2010&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#ifndef LL_LLMAINLOOPREPEATER_H
-#define LL_LLMAINLOOPREPEATER_H
-
-
-#include "llsd.h"
-#include "llthreadsafequeue.h"
-
-
-//
-// A service which creates the pump 'mainlooprepeater' to which any thread can
-// post a message that will be re-posted on the main loop.
-//
-// The posted message should contain two map elements: pump and payload. The
-// pump value is a string naming the pump to which the message should be
-// re-posted. The payload value is what will be posted to the designated pump.
-//
-class LLMainLoopRepeater:
- public LLSingleton<LLMainLoopRepeater>
-{
- LLSINGLETON(LLMainLoopRepeater);
-public:
- // Start the repeater service.
- void start(void);
-
- // Stop the repeater service.
- void stop(void);
-
-private:
- LLTempBoundListener mMainLoopConnection;
- LLTempBoundListener mRepeaterConnection;
- LLThreadSafeQueue<LLSD> * mQueue;
-
- bool onMainLoop(LLSD const &);
- bool onMessage(LLSD const & event);
-};
-
-
-#endif
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index c178f27db7..adfa1b0c10 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -35,6 +35,7 @@
#else
# include <sys/stat.h> // mkdir()
#endif
+#include <memory> // std::unique_ptr
#include "llviewermedia_streamingaudio.h"
#include "llaudioengine.h"
@@ -205,6 +206,9 @@
#include "llstacktrace.h"
+#include "threadpool.h"
+
+
#if LL_WINDOWS
#include "lldxhardware.h"
#endif
@@ -252,9 +256,10 @@ static bool mBenefitsSuccessfullyInit = false;
const F32 STATE_AGENT_WAIT_TIMEOUT = 240; //seconds
-boost::scoped_ptr<LLEventPump> LLStartUp::sStateWatcher(new LLEventStream("StartupState"));
-boost::scoped_ptr<LLStartupListener> LLStartUp::sListener(new LLStartupListener());
-boost::scoped_ptr<LLViewerStats::PhaseMap> LLStartUp::sPhases(new LLViewerStats::PhaseMap);
+std::unique_ptr<LLEventPump> LLStartUp::sStateWatcher(new LLEventStream("StartupState"));
+std::unique_ptr<LLStartupListener> LLStartUp::sListener(new LLStartupListener());
+std::unique_ptr<LLViewerStats::PhaseMap> LLStartUp::sPhases(new LLViewerStats::PhaseMap);
+std::unique_ptr<LL::ThreadPool> gGeneralThreadPool;
//
// local function declaration
@@ -1489,6 +1494,17 @@ bool idle_startup()
gAgentCamera.resetCamera();
display_startup();
+ // start up the ThreadPool we'll use for textures et al.
+ LLSD poolSizes{ gSavedSettings.getLLSD("ThreadPoolSizes") };
+ LLSD sizeSpec{ poolSizes["General"] };
+ LLSD::Integer poolSize{ sizeSpec.isInteger()? sizeSpec.asInteger() : 3 };
+ LL_DEBUGS("ThreadPool") << "Instantiating General pool with "
+ << poolSize << " threads" << LL_ENDL;
+ // We don't want anyone, especially the main thread, to have to block
+ // due to this ThreadPool being full.
+ gGeneralThreadPool.reset(new LL::ThreadPool("General", poolSize, 1024*1024));
+ gGeneralThreadPool->start();
+
// Initialize global class data needed for surfaces (i.e. textures)
LL_DEBUGS("AppInit") << "Initializing sky..." << LL_ENDL;
// Initialize all of the viewer object classes for the first time (doing things like texture fetches.
diff --git a/indra/newview/llstartup.h b/indra/newview/llstartup.h
index 116aeb36a7..fe8e215f76 100644
--- a/indra/newview/llstartup.h
+++ b/indra/newview/llstartup.h
@@ -27,7 +27,7 @@
#ifndef LL_LLSTARTUP_H
#define LL_LLSTARTUP_H
-#include <boost/scoped_ptr.hpp>
+#include <memory> // unique_ptr
class LLViewerTexture ;
class LLEventPump;
@@ -130,9 +130,9 @@ private:
static std::string startupStateToString(EStartupState state);
static EStartupState gStartupState; // Do not set directly, use LLStartup::setStartupState
- static boost::scoped_ptr<LLEventPump> sStateWatcher;
- static boost::scoped_ptr<LLStartupListener> sListener;
- static boost::scoped_ptr<LLViewerStats::PhaseMap> sPhases;
+ static std::unique_ptr<LLEventPump> sStateWatcher;
+ static std::unique_ptr<LLStartupListener> sListener;
+ static std::unique_ptr<LLViewerStats::PhaseMap> sPhases;
};
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index e20c64e48e..9f76543647 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -260,7 +260,9 @@ static bool handleAnisotropicChanged(const LLSD& newvalue)
static bool handleVSyncChanged(const LLSD& newvalue)
{
+#if LL_WINDOWS
gViewerWindow->getWindow()->toggleVSync(newvalue.asBoolean());
+#endif
return true;
}
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index e6ac701644..f932acd48c 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -679,6 +679,9 @@ void LLViewerTexture::init(bool firstinit)
mVolumeList[LLRender::LIGHT_TEX].clear();
mVolumeList[LLRender::SCULPT_TEX].clear();
+
+ mMainQueue = LL::WorkQueue::getInstance("mainloop");
+ mImageQueue = LL::WorkQueue::getInstance("LLImageGL");
}
//virtual
@@ -1622,17 +1625,26 @@ void LLViewerFetchedTexture::scheduleCreateTexture()
{
mNeedsCreateTexture = TRUE;
#if LL_WINDOWS //flip to 0 to revert to single-threaded OpenGL texture uploads
- if (!LLImageGLThread::sInstance->post([this]()
- {
- //actually create the texture on a background thread
- createTexture();
- LLImageGLThread::sInstance->postCallback([this]()
- {
- //finalize on main thread
- postCreateTexture();
- unref();
- });
- }))
+ auto mainq = mMainQueue.lock();
+ if (mainq)
+ {
+ mainq->postTo(
+ mImageQueue,
+ // work to be done on LLImageGL worker thread
+ [this]()
+ {
+ //actually create the texture on a background thread
+ createTexture();
+ },
+ // callback to be run on main thread
+ [this]()
+ {
+ //finalize on main thread
+ postCreateTexture();
+ unref();
+ });
+ }
+ else
#endif
{
gTextureList.mCreateTextureList.insert(this);
diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h
index f9f1bfef44..4cd4c7cd39 100644
--- a/indra/newview/llviewertexture.h
+++ b/indra/newview/llviewertexture.h
@@ -35,6 +35,7 @@
#include "llrender.h"
#include "llmetricperformancetester.h"
#include "httpcommon.h"
+#include "workqueue.h"
#include <map>
#include <list>
@@ -213,6 +214,9 @@ protected:
//do not use LLPointer here.
LLViewerMediaTexture* mParcelMedia ;
+ LL::WorkQueue::weak_t mMainQueue;
+ LL::WorkQueue::weak_t mImageQueue;
+
static F32 sTexelPixelRatio;
public:
static const U32 sCurrentFileVersion;