summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan "Geenz" Goodman <geenz@lindenlab.com>2026-08-25 12:56:22 -0400
committerGitHub <noreply@github.com>2026-08-25 12:56:22 -0400
commit2e9c387fee881022aec13d5c85f901539683c18f (patch)
treed2cc89444771d96871b3126c9462f072dc059e32
parent92289533c1b95bc0fb2cdcc1217ae539485ea244 (diff)
parenta402fd4da91bbce03340e174505c06feede65866 (diff)
Merge branch 'develop' into project/lua_editor
-rw-r--r--.github/workflows/build.yaml2
-rw-r--r--.github/workflows/stale.yaml2
-rw-r--r--indra/llappearance/lltexlayer.cpp11
-rw-r--r--indra/llcommon/llprocess.cpp19
-rw-r--r--indra/llcommon/llprocessor.cpp89
-rw-r--r--indra/llcommon/llprocessor.h3
-rw-r--r--indra/llcommon/llsys.cpp35
-rw-r--r--indra/llcommon/llsys.h8
-rw-r--r--indra/llcommon/llwatchdog.h5
-rw-r--r--indra/llcorehttp/httpstats.cpp3
-rw-r--r--indra/llcorehttp/httpstats.h12
-rw-r--r--indra/llmessage/CMakeLists.txt2
-rw-r--r--indra/llmessage/lltemplatemessagebuilder.cpp93
-rw-r--r--indra/llmessage/llzerocode.h211
-rw-r--r--indra/llmessage/message.cpp152
-rw-r--r--indra/llmessage/message.h1
-rw-r--r--indra/llmessage/tests/llzerocode_test.cpp253
-rw-r--r--indra/llplugin/llpluginprocessparent.cpp11
-rw-r--r--indra/llrender/llimagegl.cpp6
-rw-r--r--indra/llrender/llvertexbuffer.cpp9
-rw-r--r--indra/llui/lltexteditor.cpp13
-rw-r--r--indra/llwebrtc/llwebrtc.cpp148
-rw-r--r--indra/llwebrtc/llwebrtc_impl.h21
-rw-r--r--indra/llxml/llcontrol.cpp38
-rw-r--r--indra/llxml/llcontrol.h1
-rw-r--r--indra/newview/VIEWER_VERSION.txt2
-rw-r--r--indra/newview/app_settings/settings.xml2
-rw-r--r--indra/newview/llappviewer.cpp13
-rw-r--r--indra/newview/llavataractions.cpp11
-rw-r--r--indra/newview/llavataractions.h6
-rw-r--r--indra/newview/llcommandlineparser.cpp5
-rw-r--r--indra/newview/llinventorybridge.cpp8
-rw-r--r--indra/newview/lltexturefetch.cpp18
-rw-r--r--indra/newview/llviewercontrollistener.cpp35
-rw-r--r--indra/newview/llviewercontrollistener.h1
-rw-r--r--indra/newview/llviewermenu.cpp43
-rw-r--r--indra/newview/llviewernetwork.h11
-rw-r--r--indra/newview/llviewerparcelmgr.cpp8
-rw-r--r--indra/newview/llviewerregion.cpp13
-rw-r--r--indra/newview/llviewerstats.cpp15
-rw-r--r--indra/newview/llvoiceclient.cpp4
-rw-r--r--indra/newview/llvoicewebrtc.cpp124
-rw-r--r--indra/newview/llvoicewebrtc.h20
-rw-r--r--indra/newview/llwindowlistener.cpp90
-rw-r--r--indra/newview/llwindowlistener.h4
-rw-r--r--indra/newview/tests/llslurl_test.cpp2
-rw-r--r--indra/newview/tests/llviewernetwork_test.cpp2
47 files changed, 1232 insertions, 353 deletions
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 6aa85f9fc7..d19c2f1c02 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -183,7 +183,7 @@ jobs:
- name: Setup .NET for Velopack
if: (runner.os == 'Windows' || runner.os == 'macOS') && (github.event.inputs.installer_type || 'velopack') == 'velopack'
- uses: actions/setup-dotnet@v4
+ uses: actions/setup-dotnet@v6
with:
dotnet-version: '9.0.x'
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
index edfe71b693..e1a88a8c25 100644
--- a/.github/workflows/stale.yaml
+++ b/.github/workflows/stale.yaml
@@ -12,7 +12,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- - uses: actions/stale@v10
+ - uses: actions/stale@v11
id: stale
with:
stale-pr-message: This pull request is stale because it has been open 30 days with no activity. Remove stale label or comment or it will be closed in 7 days
diff --git a/indra/llappearance/lltexlayer.cpp b/indra/llappearance/lltexlayer.cpp
index 5a02dfe716..45f3e8dcd2 100644
--- a/indra/llappearance/lltexlayer.cpp
+++ b/indra/llappearance/lltexlayer.cpp
@@ -1430,6 +1430,15 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
// We should only be doing this when we believe something has changed with respect to the user's appearance.
{
LL_DEBUGS("Morph") << "gl alpha cache of morph mask not found, doing readback: " << getName() << LL_ENDL;
+
+ // Replace the cached mask without leaking its old allocation.
+ alpha_cache_t::iterator cached = mAlphaCache.find(cache_index);
+ if (cached != mAlphaCache.end())
+ {
+ ll_aligned_free_32(cached->second);
+ mAlphaCache.erase(cached);
+ }
+
// clear out a slot if we have filled our cache
S32 max_cache_entries = getTexLayerSet()->getAvatarAppearance()->isSelf() ? 4 : 1;
while ((S32)mAlphaCache.size() >= max_cache_entries)
@@ -1466,6 +1475,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
U8* temp = (U8*)ll_aligned_malloc_32(mem_size << 2); // allocate same size, but RGBA
if (!temp)
{
+ ll_aligned_free_32(alpha_data);
LLError::LLUserWarningMsg::showOutOfMemory();
LL_ERRS() << "Failed to allocate temporary memory for morph texture readback: " << (S32)(mem_size << 2) << LL_ENDL;
return;
@@ -1508,6 +1518,7 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
U8* temp_data = (U8*)ll_aligned_malloc_32(mem_size * TEMP_BYTES_PER_PIXEL);
if (!temp_data)
{
+ ll_aligned_free_32(alpha_data);
LLError::LLUserWarningMsg::showOutOfMemory();
LL_ERRS() << "Failed to allocate temporary memory for morph texture: " << (S32)(mem_size * TEMP_BYTES_PER_PIXEL) << LL_ENDL;
return;
diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp
index a71589a2bf..e43f386af2 100644
--- a/indra/llcommon/llprocess.cpp
+++ b/indra/llcommon/llprocess.cpp
@@ -701,8 +701,25 @@ void LLProcess::launch(const LLSDOrParams& params)
else
stdio.err = nullptr; // inherit
- // Build executable path
+ // Build executable path.
+ // Resolve bare executable names (i.e. "outleap-agent") through the
+ // environment's PATH
boost::filesystem::path executable_path(params.executable());
+ if (!executable_path.has_parent_path())
+ {
+ boost::filesystem::path resolved =
+ bp::environment::find_executable(executable_path);
+ if (!resolved.empty())
+ {
+ executable_path = resolved;
+ }
+ else
+ {
+ LL_WARNS("LLProcess") << "Could not locate '" << params.executable()
+ << "' on PATH -- launch will likely fail" << LL_ENDL;
+ // Let bp::process try to produce an error.
+ }
+ }
// In Boost.Process v2, error_code cannot be passed as an initializer.
// Use the throwing overload and catch the exception instead.
diff --git a/indra/llcommon/llprocessor.cpp b/indra/llcommon/llprocessor.cpp
index 9d0ef45bee..456f2b02d4 100644
--- a/indra/llcommon/llprocessor.cpp
+++ b/indra/llcommon/llprocessor.cpp
@@ -123,6 +123,9 @@ namespace
eSSE4_1_Features = 38,
eSSE4_2_Features = 39,
eSSE4a_Features = 40,
+ eAVX_Features = 41,
+ eAVX2_Features = 42,
+ eAVX512F_Features = 43,
};
const char* cpu_feature_names[] =
@@ -170,6 +173,9 @@ namespace
"SSE4.1 Instructions",
"SSE4.2 Instructions",
"SSE4a Instructions",
+ "AVX Instructions", // 41
+ "AVX2 Instructions", // 42
+ "AVX-512F Instructions", // 43
};
std::string intel_CPUFamilyName(int composed_family)
@@ -283,6 +289,21 @@ public:
return hasExtension(cpu_feature_names[eSSE4a_Features]);
}
+ bool hasAVX() const
+ {
+ return hasExtension(cpu_feature_names[eAVX_Features]);
+ }
+
+ bool hasAVX2() const
+ {
+ return hasExtension(cpu_feature_names[eAVX2_Features]);
+ }
+
+ bool hasAVX512F() const
+ {
+ return hasExtension(cpu_feature_names[eAVX512F_Features]);
+ }
+
bool hasAltivec() const
{
return hasExtension("Altivec");
@@ -572,6 +593,12 @@ private:
setExtension(cpu_feature_names[eSSE4_2_Features]);
}
+ // AVX: CPUID leaf 1, ECX bit 28, OSXSAVE + XCR0[1:2] enabled
+ if ((cpu_info[2] & 0x18000000) == 0x18000000 && ((_xgetbv(0) & 0x6) == 0x6))
+ {
+ setExtension(cpu_feature_names[eAVX_Features]);
+ }
+
unsigned int feature_info = (unsigned int) cpu_info[3];
for(unsigned int index = 0, bit = 1; index < eSSE3_Features; ++index, bit <<= 1)
{
@@ -581,6 +608,27 @@ private:
}
}
}
+ else if (i == 7)
+ {
+ // AVX2/AVX-512* require AVX to be usable (OSXSAVE + XCR0 state)
+ if (hasExtension(cpu_feature_names[eAVX_Features]))
+ {
+ int cpu_info7[4] = { -1 };
+ __cpuidex(cpu_info7, 7, 0);
+ // AVX2: EBX bit 5
+ if (cpu_info7[1] & 0x20)
+ {
+ setExtension(cpu_feature_names[eAVX2_Features]);
+ }
+
+ // AVX-512F: EBX bit 16; ZMM state in XCR0 (bits 5-7)
+ const unsigned long long xcr0 = _xgetbv(0);
+ if ((cpu_info7[1] & 0x10000) && ((xcr0 & 0xE6) == 0xE6))
+ {
+ setExtension(cpu_feature_names[eAVX512F_Features]);
+ }
+ }
+ }
}
// Calling __cpuid with 0x80000000 as the InfoType argument
@@ -779,6 +827,29 @@ private:
// Not supposed to happen?
setExtension(cpu_feature_names[eSSE4a_Features]);
}
+ if (cpu_features_str.find(" AVX1.0 ") != std::string::npos)
+ {
+ setExtension(cpu_feature_names[eAVX_Features]);
+ }
+
+ // AVX2 and AVX-512F are reported in machdep.cpu.leaf7_features on macOS
+ char cpu_leaf7_features[1024];
+ len = sizeof(cpu_leaf7_features);
+ memset(cpu_leaf7_features, 0, len);
+ sysctlbyname("machdep.cpu.leaf7_features", (void*)cpu_leaf7_features, &len, NULL, 0);
+
+ std::string cpu_leaf7_str(cpu_leaf7_features);
+ cpu_leaf7_str = " " + cpu_leaf7_str + " ";
+
+ if (cpu_leaf7_str.find(" AVX2 ") != std::string::npos)
+ {
+ setExtension(cpu_feature_names[eAVX2_Features]);
+ }
+
+ if (cpu_leaf7_str.find(" AVX512F ") != std::string::npos)
+ {
+ setExtension(cpu_feature_names[eAVX512F_Features]);
+ }
}
};
@@ -946,6 +1017,21 @@ private:
setExtension(cpu_feature_names[eSSE4a_Features]);
}
+ if (flags.find(" avx ") != std::string::npos)
+ {
+ setExtension(cpu_feature_names[eAVX_Features]);
+ }
+
+ if (flags.find(" avx2 ") != std::string::npos)
+ {
+ setExtension(cpu_feature_names[eAVX2_Features]);
+ }
+
+ if (flags.find(" avx512f ") != std::string::npos)
+ {
+ setExtension(cpu_feature_names[eAVX512F_Features]);
+ }
+
# endif // LL_X86
}
@@ -1010,6 +1096,9 @@ bool LLProcessorInfo::hasSSE3S() const { return mImpl->hasSSE3S(); }
bool LLProcessorInfo::hasSSE41() const { return mImpl->hasSSE41(); }
bool LLProcessorInfo::hasSSE42() const { return mImpl->hasSSE42(); }
bool LLProcessorInfo::hasSSE4a() const { return mImpl->hasSSE4a(); }
+bool LLProcessorInfo::hasAVX() const { return mImpl->hasAVX(); }
+bool LLProcessorInfo::hasAVX2() const { return mImpl->hasAVX2(); }
+bool LLProcessorInfo::hasAVX512F() const { return mImpl->hasAVX512F(); }
bool LLProcessorInfo::hasAltivec() const { return mImpl->hasAltivec(); }
std::string LLProcessorInfo::getCPUFamilyName() const { return mImpl->getCPUFamilyName(); }
std::string LLProcessorInfo::getCPUBrandName() const { return mImpl->getCPUBrandName(); }
diff --git a/indra/llcommon/llprocessor.h b/indra/llcommon/llprocessor.h
index b955f7f55c..da93a531f4 100644
--- a/indra/llcommon/llprocessor.h
+++ b/indra/llcommon/llprocessor.h
@@ -46,6 +46,9 @@ public:
bool hasSSE41() const;
bool hasSSE42() const;
bool hasSSE4a() const;
+ bool hasAVX() const;
+ bool hasAVX2() const;
+ bool hasAVX512F() const;
bool hasAltivec() const;
std::string getCPUFamilyName() const;
std::string getCPUBrandName() const;
diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp
index b1b281001e..bc38b72004 100644
--- a/indra/llcommon/llsys.cpp
+++ b/indra/llcommon/llsys.cpp
@@ -580,6 +580,9 @@ LLCPUInfo::LLCPUInfo()
mHasSSE41 = proc.hasSSE41();
mHasSSE42 = proc.hasSSE42();
mHasSSE4a = proc.hasSSE4a();
+ mHasAVX = proc.hasAVX();
+ mHasAVX2 = proc.hasAVX2();
+ mHasAVX512F = proc.hasAVX512F();
mHasAltivec = proc.hasAltivec();
mCPUMHz = (F64)proc.getCPUFrequency();
mFamily = proc.getCPUFamilyName();
@@ -621,6 +624,18 @@ LLCPUInfo::LLCPUInfo()
{
mSSEVersions.append("4a");
}
+ if (mHasAVX)
+ {
+ mSIMDVersions.append("AVX");
+ }
+ if (mHasAVX2)
+ {
+ mSIMDVersions.append("AVX2");
+ }
+ if (mHasAVX512F)
+ {
+ mSIMDVersions.append("AVX-512F");
+ }
}
bool LLCPUInfo::hasAltivec() const
@@ -663,6 +678,21 @@ bool LLCPUInfo::hasSSE4a() const
return mHasSSE4a;
}
+bool LLCPUInfo::hasAVX() const
+{
+ return mHasAVX;
+}
+
+bool LLCPUInfo::hasAVX2() const
+{
+ return mHasAVX2;
+}
+
+bool LLCPUInfo::hasAVX512F() const
+{
+ return mHasAVX512F;
+}
+
F64 LLCPUInfo::getMHz() const
{
return mCPUMHz;
@@ -678,6 +708,11 @@ const LLSD& LLCPUInfo::getSSEVersions() const
return mSSEVersions;
}
+const LLSD& LLCPUInfo::getSIMDVersions() const
+{
+ return mSIMDVersions;
+}
+
void LLCPUInfo::stream(std::ostream& s) const
{
// gather machine information.
diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h
index 0abbe047ad..aeba5563ba 100644
--- a/indra/llcommon/llsys.h
+++ b/indra/llcommon/llsys.h
@@ -81,6 +81,7 @@ public:
std::string getCPUString() const;
const LLSD& getSSEVersions() const;
+ const LLSD& getSIMDVersions() const;
bool hasAltivec() const;
bool hasSSE() const;
@@ -90,6 +91,9 @@ public:
bool hasSSE41() const;
bool hasSSE42() const;
bool hasSSE4a() const;
+ bool hasAVX() const;
+ bool hasAVX2() const;
+ bool hasAVX512F() const;
F64 getMHz() const;
// Family is "AMD Duron" or "Intel Pentium Pro"
@@ -103,11 +107,15 @@ private:
bool mHasSSE41;
bool mHasSSE42;
bool mHasSSE4a;
+ bool mHasAVX;
+ bool mHasAVX2;
+ bool mHasAVX512F;
bool mHasAltivec;
F64 mCPUMHz;
std::string mFamily;
std::string mCPUString;
LLSD mSSEVersions;
+ LLSD mSIMDVersions;
};
//=============================================================================
diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h
index a3a51113b0..9386bd6bb3 100644
--- a/indra/llcommon/llwatchdog.h
+++ b/indra/llcommon/llwatchdog.h
@@ -93,6 +93,11 @@ public:
LLWatchdog();
~LLWatchdog();
+ LLWatchdog(const LLWatchdog&) = delete;
+ LLWatchdog(LLWatchdog&&) = delete;
+ LLWatchdog& operator=(const LLWatchdog&) = delete;
+ LLWatchdog& operator=(LLWatchdog&&) = delete;
+
// Add an entry to the watchdog.
void add(LLWatchdogEntry* e);
void remove(LLWatchdogEntry* e);
diff --git a/indra/llcorehttp/httpstats.cpp b/indra/llcorehttp/httpstats.cpp
index dc2ac35c71..f64752e91e 100644
--- a/indra/llcorehttp/httpstats.cpp
+++ b/indra/llcorehttp/httpstats.cpp
@@ -41,6 +41,7 @@ HTTPStats::~HTTPStats()
void HTTPStats::resetStats()
{
+ std::lock_guard<std::mutex> lock(mStatsMutex);
mResutCodes.clear();
mDataDown.reset();
mDataUp.reset();
@@ -50,6 +51,7 @@ void HTTPStats::resetStats()
void HTTPStats::recordResultCode(S32 code)
{
+ std::lock_guard<std::mutex> lock(mStatsMutex);
std::map<S32, S32>::iterator it;
it = mResutCodes.find(code);
@@ -86,6 +88,7 @@ namespace
void HTTPStats::dumpStats()
{
+ std::lock_guard<std::mutex> lock(mStatsMutex);
std::stringstream out;
out << "HTTP DATA SUMMARY" << std::endl;
diff --git a/indra/llcorehttp/httpstats.h b/indra/llcorehttp/httpstats.h
index 5c0f26d34e..08a537cda7 100644
--- a/indra/llcorehttp/httpstats.h
+++ b/indra/llcorehttp/httpstats.h
@@ -47,17 +47,23 @@ namespace LLCore
void recordDataDown(size_t bytes)
{
+ std::lock_guard<std::mutex> lock(mStatsMutex);
mDataDown.push((F32)bytes);
}
void recordDataUp(size_t bytes)
{
+ std::lock_guard<std::mutex> lock(mStatsMutex);
mDataUp.push((F32)bytes);
}
- void recordHTTPRequest() { ++mRequests; }
+ void recordHTTPRequest()
+ {
+ std::lock_guard<std::mutex> lock(mStatsMutex);
+ ++mRequests;
+ }
- void recordResultCode(S32 code);
+ void recordResultCode(S32 code); // http thread
void dumpStats();
private:
@@ -67,6 +73,8 @@ namespace LLCore
S32 mRequests;
std::map<S32, S32> mResutCodes;
+
+ std::mutex mStatsMutex;
};
diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt
index f143ee18cf..2306c377ba 100644
--- a/indra/llmessage/CMakeLists.txt
+++ b/indra/llmessage/CMakeLists.txt
@@ -165,6 +165,7 @@ set(llmessage_HEADER_FILES
llxfer_mem.h
llxfer_vfile.h
llxorcipher.h
+ llzerocode.h
machine.h
mean_collision_data.h
message.h
@@ -228,6 +229,7 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(llservicebuilder "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lltemplatemessagebuilder "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llxfer_file "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llzerocode "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llxorcipher "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(message "" "${test_libs}")
endif (LL_TESTS)
diff --git a/indra/llmessage/lltemplatemessagebuilder.cpp b/indra/llmessage/lltemplatemessagebuilder.cpp
index 758d6d343f..c4ff63bea4 100644
--- a/indra/llmessage/lltemplatemessagebuilder.cpp
+++ b/indra/llmessage/lltemplatemessagebuilder.cpp
@@ -29,6 +29,7 @@
#include "lltemplatemessagebuilder.h"
#include "llmessagetemplate.h"
+#include "llzerocode.h"
#include "llmath.h"
#include "llquaternion.h"
#include "u64.h"
@@ -492,93 +493,29 @@ void LLTemplateMessageBuilder::addUUID(const char *varname, const LLUUID& uuid)
addData(varname, uuid.mData, MVT_LLUUID, sizeof(uuid.mData));
}
-static S32 zero_code(U8 **data, U32 *data_size)
+void LLTemplateMessageBuilder::compressMessage(U8*& buf_ptr, U32& buffer_length)
{
- // Encoded send buffer needs to be slightly larger since the zero
- // coding can potentially increase the size of the send data.
- static U8 encodedSendBuffer[2 * MAX_BUFFER_SIZE];
-
- S32 count = *data_size;
-
- S32 net_gain = 0;
- U8 num_zeroes = 0;
-
- U8 *inptr = (U8 *)*data;
- U8 *outptr = (U8 *)encodedSendBuffer;
-
-// skip the packet id field
-
- for (U32 ii = 0; ii < LL_PACKET_ID_SIZE ; ++ii)
- {
- count--;
- *outptr++ = *inptr++;
- }
-
-// build encoded packet, keeping track of net size gain
-
-// sequential zero bytes are encoded as 0 [U8 count]
-// with 0 0 [count] representing wrap (>256 zeroes)
-
- while (count--)
+ if(ME_ZEROCODED != mCurrentSMessageTemplate->getEncoding())
{
- if (!(*inptr)) // in a zero count
- {
- if (num_zeroes)
- {
- if (++num_zeroes > 254)
- {
- *outptr++ = num_zeroes;
- num_zeroes = 0;
- }
- net_gain--; // subseqent zeroes save one
- }
- else
- {
- *outptr++ = 0;
- net_gain++; // starting a zero count adds one
- num_zeroes = 1;
- }
- inptr++;
- }
- else
- {
- if (num_zeroes)
- {
- *outptr++ = num_zeroes;
- num_zeroes = 0;
- }
- *outptr++ = *inptr++;
- }
+ return;
}
- if (num_zeroes)
- {
- *outptr++ = num_zeroes;
- }
+ // Encoded send buffer needs to be slightly larger since the zero
+ // coding can potentially increase the size of the send data.
+ static U8 encodedSendBuffer[2 * MAX_BUFFER_SIZE];
- if (net_gain < 0)
+ S32 encoded_size = LLZeroCode::encode(buf_ptr, buffer_length,
+ encodedSendBuffer, sizeof(encodedSendBuffer),
+ LL_PACKET_ID_SIZE);
+ if (encoded_size >= 0)
{
// TODO: babbage: reinstate stat collecting...
//mCompressedPacketsOut++;
- //mUncompressedBytesOut += *data_size;
-
- *data = encodedSendBuffer;
- *data_size += net_gain;
- encodedSendBuffer[0] |= LL_ZERO_CODE_FLAG; // set the head bit to indicate zero coding
-
- //mCompressedBytesOut += *data_size;
-
- }
- //mTotalBytesOut += *data_size;
+ //mUncompressedBytesOut += buffer_length;
+ //mCompressedBytesOut += encoded_size;
- return(net_gain);
-}
-
-void LLTemplateMessageBuilder::compressMessage(U8*& buf_ptr, U32& buffer_length)
-{
- if(ME_ZEROCODED == mCurrentSMessageTemplate->getEncoding())
- {
- zero_code(&buf_ptr, &buffer_length);
+ buf_ptr = encodedSendBuffer;
+ buffer_length = (U32)encoded_size;
}
}
diff --git a/indra/llmessage/llzerocode.h b/indra/llmessage/llzerocode.h
new file mode 100644
index 0000000000..7246c9d946
--- /dev/null
+++ b/indra/llmessage/llzerocode.h
@@ -0,0 +1,211 @@
+/**
+ * @file llzerocode.h
+ * @brief Zero-code run-length compression used by the LLMessageSystem UDP protocol.
+ *
+ * $LicenseInfo:firstyear=2001&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_LLZEROCODE_H
+#define LL_LLZEROCODE_H
+
+#include <cstring>
+
+#include "stdtypes.h"
+
+// Zero-coding compresses runs of zero bytes in a packet body, leaving the
+// first header_size bytes of the buffer (the packet header - flags,
+// sequence number, offset, etc.) untouched aside from the flag bit below.
+//
+// Runs of zero bytes in the body are replaced by a two-byte token:
+// 0x00 N - represents N zero bytes, for N in 1..254
+// 0x00 0x00 N - represents (255 + N) zero bytes (wrap/overflow case,
+// produced by decode()'s wire format but never emitted
+// by encode(), which instead starts a fresh 0x00 token
+// every 255 zero bytes)
+namespace LLZeroCode
+{
+ // High bit of the first header byte: set by encode() and cleared by
+ // decode() to indicate whether the body that follows is zero-coded.
+ const U8 FLAG = 0x80;
+
+ // Zero-codes src (src_size bytes, the first header_size of which are the
+ // packet header and are copied verbatim) into dst.
+ //
+ // dst_capacity must be at least 2 * src_size: a pathological body of
+ // isolated zero bytes can nearly double in size when encoded.
+ //
+ // Returns the encoded size (with FLAG set in dst[0]) if doing so made the
+ // packet smaller. Returns -1 if compression would not help (or the
+ // arguments are invalid), in which case dst is left untouched and the
+ // caller should keep using the original, uncompressed buffer.
+ inline S32 encode(const U8* src, U32 src_size, U8* dst, U32 dst_capacity, U32 header_size)
+ {
+ if (src_size < header_size || dst_capacity < 2 * src_size)
+ {
+ return -1;
+ }
+
+ S32 count = (S32)(src_size - header_size);
+ S32 net_gain = 0;
+ U8 num_zeroes = 0;
+
+ const U8* inptr = src;
+ U8* outptr = dst;
+
+ // copy the header verbatim
+ for (U32 ii = 0; ii < header_size; ++ii)
+ {
+ *outptr++ = *inptr++;
+ }
+
+ // sequential zero bytes are encoded as 0 [U8 count]; a run longer
+ // than 254 bytes is split into consecutive 0 [U8 count] tokens.
+ while (count--)
+ {
+ if (!(*inptr)) // in a zero count
+ {
+ if (num_zeroes)
+ {
+ if (++num_zeroes > 254)
+ {
+ *outptr++ = num_zeroes;
+ num_zeroes = 0;
+ }
+ net_gain--; // subsequent zeroes save one
+ }
+ else
+ {
+ *outptr++ = 0;
+ net_gain++; // starting a zero count adds one
+ num_zeroes = 1;
+ }
+ inptr++;
+ }
+ else
+ {
+ if (num_zeroes)
+ {
+ *outptr++ = num_zeroes;
+ num_zeroes = 0;
+ }
+ *outptr++ = *inptr++;
+ }
+ }
+
+ if (num_zeroes)
+ {
+ *outptr++ = num_zeroes;
+ }
+
+ if (net_gain >= 0)
+ {
+ // compression did not shrink the packet; caller should keep the original
+ return -1;
+ }
+
+ dst[0] |= FLAG;
+ return (S32)src_size + net_gain;
+ }
+
+ // Expands a zero-coded src (src_size bytes) into dst.
+ //
+ // If FLAG is not set in src[0], the body is not zero-coded: no work is
+ // done and the function returns 0.
+ //
+ // On success, returns the number of bytes written to dst (always includes
+ // the header_size header bytes, copied verbatim except for FLAG being
+ // cleared from dst[0]).
+ //
+ // If expansion would write past dst_capacity - which only a malformed or
+ // malicious packet should cause - decoding is aborted, *overflow is set
+ // true, and the returned size reflects however much (if anything) was
+ // salvaged; the caller should treat the packet as invalid.
+ inline U32 decode(const U8* src, U32 src_size, U8* dst, U32 dst_capacity, U32 header_size, bool& overflow)
+ {
+ overflow = false;
+
+ if (src_size < header_size || !(src[0] & FLAG))
+ {
+ return 0;
+ }
+
+ S32 count = (S32)(src_size - header_size);
+
+ const U8* inptr = src;
+ U8* outptr = dst;
+
+ for (U32 ii = 0; ii < header_size; ++ii)
+ {
+ *outptr++ = *inptr++;
+ }
+ dst[0] &= ~FLAG;
+
+ // reconstruct the body: a 0x00 byte starts a run; the byte(s) that
+ // follow give its length (see the wire format described above).
+ while (count--)
+ {
+ if (outptr > &dst[dst_capacity - 1])
+ {
+ overflow = true;
+ outptr = dst;
+ break;
+ }
+
+ if (!((*outptr++ = *inptr++)))
+ {
+ while ((count--) && (!(*inptr)))
+ {
+ if (outptr > &dst[dst_capacity - 256])
+ {
+ overflow = true;
+ outptr = dst;
+ count = -1;
+ break;
+ }
+ *outptr++ = *inptr++;
+ memset(outptr, 0, 255);
+ outptr += 255;
+ }
+
+ if (count < 0)
+ {
+ break;
+ }
+ else
+ {
+ if (outptr > &dst[dst_capacity - (*inptr)])
+ {
+ overflow = true;
+ outptr = dst;
+ }
+ memset(outptr, 0, (*inptr) - 1);
+ outptr += ((*inptr) - 1);
+ inptr++;
+ }
+ }
+ }
+
+ return (U32)(outptr - dst);
+ }
+}
+
+#endif // LL_LLZEROCODE_H
diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp
index db7405f93f..71210f7535 100644
--- a/indra/llmessage/message.cpp
+++ b/indra/llmessage/message.cpp
@@ -67,6 +67,7 @@
#include "llsdmessagereader.h"
#include "llsdserialize.h"
#include "llstring.h"
+#include "llzerocode.h"
#include "lltransfermanager.h"
#include "lluuid.h"
#include "llxfermanager.h"
@@ -3060,85 +3061,6 @@ U32 LLMessageSystem::getListenPort( void ) const
return mPort;
}
-// TODO: babbage: remove this horror!
-S32 LLMessageSystem::zeroCodeAdjustCurrentSendTotal()
-{
- if(mMessageBuilder == mLLSDMessageBuilder)
- {
- // babbage: don't compress LLSD messages, so delta is 0
- return 0;
- }
-
- if (! mMessageBuilder->isBuilt())
- {
- mSendSize = mMessageBuilder->buildMessage(
- mSendBuffer,
- MAX_BUFFER_SIZE,
- 0);
- }
- // TODO: babbage: remove this horror
- mMessageBuilder->setBuilt(false);
-
- S32 count = mSendSize;
-
- S32 net_gain = 0;
- U8 num_zeroes = 0;
-
- U8 *inptr = (U8 *)mSendBuffer;
-
-// skip the packet id field
-
- for (U32 ii = 0; ii < LL_PACKET_ID_SIZE; ++ii)
- {
- count--;
- inptr++;
- }
-
-// don't actually build, just test
-
-// sequential zero bytes are encoded as 0 [U8 count]
-// with 0 0 [count] representing wrap (>256 zeroes)
-
- while (count--)
- {
- if (!(*inptr)) // in a zero count
- {
- if (num_zeroes)
- {
- if (++num_zeroes > 254)
- {
- num_zeroes = 0;
- }
- net_gain--; // subseqent zeroes save one
- }
- else
- {
- net_gain++; // starting a zero count adds one
- num_zeroes = 1;
- }
- inptr++;
- }
- else
- {
- if (num_zeroes)
- {
- num_zeroes = 0;
- }
- inptr++;
- }
- }
- if (net_gain < 0)
- {
- return net_gain;
- }
- else
- {
- return 0;
- }
-}
-
-
-
S32 LLMessageSystem::zeroCodeExpand(U8** data, S32* data_size)
{
if ((*data_size ) < LL_MINIMUM_VALID_PACKET_SIZE)
@@ -3159,74 +3081,18 @@ S32 LLMessageSystem::zeroCodeExpand(U8** data, S32* data_size)
mCompressedPacketsIn++;
mCompressedBytesIn += *data_size;
- *data[0] &= (~LL_ZERO_CODE_FLAG);
-
- S32 count = (*data_size);
-
- U8 *inptr = (U8 *)*data;
- U8 *outptr = (U8 *)mEncodedRecvBuffer;
-
-// skip the packet id field
-
- for (U32 ii = 0; ii < LL_PACKET_ID_SIZE; ++ii)
- {
- count--;
- *outptr++ = *inptr++;
- }
-
-// reconstruct encoded packet, keeping track of net size gain
-
-// sequential zero bytes are encoded as 0 [U8 count]
-// with 0 0 [count] representing wrap (>256 zeroes)
-
- while (count--)
+ bool overflow = false;
+ U32 decoded_size = LLZeroCode::decode(*data, (U32)*data_size,
+ mEncodedRecvBuffer, sizeof(mEncodedRecvBuffer),
+ LL_PACKET_ID_SIZE, overflow);
+ if (overflow)
{
- if (outptr > (&mEncodedRecvBuffer[MAX_BUFFER_SIZE-1]))
- {
- LL_WARNS("Messaging") << "attempt to write past reasonable encoded buffer size 1" << LL_ENDL;
- callExceptionFunc(MX_WROTE_PAST_BUFFER_SIZE);
- outptr = mEncodedRecvBuffer;
- break;
- }
- if (!((*outptr++ = *inptr++)))
- {
- while (((count--)) && (!(*inptr)))
- {
- *outptr++ = *inptr++;
- if (outptr > (&mEncodedRecvBuffer[MAX_BUFFER_SIZE-256]))
- {
- LL_WARNS("Messaging") << "attempt to write past reasonable encoded buffer size 2" << LL_ENDL;
- callExceptionFunc(MX_WROTE_PAST_BUFFER_SIZE);
- outptr = mEncodedRecvBuffer;
- count = -1;
- break;
- }
- memset(outptr,0,255);
- outptr += 255;
- }
-
- if (count < 0)
- {
- break;
- }
-
- else
- {
- if (outptr > (&mEncodedRecvBuffer[MAX_BUFFER_SIZE-(*inptr)]))
- {
- LL_WARNS("Messaging") << "attempt to write past reasonable encoded buffer size 3" << LL_ENDL;
- callExceptionFunc(MX_WROTE_PAST_BUFFER_SIZE);
- outptr = mEncodedRecvBuffer;
- }
- memset(outptr,0,(*inptr) - 1);
- outptr += ((*inptr) - 1);
- inptr++;
- }
- }
+ LL_WARNS("Messaging") << "attempt to write past reasonable encoded buffer size" << LL_ENDL;
+ callExceptionFunc(MX_WROTE_PAST_BUFFER_SIZE);
}
*data = mEncodedRecvBuffer;
- *data_size = (S32)(outptr - mEncodedRecvBuffer);
+ *data_size = (S32)decoded_size;
mUncompressedBytesIn += *data_size;
return(in_size);
diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h
index e23a67c04a..2e19e567e6 100644
--- a/indra/llmessage/message.h
+++ b/indra/llmessage/message.h
@@ -604,7 +604,6 @@ public:
//void buildMessage();
S32 zeroCodeExpand(U8 **data, S32 *data_size);
- S32 zeroCodeAdjustCurrentSendTotal();
// Uses ping-based retry
S32 sendReliable(const LLHost &host);
diff --git a/indra/llmessage/tests/llzerocode_test.cpp b/indra/llmessage/tests/llzerocode_test.cpp
new file mode 100644
index 0000000000..1aba46149d
--- /dev/null
+++ b/indra/llmessage/tests/llzerocode_test.cpp
@@ -0,0 +1,253 @@
+/**
+ * @file llzerocode_test.cpp
+ * @brief LLZeroCode test cases.
+ *
+ * $LicenseInfo:firstyear=2026&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 "linden_common.h"
+
+#include "../llzerocode.h"
+
+#include "../test/lltut.h"
+
+#include <vector>
+
+namespace tut
+{
+ struct zerocode_data
+ {
+ };
+ typedef test_group<zerocode_data> zerocode_test;
+ typedef zerocode_test::object zerocode_object;
+ tut::zerocode_test zerocode_testcase("LLZeroCode");
+
+ // Builds header_size bytes of header (with header[0] == header0) followed by body.
+ static std::vector<U8> makeBuffer(U32 header_size, U8 header0, const std::vector<U8>& body)
+ {
+ std::vector<U8> buf(header_size, 0);
+ if (header_size)
+ {
+ buf[0] = header0;
+ }
+ buf.insert(buf.end(), body.begin(), body.end());
+ return buf;
+ }
+
+ // Runs src through encode() then decode(), and (when compression is expected to help)
+ // asserts the round trip reproduces src exactly, including untouched header bits other
+ // than the zero-code flag. When compression is not expected to help, asserts encode()
+ // refuses it.
+ static void ensureRoundTrip(const char* msg, U32 header_size, U8 header0,
+ const std::vector<U8>& body, bool expect_compressed)
+ {
+ std::vector<U8> src = makeBuffer(header_size, header0, body);
+ std::vector<U8> enc(2 * src.size() + 16, 0xAA);
+
+ S32 enc_size = LLZeroCode::encode(src.data(), (U32)src.size(),
+ enc.data(), (U32)enc.size(), header_size);
+
+ if (!expect_compressed)
+ {
+ ensure(std::string(msg) + ": encode should refuse (no benefit)", enc_size < 0);
+ return;
+ }
+
+ ensure(std::string(msg) + ": encode should succeed", enc_size >= 0);
+ ensure(std::string(msg) + ": encoded size should be smaller", (U32)enc_size < src.size());
+ ensure(std::string(msg) + ": FLAG should be set on encoded output", (enc[0] & LLZeroCode::FLAG) != 0);
+ if (header_size > 0)
+ {
+ ensure_equals(std::string(msg) + ": non-flag header bits preserved on encode",
+ (U8)(enc[0] & ~LLZeroCode::FLAG), (U8)(header0 & ~LLZeroCode::FLAG));
+ }
+
+ std::vector<U8> dec(src.size() + 16, 0xBB);
+ bool overflow = false;
+ U32 dec_size = LLZeroCode::decode(enc.data(), (U32)enc_size,
+ dec.data(), (U32)dec.size(), header_size, overflow);
+ ensure(std::string(msg) + ": decode should not overflow", !overflow);
+ ensure_equals(std::string(msg) + ": decoded size should match original", dec_size, (U32)src.size());
+ ensure(std::string(msg) + ": decoded bytes should match original",
+ memcmp(dec.data(), src.data(), src.size()) == 0);
+ }
+
+ // Basic mixed body; header carries an unrelated flag bit (0x40) that must survive untouched.
+ template<> template<>
+ void zerocode_object::test<1>()
+ {
+ ensureRoundTrip("mixed body", 6, 0x40,
+ {0,0,0,5,0,0,0,0,0,0,7,8,9,0,0}, true);
+ }
+
+ // A body with no zero bytes at all cannot benefit from zero-coding.
+ template<> template<>
+ void zerocode_object::test<2>()
+ {
+ ensureRoundTrip("no zero bytes", 1, 0x00, {1,2,3,4,5,6,7,8,9}, false);
+ }
+
+ // Isolated zero-byte runs of length 1 or 2 cost as much or more than they save
+ // (marker + terminator == 2 bytes), so encode must refuse them.
+ template<> template<>
+ void zerocode_object::test<3>()
+ {
+ ensureRoundTrip("isolated single zero", 1, 0x00, {5, 0}, false);
+ ensureRoundTrip("isolated double zero", 1, 0x00, {5, 0, 0, 9}, false);
+ }
+
+ // A long enough zero run followed by other data pays off.
+ template<> template<>
+ void zerocode_object::test<4>()
+ {
+ std::vector<U8> body(20, 0);
+ body.push_back(99);
+ ensureRoundTrip("long zero run", 1, 0x00, body, true);
+ }
+
+ // Wrap boundary: runs are split into chunks of at most 255 zero bytes each
+ // (encode never emits the doubled-0x00 wire form; see test<7> for that).
+ // A run only shrinks the packet once it is longer than 2 bytes.
+ template<> template<>
+ void zerocode_object::test<5>()
+ {
+ static const U32 lengths[] = {1, 2, 253, 254, 255, 256, 257, 300, 509, 510, 511, 1000};
+ for (U32 n : lengths)
+ {
+ std::vector<U8> body(n, 0);
+ body.push_back(42); // trailing nonzero byte
+ ensureRoundTrip("wrap boundary (with trailing byte)", 1, 0x00, body, n > 2);
+ }
+ }
+
+ // Same boundary check, but with the zero run flushed at end-of-buffer
+ // (no trailing nonzero byte to force the terminator write mid-loop).
+ template<> template<>
+ void zerocode_object::test<6>()
+ {
+ static const U32 lengths[] = {1, 2, 254, 255, 256, 300};
+ for (U32 n : lengths)
+ {
+ std::vector<U8> body(n, 0);
+ ensureRoundTrip("wrap boundary (end-of-buffer flush)", 1, 0x00, body, n > 2);
+ }
+ }
+
+ // decode() must also accept the "0x00 0x00 N" doubled-marker wire format
+ // (worth +255 zero bytes per extra marker) for compatibility with any
+ // encoder other than this one's chunking strategy, even though encode()
+ // itself never emits it.
+ template<> template<>
+ void zerocode_object::test<7>()
+ {
+ // header (1 byte, FLAG set) + [0x00 marker][0x00 extra-wrap-marker][terminator=5]
+ // decoded body length = 1 (marker) + 1 (extra marker) + 255 (memset) + (5 - 1) = 261
+ std::vector<U8> encoded = {(U8)LLZeroCode::FLAG, 0x00, 0x00, 0x05};
+ std::vector<U8> dec(1024, 0xDD);
+ bool overflow = false;
+ U32 dec_size = LLZeroCode::decode(encoded.data(), (U32)encoded.size(),
+ dec.data(), (U32)dec.size(), 1, overflow);
+ ensure("wrap format: no overflow", !overflow);
+ ensure_equals("wrap format: decoded size", dec_size, (U32)262);
+ ensure_equals("wrap format: FLAG cleared on header", dec[0], (U8)0x00);
+ for (U32 i = 1; i < dec_size; ++i)
+ {
+ ensure_equals("wrap format: decoded byte is zero", dec[i], (U8)0);
+ }
+ }
+
+ // decode() is a no-op (returns 0, does not touch dst) when FLAG is not set.
+ template<> template<>
+ void zerocode_object::test<8>()
+ {
+ std::vector<U8> src = makeBuffer(6, 0x00, {1,2,3,0,0,0});
+ std::vector<U8> dec(64, 0xCC);
+ bool overflow = false;
+ U32 dec_size = LLZeroCode::decode(src.data(), (U32)src.size(),
+ dec.data(), (U32)dec.size(), 6, overflow);
+ ensure_equals("not zero-coded: decode returns 0", dec_size, (U32)0);
+ ensure("not zero-coded: no overflow", !overflow);
+ }
+
+ // encode() refuses to write into an undersized destination buffer.
+ template<> template<>
+ void zerocode_object::test<9>()
+ {
+ std::vector<U8> body(50, 0);
+ std::vector<U8> src = makeBuffer(1, 0, body);
+ std::vector<U8> enc(src.size(), 0); // smaller than the required 2 * src_size
+ S32 r = LLZeroCode::encode(src.data(), (U32)src.size(), enc.data(), (U32)enc.size(), 1);
+ ensure("encode: capacity guard rejects undersized dst", r < 0);
+ }
+
+ // decode() reports overflow (rather than writing out of bounds) when dst is too small,
+ // whether the destination is smaller than a single byte's worth of headroom...
+ template<> template<>
+ void zerocode_object::test<10>()
+ {
+ std::vector<U8> body(500, 0);
+ std::vector<U8> src = makeBuffer(1, 0, body);
+ std::vector<U8> enc(2 * src.size() + 16, 0);
+ S32 enc_size = LLZeroCode::encode(src.data(), (U32)src.size(), enc.data(), (U32)enc.size(), 1);
+ ensure("decode overflow setup: encode succeeded", enc_size >= 0);
+
+ std::vector<U8> dec(1, 0); // capacity == header_size exactly
+ bool overflow = false;
+ U32 dec_size = LLZeroCode::decode(enc.data(), (U32)enc_size,
+ dec.data(), (U32)dec.size(), 1, overflow);
+ ensure("decode overflow (minimal capacity): overflow flagged", overflow);
+ ensure("decode overflow (minimal capacity): size bounded by capacity", dec_size <= dec.size());
+ }
+
+ // ...or comfortably larger than 256 bytes but still short of the true decoded size.
+ template<> template<>
+ void zerocode_object::test<11>()
+ {
+ std::vector<U8> body(500, 0);
+ std::vector<U8> src = makeBuffer(1, 0, body);
+ std::vector<U8> enc(2 * src.size() + 16, 0);
+ S32 enc_size = LLZeroCode::encode(src.data(), (U32)src.size(), enc.data(), (U32)enc.size(), 1);
+ ensure("decode overflow setup: encode succeeded", enc_size >= 0);
+
+ std::vector<U8> dec(300, 0); // > 256, but less than the true decoded size (~501)
+ bool overflow = false;
+ U32 dec_size = LLZeroCode::decode(enc.data(), (U32)enc_size,
+ dec.data(), (U32)dec.size(), 1, overflow);
+ ensure("decode overflow (insufficient capacity): overflow flagged", overflow);
+ ensure("decode overflow (insufficient capacity): size bounded by capacity", dec_size <= dec.size());
+ }
+
+ // A pathological alternating zero/non-zero pattern grows under zero-coding
+ // (every isolated zero costs 2 output bytes for 1 input byte), so encode
+ // must refuse it and leave the original buffer in use.
+ template<> template<>
+ void zerocode_object::test<12>()
+ {
+ std::vector<U8> body;
+ for (int i = 0; i < 200; ++i)
+ {
+ body.push_back(0);
+ body.push_back((U8)(i + 1));
+ }
+ ensureRoundTrip("alternating pattern", 1, 0x00, body, false);
+ }
+}
diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp
index 4f55d75e7d..2029497790 100644
--- a/indra/llplugin/llpluginprocessparent.cpp
+++ b/indra/llplugin/llpluginprocessparent.cpp
@@ -508,6 +508,16 @@ void LLPluginProcessParent::idle(void)
else if (!mProcess && !mProcessCreationRequested)
{
mProcessCreationRequested = true;
+#if LL_DARWIN
+ // On darwin process should be created from main thread, otherwise it can
+ // cause a fork-lock deadlock. Relevant to both, boost and apr.
+ // Alternatively can remake process creation to use posix_spawn
+ if (!(mProcess = LLProcess::create(mProcessParams)))
+ {
+ mProcessCreationRequested = false;
+ errorState();
+ }
+#else
LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
// *NOTE: main_queue->postTo casts this refcounted smart pointer to a weak
// pointer
@@ -558,6 +568,7 @@ void LLPluginProcessParent::idle(void)
errorState();
}
}
+#endif
}
if (mProcess)
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index fafc92b5d0..81743713a1 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -2583,6 +2583,12 @@ bool LLImageGL::scaleDown(S32 desired_discard)
{
glDrawArrays(GL_TRIANGLES, 0, 3);
+#if LL_DARWIN
+ // On mac OS, flush before freeing, otherwise the texture may be
+ // freed before Metal's 'lazy' evaluation/restoration behavior triggers.
+ glFlush();
+#endif
+
free_tex_image(mTexName);
glTexImage2D(mTarget, 0, mFormatInternal, desired_width, desired_height, 0, mFormatPrimary, mFormatType, nullptr);
glCopyTexSubImage2D(mTarget, 0, 0, 0, 0, 0, desired_width, desired_height);
diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp
index 9b839350ae..2d64292fe7 100644
--- a/indra/llrender/llvertexbuffer.cpp
+++ b/indra/llrender/llvertexbuffer.cpp
@@ -296,18 +296,21 @@ static void delete_buffers(S32 count, GLuint* buffers)
LL_PROFILE_ZONE_SCOPED_CATEGORY_VERTEX;
// wait a few frames before actually deleting the buffers to avoid
// synchronization issues with the GPU
- static std::vector<GLuint> sFreeList[4];
+ constexpr U32 BUCKET_COUNT = 4;
+ static std::vector<GLuint> sFreeList[BUCKET_COUNT];
if (gGLManager.mInited)
{
- U32 idx = LLImageGL::sFrameCount % 4;
+ // Move current frame to free list
+ U32 idx = LLImageGL::sFrameCount % BUCKET_COUNT;
for (S32 i = 0; i < count; ++i)
{
sFreeList[idx].push_back(buffers[i]);
}
- idx = (LLImageGL::sFrameCount + 3) % 4;
+ // Clear frame -3 (equals +1), this idx will be written over on the next call
+ idx = (LLImageGL::sFrameCount + 1) % BUCKET_COUNT;
if (!sFreeList[idx].empty())
{
diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index 5f1302df88..6588c481ff 100644
--- a/indra/llui/lltexteditor.cpp
+++ b/indra/llui/lltexteditor.cpp
@@ -1693,28 +1693,25 @@ void LLTextEditor::pasteTextWithLinebreaks(LLWString & clean_string)
std::basic_string<llwchar>::size_type start = 0;
std::basic_string<llwchar>::size_type pos = clean_string.find('\n',start);
- while((pos != -1) && (pos != clean_string.length() -1))
+ while (pos != std::basic_string<llwchar>::npos)
{
- if(pos!=start)
+ if (pos != start)
{
std::basic_string<llwchar> str = std::basic_string<llwchar>(clean_string,start,pos-start);
setCursorPos(mCursorPos + insert(mCursorPos, str, true, LLTextSegmentPtr()));
}
- addLineBreakChar(true); // Add a line break and group with the next addition.
+ const bool trailing_linebreak = (pos == clean_string.length() - 1);
+ addLineBreakChar(!trailing_linebreak);
start = pos+1;
pos = clean_string.find('\n',start);
}
- if (pos != start)
+ if (start < clean_string.length())
{
std::basic_string<llwchar> str = std::basic_string<llwchar>(clean_string,start,clean_string.length()-start);
setCursorPos(mCursorPos + insert(mCursorPos, str, false, LLTextSegmentPtr()));
}
- else
- {
- addLineBreakChar(false); // Add a line break and end the grouping.
- }
}
// copy selection to primary
diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp
index bdb629172d..b33cb054d4 100644
--- a/indra/llwebrtc/llwebrtc.cpp
+++ b/indra/llwebrtc/llwebrtc.cpp
@@ -396,7 +396,7 @@ void LLWebRTCImpl::init()
}
-void LLWebRTCImpl::terminate()
+bool LLWebRTCImpl::terminate()
{
// Run all blocking WebRTC shutdown calls on a separate thread so that a
// hung BlockingCall cannot block the viewer shutdown indefinitely.
@@ -410,16 +410,55 @@ void LLWebRTCImpl::terminate()
std::vector<webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>> connections;
connections.swap(mPeerConnections);
+ // Explicitely unregister observers before shutting down the threads.
+ // shutdown_thread, if detached, can outlive observer.
+ mWorkerThread->BlockingCall([this]()
+ {
+ if (mDeviceModule)
+ {
+ mDeviceModule->SetObserver(nullptr);
+ }
+ });
+ mVoiceDevicesObserverList.clear();
+
+ // shutdown_thread can be detached, then LLWebRTCImpl will be nulled out.
+ // Capture what's needed in lambda, don't rely on [this].
std::thread shutdown_thread(
- [this, connections = std::move(connections), done_promise]() mutable
+ [networkThread = std::move(mNetworkThread),
+ workerThread = std::move(mWorkerThread),
+ signalingThread = std::move(mSignalingThread),
+ deviceModule = std::move(mDeviceModule),
+ factory = std::move(mPeerConnectionFactory),
+ connections = std::move(connections),
+ done_promise]() mutable
{
- mWorkerThread->BlockingCall(
- [this]()
+ // Stop the capture/render devices alongside the connection teardown
+ // below rather than ahead of it. Both of these calls end in a
+ // WaitForSingleObject on a WASAPI thread with a 2s timeout apiece
+ // (AudioDeviceWindowsCore::StopRecording / StopPlayout), so blocking on
+ // them here can spend most of the shutdown budget before the
+ // connections have been touched at all -- and after an OS sleep they
+ // tend to hit the full timeout.
+ //
+ // This work has to stay on the worker thread: the device module was
+ // created there and its AudioDeviceBuffer is guarded by a sequence
+ // checker bound to that thread. Posting instead of blocking lets the
+ // signaling close below get on with its network-thread work (data
+ // channel close, transport teardown) while the device stop is still
+ // waiting on WASAPI.
+ //
+ // No explicit join is needed: Thread's task queue is FIFO and
+ // BlockingCall posts through it, so the ForceTerminate call at the end
+ // of this lambda can't run until this task has finished. Any
+ // worker-thread work the signaling close does is likewise ordered
+ // after it, so nothing sees the device module half torn down.
+ workerThread->PostTask(
+ [&deviceModule]()
{
- if (mDeviceModule)
+ if (deviceModule)
{
- mDeviceModule->ForceStopRecording();
- mDeviceModule->StopPlayout();
+ deviceModule->ForceStopRecording();
+ deviceModule->StopPlayout();
}
});
@@ -434,7 +473,7 @@ void LLWebRTCImpl::terminate()
// callback inline, and that callback calls back into the viewer's
// signaling observers. Those observers are only valid until
// llwebrtc::terminate() returns.
- mSignalingThread->BlockingCall(
+ signalingThread->BlockingCall(
[&connections]()
{
for (auto& connection : connections)
@@ -447,22 +486,29 @@ void LLWebRTCImpl::terminate()
});
// Drain anything the closes posted before dropping the factory.
- mSignalingThread->BlockingCall([]() {});
+ signalingThread->BlockingCall([]() {});
- mSignalingThread->BlockingCall([this]() {
- mPeerConnectionFactory = nullptr;
+ signalingThread->BlockingCall([&factory]() {
+ factory = nullptr;
});
- mWorkerThread->BlockingCall(
- [this]()
+ workerThread->BlockingCall(
+ [&deviceModule]()
{
- if (mDeviceModule)
+ if (deviceModule)
{
- mDeviceModule->ForceTerminate();
+ deviceModule->ForceTerminate();
}
- mDeviceModule = nullptr;
+ deviceModule = nullptr;
});
+ // Explicitly clean WebRTC threads in dependency order before signalling completion.
+ // The connections were closed and destroyed on the signaling thread, so it's safe
+ // to clean.
+ signalingThread.reset();
+ workerThread.reset();
+ networkThread.reset();
+
done_promise->set_value();
});
@@ -474,29 +520,29 @@ void LLWebRTCImpl::terminate()
" Detaching — some WebRTC resources will be leaked.";
shutdown_thread.detach();
- // Release the unique_ptrs WITHOUT joining/deleting: the detached thread
- // may still be using these thread objects.
- // The raw pointers are intentionally leaked — the process is exiting anyway
- // and our priority is saving cache and personal data.
- (void)mNetworkThread.release();
- (void)mWorkerThread.release();
- (void)mSignalingThread.release();
-
+ // Leave every member exactly as it is. The detached thread is still
+ // running the lambda above, which reads mSignalingThread, mWorkerThread,
+ // mDeviceModule and mPeerConnectionFactory through `this` -- clearing or
+ // releasing them here would pull them out from under it mid-shutdown
+ // (a null mSignalingThread is an immediate segfault at the next
+ // BlockingCall). Instead we report the failure so the caller leaks this
+ // object rather than deleting it; the process is exiting anyway and our
+ // priority is saving cache and personal data.
+ //
// mPeerConnections is already empty -- the detached thread owns the
// connections now and must be left to finish with them.
+ //
+ // The log sink is unhooked here (and deliberately not deleted, since the
+ // detached thread may still log) because the viewer-side log callback
+ // behind it doesn't outlive this call.
webrtc::LogMessage::RemoveLogToStream(mLogSink);
- return;
+ return false;
}
shutdown_thread.join();
- // The connections were closed and destroyed on the signaling thread before
- // the shutdown thread finished, so it's safe to drop the threads now.
- mNetworkThread = nullptr;
- mWorkerThread = nullptr;
- mSignalingThread = nullptr;
-
webrtc::LogMessage::RemoveLogToStream(mLogSink);
+ return true;
}
@@ -1124,17 +1170,23 @@ void LLWebRTCPeerConnectionImpl::closeOnSignalingThread()
mLocalStream = nullptr;
}
mPeerConnection = nullptr;
+ }
- for (auto &observer : mSignalingObserverList)
- {
- observer->OnPeerConnectionClosed();
- }
+ // Notify unconditionally, even if there was no peer connection to close --
+ // a connection can be shut down before it ever finished initializing, and
+ // the caller is still waiting to hear that the close is done. Withholding
+ // this leaves the viewer's connection state machine parked in
+ // VOICE_STATE_WAIT_FOR_CLOSE, which has no timeout of its own.
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnPeerConnectionClosed();
}
- // Nothing may call back into the viewer past this point. On shutdown the
- // viewer's connection objects are torn down as soon as llwebrtc::terminate()
- // returns and they deliberately don't unset themselves as observers, so any
- // late callback would be reaching into freed memory.
+ // Nothing may call back into the viewer past this point. Connections
+ // closed while the viewer is still running unset themselves as observers
+ // when they're destroyed, but any that are left for llwebrtc::terminate()
+ // to close deliberately don't -- they're torn down as soon as it returns,
+ // so a late callback would be reaching into freed memory.
mSignalingObserverList.clear();
mDataObserverList.clear();
}
@@ -1759,20 +1811,20 @@ void LLWebRTCPeerConnectionImpl::OnStateChange()
switch (mDataChannel->state())
{
case webrtc::DataChannelInterface::kOpen:
- RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State Open";
+ RTC_LOG(LS_VERBOSE) << __FUNCTION__ << " Data Channel State Open";
for (auto &observer : mSignalingObserverList)
{
observer->OnDataChannelReady(this);
}
break;
case webrtc::DataChannelInterface::kConnecting:
- RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State Connecting";
+ RTC_LOG(LS_VERBOSE) << __FUNCTION__ << " Data Channel State Connecting";
break;
case webrtc::DataChannelInterface::kClosing:
- RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State closing";
+ RTC_LOG(LS_VERBOSE) << __FUNCTION__ << " Data Channel State closing";
break;
case webrtc::DataChannelInterface::kClosed:
- RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State closed";
+ RTC_LOG(LS_VERBOSE) << __FUNCTION__ << " Data Channel State closed";
break;
default:
break;
@@ -1931,8 +1983,14 @@ void terminate()
{
if (gWebRTCImpl)
{
- gWebRTCImpl->terminate();
- delete gWebRTCImpl;
+ if (gWebRTCImpl->terminate())
+ {
+ delete gWebRTCImpl;
+ }
+ // Otherwise shutdown timed out and was left to a detached thread that is
+ // still using this object -- and the webrtc threads it owns -- so it's
+ // intentionally leaked. Deleting it would hand that thread a freed
+ // object to finish shutting down with.
gWebRTCImpl = nullptr;
}
}
diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h
index cee42cf19a..ae3a6a4903 100644
--- a/indra/llwebrtc/llwebrtc_impl.h
+++ b/indra/llwebrtc/llwebrtc_impl.h
@@ -81,19 +81,28 @@ public:
{
if (mCallback)
{
+ // RTC_LOG prefixes each message with its "(file:line): " origin. Use it to demote
+ // libwebrtc's own chatty INFO/WARNING logging to verbose, keeping ours as-is.
+ static const std::string file_prefix("(llwebrtc.cpp:");
switch (severity)
{
case webrtc::LS_VERBOSE:
mCallback->LogMessage(LLWebRTCLogCallback::LOG_LEVEL_VERBOSE, msg);
break;
case webrtc::LS_INFO:
- mCallback->LogMessage(LLWebRTCLogCallback::LOG_LEVEL_VERBOSE, msg);
+ mCallback->LogMessage(msg.rfind(file_prefix, 0) == 0
+ ? LLWebRTCLogCallback::LOG_LEVEL_INFO
+ : LLWebRTCLogCallback::LOG_LEVEL_VERBOSE,
+ msg);
break;
case webrtc::LS_WARNING:
- mCallback->LogMessage(LLWebRTCLogCallback::LOG_LEVEL_VERBOSE, msg);
+ mCallback->LogMessage(msg.rfind(file_prefix, 0) == 0
+ ? LLWebRTCLogCallback::LOG_LEVEL_WARNING
+ : LLWebRTCLogCallback::LOG_LEVEL_VERBOSE,
+ msg);
break;
case webrtc::LS_ERROR:
- mCallback->LogMessage(LLWebRTCLogCallback::LOG_LEVEL_VERBOSE, msg);
+ mCallback->LogMessage(LLWebRTCLogCallback::LOG_LEVEL_ERROR, msg);
break;
default:
break;
@@ -413,7 +422,11 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO
}
void init();
- void terminate();
+ // Returns true if shutdown completed cleanly and this object may be
+ // destroyed. Returns false if it timed out: a detached thread is still
+ // using this object and its webrtc threads, so it must be leaked, not
+ // deleted.
+ bool terminate();
//
// LLWebRTCDeviceInterface
diff --git a/indra/llxml/llcontrol.cpp b/indra/llxml/llcontrol.cpp
index 84d03a6d0c..eccf672e46 100644
--- a/indra/llxml/llcontrol.cpp
+++ b/indra/llxml/llcontrol.cpp
@@ -192,20 +192,6 @@ LLSD LLControlVariable::getComparableValue(const LLSD& value)
storable_value = false;
}
}
- else if (TYPE_LLSD == type() && value.isString())
- {
- LLPointer<LLSDNotationParser> parser = new LLSDNotationParser;
- LLSD result;
- std::stringstream value_stream(value.asString());
- if (parser->parse(value_stream, result, LLSDSerialize::SIZE_UNLIMITED) != LLSDParser::PARSE_FAILURE)
- {
- storable_value = result;
- }
- else
- {
- storable_value = value;
- }
- }
else
{
storable_value = value;
@@ -735,6 +721,30 @@ void LLControlGroup::setLLSD(std::string_view name, const LLSD& val)
set(name, val);
}
+bool LLControlVariable::setValueFromNotation(const std::string& notation, bool saved_value)
+{
+ if (mType == TYPE_LLSD)
+ {
+ LLPointer<LLSDNotationParser> parser = new LLSDNotationParser;
+ LLSD result;
+ std::stringstream value_stream(notation);
+ S32 parse_count = parser->parse(value_stream, result, LLSDSerialize::SIZE_UNLIMITED);
+ if (parse_count != LLSDParser::PARSE_FAILURE)
+ {
+ setValue(result, saved_value);
+ return true;
+ }
+ LL_WARNS("Controls") << "Failed to parse LLSD notation for control '"
+ << mName << "': " << notation << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("Controls") << "setValueFromNotation() called on non-LLSD control '"
+ << mName << "' (type " << LLControlGroup::typeEnumToString(mType) << "); ignoring." << LL_ENDL;
+ }
+ return false;
+}
+
void LLControlGroup::setUntypedValue(std::string_view name, const LLSD& val)
{
if (name.empty())
diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h
index ad283c89b1..7f28c71232 100644
--- a/indra/llxml/llcontrol.h
+++ b/indra/llxml/llcontrol.h
@@ -127,6 +127,7 @@ public:
void setPersist(ePersist);
void setHiddenFromSettingsEditor(bool hide);
void setComment(const std::string& comment);
+ bool setValueFromNotation(const std::string& notation, bool saved_value = true);
private:
void firePropertyChanged(const LLSD &pPreviousValue)
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index c4697fd566..bb6eac98a9 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-26.3.0
+26.4.0
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index b23acc6bd1..ad307f9648 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -16447,7 +16447,7 @@
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
- <string>Boolean</string>
+ <string>U32</string>
<key>Value</key>
<integer>0</integer>
</map>
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 03c8d51168..a273e36b58 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -2182,6 +2182,7 @@ bool LLAppViewer::cleanup()
LLWorld::deleteSingleton();
LLVoiceClient::deleteSingleton();
LLUI::deleteSingleton();
+ LLGridManager::deleteSingleton();
LLWatchdog::deleteSingleton();
// It's not at first obvious where, in this long sequence, a generic cleanup
@@ -3016,6 +3017,8 @@ bool LLAppViewer::initConfiguration()
}
}
+ LLGridManager::createInstance();
+
LLSLURL start_slurl;
if (!starting_location.empty())
{
@@ -3064,6 +3067,7 @@ bool LLAppViewer::initConfiguration()
// Do not save settings.
// Might be smarter to have an exit code for a more reliable
// "early exit, needs cleanup" case.
+ LLGridManager::deleteSingleton();
LLSingletonBase::deleteAll();
cleanupConsole();
delete mSettingsLocationList;
@@ -3124,6 +3128,7 @@ bool LLAppViewer::initConfiguration()
// Since returning 'false' is basically an error without cleanup,
// do cleanup here. No need to worry about marker files here.
+ LLGridManager::deleteSingleton();
LLSingletonBase::deleteAll();
cleanupConsole();
return false;
@@ -3889,8 +3894,12 @@ void LLAppViewer::writeSystemInfo()
gDebugInfo["CPUInfo"]["CPUFamily"] = gSysCPU.getFamily();
gDebugInfo["CPUInfo"]["CPUMhz"] = (S32)gSysCPU.getMHz();
gDebugInfo["CPUInfo"]["CPUAltivec"] = gSysCPU.hasAltivec();
- gDebugInfo["CPUInfo"]["CPUSSE"] = gSysCPU.hasSSE();
- gDebugInfo["CPUInfo"]["CPUSSE2"] = gSysCPU.hasSSE2();
+ gDebugInfo["CPUInfo"]["CPUSSE42"] = gSysCPU.hasSSE42();
+ gDebugInfo["CPUInfo"]["CPUSSE4a"] = gSysCPU.hasSSE4a();
+ gDebugInfo["CPUInfo"]["CPUAVX"] = gSysCPU.hasAVX();
+ gDebugInfo["CPUInfo"]["CPUAVX2"] = gSysCPU.hasAVX2();
+ gDebugInfo["CPUInfo"]["CPUAVX512F"] = gSysCPU.hasAVX512F();
+
gDebugInfo["RAMInfo"]["Physical"] = LLSD::Integer(gSysMemory.getPhysicalMemoryKB().value());
gDebugInfo["RAMInfo"]["Allocated"] = LLSD::Integer(gMemoryAllocated.valueInUnits<LLUnits::Kilobytes>());
diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp
index c95e98982d..b6d59a4a39 100644
--- a/indra/newview/llavataractions.cpp
+++ b/indra/newview/llavataractions.cpp
@@ -337,6 +337,17 @@ void LLAvatarActions::showProfile(const LLUUID& avatar_id)
}
}
+bool LLAvatarActions::myProfileVisible()
+{
+ LLFloater* floaterp = getProfileFloater(gAgentID);
+ return floaterp && floaterp->isInVisibleChain();
+}
+
+bool LLAvatarActions::myPicksTabVisible()
+{
+ return myProfileVisible() && isPickTabSelected(gAgentID);
+}
+
// static
void LLAvatarActions::showPicks(const LLUUID& avatar_id)
{
diff --git a/indra/newview/llavataractions.h b/indra/newview/llavataractions.h
index 1f5a42ed22..90f1e96fb7 100644
--- a/indra/newview/llavataractions.h
+++ b/indra/newview/llavataractions.h
@@ -109,6 +109,12 @@ public:
static LLFloater* getProfileFloater(const LLUUID& avatar_id);
/**
+ * Profile helpers.
+ */
+ static bool myProfileVisible();
+ static bool myPicksTabVisible();
+
+ /**
* Show avatar on world map.
*/
static void showOnMap(const LLUUID& id);
diff --git a/indra/newview/llcommandlineparser.cpp b/indra/newview/llcommandlineparser.cpp
index 0734b12531..84d3ff90d5 100644
--- a/indra/newview/llcommandlineparser.cpp
+++ b/indra/newview/llcommandlineparser.cpp
@@ -611,6 +611,11 @@ void setControlValueCB(const LLCommandLineParser::token_vector_t& value,
ctrl->setValue(llsdArray, false);
}
+ else if (ctrl->isType(TYPE_LLSD))
+ {
+ // Command-line LLSD should support a notation format string
+ ctrl->setValueFromNotation(onevalue(option, value), false);
+ }
else
{
ctrl->setValue(onevalue(option, value), false);
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index a8e8f122e1..cb067ef011 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -7214,8 +7214,8 @@ void LLObjectBridge::performAction(LLInventoryModel* model, std::string action)
item = (LLViewerInventoryItem*)gInventory.getItem(object_id);
if(item && gInventory.isObjectDescendentOf(object_id, gInventory.getRootFolderID()))
{
- static LLCachedControl<bool> replace_item(gSavedSettings, "InventoryAddAttachmentBehavior", false);
- rez_attachment(item, NULL, ("attach" == action) ? replace_item() : true); // Replace if "Wear"ing.
+ static LLCachedControl<U32> add_attachment_behavior(gSavedSettings, "InventoryAddAttachmentBehavior", 0);
+ rez_attachment(item, NULL, ("attach" == action) ? (add_attachment_behavior() == 1) : true); // Replace if "Wear"ing.
}
else if(item && item->isFinished())
{
@@ -8372,8 +8372,8 @@ void LLObjectBridgeAction::attachOrDetach()
}
else
{
- static LLCachedControl<bool> inventory_linking(gSavedSettings, "InventoryAddAttachmentBehavior", false);
- LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, inventory_linking()); // Don't replace if adding.
+ static LLCachedControl<U32> add_attachment_behavior(gSavedSettings, "InventoryAddAttachmentBehavior", 0);
+ LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, add_attachment_behavior() == 1); // Don't replace if adding.
}
}
diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp
index 144940c705..a0d7aac3f3 100644
--- a/indra/newview/lltexturefetch.cpp
+++ b/indra/newview/lltexturefetch.cpp
@@ -2527,18 +2527,26 @@ S32 LLTextureFetch::createRequest(FTType f_type, const std::string& url, const L
{
LL_DEBUGS("Avatar") << " requesting " << id << " " << w << "x" << h << " discard " << desired_discard << " type " << f_type << LL_ENDL;
}
- LLTextureFetchWorker* worker = getWorker(id);
+
+ // Potentially we might remove a request, lock queue here instead
+ // of getWorker to make sure request will persist till removeRequest
+ lockQueue();
+ LLTextureFetchWorker* worker = getWorkerAfterLock(id);
if (worker)
{
if (worker->mHost != host)
{
LL_WARNS(LOG_TXT) << "LLTextureFetch::createRequest " << id << " called with multiple hosts: "
<< host << " != " << worker->mHost << LL_ENDL;
- removeRequest(worker, true);
+ size_t erased_1 = mRequestMap.erase(worker->mID);
+ llassert_always(erased_1 > 0);
+ unlockQueue();
+ worker->scheduleDelete();
worker = NULL;
return CREATE_REQUEST_ERROR_MHOSTS;
}
}
+ unlockQueue();
S32 desired_size;
std::string exten = gDirUtilp->getExtension(url);
@@ -2717,9 +2725,11 @@ void LLTextureFetch::deleteAllRequests()
}
LLTextureFetchWorker* worker = mRequestMap.begin()->second;
- unlockQueue() ;
+ size_t erased_1 = mRequestMap.erase(worker->mID);
+ llassert_always(erased_1 > 0);
+ unlockQueue();
- removeRequest(worker, true);
+ worker->scheduleDelete();
}
}
diff --git a/indra/newview/llviewercontrollistener.cpp b/indra/newview/llviewercontrollistener.cpp
index 6f77e21fcc..f8e829deb9 100644
--- a/indra/newview/llviewercontrollistener.cpp
+++ b/indra/newview/llviewercontrollistener.cpp
@@ -65,6 +65,11 @@ LLViewerControlListener::LLViewerControlListener()
grouphelp + replyhelp,
&LLViewerControlListener::set,
LLSDMap("group", LLSD())("key", LLSD()));
+ add("set_notation",
+ std::string("Set [\"group\"] TYPE_LLSD control [\"key\"] by parsing [\"value\"] as LLSD notation.\n") +
+ grouphelp + replyhelp,
+ &LLViewerControlListener::set_notation,
+ LLSDMap("group", LLSD())("key", LLSD())("value", LLSD()));
add("toggle",
std::string("Toggle [\"group\"] control [\"key\"], if boolean\n") + grouphelp + replyhelp,
&LLViewerControlListener::toggle,
@@ -150,6 +155,36 @@ void LLViewerControlListener::set(LLSD const & request)
}
//static
+void LLViewerControlListener::set_notation(LLSD const& request)
+{
+ Info info(request);
+ if (!info.control)
+ return;
+
+ if (!info.control->isType(TYPE_LLSD))
+ {
+ info.response.error(STRINGIZE("set_notation requires a TYPE_LLSD control, but '"
+ << info.key << "' in group '" << info.groupname
+ << "' has type "
+ << LLControlGroup::typeEnumToString(info.control->type())));
+ return;
+ }
+
+ if (!request["value"].isString())
+ {
+ info.response.error(STRINGIZE("set_notation requires [\"value\"] to be a string "
+ "containing LLSD notation, but received type: "
+ << LLSD::typeString(request["value"].type())));
+ return;
+ }
+
+ if (!info.control->setValueFromNotation(request["value"].asString()))
+ {
+ info.response.error(STRINGIZE("set_notation failed to parse notation"));
+ }
+}
+
+//static
void LLViewerControlListener::toggle(LLSD const & request)
{
Info info(request);
diff --git a/indra/newview/llviewercontrollistener.h b/indra/newview/llviewercontrollistener.h
index 346acac439..c5a3a31baf 100644
--- a/indra/newview/llviewercontrollistener.h
+++ b/indra/newview/llviewercontrollistener.h
@@ -41,6 +41,7 @@ public:
private:
static void set(LLSD const & event_data);
+ static void set_notation(LLSD const & event_data);
static void toggle(LLSD const & event_data);
static void get(LLSD const & event_data);
static void groups(LLSD const & event_data);
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 13d57a4ad2..87a891915e 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -4054,17 +4054,6 @@ void handle_avatar_eject(const LLSD& avatar_id)
}
}
-bool my_profile_visible()
-{
- LLFloater* floaterp = LLAvatarActions::getProfileFloater(gAgentID);
- return floaterp && floaterp->isInVisibleChain();
-}
-
-bool picks_tab_visible()
-{
- return my_profile_visible() && LLAvatarActions::isPickTabSelected(gAgentID);
-}
-
bool enable_freeze_eject(const LLSD& avatar_id)
{
// Use avatar_id if available, otherwise default to right-click avatar
@@ -6820,7 +6809,7 @@ class LLAvatarTogglePicks : public view_listener_t
instance->setFocus(true);
LLAvatarActions::showPicks(gAgent.getID());
}
- else if (picks_tab_visible())
+ else if (LLAvatarActions::myPicksTabVisible())
{
instance->closeFloater();
}
@@ -9898,6 +9887,23 @@ void show_topinfobar_context_menu(LLView* ctrl, S32 x, S32 y)
LLMenuGL::showPopup(ctrl, show_topbarinfo_context_menu, x, y);
}
+static void register_agent_ui_callbacks()
+{
+ // These functions are used by menu and commands, they need to persist for the lifetime of the application
+ // Note: executes before LLToolBar::createButton and before menu builds
+ LLUICtrl::EnableCallbackRegistry::Registrar& global_enable = LLUICtrl::EnableCallbackRegistry::defaultRegistrar();
+ LLUICtrl::CommitCallbackRegistry::Registrar& global_commit = LLUICtrl::CommitCallbackRegistry::defaultRegistrar();
+ global_commit.add("Agent.toggleFlying", boost::bind(&LLAgent::toggleFlying));
+ global_enable.add("Agent.enableFlyLand", boost::bind(&enable_fly_land));
+ global_commit.add("Agent.PressMicrophone", boost::bind(&LLAgent::pressMicrophone, _2));
+ global_commit.add("Agent.ReleaseMicrophone", boost::bind(&LLAgent::releaseMicrophone, _2));
+ global_commit.add("Agent.ToggleMicrophone", boost::bind(&LLAgent::toggleMicrophone, _2));
+ global_enable.add("Agent.IsMicrophoneOn", boost::bind(&LLAgent::isMicrophoneOn, _2));
+ global_enable.add("Agent.IsActionAllowed", boost::bind(&LLAgent::isActionAllowed, _2));
+ global_enable.add("Avatar.IsMyProfileOpen", boost::bind(&LLAvatarActions::myProfileVisible));
+ global_enable.add("Avatar.IsPicksTabOpen", boost::bind(&LLAvatarActions::myPicksTabVisible));
+}
+
void initialize_edit_menu()
{
view_listener_t::addMenu(new LLEditUndo(), "Edit.Undo");
@@ -9955,6 +9961,8 @@ void initialize_menus()
bool mMult;
};
+ register_agent_ui_callbacks();
+
LLUICtrl::EnableCallbackRegistry::Registrar& enable = LLUICtrl::EnableCallbackRegistry::currentRegistrar();
LLUICtrl::CommitCallbackRegistry::Registrar& commit = LLUICtrl::CommitCallbackRegistry::currentRegistrar();
@@ -9970,15 +9978,6 @@ void initialize_menus()
enable.add("Conversation.IsConversationLoggingAllowed", boost::bind(&LLFloaterIMContainer::isConversationLoggingAllowed));
- // Agent
- commit.add("Agent.toggleFlying", boost::bind(&LLAgent::toggleFlying));
- enable.add("Agent.enableFlyLand", boost::bind(&enable_fly_land));
- commit.add("Agent.PressMicrophone", boost::bind(&LLAgent::pressMicrophone, _2));
- commit.add("Agent.ReleaseMicrophone", boost::bind(&LLAgent::releaseMicrophone, _2));
- commit.add("Agent.ToggleMicrophone", boost::bind(&LLAgent::toggleMicrophone, _2));
- enable.add("Agent.IsMicrophoneOn", boost::bind(&LLAgent::isMicrophoneOn, _2));
- enable.add("Agent.IsActionAllowed", boost::bind(&LLAgent::isActionAllowed, _2));
-
// File menu
init_menu_file();
@@ -10346,8 +10345,6 @@ void initialize_menus()
view_listener_t::addMenu(new LLAvatarResetSkeletonAndAnimations(), "Avatar.ResetSkeletonAndAnimations");
view_listener_t::addMenu(new LLAvatarResetSelfSkeleton(), "Avatar.ResetSelfSkeleton");
view_listener_t::addMenu(new LLAvatarResetSelfSkeletonAndAnimations(), "Avatar.ResetSelfSkeletonAndAnimations");
- enable.add("Avatar.IsMyProfileOpen", boost::bind(&my_profile_visible));
- enable.add("Avatar.IsPicksTabOpen", boost::bind(&picks_tab_visible));
commit.add("Avatar.OpenMarketplace", boost::bind(&LLWeb::loadURLExternal, gSavedSettings.getString("MarketplaceURL")));
diff --git a/indra/newview/llviewernetwork.h b/indra/newview/llviewernetwork.h
index 0937425a18..18c23cf7ff 100644
--- a/indra/newview/llviewernetwork.h
+++ b/indra/newview/llviewernetwork.h
@@ -57,13 +57,18 @@ protected:
* This class maintains the currently selected grid, and provides short
* form accessors for each of the properties of the selected grid.
**/
-class LLGridManager : public LLSingleton<LLGridManager>
+class LLGridManager : public LLSimpleton<LLGridManager>
{
+public:
/// Instantiate the grid manager, load default grids, selects the default grid
- LLSINGLETON(LLGridManager);
+ LLGridManager();
~LLGridManager();
- public:
+ LLGridManager(const LLGridManager&) = delete;
+ LLGridManager(LLGridManager&&) = delete;
+ LLGridManager& operator=(const LLGridManager&) = delete;
+ LLGridManager& operator=(LLGridManager&&) = delete;
+
/* ================================================================
* @name Initialization and Configuration
* @{
diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp
index b794047135..63eb08e4ae 100644
--- a/indra/newview/llviewerparcelmgr.cpp
+++ b/indra/newview/llviewerparcelmgr.cpp
@@ -309,6 +309,14 @@ void LLViewerParcelMgr::resetSegments(U8* segments)
void LLViewerParcelMgr::writeHighlightSegments(F32 west, F32 south, F32 east,
F32 north)
{
+ if (west < 0 || south < 0 || east < 0 || north < 0)
+ {
+ LL_WARNS() << "Invalid parcel edge coordinates provided to writeHighlightSegments- "
+ << "west: " << west << ", south: " << south << ", east: " << east << ", north: " << north
+ << LL_ENDL;
+ return;
+ }
+
S32 x, y;
S32 min_x = ll_round( west / PARCEL_GRID_STEP_METERS );
S32 max_x = ll_round( east / PARCEL_GRID_STEP_METERS );
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index a4298accc1..c37daa53a3 100644
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -1891,8 +1891,17 @@ LLViewerObject* LLViewerRegion::addNewObject(LLVOCacheEntry* entry)
}
else
{
- LLViewerRegion* old_regionp = ((LLDrawable*)entry->getEntry()->getDrawable())->getRegion();
- if(old_regionp != this)
+ LLDrawable* drawablep = (LLDrawable*)entry->getEntry()->getDrawable();
+ if (!drawablep || drawablep->isDead() || drawablep->getVObj().isNull())
+ {
+ LL_WARNS() << "Entry: " << entry->getLocalID() << " has a dead or invalid drawable; resetting to inactive." << LL_ENDL;
+ mImpl->mVisibleEntries.erase(entry);
+ entry->setState(LLVOCacheEntry::INACTIVE);
+ return NULL;
+ }
+
+ LLViewerRegion* old_regionp = drawablep->getRegion();
+ if (old_regionp != this)
{
//this object exists in two regions at the same time;
//this case can be safely ignored here because
diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp
index 45f7058acb..742ff95f26 100644
--- a/indra/newview/llviewerstats.cpp
+++ b/indra/newview/llviewerstats.cpp
@@ -716,6 +716,8 @@ void send_viewer_stats(bool include_preferences)
system["os"] = LLOSInfo::instance().getOSStringSimple();
system["cpu"] = gSysCPU.getCPUString();
system["cpu_sse"] = gSysCPU.getSSEVersions();
+ system["cpu_simd"] = gSysCPU.getSIMDVersions();
+ system["cpu_mhz"] = gSysCPU.getMHz();
system["address_size"] = ADDRESS_SIZE;
system["os_bitness"] = LLOSInfo::instance().getOSBitness();
system["hardware_concurrency"] = (LLSD::Integer) std::thread::hardware_concurrency();
@@ -738,6 +740,19 @@ void send_viewer_stats(bool include_preferences)
system["gpu_vendor"] = gGLManager.mGLVendorShort;
system["gpu_version"] = gGLManager.mDriverVersionVendorString;
system["opengl_version"] = gGLManager.mGLVersionString;
+ system["gpu_vram_mb"] = (S32)gGLManager.mVRAM;
+ system["glsl_version"] = llformat("%d.%d", gGLManager.mGLSLVersionMajor, gGLManager.mGLSLVersionMinor);
+
+ // Resource usage
+ // getAvailableMemKB might be a bit stale, but that's fine, this is statistics,
+ // not a debug tool.
+ // TODO: 26.3 branch introduced getAvailableCommitMemMB, use that on windows
+ system["ram_avail_mb"] = (S32Megabytes)(LLMemory::getAvailableMemKB()).value();
+ system["ram_allocated_mb"] = (S32Megabytes)(LLMemory::getAllocatedMemKB()).value();
+ static constexpr F64 BYTES_TO_MB = 1024.0 * 1024.0;
+ F64 texture_bytes_alloc = LLImageGL::getTextureBytesAllocated() / BYTES_TO_MB;
+ F64 vertex_bytes_alloc = LLVertexBuffer::getBytesAllocated() / BYTES_TO_MB;
+ system["gpu_vram_tracked_mb"] = (F32)(texture_bytes_alloc + vertex_bytes_alloc);
gGLManager.asLLSD(system["gl"]);
diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp
index b91b4b180b..e005e93cfb 100644
--- a/indra/newview/llvoiceclient.cpp
+++ b/indra/newview/llvoiceclient.cpp
@@ -120,7 +120,7 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv
LLVoiceModuleInterface *getVoiceModule(const std::string &voice_server_type)
{
#ifndef DISABLE_WEBRTC
- if (voice_server_type == WEBRTC_VOICE_SERVER_TYPE)
+ if (voice_server_type == WEBRTC_VOICE_SERVER_TYPE || voice_server_type.empty())
{
return (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance();
}
@@ -972,7 +972,7 @@ class LLViewerRequiredVoiceVersion : public LLHTTPNode
LLVoiceModuleInterface *voiceModule = NULL;
#ifndef DISABLE_WEBRTC
- if (voice_server_type == "webrtc" || voice_server_type.empty())
+ if (voice_server_type == WEBRTC_VOICE_SERVER_TYPE || voice_server_type.empty())
{
voiceModule = (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance();
}
diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp
index 9b4a371cec..c82a005a77 100644
--- a/indra/newview/llvoicewebrtc.cpp
+++ b/indra/newview/llvoicewebrtc.cpp
@@ -208,6 +208,7 @@ LLSD LLVoiceWebRTCStats::read()
///////////////////////////////////////////////////////////////////////////////////////////////
bool LLWebRTCVoiceClient::sShuttingDown = false;
+bool LLWebRTCVoiceClient::sWebRTCTerminated = false;
LLWebRTCVoiceClient::LLWebRTCVoiceClient() :
mHidden(false),
@@ -233,6 +234,7 @@ LLWebRTCVoiceClient::LLWebRTCVoiceClient() :
mWebRTCDeviceInterface(nullptr)
{
sShuttingDown = false;
+ sWebRTCTerminated = false;
mSpeakerVolume = 0.0;
@@ -301,11 +303,81 @@ void LLWebRTCVoiceClient::terminate()
mVoiceEnabled = false;
sShuttingDown = true; // so that coroutines won't post more work.
+
+ drainConnections();
+
+ sWebRTCTerminated = true;
llwebrtc::terminate();
mWebRTCDeviceInterface = nullptr;
}
+// Close the live peer connections before handing control to
+// llwebrtc::terminate().
+//
+// Anything still open when terminate() runs gets closed inline and serially on
+// the signaling thread, under a single 10s budget that is also paying for the
+// audio device shutdown. An estate session can hold ten live connections --
+// the current region plus up to eight neighbours, plus any group or ad-hoc
+// session -- each needing a full DTLS/SCTP teardown, so that budget is not
+// generous. Closing them here lets the teardown proceed asynchronously on the
+// signaling thread while this thread keeps pumping.
+//
+// It also quiets the per-connection stats poll before terminate() runs. A
+// GetStats request left in flight makes PeerConnection::Close() block in
+// RTCStatsCollector::WaitForPendingRequest(), which waits on the network thread
+// with no timeout at all.
+//
+// Best effort: whatever hasn't closed by the deadline is left to
+// llwebrtc::terminate(), exactly as before.
+void LLWebRTCVoiceClient::drainConnections()
+{
+ // Marks every session and connection as shutting down. This also stops
+ // estateSessionState::processConnectionStates() from spinning up
+ // replacement connections to neighbouring regions while we drain.
+ sessionState::for_each(boost::bind(predShutdownSession, _1));
+
+ // Long enough for a local close to complete, short enough that a wedged
+ // connection doesn't noticeably delay quitting. The remaining budget in
+ // llwebrtc::terminate() is the real backstop.
+ constexpr F32 DRAIN_TIMEOUT_SECONDS = 3.0f;
+ constexpr U32 DRAIN_POLL_MS = 10;
+
+ // Wait on the peer connections being closed rather than on the sessions
+ // being reaped. A connection that still has an HTTP coroutine in flight
+ // holds its session alive until mOutstandingRequests unwinds, and those
+ // coroutines don't run from here -- but its peer connection has already
+ // been closed by then, which is all terminate() cares about.
+ LLTimer timer;
+ while (!sessionState::allSessionsClosed() && timer.getElapsedTimeF32() < DRAIN_TIMEOUT_SECONDS)
+ {
+ // OnPeerConnectionClosed comes back through the main queue, so it has
+ // to be pumped or the state machines never see connections finish.
+ if (auto main_queue = mMainQueue.lock())
+ {
+ main_queue->runFor(std::chrono::milliseconds(DRAIN_POLL_MS));
+ }
+ sessionState::processSessionStates();
+
+ if (!sessionState::allSessionsClosed())
+ {
+ ms_sleep(DRAIN_POLL_MS);
+ }
+ }
+
+ if (!sessionState::allSessionsClosed())
+ {
+ LL_WARNS("Voice") << "Timed out draining voice connections after "
+ << DRAIN_TIMEOUT_SECONDS
+ << "s; leaving the rest to llwebrtc::terminate()." << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS("Voice") << "Voice connections drained in "
+ << timer.getElapsedTimeF32() << "s." << LL_ENDL;
+ }
+}
+
//---------------------------------------------------
void LLWebRTCVoiceClient::cleanUp()
@@ -1890,6 +1962,7 @@ void LLWebRTCVoiceClient::userAuthorized(const std::string& user_id, const LLUUI
if (sShuttingDown)
{
sShuttingDown = false; // was terminated, restart
+ sWebRTCTerminated = false;
initWebRTC();
}
}
@@ -2173,6 +2246,30 @@ void LLWebRTCVoiceClient::sessionState::processSessionStates()
}
}
+bool LLWebRTCVoiceClient::sessionState::allConnectionsClosed() const
+{
+ for (const auto &connection : mWebRTCConnections)
+ {
+ if (!connection->isClosed())
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool LLWebRTCVoiceClient::sessionState::allSessionsClosed()
+{
+ for (const auto &session : sSessions)
+ {
+ if (session.second && !session.second->allConnectionsClosed())
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
// process the states on each connection associated with a session.
bool LLWebRTCVoiceClient::sessionState::processConnectionStates()
{
@@ -2425,12 +2522,18 @@ LLVoiceWebRTCConnection::LLVoiceWebRTCConnection(const LLUUID &regionID, const s
LLVoiceWebRTCConnection::~LLVoiceWebRTCConnection()
{
- if (LLWebRTCVoiceClient::isShuttingDown())
+ if (LLWebRTCVoiceClient::isWebRTCTerminated())
{
- // peer connection and observers will be cleaned up
- // by llwebrtc::terminate() on shutdown.
+ // peer connection and observers have already been cleaned up
+ // by llwebrtc::terminate().
return;
}
+ // Note this is deliberately keyed off isWebRTCTerminated() rather than
+ // isShuttingDown(): connections drained by drainConnections() are destroyed
+ // while the webrtc library is still fully alive, and must unregister
+ // themselves and release the peer connection like any other close. Leaving
+ // a freed observer registered would hand llwebrtc::terminate() a dangling
+ // pointer to call OnPeerConnectionClosed() on.
mWebRTCPeerConnectionInterface->unsetSignalingObserver(this);
llwebrtc::freePeerConnection(mWebRTCPeerConnectionInterface);
}
@@ -3092,8 +3195,10 @@ bool LLVoiceWebRTCConnection::connectionStateMachine()
}
else
{
- // llwebrtc::terminate() is already shuting down the connection.
- setVoiceConnectionState(VOICE_STATE_WAIT_FOR_CLOSE);
+ // Shutting down: skip the courtesy logout to the sim (the HTTP
+ // round trip would just delay quitting) and go straight to
+ // dropping the webrtc connection.
+ setVoiceConnectionState(VOICE_STATE_SESSION_EXIT);
}
break;
@@ -3104,11 +3209,10 @@ bool LLVoiceWebRTCConnection::connectionStateMachine()
{
setVoiceConnectionState(VOICE_STATE_WAIT_FOR_CLOSE);
mOutstandingRequests++;
- if (!LLWebRTCVoiceClient::isShuttingDown())
- {
- mWebRTCPeerConnectionInterface->shutdownConnection();
- }
- // else was already posted by llwebrtc::terminate().
+ // Always drop the connection ourselves, including during shutdown:
+ // drainConnections() runs before llwebrtc::terminate(), so nothing
+ // else has posted the close yet.
+ mWebRTCPeerConnectionInterface->shutdownConnection();
break;
}
diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h
index 818bce5bc3..ec01b8c08c 100644
--- a/indra/newview/llvoicewebrtc.h
+++ b/indra/newview/llvoicewebrtc.h
@@ -79,6 +79,12 @@ public:
static bool isShuttingDown() { return sShuttingDown; }
+ // True once llwebrtc::terminate() has been entered. Between
+ // isShuttingDown() and this, the webrtc library is still fully alive and
+ // connections must still release their peer connections normally -- see
+ // drainConnections() and ~LLVoiceWebRTCConnection().
+ static bool isWebRTCTerminated() { return sWebRTCTerminated; }
+
const LLVoiceVersionInfo& getVersion() override;
void updateVersion();
@@ -306,6 +312,9 @@ public:
bool isEmpty() { return mWebRTCConnections.empty(); }
+ bool allConnectionsClosed() const;
+ static bool allSessionsClosed();
+
virtual bool isSpatial() = 0;
virtual bool isEstate() = 0;
virtual bool isCallbackPossible() = 0;
@@ -455,6 +464,10 @@ private:
/// Clean up objects created during a voice session.
void cleanUp();
+ /// Close the live peer connections before handing off to
+ /// llwebrtc::terminate(). Bounded and best effort.
+ void drainConnections();
+
LL::WorkQueue::weak_t mMainQueue;
F32 mTuningMicGain;
@@ -539,6 +552,7 @@ private:
// These variables can last longer than WebRTC in coroutines so we need them as static
static bool sShuttingDown;
+ static bool sWebRTCTerminated;
LLEventMailDrop mWebRTCPump;
@@ -643,6 +657,12 @@ class LLVoiceWebRTCConnection :
return mShutDown;
}
+ // True once the webrtc peer connection has finished closing. The
+ // connection object can outlive this while it waits for outstanding
+ // requests to unwind, so this -- not reaping -- is what drainConnections()
+ // waits on.
+ bool isClosed() const { return mVoiceConnectionState == VOICE_STATE_CLOSED; }
+
void OnVoiceConnectionRequestSuccess(const LLSD &body);
void resetConnectionStats();
diff --git a/indra/newview/llwindowlistener.cpp b/indra/newview/llwindowlistener.cpp
index 54eb61a6bf..69160bcd6e 100644
--- a/indra/newview/llwindowlistener.cpp
+++ b/indra/newview/llwindowlistener.cpp
@@ -120,6 +120,22 @@ LLWindowListener::LLWindowListener(LLViewerWindow *window, const KeyboardGetter&
"Paste specified [\"text\"] into the current edit field\n"
"Optional [\"path\"] specifies target UI element (must be focusable).",
&LLWindowListener::pasteText);
+ add("cut",
+ "Cut selected content from the focused edit field.\n"
+ "Optional [\"path\"] specifies target UI element (must be focusable).",
+ &LLWindowListener::cut);
+ add("copy",
+ "Copy selected content from the focused edit field.\n"
+ "Optional [\"path\"] specifies target UI element (must be focusable).",
+ &LLWindowListener::copy);
+ add("paste",
+ "Paste clipboard contents into the focused edit field.\n"
+ "Optional [\"path\"] specifies target UI element (must be focusable).",
+ &LLWindowListener::paste);
+ add("selectAll",
+ "Select all content in the focused edit field.\n"
+ "Optional [\"path\"] specifies target UI element (must be focusable).",
+ &LLWindowListener::selectAll);
}
template <typename MAPPED>
@@ -657,3 +673,77 @@ void LLWindowListener::pasteText(LLSD const & evt)
LLClipboard::instance().reset();
}
}
+
+// Helper: optionally focus a view by path; returns false and sets response error on failure.
+static bool focusViewByPath(const LLSD& evt, LLEventAPI::Response& response)
+{
+ if (!evt.has("path"))
+ return true;
+
+ std::string path(evt["path"]);
+ LLView* target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path);
+ if (!target_view)
+ {
+ response.error(STRINGIZE(evt["op"].asString() << " request specified invalid \"path\": " << path));
+ return false;
+ }
+ if (!target_view->isAvailable())
+ {
+ response.error(STRINGIZE("Target view specified by \"path\": " << path << " is not visible"));
+ return false;
+ }
+ gFocusMgr.setKeyboardFocus(target_view);
+ return true;
+}
+
+void LLWindowListener::cut(LLSD const & evt)
+{
+ Response response(LLSD(), evt);
+ if (!focusViewByPath(evt, response))
+ return;
+ if (!LLEditMenuHandler::gEditMenuHandler)
+ {
+ response.error(STRINGIZE(evt["op"].asString() << " request failed: no edit menu handler available"));
+ return;
+ }
+ LLEditMenuHandler::gEditMenuHandler->cut();
+}
+
+void LLWindowListener::copy(LLSD const & evt)
+{
+ Response response(LLSD(), evt);
+ if (!focusViewByPath(evt, response))
+ return;
+ if (!LLEditMenuHandler::gEditMenuHandler)
+ {
+ response.error(STRINGIZE(evt["op"].asString() << " request failed: no edit menu handler available"));
+ return;
+ }
+ LLEditMenuHandler::gEditMenuHandler->copy();
+}
+
+void LLWindowListener::paste(LLSD const & evt)
+{
+ Response response(LLSD(), evt);
+ if (!focusViewByPath(evt, response))
+ return;
+ if (!LLEditMenuHandler::gEditMenuHandler)
+ {
+ response.error(STRINGIZE(evt["op"].asString() << " request failed: no edit menu handler available"));
+ return;
+ }
+ LLEditMenuHandler::gEditMenuHandler->paste();
+}
+
+void LLWindowListener::selectAll(LLSD const & evt)
+{
+ Response response(LLSD(), evt);
+ if (!focusViewByPath(evt, response))
+ return;
+ if (!LLEditMenuHandler::gEditMenuHandler)
+ {
+ response.error(STRINGIZE(evt["op"].asString() << " request failed: no edit menu handler available"));
+ return;
+ }
+ LLEditMenuHandler::gEditMenuHandler->selectAll();
+}
diff --git a/indra/newview/llwindowlistener.h b/indra/newview/llwindowlistener.h
index 537322b19b..06e4090f6d 100644
--- a/indra/newview/llwindowlistener.h
+++ b/indra/newview/llwindowlistener.h
@@ -49,6 +49,10 @@ public:
void mouseMove(LLSD const & evt);
void mouseScroll(LLSD const & evt);
void pasteText(LLSD const & evt);
+ void cut(LLSD const & evt);
+ void copy(LLSD const & evt);
+ void paste(LLSD const & evt);
+ void selectAll(LLSD const & evt);
private:
LLViewerWindow * mWindow;
diff --git a/indra/newview/tests/llslurl_test.cpp b/indra/newview/tests/llslurl_test.cpp
index 3ff38ea372..bf23538201 100644
--- a/indra/newview/tests/llslurl_test.cpp
+++ b/indra/newview/tests/llslurl_test.cpp
@@ -147,10 +147,12 @@ namespace tut
{
slurlTest()
{
+ LLGridManager::createInstance();
LLGridManager::getInstance()->initialize(std::string(""));
}
~slurlTest()
{
+ LLGridManager::deleteSingleton();
}
};
diff --git a/indra/newview/tests/llviewernetwork_test.cpp b/indra/newview/tests/llviewernetwork_test.cpp
index 1cbb8b50eb..6190d7052b 100644
--- a/indra/newview/tests/llviewernetwork_test.cpp
+++ b/indra/newview/tests/llviewernetwork_test.cpp
@@ -175,6 +175,7 @@ namespace tut
{
viewerNetworkTest()
{
+ LLGridManager::createInstance();
LLFile::remove(TEST_FILENAME);
gCmdLineLoginURI.clear();
gCmdLineGridChoice.clear();
@@ -185,6 +186,7 @@ namespace tut
~viewerNetworkTest()
{
LLFile::remove(TEST_FILENAME);
+ LLGridManager::deleteSingleton();
}
};