diff options
| author | Erik Kundiman <erik@megapahit.org> | 2026-08-18 13:57:14 +0800 |
|---|---|---|
| committer | Erik Kundiman <erik@megapahit.org> | 2026-08-18 13:57:14 +0800 |
| commit | 2fa8281b2d8ce8bdbcacf139ec674480da6c9d81 (patch) | |
| tree | ffb343fee97f21942123fd322d88fea3ef1a4811 | |
| parent | 742d802f073e81abc6d4cd512e58a4d03b414b83 (diff) | |
| parent | 214fa765986b4c1e1de1b917581f8508278abd8e (diff) | |
Merge branch '26.3'
171 files changed, 4442 insertions, 10853 deletions
diff --git a/autobuild.xml b/autobuild.xml index 7184b72acb..c913905519 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2219,64 +2219,6 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>description</key> <string>Generated headers and sources for OpenXR loader.</string> </map> - <key>slvoice</key> - <map> - <key>platforms</key> - <map> - <key>darwin64</key> - <map> - <key>archive</key> - <map> - <key>hash</key> - <string>1e70b06fe6eb9796097010871b32d8e95167e373</string> - <key>hash_algorithm</key> - <string>sha1</string> - <key>url</key> - <string>https://automated-builds-secondlife-com.s3.amazonaws.com/gh/secondlife/3p-slvoice/slvoice-4.10.0000.32327.5fc3fe7c.5942f08-darwin64-5942f08.tar.zst</string> - </map> - <key>name</key> - <string>darwin64</string> - </map> - <key>linux64</key> - <map> - <key>archive</key> - <map> - <key>hash</key> - <string>92b0ae08832bd0e99c34ef8f3e6346ad</string> - <key>url</key> - <string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/613/1289/slvoice-3.2.0002.10426.500605-linux64-500605.tar.bz2</string> - </map> - <key>name</key> - <string>linux64</string> - </map> - <key>windows64</key> - <map> - <key>archive</key> - <map> - <key>hash</key> - <string>ddfb7c30d9756915e8b26f44e2ee3a69ee87fb9a</string> - <key>hash_algorithm</key> - <string>sha1</string> - <key>url</key> - <string>https://automated-builds-secondlife-com.s3.amazonaws.com/gh/secondlife/3p-slvoice/slvoice-4.10.0000.32327.5fc3fe7c.5942f08-windows64-5942f08.tar.zst</string> - </map> - <key>name</key> - <string>windows64</string> - </map> - </map> - <key>license</key> - <string>Mixed</string> - <key>license_file</key> - <string>LICENSES/vivox_licenses.txt</string> - <key>copyright</key> - <string>2010 Vivox, including audio coding using Polycom¨ Siren14TM (ITU-T Rec. G.722.1 Annex C)</string> - <key>version</key> - <string>4.10.0000.32327.5fc3fe7c.5942f08</string> - <key>name</key> - <string>slvoice</string> - <key>description</key> - <string>Vivox SDK components</string> - </map> <key>sse2neon</key> <map> <key>platforms</key> @@ -2700,11 +2642,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>72ed1f6d469a8ffaffd69be39b7af186d7c3b1d7</string> + <string>9622dbeb12d1c9592de3ea3f83c2c1a95b3c0822</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.22/webrtc-m137.7151.04.22.21966754211-darwin64-21966754211.tar.zst</string> + <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.19.hot-mic/webrtc-m144.7559.06.19.hot-mic.29761579711-darwin64-29761579711.tar.zst</string> </map> <key>name</key> <string>darwin64</string> @@ -2714,11 +2656,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>b4d0c836d99491841c3816ff93bb2655a2817bd3</string> + <string>9113fdfafca5ca750e415abafaf080540346e068</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.22/webrtc-m137.7151.04.22.21966754211-linux64-21966754211.tar.zst</string> + <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.19.hot-mic/webrtc-m144.7559.06.19.hot-mic.29761579711-linux64-29761579711.tar.zst</string> </map> <key>name</key> <string>linux64</string> @@ -2728,11 +2670,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>ab2bddd77b1568b22b50ead13c1c33da94f4d59a</string> + <string>1d8a82b72887abfb1c5014fcca4337703dfdcd25</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m137.7151.04.22/webrtc-m137.7151.04.22.21966754211-windows64-21966754211.tar.zst</string> + <string>https://github.com/secondlife/3p-webrtc-build/releases/download/m144.7559.06.19.hot-mic/webrtc-m144.7559.06.19.hot-mic.29761579711-windows64-29761579711.tar.zst</string> </map> <key>name</key> <string>windows64</string> @@ -2745,7 +2687,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>copyright</key> <string>Copyright (c) 2011, The WebRTC project authors. All rights reserved.</string> <key>version</key> - <string>m137.7151.04.22.21966754211</string> + <string>m144.7559.06.19.hot-mic.29761579711</string> <key>name</key> <string>webrtc</string> <key>vcs_branch</key> @@ -2989,14 +2931,12 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <map> <key>archive</key> <map> - <key>creds</key> - <string>github</string> <key>hash</key> - <string>91abbc360640b5b2e0a4c001a36ad411a9a42602</string> + <string>df2260187110aa51c20101183e18a55f86e71090</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583560</string> + <string>https://github.com/secondlife-3p/3p-velopack/releases/download/v0.0.1535-r2/velopack-40232ef.24685318450-windows64-24685318450.tar.zst</string> </map> <key>name</key> <string>windows64</string> @@ -3005,14 +2945,12 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <map> <key>archive</key> <map> - <key>creds</key> - <string>github</string> <key>hash</key> - <string>05563a79bdeb83d66a72ac1e97587dc2a8f64511</string> + <string>ead94386d7b9a143824d7ccedc86433127028a91</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583554</string> + <string>https://github.com/secondlife-3p/3p-velopack/releases/download/v0.0.1535-r2/velopack-40232ef.24685318450-darwin64-24685318450.tar.zst</string> </map> <key>name</key> <string>darwin64</string> @@ -3025,7 +2963,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>copyright</key> <string>Velopack Ltd.</string> <key>version</key> - <string>40232ef.23500976684</string> + <string>40232ef.24685318450</string> <key>name</key> <string>velopack</string> <key>description</key> @@ -3484,7 +3422,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>license_file</key> <string>docs/LICENSE-source.txt</string> <key>copyright</key> - <string>Copyright (c) 2020, Linden Research, Inc.</string> + <string>Copyright (c) 2026, Linden Research, Inc.</string> <key>version_file</key> <string>newview/viewer_version.txt</string> <key>name</key> diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 680f2f3ac2..667a04a1c5 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -29,29 +29,6 @@ endmacro() ################################################################### if(WINDOWS) #******************************* - # VIVOX - *NOTE: no debug version - set(vivox_lib_dir "${ARCH_PREBUILT_DIRS_RELEASE}") - - # ND, it seems there is no such thing defined. At least when building a viewer - # Does this maybe matter on some LL buildserver? Otherwise this and the snippet using slvoice_src_dir - # can all go - if( ARCH_PREBUILT_BIN_RELEASE ) - set(slvoice_src_dir "${ARCH_PREBUILT_BIN_RELEASE}") - endif() - set(slvoice_files SLVoice.exe ) - if (ADDRESS_SIZE EQUAL 64) - list(APPEND vivox_libs - vivoxsdk_x64.dll - ortp_x64.dll - ) - else (ADDRESS_SIZE EQUAL 64) - list(APPEND vivox_libs - vivoxsdk.dll - ortp.dll - ) - endif (ADDRESS_SIZE EQUAL 64) - - #******************************* # Misc shared libs set(release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}") @@ -159,12 +136,6 @@ if(WINDOWS) endforeach() elseif(DARWIN) - set(vivox_lib_dir "${ARCH_PREBUILT_DIRS_RELEASE}") - set(slvoice_files SLVoice) - set(vivox_libs - libortp.dylib - libvivoxsdk.dylib - ) set(debug_src_dir "${ARCH_PREBUILT_DIRS_DEBUG}") set(debug_files ) @@ -188,15 +159,6 @@ elseif(LINUX) set(SHARED_LIB_STAGING_DIR_RELWITHDEBINFO "${SHARED_LIB_STAGING_DIR}") set(SHARED_LIB_STAGING_DIR_RELEASE "${SHARED_LIB_STAGING_DIR}") - set(vivox_lib_dir "${ARCH_PREBUILT_DIRS_RELEASE}") - set(vivox_libs - libsndfile.so.1 - libortp.so - libvivoxoal.so.1 - libvivoxsdk.so - ) - set(slvoice_files SLVoice) - # *TODO - update this to use LIBS_PREBUILT_DIR and LL_ARCH_DIR variables # or ARCH_PREBUILT_DIRS set(debug_src_dir "${ARCH_PREBUILT_DIRS_DEBUG}") @@ -226,8 +188,6 @@ elseif(LINUX) else(WINDOWS) message(STATUS "WARNING: unrecognized platform for staging 3rd party libs, skipping...") - set(vivox_lib_dir "${CMAKE_SOURCE_DIR}/newview/vivox-runtime/i686-linux") - set(vivox_libs "") # *TODO - update this to use LIBS_PREBUILT_DIR and LL_ARCH_DIR variables # or ARCH_PREBUILT_DIRS set(debug_src_dir "${CMAKE_SOURCE_DIR}/../libraries/i686-linux/lib/debug") @@ -249,26 +209,6 @@ endif(WINDOWS) # Done building the file lists, now set up the copy commands. ################################################################ -# Curiously, slvoice_files are only copied to SHARED_LIB_STAGING_DIR_RELEASE. -# It's unclear whether this is oversight or intentional, but anyway leave the -# single copy_if_different command rather than using to_staging_dirs. - -if( slvoice_src_dir ) - copy_if_different( - ${slvoice_src_dir} - "${SHARED_LIB_STAGING_DIR_RELEASE}" - out_targets - ${slvoice_files} - ) - list(APPEND third_party_targets ${out_targets}) -endif() - -to_staging_dirs( - ${vivox_lib_dir} - third_party_targets - ${vivox_libs} - ) - to_staging_dirs( ${release_src_dir} third_party_targets diff --git a/indra/cmake/ViewerMiscLibs.cmake b/indra/cmake/ViewerMiscLibs.cmake index c1f7678235..5efca62032 100644 --- a/indra/cmake/ViewerMiscLibs.cmake +++ b/indra/cmake/ViewerMiscLibs.cmake @@ -3,19 +3,10 @@ include(Prebuilt) if (NOT (DARWIN OR WINDOWS)) add_library( ll::fontconfig INTERFACE IMPORTED ) - find_package(Fontconfig REQUIRED) target_link_libraries( ll::fontconfig INTERFACE Fontconfig::Fontconfig ) endif () -if (FALSE) -if( NOT USE_CONAN ) - use_prebuilt_binary(libhunspell) -endif() - -use_prebuilt_binary(slvoice) -endif (FALSE) - if (DARWIN OR (${LINUX_DISTRO} MATCHES freedesktop)) use_prebuilt_binary(nanosvg) endif () diff --git a/indra/cmake/WebRTC.cmake b/indra/cmake/WebRTC.cmake index 286d3aa172..1575c30d82 100644 --- a/indra/cmake/WebRTC.cmake +++ b/indra/cmake/WebRTC.cmake @@ -6,27 +6,18 @@ include(Prebuilt) add_library( ll::webrtc INTERFACE IMPORTED ) target_include_directories( ll::webrtc SYSTEM INTERFACE "${LIBS_PREBUILT_DIR}/include/webrtc" "${LIBS_PREBUILT_DIR}/include/webrtc/third_party/abseil-cpp") -if (DARWIN OR WINDOWS) -use_prebuilt_binary(webrtc) -elseif (NOT (CMAKE_SYSTEM_NAME MATCHES FreeBSD OR ($ENV{MSYSTEM_CARCH} MATCHES aarch64))) +if (CMAKE_SYSTEM_PROCESSOR MATCHES aarch64 AND LINUX) target_compile_definitions(ll::webrtc INTERFACE CM_WEBRTC=1) if (${PREBUILD_TRACKING_DIR}/sentinel_installed IS_NEWER_THAN ${PREBUILD_TRACKING_DIR}/webrtc_installed OR NOT ${webrtc_installed} EQUAL 0) - if (DARWIN) - set(WEBRTC_PLATFORM macos-arm64) - elseif (CMAKE_SYSTEM_PROCESSOR MATCHES aarch64) - set(WEBRTC_PLATFORM linux-arm64) - else () - set(WEBRTC_PLATFORM linux-x64) - endif () - if (NOT EXISTS ${CMAKE_BINARY_DIR}/libwebrtc-${WEBRTC_PLATFORM}.tar.xz) + if (NOT EXISTS ${CMAKE_BINARY_DIR}/libwebrtc-linux-arm64.tar.xz) file(DOWNLOAD - https://github.com/crow-misia/libwebrtc-bin/releases/download/137.7151.3.1/libwebrtc-${WEBRTC_PLATFORM}.tar.xz - ${CMAKE_BINARY_DIR}/libwebrtc-${WEBRTC_PLATFORM}.tar.xz + https://github.com/crow-misia/libwebrtc-bin/releases/download/139.7258.3.1/libwebrtc-linux-arm64.tar.xz + ${CMAKE_BINARY_DIR}/libwebrtc-linux-arm64.tar.xz SHOW_PROGRESS ) endif () file(ARCHIVE_EXTRACT - INPUT ${CMAKE_BINARY_DIR}/libwebrtc-${WEBRTC_PLATFORM}.tar.xz + INPUT ${CMAKE_BINARY_DIR}/libwebrtc-linux-arm64.tar.xz DESTINATION ${LIBS_PREBUILT_DIR} ) file(REMOVE_RECURSE ${LIBS_PREBUILT_DIR}/include/webrtc) @@ -34,7 +25,6 @@ elseif (NOT (CMAKE_SYSTEM_NAME MATCHES FreeBSD OR ($ENV{MSYSTEM_CARCH} MATCHES a foreach(directory api audio - base build buildtools call @@ -67,16 +57,10 @@ elseif (NOT (CMAKE_SYSTEM_NAME MATCHES FreeBSD OR ($ENV{MSYSTEM_CARCH} MATCHES a ${LIBS_PREBUILT_DIR}/lib/libwebrtc.a ${ARCH_PREBUILT_DIRS_RELEASE}/libwebrtc.a ) - if (CMAKE_OSX_ARCHITECTURES MATCHES arm64) - file(REMOVE_RECURSE ${ARCH_PREBUILT_DIRS_RELEASE}/WebRTC.framework) - file(RENAME - ${LIBS_PREBUILT_DIR}/Frameworks/WebRTC.xcframework/${WEBRTC_PLATFORM}/WebRTC.framework - ${ARCH_PREBUILT_DIRS_RELEASE}/WebRTC.framework - ) - file(REMOVE_RECURSE ${LIBS_PREBUILT_DIR}/Frameworks) - endif () file(WRITE ${PREBUILD_TRACKING_DIR}/webrtc_installed "0") endif () +elseif (NOT (CMAKE_SYSTEM_NAME MATCHES FreeBSD OR ($ENV{MSYSTEM_CARCH} MATCHES aarch64))) +use_prebuilt_binary(webrtc) endif () find_library(WEBRTC_LIBRARY diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index ba48319a16..4e44b9a56a 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -52,6 +52,9 @@ //static +// On windows commit charge information is vital for OOM diagnosis. +U32Megabytes LLMemory::sAvailCommitMemInMB(U32_MAX); + // most important memory metric for texture streaming // On Windows, this should agree with resource monitor -> performance -> memory -> available // On OS X, this should be activity monitor -> memory -> (physical memory - memory used) @@ -70,6 +73,12 @@ U32Kilobytes LLMemory::sAllocatedMemInKB(0); U32Kilobytes LLMemory::sAllocatedPageSizeInKB(0); +LLFrameTimer LLMemory::sMemoryCheckTimer; +F32 LLMemory::sSysMemoryFactor = 1.f; +U32 LLMemory::sFactorLastFrameCount = 0; + +static const S32Megabytes MEM_LOW_THRESHOLD = S32Megabytes(256); + static LLTrace::SampleStatHandle<F64Megabytes> sAllocatedMem("allocated_mem", "active memory in use by application"); static LLTrace::SampleStatHandle<F64Megabytes> sVirtualMem("virtual_mem", "virtual memory assigned to application"); @@ -102,23 +111,14 @@ void LLMemory::updateMemoryInfo() { LL_PROFILE_ZONE_SCOPED; + sMemoryCheckTimer.reset(); sMaxPhysicalMemInKB = gSysMemory.getPhysicalMemoryKB(); - U32Kilobytes avail_mem; - LLMemoryInfo::getAvailableMemoryKB(avail_mem); - sAvailPhysicalMemInKB = avail_mem; + LLMemoryInfo::updateAvailableMemory(); #if LL_WINDOWS - PROCESS_MEMORY_COUNTERS counters; - - if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters))) - { - LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL; - return ; - } - - sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize)); - sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage)); + // On windows updateAvailableMemory fills sAvailPhysicalMemInKB, + //sAllocatedMemInKB and sAllocatedPageSizeInKB sample(sVirtualMem, sAllocatedPageSizeInKB); #elif defined(LL_DARWIN) @@ -201,6 +201,97 @@ void LLMemory::logMemoryInfo(bool update) LL_INFOS() << llformat("Current max usable memory: %.2f MB", sMaxPhysicalMemInKB / 1024.0) << LL_ENDL; } +void LLMemory::updateFreeSystemMemory() +{ + if (sMemoryCheckTimer.getElapsedTimeF32() >= 1.f) //once per second. + { + LLMemory::updateMemoryInfo(); // resets the timer + } +} + +F32 LLMemory::getSystemMemoryBudgetFactor() +{ + // Only update once per frame + U32 current_frame = LLFrameTimer::getFrameCount(); + if (sFactorLastFrameCount == current_frame) + { + return sSysMemoryFactor; + } + sFactorLastFrameCount = current_frame; + + updateFreeSystemMemory(); +#if LL_WINDOWS + S32Megabytes free_sys_mem = getAvailableCommitMemMB(); +#else + S32Megabytes free_sys_mem = getAvailableMemKB(); +#endif + bool is_sys_low = free_sys_mem < MEM_LOW_THRESHOLD; + static bool was_low = false; + + // sSysMemoryFactor affects draw distance + // + // We only decrement when more than 406MB is free, but increment + // when below 256MB free. This should provide a stable value + // in the 256-406MB range to avoid draw range fluctuations. + // + // Draw range reduction is a last resort, texture bias is supposed + // to free at least some memory before we get here. + // Note: textures were mostly moved to vram, we might want to + // detach texture bias from system memory. + if (is_sys_low) + { + // debt is a negative value since MIN_FREE_MAIN_MEMORY > free memory. + S32Megabytes sys_budget_debt = free_sys_mem - MEM_LOW_THRESHOLD; + + // Leave some padding, otherwise we will crash out of memory before hitting factor 2. + const S32Megabytes PAD_BUFFER(32); + S32Megabytes budget_target = MEM_LOW_THRESHOLD - PAD_BUFFER; + if (!was_low) + { + // Result should range from 1 at 0 debt to 2 at -224 debt, 2.14 at -256MB + F32 new_factor = 1.f - (F32)sys_budget_debt.value() / (F32)budget_target.value(); + sSysMemoryFactor = llmax(sSysMemoryFactor, new_factor); + } + else + { + // Slowly ramp up factor to free memory (increasing factor decreases draw range) + constexpr F32 MAX_INCREMENT = 0.05f; + F32 increment = MAX_INCREMENT * llmax(-(F32)sys_budget_debt.value() / (F32)budget_target.value(), 0.f); + sSysMemoryFactor += increment * LLFrameTimer::getFrameDeltaTimeF32(); + } + sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f); + } + else + { + // Only start ramping down when we have breathing room. + // This should be under the value of isSystemMemoryLow to not throw texture + // bias into 1.5+ territory each time we fluctuate around isSystemMemoryLow's + // threshold. + const S32Megabytes MEM_THRESHOLD = MEM_LOW_THRESHOLD + S32Megabytes(150); + if (free_sys_mem > MEM_THRESHOLD && sSysMemoryFactor > 1.f) + { + // Ramp down factor over time. + constexpr F32 DECREMENT = 0.02f; + sSysMemoryFactor -= DECREMENT * LLFrameTimer::getFrameDeltaTimeF32(); + sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f); + } + } + was_low = is_sys_low; + + return sSysMemoryFactor; +} + +#if LL_WINDOWS +//static +U32Megabytes LLMemory::getAvailableCommitMemMB() +{ + // Commit charge combines page file and ram, + // theoretical limit is 128TB on 64bit windows. + // Store as MB instead of KB to prevent overflow. + return sAvailCommitMemInMB; +} +#endif + //static U32Kilobytes LLMemory::getAvailableMemKB() { diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index c06ae93766..5ac114cac9 100644 --- a/indra/llcommon/llmemory.h +++ b/indra/llcommon/llmemory.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -27,6 +27,7 @@ #define LLMEMORY_H #include "linden_common.h" +#include "llframetimer.h" #include "llunits.h" #include "stdtypes.h" #if !LL_WINDOWS @@ -428,17 +429,31 @@ public: static void initMaxHeapSizeGB(F32Gigabytes max_heap_size); static void updateMemoryInfo() ; static void logMemoryInfo(bool update = false); + static F32 getSystemMemoryBudgetFactor(); +#if LL_WINDOWS + // Commit charge is a Windows-only concept, combines page file and ram + static U32Megabytes getAvailableCommitMemMB(); +#endif static U32Kilobytes getAvailableMemKB() ; static U32Kilobytes getMaxMemKB() ; static U32Kilobytes getAllocatedMemKB() ; private: + static void updateFreeSystemMemory(); + // LLMemoryInfo directly updates memory stats + friend class LLMemoryInfo; + + static U32Megabytes sAvailCommitMemInMB; static U32Kilobytes sAvailPhysicalMemInKB ; static U32Kilobytes sMaxPhysicalMemInKB ; static U32Kilobytes sAllocatedMemInKB; static U32Kilobytes sAllocatedPageSizeInKB ; static U32Kilobytes sMaxHeapSizeInKB; + + static LLFrameTimer sMemoryCheckTimer; + static F32 sSysMemoryFactor; + static U32 sFactorLastFrameCount; }; // LLRefCount moved to llrefcount.h diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 542de4ee67..452acf4322 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -43,6 +43,7 @@ #include "llerrorcontrol.h" #include "llevents.h" #include "llformat.h" +#include "llmemory.h" #include "llregex.h" #include "lltimer.h" #include "llsdserialize.h" @@ -811,15 +812,13 @@ U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const } //static -void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_mem_kb) +void LLMemoryInfo::updateAvailableMemory() { LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; #if LL_WINDOWS - // Sigh, this shouldn't be a static method, then we wouldn't have to - // reload this data separately from refresh() - LLSD statsMap(loadStatsMap()); - - avail_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger(); + // On windows loadStatsMap will fill sAvailPhysicalMemInKB, + // sAvailCommitMemInMB, sAllocatedMemInKB and sAllocatedPageSizeInKB + loadStatsMap(); #elif LL_DARWIN // use host_statistics64 to get memory info @@ -831,11 +830,11 @@ void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_mem_kb) kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast<host_info_t>(&vmstat), &count); if (result == KERN_SUCCESS) { - avail_mem_kb = U64Bytes((vmstat.free_count + vmstat.inactive_count) * page_size); + LLMemory::sAvailPhysicalMemInKB = U64Bytes((vmstat.free_count + vmstat.inactive_count) * page_size); } else { - avail_mem_kb = (U32Kilobytes)-1; + LLMemory::sAvailPhysicalMemInKB = (U32Kilobytes)-1; } #elif LL_LINUX @@ -889,12 +888,12 @@ void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_mem_kb) // (could also run 'free', but easier to read a file than run a program) LLSD statsMap(loadStatsMap()); - avail_mem_kb = (U32Kilobytes)statsMap["MemFree"].asInteger(); + LLMemory::sAvailPhysicalMemInKB = (U32Kilobytes)statsMap["MemFree"].asInteger(); #else //do not know how to collect available memory info for other systems. //leave it blank here for now. - avail_mem_kb = (U32Kilobytes)-1 ; + LLMemory::sAvailPhysicalMemInKB = (U32Kilobytes)-1 ; #endif } @@ -965,15 +964,24 @@ LLSD LLMemoryInfo::loadStatsMap() state.dwLength = sizeof(state); GlobalMemoryStatusEx(&state); - DWORDLONG div = 1024; + static constexpr DWORDLONG div = 1024; - stats.add("Percent Memory use", state.dwMemoryLoad/div); + stats.add("Percent Memory use", state.dwMemoryLoad); stats.add("Total Physical KB", state.ullTotalPhys/div); stats.add("Avail Physical KB", state.ullAvailPhys/div); + + // Despite the confusing naming "PageFile" , these values + // actually represent the committed memory limit for + // the system or the current process, whichever is smaller. stats.add("Total page KB", state.ullTotalPageFile/div); stats.add("Avail page KB", state.ullAvailPageFile/div); - stats.add("Total Virtual KB", state.ullTotalVirtual/div); - stats.add("Avail Virtual KB", state.ullAvailVirtual/div); + + static constexpr DWORDLONG mb_div = 1024 * 1024; + stats.add("Total Virtual MB", state.ullTotalVirtual/mb_div); // ~134 million MB + stats.add("Avail Virtual MB", state.ullAvailVirtual/mb_div); + + LLMemory::sAvailPhysicalMemInKB = U32Kilobytes::convert(U64Bytes(state.ullAvailPhys)); + LLMemory::sAvailCommitMemInMB = U32Megabytes::convert(U64Bytes(state.ullAvailPageFile)); // SL-12122 - Call to GetPerformanceInfo() was removed here. Took // on order of 10 ms, causing unacceptable frame time spike every @@ -989,18 +997,38 @@ LLSD LLMemoryInfo::loadStatsMap() // specifically accepts PROCESS_MEMORY_COUNTERS*, and since this is a // classic-C API, PROCESS_MEMORY_COUNTERS_EX isn't a subclass. Cast the // pointer. - GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*) &pmem, sizeof(pmem)); + if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmem, sizeof(pmem))) + { + LLMemory::sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(pmem.WorkingSetSize)); + LLMemory::sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(pmem.PagefileUsage)); - stats.add("Page Fault Count", pmem.PageFaultCount); - stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize/div); - stats.add("WorkingSetSize KB", pmem.WorkingSetSize/div); - stats.add("QutaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage/div); - stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage/div); - stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage/div); - stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage/div); - stats.add("PagefileUsage KB", pmem.PagefileUsage/div); - stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage/div); - stats.add("PrivateUsage KB", pmem.PrivateUsage/div); + stats.add("Page Fault Count", pmem.PageFaultCount); + stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize / div); + stats.add("WorkingSetSize KB", pmem.WorkingSetSize / div); + stats.add("QuotaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage / div); + stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage / div); + stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage / div); + stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage / div); + stats.add("PagefileUsage KB", pmem.PagefileUsage / div); + stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage / div); + stats.add("PrivateUsage KB", pmem.PrivateUsage / div); + } + else + { + LLMemory::sAllocatedMemInKB = U32Kilobytes(0); + LLMemory::sAllocatedPageSizeInKB = U32Kilobytes(0); + + stats.add("Page Fault Count", 0); + stats.add("PeakWorkingSetSize KB", 0); + stats.add("WorkingSetSize KB", 0); + stats.add("QuotaPeakPagedPoolUsage KB", 0); + stats.add("QuotaPagedPoolUsage KB", 0); + stats.add("QuotaPeakNonPagedPoolUsage KB", 0); + stats.add("QuotaNonPagedPoolUsage KB", 0); + stats.add("PagefileUsage KB", 0); + stats.add("PeakPagefileUsage KB", 0); + stats.add("PrivateUsage KB", 0); + } #elif LL_DARWIN diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h index c8ff4dd98a..91fa2754ab 100644 --- a/indra/llcommon/llsys.h +++ b/indra/llcommon/llsys.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -134,11 +134,12 @@ public: static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib. #endif - //get the available memory in KiloBytes. - static void getAvailableMemoryKB(U32Kilobytes& avail_mem_kb); + // Updates LLMemory's values. + static void updateAvailableMemory(); // Retrieve a map of memory statistics. The keys of the map are platform- - // dependent. The values are in kilobytes to try to avoid integer overflow. + // dependent. + // On Windows updates LLMemory values. LLSD getStatsMap() const; // Re-fetch memory data (as reported by stream() and getStatsMap()) from the diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp index 66b565c763..886f19366c 100644 --- a/indra/llcommon/llwatchdog.cpp +++ b/indra/llcommon/llwatchdog.cpp @@ -116,6 +116,11 @@ bool LLWatchdogTimeout::isAlive() const return (mTimer.getStarted() && !mTimer.hasExpired()); } +bool LLWatchdogTimeout::started() const +{ + return mTimer.getStarted(); +} + void LLWatchdogTimeout::reset() { mTimer.setTimerExpirySec(mTimeout); diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h index f138fbccb0..d55bf434f3 100644 --- a/indra/llcommon/llwatchdog.h +++ b/indra/llcommon/llwatchdog.h @@ -47,6 +47,7 @@ public: // This may mean that resources used by // isAlive and other method may need synchronization. virtual bool isAlive() const = 0; + virtual bool started() const = 0; virtual void reset() = 0; virtual void start(); virtual void stop(); @@ -66,6 +67,7 @@ public: virtual ~LLWatchdogTimeout(); bool isAlive() const override; + bool started() const override; void reset() override; void start() override { start(""); } void stop() override; diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index e7ac6bdb31..cf45f93168 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -158,6 +158,9 @@ public: kdu_codestream* codestreamp); ~LLKDUDecodeState(); bool processTileDecode(F32 decode_time, bool limit_time = true); +#ifdef LL_WINDOWS + bool processTileDecodeSEH(F32 decode_time, bool limit_time = true); +#endif private: S32 mNumComponents; @@ -282,10 +285,16 @@ void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, bool keep_codestream, ECod try { - S32 data_size = base.getDataSize(); S32 max_bytes = (base.getMaxBytes() ? base.getMaxBytes() : data_size); + // Data sanity checks + // 0 data size appears to be valid. + if (data_size < 0 || data_size > 128 * 1024 * 1024) // 128MB + { + LLTHROW(KDUError(STRINGIZE("Invalid data size: " << data_size))); + } + // // Initialization // @@ -385,7 +394,8 @@ void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, bool keep_codestream, ECod catch (std::bad_alloc&) { // we are in a thread, can't show an 'out of memory' here, - // main thread will keep going + // as main thread will keep going + // Todo: should cause main thread to stop and show an error. throw; } catch (...) @@ -602,7 +612,11 @@ bool LLImageJ2CKDU::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 deco // Do the actual processing F32 remaining_time = limit_time ? decode_time - decode_timer.getElapsedTimeF32().value() : 0.0f; // This is where we do the actual decode. If we run out of time, return false. +#ifdef LL_WINDOWS + if (mDecodeState->processTileDecodeSEH(remaining_time, limit_time)) +#else if (mDecodeState->processTileDecode(remaining_time, limit_time)) +#endif { mDecodeState.reset(); } @@ -1302,6 +1316,11 @@ all necessary level shifting, type conversion, rounding and truncation. */ LLKDUDecodeState::LLKDUDecodeState(kdu_tile tile, kdu_byte *buf, S32 row_gap, kdu_codestream* codestreamp) { + if (!buf) + { + LLTHROW(KDUError("Null buffer passed to LLKDUDecodeState")); + } + S32 c; mTile = tile; @@ -1311,6 +1330,11 @@ LLKDUDecodeState::LLKDUDecodeState(kdu_tile tile, kdu_byte *buf, S32 row_gap, mNumComponents = tile.get_num_components(); llassert(mNumComponents <= 4); + if (mNumComponents <= 0 || mNumComponents > 4) + { + LL_WARNS() << "Invalid component count: " << mNumComponents << ", clamping to valid range" << LL_ENDL; + mNumComponents = llclamp(mNumComponents, 1, 4); + } mUseYCC = tile.get_ycc(); for (c = 0; c < 4; ++c) @@ -1363,6 +1387,20 @@ LLKDUDecodeState::~LLKDUDecodeState() mTile.close(); } +#ifdef LL_WINDOWS +bool LLKDUDecodeState::processTileDecodeSEH(F32 decode_time, bool limit_time) +{ + __try + { + return processTileDecode(decode_time, limit_time); + } + __except (msc_exception_filter(GetExceptionCode(), GetExceptionInformation())) + { + return false; + } +} +#endif + bool LLKDUDecodeState::processTileDecode(F32 decode_time, bool limit_time) /* Decompresses a tile, writing the data into the supplied byte buffer. The buffer contains interleaved image components, if there are any. diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp index 4c3acb27f4..b6a98575f9 100644 --- a/indra/llmessage/llassetstorage.cpp +++ b/indra/llmessage/llassetstorage.cpp @@ -453,6 +453,7 @@ bool LLAssetStorage::findInCacheAndInvokeCallback(const LLUUID& uuid, LLAssetTyp bool exists = LLFileSystem::getExists(uuid, type); if (exists) { + LL_PROFILE_ZONE_SCOPED; LLFileSystem file(uuid, type); U32 size = file.getSize(); if (size > 0) @@ -562,7 +563,7 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, if (callback == tmp->mDownCallback && user_data == tmp->mUserData) { // this is a duplicate from the same subsystem - throw it away - LL_WARNS("AssetStorage") << "Discarding duplicate request for asset " << uuid + LL_DEBUGS("AssetStorage") << "Discarding duplicate request for asset " << uuid << "." << LLAssetType::lookup(type) << LL_ENDL; return; } diff --git a/indra/llmessage/lldatapacker.cpp b/indra/llmessage/lldatapacker.cpp index e911150787..ecd0b4ee8d 100644 --- a/indra/llmessage/lldatapacker.cpp +++ b/indra/llmessage/lldatapacker.cpp @@ -289,32 +289,46 @@ bool LLDataPackerBinaryBuffer::packBinaryData(const U8 *value, S32 size, const c } -bool LLDataPackerBinaryBuffer::unpackBinaryData(U8 *value, S32 &size, const char *name) +bool LLDataPackerBinaryBuffer::unpackBinaryData(U8 *value, S32 value_size, S32 &out_size, const char *name) { if (!verifyLength(4, name)) { LL_WARNS() << "LLDataPackerBinaryBuffer::unpackBinaryData would unpack invalid data, aborting!" << LL_ENDL; + out_size = 0; return false; } - htolememcpy(&size, mCurBufferp, MVT_S32, 4); + if (value_size < 0) + { + LL_WARNS() << "LLDataPackerBinaryBuffer::unpackBinaryData passed negative buffer size, aborting!" << LL_ENDL; + out_size = 0; + return false; + } + + htolememcpy(&out_size, mCurBufferp, MVT_S32, 4); - if (size < 0) + if (out_size < 0) { LL_WARNS() << "LLDataPackerBinaryBuffer::unpackBinaryData unpacked invalid size, aborting!" << LL_ENDL; + out_size = 0; return false; } mCurBufferp += 4; - if (!verifyLength(size, name)) + if (!verifyLength(out_size, name)) { LL_WARNS() << "LLDataPackerBinaryBuffer::unpackBinaryData would unpack invalid data, aborting!" << LL_ENDL; return false; } + S32 copy_size = llmin(out_size, value_size); + htolememcpy(value, mCurBufferp, MVT_VARIABLE, copy_size); + mCurBufferp += out_size; - htolememcpy(value, mCurBufferp, MVT_VARIABLE, size); - mCurBufferp += size; + if (value_size < out_size) + { + LL_WARNS() << "LLDataPackerBinaryBuffer::unpackBinaryData buffer too small for data, truncating!" << LL_ENDL; + } return true; } @@ -836,21 +850,34 @@ bool LLDataPackerAsciiBuffer::packBinaryData(const U8 *value, S32 size, const ch } -bool LLDataPackerAsciiBuffer::unpackBinaryData(U8 *value, S32 &size, const char *name) +bool LLDataPackerAsciiBuffer::unpackBinaryData(U8 *value, S32 value_size, S32 &out_size, const char *name) { bool success = true; char valuestr[DP_BUFSIZE]; /* Flawfinder: ignore */ if (!getValueStr(name, valuestr, DP_BUFSIZE)) { + out_size = 0; + return false; + } + + if (value_size < 0) + { + LL_WARNS() << "LLDataPackerBinaryBuffer::unpackBinaryData passed negative buffer size, aborting!" << LL_ENDL; + out_size = 0; return false; } char *cur_pos = &valuestr[0]; - sscanf(valuestr,"%010d", &size); + sscanf(valuestr,"%010d", &out_size); cur_pos += 11; + S32 max_bytes = llmin(out_size, value_size); + if (max_bytes != out_size) + { + LL_WARNS() << "LLDataPackerAsciiBuffer::unpackBinaryData: buffer too small for data, truncating!" << LL_ENDL; + } S32 i; - for (i = 0; i < size; i++) + for (i = 0; i < max_bytes; i++) { S32 val; sscanf(cur_pos,"%02x", &val); @@ -1634,28 +1661,40 @@ bool LLDataPackerAsciiFile::packBinaryData(const U8 *value, S32 size, const char } -bool LLDataPackerAsciiFile::unpackBinaryData(U8 *value, S32 &size, const char *name) +bool LLDataPackerAsciiFile::unpackBinaryData(U8 *value, S32 value_size, S32 &out_size, const char *name) { - bool success = true; char valuestr[DP_BUFSIZE]; /*Flawfinder: ignore*/ if (!getValueStr(name, valuestr, DP_BUFSIZE)) { + out_size = 0; + return false; + } + + if (value_size < 0) + { + LL_WARNS() << "LLDataPackerBinaryBuffer::unpackBinaryData passed negative buffer size, aborting!" << LL_ENDL; + out_size = 0; return false; } char *cur_pos = &valuestr[0]; - sscanf(valuestr,"%010d", &size); + sscanf(valuestr,"%010d", &out_size); cur_pos += 11; + S32 max_bytes = llmin(out_size, value_size); + if (max_bytes != out_size) + { + LL_WARNS() << "LLDataPackerAsciiBuffer::unpackBinaryData: buffer too small for data, truncating!" << LL_ENDL; + } S32 i; - for (i = 0; i < size; i++) + for (i = 0; i < max_bytes; i++) { S32 val; sscanf(cur_pos,"%02x", &val); value[i] = val; cur_pos += 3; } - return success; + return true; } diff --git a/indra/llmessage/lldatapacker.h b/indra/llmessage/lldatapacker.h index 167c102b43..5ac45356cf 100644 --- a/indra/llmessage/lldatapacker.h +++ b/indra/llmessage/lldatapacker.h @@ -49,7 +49,7 @@ public: virtual bool unpackString(std::string& value, const char *name) = 0; virtual bool packBinaryData(const U8 *value, S32 size, const char *name) = 0; - virtual bool unpackBinaryData(U8 *value, S32 &size, const char *name) = 0; + virtual bool unpackBinaryData(U8 *value, S32 value_size, S32 &out_size, const char *name) = 0; // Constant size binary data packing virtual bool packBinaryDataFixed(const U8 *value, S32 size, const char *name) = 0; @@ -135,7 +135,7 @@ public: /*virtual*/ bool unpackString(std::string& value, const char *name); /*virtual*/ bool packBinaryData(const U8 *value, S32 size, const char *name); - /*virtual*/ bool unpackBinaryData(U8 *value, S32 &size, const char *name); + /*virtual*/ bool unpackBinaryData(U8 *value, S32 value_size, S32 &out_size, const char *name); // Constant size binary data packing /*virtual*/ bool packBinaryDataFixed(const U8 *value, S32 size, const char *name); @@ -246,7 +246,7 @@ public: /*virtual*/ bool unpackString(std::string& value, const char *name); /*virtual*/ bool packBinaryData(const U8 *value, S32 size, const char *name); - /*virtual*/ bool unpackBinaryData(U8 *value, S32 &size, const char *name); + /*virtual*/ bool unpackBinaryData(U8 *value, S32 value_size, S32 &out_size, const char *name); // Constant size binary data packing /*virtual*/ bool packBinaryDataFixed(const U8 *value, S32 size, const char *name); @@ -378,7 +378,7 @@ public: /*virtual*/ bool unpackString(std::string& value, const char *name); /*virtual*/ bool packBinaryData(const U8 *value, S32 size, const char *name); - /*virtual*/ bool unpackBinaryData(U8 *value, S32 &size, const char *name); + /*virtual*/ bool unpackBinaryData(U8 *value, S32 value_size, S32 &out_size, const char *name); /*virtual*/ bool packBinaryDataFixed(const U8 *value, S32 size, const char *name); /*virtual*/ bool unpackBinaryDataFixed(U8 *value, S32 size, const char *name); diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp index 39f3bdce6d..6ab378f30f 100644 --- a/indra/llplugin/llpluginclassmedia.cpp +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -168,6 +168,7 @@ void LLPluginClassMedia::reset() void LLPluginClassMedia::idle(void) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; if(mPlugin) { mPlugin->idle(); diff --git a/indra/llprimitive/llprimitive.cpp b/indra/llprimitive/llprimitive.cpp index c116f69a6d..e74adc16b8 100644 --- a/indra/llprimitive/llprimitive.cpp +++ b/indra/llprimitive/llprimitive.cpp @@ -1515,7 +1515,7 @@ S32 LLPrimitive::unpackTEMessage(LLDataPacker &dp) S32 size; U32 face_count = 0; - if (!dp.unpackBinaryData(packed_buffer, size, "TextureEntry")) + if (!dp.unpackBinaryData(packed_buffer, MAX_TE_BUFFER, size, "TextureEntry")) { retval = TEM_INVALID; LL_WARNS() << "Bad texture entry block! Abort!" << LL_ENDL; diff --git a/indra/llprimitive/lltextureanim.cpp b/indra/llprimitive/lltextureanim.cpp index 579538075a..963b7456b8 100644 --- a/indra/llprimitive/lltextureanim.cpp +++ b/indra/llprimitive/lltextureanim.cpp @@ -155,7 +155,7 @@ void LLTextureAnim::unpackTAMessage(LLDataPacker &dp) { S32 size; U8 data[TA_BLOCK_SIZE]; - dp.unpackBinaryData(data, size, "TextureAnimation"); + dp.unpackBinaryData(data, TA_BLOCK_SIZE, size, "TextureAnimation"); if (size != TA_BLOCK_SIZE) { if (size) diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp index 91898e6de1..357dfc8ea8 100644 --- a/indra/llrender/llfontfreetype.cpp +++ b/indra/llrender/llfontfreetype.cpp @@ -990,30 +990,39 @@ namespace ll U8 const* LLFontManager::loadFont( std::string const &aFilename, long &a_Size) { - a_Size = 0; - std::map< std::string, std::shared_ptr<ll::fonts::LoadedFont> >::iterator itr = m_LoadedFonts.find( aFilename ); - if( itr != m_LoadedFonts.end() ) + try { - ++itr->second->mRefs; - // A possible overflow cannot happen here, as it is asserted that the size is less than std::numeric_limits<long>::max() a few lines below. - a_Size = static_cast<long>(itr->second->mSize); - return reinterpret_cast<U8 const*>(itr->second->mAddress.c_str()); - } + a_Size = 0; + std::map< std::string, std::shared_ptr<ll::fonts::LoadedFont> >::iterator itr = m_LoadedFonts.find(aFilename); + if (itr != m_LoadedFonts.end()) + { + ++itr->second->mRefs; + // A possible overflow cannot happen here, as it is asserted that the size is less than std::numeric_limits<long>::max() a few lines below. + a_Size = static_cast<long>(itr->second->mSize); + return reinterpret_cast<U8 const*>(itr->second->mAddress.c_str()); + } - auto strContent = LLFile::getContents(aFilename); + auto strContent = LLFile::getContents(aFilename); - if( strContent.empty() ) - return nullptr; + if (strContent.empty()) + return nullptr; - // For fontconfig a type of long is required, std::string::size() returns size_t. I think it is safe to limit this to 2GiB and not support fonts that huge (can that even be a thing?) - llassert_always( strContent.size() < std::numeric_limits<long>::max() ); + // For fontconfig a type of long is required, std::string::size() returns size_t. I think it is safe to limit this to 2GiB and not support fonts that huge (can that even be a thing?) + llassert_always(strContent.size() < std::numeric_limits<long>::max()); - a_Size = static_cast<long>(strContent.size()); + a_Size = static_cast<long>(strContent.size()); - auto pCache = std::make_shared<ll::fonts::LoadedFont>( aFilename, strContent, a_Size ); - itr = m_LoadedFonts.insert( std::make_pair( aFilename, pCache ) ).first; + auto pCache = std::make_shared<ll::fonts::LoadedFont>(aFilename, strContent, a_Size); + itr = m_LoadedFonts.insert(std::make_pair(aFilename, pCache)).first; - return reinterpret_cast<U8 const*>(itr->second->mAddress.c_str()); + return reinterpret_cast<U8 const*>(itr->second->mAddress.c_str()); + } + catch (const std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS() << "Failed to load font. Out of memory." << LL_ENDL; + } + return nullptr; } void LLFontManager::unloadAllFonts() diff --git a/indra/llrender/llfontvertexbuffer.cpp b/indra/llrender/llfontvertexbuffer.cpp index a223509d30..2a0115265f 100644 --- a/indra/llrender/llfontvertexbuffer.cpp +++ b/indra/llrender/llfontvertexbuffer.cpp @@ -237,3 +237,80 @@ void LLFontVertexBuffer::renderBuffers() gGL.popUIMatrix(); } +// LLFontWidthBuffer +bool LLFontWidthBuffer::sEnableBufferCollection = true; + +LLFontWidthBuffer::LLFontWidthBuffer() +{ +} + +LLFontWidthBuffer::~LLFontWidthBuffer() +{ +} + +void LLFontWidthBuffer::reset() +{ + mLastFont = nullptr; + mLastOffset = 0; + mLastMaxChars = 0; + mLastNoPadding = false; + mWidth = -1.f; + mLastScaleX = 1.f; + mLastScaleY = 1.f; + mLastVertDPI = 0.f; + mLastHorizDPI = 0.f; + mLastResGeneration = 0; + mLastFontCacheGen = 0; +} + +F32 LLFontWidthBuffer::getWidth( + const LLFontGL* fontp, + const llwchar* wchars, + S32 begin_offset, + S32 max_chars, + bool no_padding) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + if (!fontp || !wchars) + { + return 0.f; + } + + if (!sEnableBufferCollection) + { + return fontp->getWidthF32(wchars, begin_offset, max_chars, no_padding); + } + + // Check if we can use cached width + bool needs_recalc = (mWidth < 0.f) + || (mLastFont != fontp) + || (mLastOffset != begin_offset) + || (mLastMaxChars != max_chars) + || (mLastNoPadding != no_padding) + || (mLastScaleX != LLFontGL::sScaleX) + || (mLastScaleY != LLFontGL::sScaleY) + || (mLastVertDPI != LLFontGL::sVertDPI) + || (mLastHorizDPI != LLFontGL::sHorizDPI) + || (mLastResGeneration != LLFontGL::sResolutionGeneration) + || (mLastFontCacheGen != fontp->getCacheGeneration()); + + if (needs_recalc) + { + // Calculate width using the font + mWidth = fontp->getWidthF32(wchars, begin_offset, max_chars, no_padding); + + // Cache the parameters + mLastFont = fontp; + mLastOffset = begin_offset; + mLastMaxChars = max_chars; + mLastNoPadding = no_padding; + mLastScaleX = LLFontGL::sScaleX; + mLastScaleY = LLFontGL::sScaleY; + mLastVertDPI = LLFontGL::sVertDPI; + mLastHorizDPI = LLFontGL::sHorizDPI; + mLastResGeneration = LLFontGL::sResolutionGeneration; + mLastFontCacheGen = fontp->getCacheGeneration(); + } + + return mWidth; +} diff --git a/indra/llrender/llfontvertexbuffer.h b/indra/llrender/llfontvertexbuffer.h index a9e1e2337c..94b833d227 100644 --- a/indra/llrender/llfontvertexbuffer.h +++ b/indra/llrender/llfontvertexbuffer.h @@ -32,6 +32,11 @@ class LLVertexBufferData; +// Rendering fonts is expensive, this class is intended to store +// vertex buffers for rendered text, so that they can be reused. +// LLFontVertexBuffer tracks font and rendering parameters, but +// expects caller to track text changes and call reset() when +// text changes. class LLFontVertexBuffer { public: @@ -127,4 +132,45 @@ private: static bool sEnableBufferCollection; }; +// Extracting width from a font is expensive, and due to +// mechanics of font rendering, we need width separately +// and usually before rendering. +// LLFontWidthBuffer tracks font and rendering parameters, +// but expects caller to track text changes and call reset() +// when text changes. +class LLFontWidthBuffer +{ +public: + LLFontWidthBuffer(); + ~LLFontWidthBuffer(); + + void reset(); + + F32 getWidth(const LLFontGL* fontp, + const llwchar* wchars, + S32 begin_offset, + S32 max_chars, + bool no_padding); + + static void enableBufferCollection(bool enable) { sEnableBufferCollection = enable; } +private: + const LLFontGL* mLastFont = nullptr; + S32 mLastOffset = 0; + S32 mLastMaxChars = 0; + bool mLastNoPadding = false; + F32 mWidth = -1.f; + + // LLFontGL's values that affect width calculation + F32 mLastScaleX = 1.f; + F32 mLastScaleY = 1.f; + F32 mLastVertDPI = 0.f; + F32 mLastHorizDPI = 0.f; + S32 mLastResGeneration = 0; + + // Cache generation tracking + S32 mLastFontCacheGen = 0; + + static bool sEnableBufferCollection; +}; + #endif diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 4901cb423b..1942ddb648 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -1955,7 +1955,7 @@ bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compre LLGLint is_compressed = 0; if (compressed_ok) { - glGetTexLevelParameteriv(mTarget, is_compressed, GL_TEXTURE_COMPRESSED, (GLint*)&is_compressed); + glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_COMPRESSED, (GLint*)&is_compressed); } //----------------------------------------------------------------------------------------------- diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp index 658947d531..bb98696108 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -1788,7 +1788,16 @@ LLVertexBuffer* LLRender::genBuffer(U32 attribute_mask, S32 count) LLVertexBuffer * vb = new LLVertexBuffer(attribute_mask); vb->allocateBuffer(count, 0); - vb->setBuffer(); + // Non-Apple path uses glBufferSubData inside setXxxData, so the VBO + // must already be bound. On Apple, the VBO is lazily created in + // _unmapBuffer (LLAppleVBOPool); calling setBuffer() here would bind + // mGLBuffer == 0 and then setupVertexBuffer would issue + // glVertexAttribIPointer with a non-null offset against no bound + // GL_ARRAY_BUFFER -> GL_INVALID_OPERATION in core profile. + if (!gGLManager.mIsApple) + { + vb->setBuffer(); + } vb->setPositionData(mVerticesp.get()); @@ -1804,6 +1813,9 @@ LLVertexBuffer* LLRender::genBuffer(U32 attribute_mask, S32 count) if(gGLManager.mIsApple && LLVertexBuffer::getVertexBufferMode() == 0) { + // unmapBuffer creates the GL buffer, uploads, and leaves it bound, + // drawBuffer's later setBuffer() then runs setupVertexBuffer against + // a valid VBO. vb->unmapBuffer(); } diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp index f50ce26793..fae2bd5034 100644 --- a/indra/llrender/llshadermgr.cpp +++ b/indra/llrender/llshadermgr.cpp @@ -1026,8 +1026,23 @@ void LLShaderMgr::initShaderCache(bool enabled, const LLUUID& old_cache_version, llifstream instream(meta_out_path, std::ifstream::in | std::ifstream::binary); LLSD in_data; - // todo: this is likely very expensive to parse, should use binary - LLSDSerialize::fromBinary(in_data, instream, LLSDSerialize::SIZE_UNLIMITED); + try + { + LLSDSerialize::fromBinary(in_data, instream, LLSDSerialize::SIZE_UNLIMITED); + } + catch( std::bad_alloc& ) + { + // Try to get a bit more memory back before we try to clear the cache. + in_data.clear(); + // Just in case it was somehow the cause, clear cache. + clearShaderCache(); + // If user run out of memory this early in init, + // we don't want to keep going just to crash again. + // Notify user and close. + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("ShaderMgr") << "Failed to parse shader cache metadata, potentially due to size. Purged cache." << LL_ENDL; + return; + } instream.close(); if (old_cache_version == current_cache_version diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index ae676251ff..01463b4962 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -1323,6 +1323,39 @@ bool LLComboBox::selectItemRange( S32 first, S32 last ) return mList->selectItemRange(first, last); } +void LLComboBox::addInfo(LLSD& info) +{ + LLUICtrl::addInfo(info); + + if (mList && mList->getItemCount() > 0) + { + LLSD items_array; + std::vector<LLScrollListItem*> item_list = mList->getAllData(); + for (std::vector<LLScrollListItem*>::iterator iter = item_list.begin(); iter != item_list.end(); ++iter) + { + if (LLScrollListItem* item = *iter) + { + LLSD item_info; + item_info["value"] = item->getValue(); + if (item->getNumColumns() > 0) + { + if (LLScrollListCell* cell = item->getColumn(0)) + { + item_info["label"] = cell->getValue(); + } + } + items_array.append(item_info); + } + } + info["items"] = items_array; + info["item_count"] = mList->getItemCount(); + info["current_selection"] = getSelectedItemLabel(); + } + else + { + info["item_count"] = 0; + } +} static LLDefaultChildRegistry::Register<LLIconsComboBox> register_icons_combo_box("icons_combo_box"); diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h index d6ea1202d3..dad60ffb65 100644 --- a/indra/llui/llcombobox.h +++ b/indra/llui/llcombobox.h @@ -219,6 +219,10 @@ public: void setButtonVisible(bool visible); + // Populates the provided LLSD with combo box-specific information(list of items, item count, current selection label) + // also includes base LLUICtrl information via parent class + void addInfo(LLSD & info); + void onButtonMouseDown(); void onListMouseUp(); void onItemSelected(const LLSD& data); diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 9361358ced..6de2c18620 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -569,6 +569,8 @@ void LLFloater::storeRectControl() void LLFloater::storeVisibilityControl() { + // Todo: this is a bit pricey, gets called each frame + // on LLAppViewer::idle(), optimize! if( !sQuitting && mVisibilityControl.size() > 1 ) { getControlGroup()->setBOOL( mVisibilityControl, getVisible() ); diff --git a/indra/llui/llfloaterreglistener.cpp b/indra/llui/llfloaterreglistener.cpp index 17641b8375..7cf09369ec 100644 --- a/indra/llui/llfloaterreglistener.cpp +++ b/indra/llui/llfloaterreglistener.cpp @@ -60,6 +60,10 @@ LLFloaterRegListener::LLFloaterRegListener(): "Ask to toggle the state of the floater specified in [\"name\"]", &LLFloaterRegListener::toggleInstance, requiredName); + add("toggleInstanceOrBringToFront", + "Ask to toggle the state of the floater specified in [\"name\"] or bring it to front if already opened", + &LLFloaterRegListener::toggleInstance, + requiredName); add("instanceVisible", "Return on [\"reply\"] an event whose [\"visible\"] indicates the visibility " "of the floater specified in [\"name\"]", @@ -107,6 +111,11 @@ void LLFloaterRegListener::toggleInstance(const LLSD& event) const LLFloaterReg::toggleInstance(event["name"].asString(), event["key"]); } +void LLFloaterRegListener::toggleInstanceOrBringToFront(const LLSD& event) const +{ + LLFloaterReg::toggleInstanceOrBringToFront(event["name"].asString(), event["key"]); +} + void LLFloaterRegListener::instanceVisible(const LLSD& event) const { sendReply(LLSDMap("visible", LLFloaterReg::instanceVisible(event["name"].asString(), event["key"])), diff --git a/indra/llui/llfloaterreglistener.h b/indra/llui/llfloaterreglistener.h index 28f6e7c66b..610a2831dc 100644 --- a/indra/llui/llfloaterreglistener.h +++ b/indra/llui/llfloaterreglistener.h @@ -46,6 +46,7 @@ private: void showInstance(const LLSD& event) const; void hideInstance(const LLSD& event) const; void toggleInstance(const LLSD& event) const; + void toggleInstanceOrBringToFront(const LLSD& event) const; void instanceVisible(const LLSD& event) const; void clickButton(const LLSD& event) const; }; diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp index 2100b23783..ff5a28b250 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -489,58 +489,69 @@ bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu LL_RECORD_BLOCK_TIME(FTM_PANEL_SETUP); LLXMLNodePtr referenced_xml; - std::string xml_filename = mXMLFilename; // if the panel didn't provide a filename, check the node - if (xml_filename.empty()) + if (mXMLFilename.empty()) { - node->getAttributeString("filename", xml_filename); - setXMLFilename(xml_filename); + std::string temp_filename; + node->getAttributeString("filename", temp_filename); + setXMLFilename(temp_filename); } + // Cache singleton and filename to avoid repeated calls + LLUICtrlFactory* factory = LLUICtrlFactory::getInstance(); + + // Cache node name pointer to avoid repeated dereferencing + const LLStringTableEntry* node_name = node->getName(); + + // Cache registry to avoid repeated singleton access + const child_registry_t& registry = child_registry_t::instance(); + LLXUIParser parser; - if (!xml_filename.empty()) + if (!mXMLFilename.empty()) { if (output_node) { //if we are exporting, we want to export the current xml //not the referenced xml - parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + parser.readXUI(node, params, factory->getCurFileName()); Params output_params(params); setupParamsForExport(output_params, parent); - output_node->setName(node->getName()->mString); + output_node->setName(node_name->mString); parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); return true; } - LLUICtrlFactory::instance().pushFileName(xml_filename); + factory->pushFileName(mXMLFilename); LL_RECORD_BLOCK_TIME(FTM_EXTERNAL_PANEL_LOAD); - if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml)) + if (!LLUICtrlFactory::getLayeredXMLNode(mXMLFilename, referenced_xml)) { - LL_WARNS() << "Couldn't parse panel from: " << xml_filename << LL_ENDL; + LL_WARNS() << "Couldn't parse panel from: " << mXMLFilename << LL_ENDL; return false; } - parser.readXUI(referenced_xml, params, LLUICtrlFactory::getInstance()->getCurFileName()); + // Get filename after pushFileName + const std::string& updated_filename = factory->getCurFileName(); + parser.readXUI(referenced_xml, params, updated_filename); // add children using dimensions from referenced xml for consistent layout setShape(params.rect); - LLUICtrlFactory::createChildren(this, referenced_xml, child_registry_t::instance()); + LLUICtrlFactory::createChildren(this, referenced_xml, registry); - LLUICtrlFactory::instance().popFileName(); + factory->popFileName(); } // ask LLUICtrlFactory for filename, since xml_filename might be empty - parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + parser.readXUI(node, params, factory->getCurFileName()); if (output_node) { Params output_params(params); setupParamsForExport(output_params, parent); - output_node->setName(node->getName()->mString); + output_node->setName(node_name->mString); parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); } @@ -552,7 +563,7 @@ bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu } // add children - LLUICtrlFactory::createChildren(this, node, child_registry_t::instance(), output_node); + LLUICtrlFactory::createChildren(this, node, registry, output_node); // Connect to parent after children are built, because tab containers // do a reshape() on their child panels, which requires that the children diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index df99c4f636..e36fd45bb4 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -480,6 +480,7 @@ void LLScrollContainer::calcVisibleSize( S32 *visible_width, S32 *visible_height void LLScrollContainer::draw() { + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; static LLUICachedControl<S32> scrollbar_size_control ("UIScrollbarSize", 0); S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 5882c1edbb..0521853b02 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -225,6 +225,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mTrustedContent(p.trusted_content), mAlwaysShowIcons(p.always_show_icons), mTrackEnd( p.track_end ), + mTrackValueChange(true), mScrollIndex(-1), mSelectionStart( 0 ), mSelectionEnd( 0 ), @@ -967,7 +968,10 @@ void LLTextBase::drawText() S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::segment_vec_t* segments ) { - beforeValueChange(); + if (mTrackValueChange) + { + beforeValueChange(); + } S32 old_len = getLength(); // length() returns character length S32 insert_len = static_cast<S32>(wstr.length()); @@ -1090,12 +1094,14 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s getViewModel()->getEditableDisplay().insert(pos, wstr); - if ( truncate() ) + if (mTrackValueChange) { - insert_len = getLength() - old_len; + if (truncate()) + { + insert_len = getLength() - old_len; + } + onValueChange(pos, pos + insert_len); } - - onValueChange(pos, pos + insert_len); needsReflow(pos); return insert_len; @@ -1111,7 +1117,10 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) // Clamp length to not go past the end of the text length = std::min(length, text_length - pos); - beforeValueChange(); + if (mTrackValueChange) + { + beforeValueChange(); + } segment_set_t::iterator seg_iter = getSegIterContaining(pos); while(seg_iter != mSegments.end()) { @@ -1162,7 +1171,10 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) // recreate default segment in case we erased everything createDefaultSegment(); - onValueChange(pos, pos); + if (mTrackValueChange) + { + onValueChange(pos, pos); + } needsReflow(pos); return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length @@ -1170,7 +1182,10 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) { - beforeValueChange(); + if (mTrackValueChange) + { + beforeValueChange(); + } if (pos > (S32)getLength()) { @@ -1178,7 +1193,10 @@ S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) } getViewModel()->getEditableDisplay()[pos] = wc; - onValueChange(pos, pos + 1); + if (mTrackValueChange) + { + onValueChange(pos, pos + 1); + } needsReflow(pos); return 1; @@ -1625,7 +1643,7 @@ void LLTextBase::deselect() bool LLTextBase::getSpellCheck() const { - return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck); + return (!mReadOnly) && (LLSpellChecker::getUseSpellCheck()) && (mSpellCheck); } const std::string& LLTextBase::getSuggestion(U32 index) const @@ -2333,6 +2351,10 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) { + beforeValueChange(); + // Can insert a lot of different segments, don't want to spam events. + mTrackValueChange = false; + // clear out the existing text and segments getViewModel()->setDisplay(LLWStringUtil::null); @@ -2353,6 +2375,8 @@ void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& startOfDoc(); } + truncate(); // was postponed to avoid micro truncations and expensive checks + mTrackValueChange = true; onValueChange(0, getLength()); } @@ -2383,6 +2407,10 @@ void LLTextBase::appendTextImpl(const std::string& new_text, const LLStyle::Para LLStyle::Params style_params(getStyleParams()); style_params.overwriteFrom(input_params); + // todo: this does not check for maximum size, might + // want to stop once maximum size was reached to avoid + // expensive findUrl, replaceUrl calls. + S32 part = (S32)LLTextParser::WHOLE; if ((mParseHTML || force_slurl) && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358). { @@ -2556,6 +2584,10 @@ void LLTextBase::copyContents(const LLTextBase* source) beforeValueChange(); deselect(); + // Can insert a lot of different segments, don't want to spam events. + // Do one full length onValueChange() at the end of this function. + mTrackValueChange = false; + mSegments.clear(); for (const LLTextSegmentPtr& segp : source->mSegments) { @@ -2570,6 +2602,8 @@ void LLTextBase::copyContents(const LLTextBase* source) getViewModel()->setDisplay(source->getViewModel()->getDisplay()); + truncate(); // was postponed to avoid micro truncations and expensive checks + mTrackValueChange = true; onValueChange(0, getLength()); needsReflow(); } @@ -2824,7 +2858,7 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, bool round, line_seg_iter != mSegments.end(); ++line_seg_iter, line_seg_offset = 0) { - const LLTextSegmentPtr segmentp = *line_seg_iter; + LLTextSegmentPtr segmentp = *line_seg_iter; S32 segment_line_start = segmentp->getStart() + line_seg_offset; S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start; @@ -2915,7 +2949,7 @@ LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const while(line_seg_iter != mSegments.end()) { - const LLTextSegmentPtr segmentp = *line_seg_iter; + LLTextSegmentPtr segmentp = *line_seg_iter; if (line_seg_iter == cursor_seg_iter) { @@ -3466,8 +3500,8 @@ LLStyleSP LLTextSegment::cloneStyle(LLTextBase& target, const LLStyle* source) } -bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; height = 0; return false; } -bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { width = 0; height = 0; return false; } +bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) { F32 fwidth = 0; bool result = getDimensionsF32(first_char, num_chars, fwidth, height); @@ -3564,6 +3598,7 @@ F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selec mFontBufferPreSelection.reset(); mFontBufferSelection.reset(); mFontBufferPostSelection.reset(); + mFontWidthBuffer.reset(); } return draw_rect.mLeft; } @@ -3589,6 +3624,7 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele mFontBufferPreSelection.reset(); mFontBufferSelection.reset(); mFontBufferPostSelection.reset(); + mFontWidthBuffer.reset(); } const LLFontGL* font = mStyle->getFont(); @@ -3820,17 +3856,19 @@ LLTextSegmentPtr LLNormalTextSegment::clone(LLTextBase& target) const return new LLNormalTextSegment(sp, mStart, mEnd, target); } -bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { height = 0; width = 0; if (num_chars > 0 && (mStart + first_char >= 0)) { height = mFontHeight; - const LLWString &text = getWText(); - // if last character is a newline, then return true, forcing line break - width = mStyle->getFont()->getWidthF32(text.c_str(), mStart + first_char, num_chars, true); + + const LLWString& text = getWText(); + const LLFontGL* font = mStyle->getFont(); + width += mFontWidthBuffer.getWidth(font, text.c_str(), mStart + first_char, num_chars, true); } + // if last character is a newline, then return true, forcing line break return false; } @@ -3912,6 +3950,7 @@ void LLNormalTextSegment::updateLayout(const class LLTextBase& editor) mFontBufferPreSelection.reset(); mFontBufferSelection.reset(); mFontBufferPostSelection.reset(); + mFontWidthBuffer.reset(); } void LLNormalTextSegment::dump() const @@ -4063,7 +4102,7 @@ LLTextSegmentPtr LLInlineViewSegment::clone(LLTextBase& target) const return nullptr; } -bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { if (first_char == 0 && num_chars == 0) { @@ -4155,7 +4194,7 @@ LLTextSegmentPtr LLLineBreakTextSegment::clone(LLTextBase& target) const copy->mFontHeight = mFontHeight; return copy; } -bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { width = 0; height = mFontHeight; @@ -4192,7 +4231,7 @@ LLTextSegmentPtr LLImageTextSegment::clone(LLTextBase& target) const static const S32 IMAGE_HPAD = 3; // virtual -bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { width = 0; height = mStyle->getFont()->getLineHeight(); diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 35477bdea9..bc39d9732c 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -68,10 +68,10 @@ public: virtual LLTextSegmentPtr clone(LLTextBase& terget) const { return new LLTextSegment(mStart, mEnd); } static LLStyleSP cloneStyle(LLTextBase& target, const LLStyle* source); - bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const; + bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height); bool getPermitsEmoji() const { return mPermitsEmoji; }; - virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); virtual S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; /** @@ -139,7 +139,7 @@ public: virtual ~LLNormalTextSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; /*virtual*/ void updateLayout(const class LLTextBase& editor); @@ -182,6 +182,7 @@ protected: LLFontVertexBuffer mFontBufferPreSelection; LLFontVertexBuffer mFontBufferSelection; LLFontVertexBuffer mFontBufferPostSelection; + LLFontWidthBuffer mFontWidthBuffer; S32 mLastGeneration = -1; }; @@ -254,7 +255,7 @@ public: ~LLInlineViewSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; /*virtual*/ void updateLayout(const class LLTextBase& editor); /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); @@ -280,7 +281,7 @@ public: LLLineBreakTextSegment(S32 pos); ~LLLineBreakTextSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); @@ -295,7 +296,7 @@ public: ~LLImageTextSegment(); /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height); S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 char_offset, S32 max_chars, S32 line_ind) const; F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); @@ -762,6 +763,7 @@ protected: bool mUseEmoji; bool mUseColor; bool mTrackEnd; // if true, keeps scroll position at end of document during resize + bool mTrackValueChange; // if true, send out onValueChange() from low level text modification methods bool mReadOnly; bool mBGVisible; // render background? bool mClip; // clip text to widget rect diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp index 33ffc3dfc6..740f187ed8 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -67,7 +67,7 @@ public: static LLDefaultChildRegistry::Register<LLUICtrlLocate> r1("locate"); // Build time optimization, generate this once in .cpp file -template class LLUICtrlFactory* LLSingleton<class LLUICtrlFactory>::getInstance(); +template class LLUICtrlFactory* LLSimpleton<class LLUICtrlFactory>::getInstance(); //----------------------------------------------------------------------------- // LLUICtrlFactory() diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h index f44b4ba4dc..75cee5f004 100644 --- a/indra/llui/lluictrlfactory.h +++ b/indra/llui/lluictrlfactory.h @@ -81,12 +81,13 @@ class LLWidgetNameRegistry // Build time optimization, generate this once in .cpp file #ifndef LLUICTRLFACTORY_CPP -extern template class LLUICtrlFactory* LLSingleton<class LLUICtrlFactory>::getInstance(); +extern template class LLUICtrlFactory* LLSimpleton<class LLUICtrlFactory>::getInstance(); #endif -class LLUICtrlFactory : public LLSingleton<LLUICtrlFactory> +class LLUICtrlFactory : public LLSimpleton<LLUICtrlFactory> { - LLSINGLETON(LLUICtrlFactory); +public: + LLUICtrlFactory(); ~LLUICtrlFactory(); // only partial specialization allowed in inner classes, so use extra dummy parameter @@ -313,6 +314,10 @@ template<typename T> LLChildRegistry<DERIVED>::Register<T>::Register(const char* tag, LLWidgetCreatorFunc func) : LLChildRegistry<DERIVED>::StaticRegistrar(tag, func == nullptr ? (LLWidgetCreatorFunc)&LLUICtrlFactory::defaultBuilder<T> : func) { + if (!LLUICtrlFactory::instanceExists()) + { + LLUICtrlFactory::createInstance(); + } // add this widget to various registries LLUICtrlFactory::instance().registerWidget(typeid(T), typeid(typename T::Params), tag); diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index cb101d325d..8a21222b3c 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -149,12 +149,77 @@ static bool stringHasUrl(const std::string &text) // fast heuristic test for a URL in a string. This is used // to avoid lots of costly regex calls, BUT it needs to be // kept in sync with the LLUrlEntry regexes we support. - return (text.find("://") != std::string::npos || - text.find("www.") != std::string::npos || - text.find(".com") != std::string::npos || - text.find("<nolink>") != std::string::npos || - text.find("<icon") != std::string::npos || - text.find("@") != std::string::npos); + + // Early exit for empty or very short strings + // Smallest url is 5 characters + if (text.length() < 3) + { + return false; + } + + // Single pass search for common URL indicators + for (size_t i = 0; i < text.length(); ++i) + { + char c = text[i]; + + // Check for @ (email or mention) + if (c == '@') + { + return true; + } + + if (i + 3 >= text.length()) + { + // Nothing else is going to match or fit if we don't + // have at least 4 characters left + // Ex: expectation is that there is something after protocol delimiter + // and .com takes 4 characters. + return false; + } + + // Check for protocol delimiter + if (c == ':' && text[i + 1] == '/' && text[i + 2] == '/') + { + return true; + } + + // Check for www. at start of word + if (c == 'w' + && text[i + 1] == 'w' + && text[i + 2] == 'w' + && text[i + 3] == '.') + { + return true; + } + + // Check for .com (and similar) + if (c == '.') + { + const char* suffix = text.c_str() + i + 1; + if ((suffix[0] == 'c' && suffix[1] == 'o' && suffix[2] == 'm') || + (suffix[0] == 'n' && suffix[1] == 'e' && suffix[2] == 't') || + (suffix[0] == 'o' && suffix[1] == 'r' && suffix[2] == 'g') || + (suffix[0] == 'e' && suffix[1] == 'd' && suffix[2] == 'u')) + { + return true; + } + } + + // Check for <nolink> or <icon + if (c == '<') + { + if (i + 7 < text.length() && text.compare(i + 1, 6, "nolink") == 0) + { + return true; + } + if (i + 4 < text.length() && text.compare(i + 1, 4, "icon") == 0) + { + return true; + } + } + } + + return false; } bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb, bool is_content_trusted, bool skip_non_mentions) diff --git a/indra/llwebrtc/CMakeLists.txt b/indra/llwebrtc/CMakeLists.txt index ff505f498a..31ba0af208 100644 --- a/indra/llwebrtc/CMakeLists.txt +++ b/indra/llwebrtc/CMakeLists.txt @@ -47,7 +47,14 @@ if (WINDOWS) set_target_properties(llwebrtc PROPERTIES PDB_OUTPUT_DIRECTORY "${SYMBOLS_STAGING_DIR}") endif (USE_BUGSPLAT) elseif (DARWIN) - target_link_libraries(llwebrtc PRIVATE ll::webrtc) + target_link_libraries(llwebrtc PRIVATE ll::webrtc + "-framework AVFoundation" + "-framework AudioToolbox" + "-framework CoreAudio" + "-framework CoreMedia" + "-framework CoreVideo" + "-framework Foundation" + ) if (USE_BUGSPLAT) set_target_properties(llwebrtc PROPERTIES XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym" XCODE_ATTRIBUTE_DWARF_DSYM_FOLDER_PATH "${SYMBOLS_STAGING_DIR}/dSYMs") diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp index 589934fcce..8b77a39541 100644 --- a/indra/llwebrtc/llwebrtc.cpp +++ b/indra/llwebrtc/llwebrtc.cpp @@ -26,8 +26,11 @@ #include "llwebrtc_impl.h" #include <algorithm> +#include <chrono> +#include <future> +#include <thread> #include <string.h> - +#include "api/audio/create_audio_device_module.h" #include "api/audio_codecs/audio_decoder_factory.h" #include "api/audio_codecs/audio_encoder_factory.h" #include "api/audio_codecs/builtin_audio_decoder_factory.h" @@ -56,8 +59,8 @@ webrtc::LossBasedBweV2::Config::Config() namespace llwebrtc { #if WEBRTC_WIN -static int16_t PLAYOUT_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultDevice; -static int16_t RECORD_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultDevice; +static int16_t PLAYOUT_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultCommunicationDevice; +static int16_t RECORD_DEVICE_DEFAULT = webrtc::AudioDeviceModule::kDefaultCommunicationDevice; #else static int16_t PLAYOUT_DEVICE_DEFAULT = 0; static int16_t RECORD_DEVICE_DEFAULT = 0; @@ -148,7 +151,9 @@ int32_t LLWebRTCAudioTransport::NeedMorePlayData(size_t number_of_frames, if (!engine) { // No engine sink; output silence to be safe. - const size_t bytes = number_of_frames * bytes_per_frame * number_of_channels; + // bytes_per_frame already accounts for all channels, so do not multiply + // by number_of_channels again (that would overrun the playout buffer). + const size_t bytes = number_of_frames * bytes_per_frame; memset(audio_data, 0, bytes); number_of_samples_out = bytes_per_frame; return 0; @@ -264,17 +269,47 @@ void LLCustomProcessor::Process(webrtc::AudioBuffer *audio) mState->setMicrophoneEnergy(std::sqrt(totalSum / (audio->num_channels() * audio->num_frames() * buffer_size))); } + +// +// LLWebRTCImpl implementation +// + +void LLWebRTCAudioDeviceModule::SetTuning(bool tuning, bool mute) +{ + tuning_ = tuning; + if (tuning) + { + // Ensure capture is running (it's normally already running -- capture is + // session-long) so the mic-level meter works, and stop rendering the + // call while tuning. The recording calls are no-ops if capture is + // already active, so this won't cold-start it. + inner_->InitMicrophone(); + inner_->InitRecording(); + inner_->StartRecording(); + inner_->StopPlayout(); + } + // On exit, capture is deliberately left running (mute is handled by gain, + // not by stopping the device, so there's no AEC cold-start hiss). Playout + // is restored by the caller via workerOpenPlayout(), keeping it gated on + // there being a connection to render. +} + // // LLWebRTCImpl implementation // LLWebRTCImpl::LLWebRTCImpl(LLWebRTCLogCallback* logCallback) : + mEnv(webrtc::CreateEnvironment(webrtc::CreateDefaultTaskQueueFactory())), mLogSink(new LLWebRTCLogSink(logCallback)), mPeerCustomProcessor(nullptr), mMute(true), + mVoiceEnabled(false), mTuningMode(false), mDevicesDeploying(0), - mGain(0.0f) + mGain(0.0f), + mBuiltinNS(false), + mBuiltinAGC(false), + mBuiltinAEC(false) { } @@ -287,8 +322,6 @@ void LLWebRTCImpl::init() webrtc::LogMessage::SetLogToStderr(true); webrtc::LogMessage::AddLogToStream(mLogSink, webrtc::LS_VERBOSE); - mTaskQueueFactory = webrtc::CreateDefaultTaskQueueFactory(); - // Create the native threads. mNetworkThread = webrtc::Thread::CreateWithSocketServer(); mNetworkThread->SetName("WebRTCNetworkThread", nullptr); @@ -304,11 +337,19 @@ void LLWebRTCImpl::init() [this]() { webrtc::scoped_refptr<webrtc::AudioDeviceModule> realADM = - webrtc::AudioDeviceModule::Create(webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio, mTaskQueueFactory.get()); + webrtc::CreateAudioDeviceModule(mEnv, webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio); mDeviceModule = webrtc::make_ref_counted<LLWebRTCAudioDeviceModule>(realADM); #if !CM_WEBRTC mDeviceModule->SetObserver(this); #endif + mDeviceModule->Init(); + + mBuiltinNS = mDeviceModule->BuiltInNSIsAvailable(); + mBuiltinAEC = mDeviceModule->BuiltInAECIsAvailable(); + mBuiltinAGC = mDeviceModule->BuiltInAGCIsAvailable(); + // All audio processing is done by WebRTC's software APM (configured + // below); make sure the hardware processors stay off. + workerDisableBuiltInAudioProcessing(); }); // The custom processor allows us to retrieve audio data (and levels) @@ -318,17 +359,22 @@ void LLWebRTCImpl::init() apb.SetCapturePostProcessing(std::make_unique<LLCustomProcessor>(mPeerCustomProcessor)); mAudioProcessingModule = apb.Build(webrtc::CreateEnvironment()); + // Initial software-APM state, matching setAudioConfig() so there's no + // window where processing differs before the viewer's first config call. + // All processing is done here in software (the hardware AEC/AGC/NS is kept + // disabled), so enable echo cancellation from the very first frame. webrtc::AudioProcessing::Config apm_config; - apm_config.echo_canceller.enabled = false; - apm_config.echo_canceller.mobile_mode = false; - apm_config.gain_controller1.enabled = false; - apm_config.gain_controller2.enabled = true; - apm_config.high_pass_filter.enabled = true; - apm_config.noise_suppression.enabled = true; - apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh; - apm_config.transient_suppression.enabled = true; - apm_config.pipeline.multi_channel_render = true; - apm_config.pipeline.multi_channel_capture = false; + apm_config.echo_canceller.enabled = true; + apm_config.echo_canceller.mobile_mode = false; + apm_config.gain_controller1.enabled = false; + apm_config.gain_controller2.enabled = true; + apm_config.gain_controller2.adaptive_digital.enabled = true; // auto-level speech + apm_config.high_pass_filter.enabled = true; + apm_config.noise_suppression.enabled = true; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh; + apm_config.transient_suppression.enabled = true; + apm_config.pipeline.multi_channel_render = true; + apm_config.pipeline.multi_channel_capture = true; mAudioProcessingModule->ApplyConfig(apm_config); @@ -360,7 +406,6 @@ void LLWebRTCImpl::init() { if (mDeviceModule) { - mDeviceModule->EnableBuiltInAEC(false); updateDevices(); } }); @@ -369,8 +414,23 @@ void LLWebRTCImpl::init() void LLWebRTCImpl::terminate() { - mWorkerThread->BlockingCall( - [this]() + // Run all blocking WebRTC shutdown calls on a separate thread so that a + // hung BlockingCall cannot block the viewer shutdown indefinitely. + // Webrtc is not mission critical, we need to save personal data. + auto done_promise = std::make_shared<std::promise<void> >(); + std::future<void> done_future = done_promise->get_future(); + + // Hand ownership of the connections to the shutdown thread. Nothing on + // this thread may touch them afterwards -- in the timeout case below the + // shutdown thread is detached and may still be working through them. + std::vector<webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>> connections; + connections.swap(mPeerConnections); + + std::thread shutdown_thread( + [this, connections = std::move(connections), done_promise]() mutable + { + mWorkerThread->BlockingCall( + [this]() { if (mDeviceModule) { @@ -379,79 +439,155 @@ void LLWebRTCImpl::terminate() } }); - for (auto &connection : mPeerConnections) - { - connection->terminate(); - } + // Close the connections inline on the signaling thread. This can't be + // connection->terminate(), which only *posts* the close: that queues the + // real work behind everything below, so the connections would be closed + // after the factory and the device module are gone -- or not at all, if + // the thread is destroyed with the task still queued. + // + // It matters that the close completes here because closing a peer + // connection flushes any in-flight GetStats request and runs its + // callback inline, and that callback calls back into the viewer's + // signaling observers. Those observers are only valid until + // llwebrtc::terminate() returns. + mSignalingThread->BlockingCall( + [&connections]() + { + for (auto& connection : connections) + { + connection->closeOnSignalingThread(); + } + // Destroy the connections here, on the signaling thread, while + // it's still running. + connections.clear(); + }); - // connection->terminate() above spawns a number of Signaling thread calls to - // shut down the connection. The following Blocking Call will wait - // until they're done before it's executed, allowing time to clean up. + // Drain anything the closes posted before dropping the factory. + mSignalingThread->BlockingCall([]() {}); - mSignalingThread->BlockingCall([this]() { mPeerConnectionFactory = nullptr; }); + mSignalingThread->BlockingCall([this]() { + mPeerConnectionFactory = nullptr; + }); - mWorkerThread->BlockingCall( - [this]() + mWorkerThread->BlockingCall( + [this]() { if (mDeviceModule) { - mDeviceModule->Terminate(); + mDeviceModule->ForceTerminate(); } - mDeviceModule = nullptr; - mTaskQueueFactory = nullptr; + mDeviceModule = nullptr; }); - // In case peer connections still somehow have jobs in workers, - // only clear connections up after clearing workers. + done_promise->set_value(); + }); + + constexpr auto WEBRTC_TERMINATE_TIMEOUT = std::chrono::seconds(10); + if (done_future.wait_for(WEBRTC_TERMINATE_TIMEOUT) == std::future_status::timeout) + { + RTC_LOG(LS_WARNING) << __FUNCTION__ + << ": timed out waiting for WebRTC thread shutdown." + " 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(); + + // mPeerConnections is already empty -- the detached thread owns the + // connections now and must be left to finish with them. + webrtc::LogMessage::RemoveLogToStream(mLogSink); + return; + } + + 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; - mPeerConnections.clear(); webrtc::LogMessage::RemoveLogToStream(mLogSink); } + void LLWebRTCImpl::setAudioConfig(LLWebRTCDeviceInterface::AudioConfig config) { + // All audio processing is handled by WebRTC's software APM here. The + // platform/hardware AEC/AGC/NS is always disabled (see + // workerDisableBuiltInAudioProcessing), so these are enabled purely on the + // requested config without deferring to any built-in processor. webrtc::AudioProcessing::Config apm_config; - apm_config.echo_canceller.enabled = config.mEchoCancellation; - apm_config.echo_canceller.mobile_mode = false; - apm_config.gain_controller1.enabled = false; - apm_config.gain_controller2.enabled = config.mAGC; + apm_config.echo_canceller.enabled = config.mEchoCancellation; + apm_config.echo_canceller.mobile_mode = false; + apm_config.gain_controller1.enabled = false; + apm_config.gain_controller2.enabled = config.mAGC; apm_config.gain_controller2.adaptive_digital.enabled = true; // auto-level speech - apm_config.high_pass_filter.enabled = true; - apm_config.transient_suppression.enabled = true; - apm_config.pipeline.multi_channel_render = true; - apm_config.pipeline.multi_channel_capture = true; - apm_config.pipeline.multi_channel_capture = true; + apm_config.high_pass_filter.enabled = true; + apm_config.transient_suppression.enabled = true; + apm_config.pipeline.multi_channel_render = true; + apm_config.pipeline.multi_channel_capture = true; switch (config.mNoiseSuppressionLevel) { case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_NONE: apm_config.noise_suppression.enabled = false; - apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow; break; case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_LOW: apm_config.noise_suppression.enabled = true; - apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow; break; case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_MODERATE: apm_config.noise_suppression.enabled = true; - apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate; break; case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_HIGH: apm_config.noise_suppression.enabled = true; - apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh; break; case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_VERY_HIGH: apm_config.noise_suppression.enabled = true; - apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh; break; default: apm_config.noise_suppression.enabled = false; - apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow; + apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow; } mAudioProcessingModule->ApplyConfig(apm_config); + + // Keep the hardware processors off; the APM above is the only processing. + PostWorkerTask([this]() { workerDisableBuiltInAudioProcessing(); }); +} + +void LLWebRTCImpl::workerDisableBuiltInAudioProcessing() +{ + if (!mDeviceModule) + { + return; + } + + // We always use WebRTC's internal (software APM) audio processing. Running + // the platform/hardware AEC, AGC, or NS alongside it causes the two to + // fight -- pumping levels, double noise suppression, and mismatched AEC + // references -- so disable any that the device exposes. + if (mBuiltinNS) + { + mDeviceModule->EnableBuiltInNS(false); + } + if (mBuiltinAGC) + { + mDeviceModule->EnableBuiltInAGC(false); + } + if (mBuiltinAEC) + { + mDeviceModule->EnableBuiltInAEC(false); + } } void LLWebRTCImpl::refreshDevices() @@ -471,20 +607,25 @@ void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer) } } -// must be run in the worker thread. -void LLWebRTCImpl::workerDeployDevices() +// must be run in the worker thread. Selects the configured capture device and +// starts recording. Capture runs the whole time voice is enabled (it's never +// stopped for mute or between calls, so the AEC never cold-starts -- there's no +// hiss on unmute), so this is a no-op when already recording. Device changes +// go through workerDeployDevices(), which stops recording first to force a +// clean re-select; voice off goes through setVoiceEnabled(false). +void LLWebRTCImpl::workerStartRecording() { - if (!mDeviceModule) + // Only run capture while voice is enabled, and never cold-start it when + // it's already running (that would cause the unmute hiss). + if (!mDeviceModule || !mVoiceEnabled || mDeviceModule->Recording()) { return; } int16_t recordingDevice = RECORD_DEVICE_DEFAULT; - int16_t recording_device_start = 0; - if (mRecordingDevice != "Default") { - for (int16_t i = recording_device_start; i < mRecordingDeviceList.size(); i++) + for (int16_t i = 0; i < mRecordingDeviceList.size(); i++) { if (mRecordingDeviceList[i].mID == mRecordingDevice) { @@ -500,8 +641,6 @@ void LLWebRTCImpl::workerDeployDevices() } } - mDeviceModule->StopPlayout(); - mDeviceModule->ForceStopRecording(); #if WEBRTC_WIN if (recordingDevice < 0) { @@ -516,13 +655,32 @@ void LLWebRTCImpl::workerDeployDevices() #endif mDeviceModule->InitMicrophone(); mDeviceModule->SetStereoRecording(false); + // A newly-selected capture device may default its hardware AEC/AGC/NS on; + // disable before InitRecording so the recording stream is configured to + // use only WebRTC's software APM. + workerDisableBuiltInAudioProcessing(); mDeviceModule->InitRecording(); + mDeviceModule->ForceStartRecording(); +} + +// must be run in the worker thread. Selects the configured playout device and +// starts playout. Playout only runs while there's a connection to render +// (running the output device with no engine data is heard as a buzz), so this +// is a no-op when there are no connections or when already playing. Device +// changes go through workerDeployDevices(), which stops playout first. +void LLWebRTCImpl::workerStartPlayout() +{ + // Only run playout while voice is enabled and there's a connection to + // render (running the output device otherwise is heard as a buzz). + if (!mDeviceModule || !mVoiceEnabled || mTuningMode || mDeviceModule->Playing() || mPeerConnections.empty()) + { + return; + } int16_t playoutDevice = PLAYOUT_DEVICE_DEFAULT; - int16_t playout_device_start = 0; if (mPlayoutDevice != "Default") { - for (int16_t i = playout_device_start; i < mPlayoutDeviceList.size(); i++) + for (int16_t i = 0; i < mPlayoutDeviceList.size(); i++) { if (mPlayoutDeviceList[i].mID == mPlayoutDevice) { @@ -553,16 +711,29 @@ void LLWebRTCImpl::workerDeployDevices() mDeviceModule->InitSpeaker(); mDeviceModule->SetStereoPlayout(true); mDeviceModule->InitPlayout(); + mDeviceModule->StartPlayout(); +} - if ((!mMute && mPeerConnections.size()) || mTuningMode) +// must be run in the worker thread. Used for device changes and tuning: forces +// a clean re-select of both devices, then re-applies per-connection mute/track +// state. To merely bring playout up when a connection is established (without +// disturbing the connection's own mute/track management) call +// workerOpenPlayout() directly -- see startPlayout(). +void LLWebRTCImpl::workerDeployDevices() +{ + if (!mDeviceModule) { - mDeviceModule->ForceStartRecording(); + return; } - if (!mTuningMode) - { - mDeviceModule->StartPlayout(); - } + // Stop first so the start helpers (which no-op when already running) will + // re-select the now-current device. + mDeviceModule->StopPlayout(); + mDeviceModule->ForceStopRecording(); + + workerStartRecording(); + workerStartPlayout(); + mSignalingThread->PostTask( [this] { @@ -604,6 +775,35 @@ void LLWebRTCImpl::setRenderDevice(const std::string &id) } } +void LLWebRTCImpl::setVoiceEnabled(bool enable) +{ + mVoiceEnabled = enable; + mWorkerThread->PostTask( + [this, enable]() + { + if (!mDeviceModule) + { + return; + } + if (enable) + { + // Voice on: start the capture device (it then stays running + // across calls and mute/unmute), and start playout if there's + // already a connection to render. + mDeviceModule->Init(); + workerDeployDevices(); + } + else + { + // Voice off: release both devices so the OS mic/speaker aren't + // held open. + mDeviceModule->ForceStopRecording(); + mDeviceModule->StopPlayout(); + mDeviceModule->ForceTerminate(); + } + }); +} + // updateDevices needs to happen on the worker thread. void LLWebRTCImpl::updateDevices() { @@ -627,6 +827,7 @@ void LLWebRTCImpl::updateDevices() char name[webrtc::kAdmMaxDeviceNameSize]; char guid[webrtc::kAdmMaxGuidSize]; mDeviceModule->PlayoutDeviceName(index, name, guid); + RTC_LOG(LS_VERBOSE) << "updateDevices: playout device [" << index << "] name='" << name << "' guid='" << guid << "'"; mPlayoutDeviceList.emplace_back(name, guid); } @@ -645,13 +846,18 @@ void LLWebRTCImpl::updateDevices() char name[webrtc::kAdmMaxDeviceNameSize]; char guid[webrtc::kAdmMaxGuidSize]; mDeviceModule->RecordingDeviceName(index, name, guid); + RTC_LOG(LS_VERBOSE) << "updateDevices: recording device [" << index << "] name='" << name << "' guid='" << guid << "'"; mRecordingDeviceList.emplace_back(name, guid); } + RTC_LOG(LS_INFO) << "updateDevices, playout count: " << renderDeviceCount << "; capture count: " << captureDeviceCount; + for (auto &observer : mVoiceDevicesObserverList) { observer->OnDevicesChanged(mPlayoutDeviceList, mRecordingDeviceList); } + + deployDevices(); } void LLWebRTCImpl::OnDevicesUpdated() @@ -674,6 +880,13 @@ void LLWebRTCImpl::setTuningMode(bool enable) [this] { mDeviceModule->SetTuning(mTuningMode, mMute); + if (!mTuningMode) + { + // Restore playout after tuning, gated on there being a + // connection to render (so the output device isn't left + // spinning with no engine data). + workerStartPlayout(); + } mSignalingThread->PostTask( [this] { @@ -745,39 +958,16 @@ void LLWebRTCImpl::setMute(bool mute, int delay_ms) void LLWebRTCImpl::intSetMute(bool mute, int delay_ms) { + // Mute by zeroing the captured (post-APM) gain; the sender track is also + // disabled per connection (see LLWebRTCPeerConnectionImpl::setMute). The + // capture device deliberately stays running for the whole session, so + // muting/unmuting never stops or starts it -- that's what avoids the AEC + // cold-start hiss on unmute. Capture start/stop is tied to device + // selection (workerStartRecording) and shutdown, not to mute. if (mPeerCustomProcessor) { mPeerCustomProcessor->setGain(mMute ? 0.0f : mGain); } - - // Sequence counter to prevent race conditions from rapid requests to mute/unmute - static std::atomic<uint32_t> mute_sequence(0); - uint32_t current_sequence = ++mute_sequence; - - if (mMute) - { - mWorkerThread->PostDelayedTask( - [this, current_sequence] - { - if (mDeviceModule && (current_sequence == mute_sequence.load())) - { - mDeviceModule->ForceStopRecording(); - } - }, - webrtc::TimeDelta::Millis(delay_ms)); - } - else - { - mWorkerThread->PostTask( - [this, current_sequence] - { - if (mDeviceModule && (current_sequence == mute_sequence.load())) - { - mDeviceModule->InitRecording(); - mDeviceModule->ForceStartRecording(); - } - }); - } } // @@ -786,8 +976,7 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms) LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection() { - bool empty = mPeerConnections.empty(); - webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> peerConnection = webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>(new webrtc::RefCountedObject<LLWebRTCPeerConnectionImpl>()); + webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> peerConnection = webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl>(new webrtc::RefCountedObject<LLWebRTCPeerConnectionImpl>(mEnv)); peerConnection->init(this); if (mPeerConnections.empty()) { @@ -795,6 +984,13 @@ LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection() } mPeerConnections.emplace_back(peerConnection); + // Playout is intentionally NOT started here. This runs when the connection + // is created/connecting; starting the output device now leaves it spinning + // with no decoded audio during the handshake, which is heard as a buzz. + // Playout is started from OnConnectionChange(kConnected) instead, once audio + // is actually established (see startPlayout()). Capture follows + // voice-enabled state, so it's not touched here either. + peerConnection->enableSenderTracks(false); peerConnection->resetMute(); return peerConnection.get(); @@ -811,10 +1007,45 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn if (mPeerConnections.empty()) { intSetMute(true); + // Last connection gone: stop playout (there's nothing to render). + // Capture stays running while voice is enabled so it's ready -- with + // no cold-start hiss -- when the next call comes up. But if voice + // has been disabled, stop capture now: setVoiceEnabled(false) tried + // to, but the engine's send stream was still active then (and the + // engine's own StopRecording is intentionally a no-op), so the stop + // only sticks once the connection -- and its stream -- is gone. + mWorkerThread->PostTask( + [this]() + { + if (mDeviceModule) + { + mDeviceModule->StopPlayout(); + if (!mVoiceEnabled) + { + mDeviceModule->ForceStopRecording(); + } + } + }); } } } +void LLWebRTCImpl::startPlayout() +{ + // Called when a connection's audio is established. Only playout is started + // here: it's gated on there being a connection to render, because running + // the output device with no engine data is heard as a buzz. Capture is + // NOT touched here -- it follows voice-enabled state (setVoiceEnabled), so + // it's already running if voice is on and must stay off if voice is off. + // Starting it here would also let a stray kConnected during voice-disable + // teardown re-open the mic. + mWorkerThread->PostTask( + [this]() + { + workerStartPlayout(); + }); +} + // // LLWebRTCPeerConnectionImpl implementation. @@ -822,13 +1053,16 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn // Most peer connection (signaling) happens on // the signaling thread. -LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl() : +LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl(const webrtc::Environment& env) : + mEnv(env), mWebRTCImpl(nullptr), mPeerConnection(nullptr), mMute(MUTE_INITIAL), mAnswerReceived(false), mPeerConnectionState(webrtc::PeerConnectionInterface::PeerConnectionState::kNew), mDisconnectCount(0), + mStatsRequestPending(false), + mShuttingDown(false), mPendingJobs(0) { } @@ -857,50 +1091,68 @@ void LLWebRTCPeerConnectionImpl::init(LLWebRTCImpl * webrtc_impl) void LLWebRTCPeerConnectionImpl::terminate() { mPendingJobs++; + webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this); mWebRTCImpl->PostSignalingTask( - [this]() + [self]() { - if (mPeerConnection) - { - if (mDataChannel) - { - { - mDataChannel->Close(); - mDataChannel = nullptr; - } - } + self->closeOnSignalingThread(); + self->mPendingJobs--; + }); +} - // to remove 'Secondlife is recording' icon from taskbar - // if user was speaking - auto senders = mPeerConnection->GetSenders(); - for (auto& sender : senders) - { - auto track = sender->track(); - if (track) - { - track->set_enabled(false); - } - } +// Signaling thread only. +void LLWebRTCPeerConnectionImpl::closeOnSignalingThread() +{ + // Stop issuing stats requests; one may already be in flight, and + // Close() below will flush it. + mShuttingDown = true; - mPeerConnection->Close(); - if (mLocalStream) - { - auto tracks = mLocalStream->GetAudioTracks(); - for (auto& track : tracks) - { - mLocalStream->RemoveTrack(track); - } - mLocalStream = nullptr; - } - mPeerConnection = nullptr; + if (mPeerConnection) + { + if (mDataChannel) + { + mDataChannel->Close(); + mDataChannel = nullptr; + } - for (auto &observer : mSignalingObserverList) - { - observer->OnPeerConnectionClosed(); - } + // to remove 'Secondlife is recording' icon from taskbar + // if user was speaking + auto senders = mPeerConnection->GetSenders(); + for (auto& sender : senders) + { + auto track = sender->track(); + if (track) + { + track->set_enabled(false); } - mPendingJobs--; - }); + } + + // NOTE: Close() delivers any pending GetStats report inline, before it + // returns, so the observer list below must still be valid here. + mPeerConnection->Close(); + if (mLocalStream) + { + auto tracks = mLocalStream->GetAudioTracks(); + for (auto& track : tracks) + { + mLocalStream->RemoveTrack(track); + } + mLocalStream = nullptr; + } + mPeerConnection = nullptr; + + 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. + mSignalingObserverList.clear(); + mDataObserverList.clear(); } void LLWebRTCPeerConnectionImpl::setSignalingObserver(LLWebRTCSignalingObserver *observer) { mSignalingObserverList.emplace_back(observer); } @@ -922,8 +1174,9 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti mAnswerReceived = false; mPendingJobs++; + webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this); mWebRTCImpl->PostSignalingTask( - [this,options]() + [self,options]() { webrtc::PeerConnectionInterface::RTCConfiguration config; for (auto server : options.mServers) @@ -942,42 +1195,42 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti config.set_min_port(60000); config.set_max_port(60100); - webrtc::PeerConnectionDependencies pc_dependencies(this); + webrtc::PeerConnectionDependencies pc_dependencies(self.get()); // Other thread manages mPeerConnectionFactory's lifetime and it can be reset // at any momment, create own scoped_refptr (atomic). - webrtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> peer_connection_factory = mPeerConnectionFactory; + webrtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> peer_connection_factory = self->mPeerConnectionFactory; if (peer_connection_factory == nullptr) { RTC_LOG(LS_ERROR) << __FUNCTION__ << "Error creating peer connection, factory doesn't exist"; // Too early? - mPendingJobs--; + self->mPendingJobs--; return; } auto error_or_peer_connection = peer_connection_factory->CreatePeerConnectionOrError(config, std::move(pc_dependencies)); if (error_or_peer_connection.ok()) { - mPeerConnection = std::move(error_or_peer_connection.value()); + self->mPeerConnection = std::move(error_or_peer_connection.value()); } else { RTC_LOG(LS_ERROR) << __FUNCTION__ << "Error creating peer connection: " << error_or_peer_connection.error().message(); - for (auto &observer : mSignalingObserverList) + for (auto &observer : self->mSignalingObserverList) { observer->OnRenegotiationNeeded(); } - mPendingJobs--; + self->mPendingJobs--; return; } webrtc::DataChannelInit init; init.ordered = true; - auto data_channel_or_error = mPeerConnection->CreateDataChannelOrError("SLData", &init); + auto data_channel_or_error = self->mPeerConnection->CreateDataChannelOrError("SLData", &init); if (data_channel_or_error.ok()) { - mDataChannel = std::move(data_channel_or_error.value()); + self->mDataChannel = std::move(data_channel_or_error.value()); - mDataChannel->RegisterObserver(this); + self->mDataChannel->RegisterObserver(self.get()); } webrtc::AudioOptions audioOptions; @@ -986,16 +1239,16 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti audioOptions.noise_suppression = true; audioOptions.init_recording_on_send = false; - mLocalStream = peer_connection_factory->CreateLocalMediaStream("SLStream"); + self->mLocalStream = peer_connection_factory->CreateLocalMediaStream("SLStream"); webrtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track( peer_connection_factory->CreateAudioTrack("SLAudio", peer_connection_factory->CreateAudioSource(audioOptions).get())); audio_track->set_enabled(false); - mLocalStream->AddTrack(audio_track); + self->mLocalStream->AddTrack(audio_track); - mPeerConnection->AddTrack(audio_track, {"SLStream"}); + self->mPeerConnection->AddTrack(audio_track, {"SLStream"}); - auto senders = mPeerConnection->GetSenders(); + auto senders = self->mPeerConnection->GetSenders(); for (auto &sender : senders) { @@ -1011,7 +1264,7 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti sender->SetParameters(params); } - auto receivers = mPeerConnection->GetReceivers(); + auto receivers = self->mPeerConnection->GetReceivers(); for (auto &receiver : receivers) { webrtc::RtpParameters params; @@ -1027,9 +1280,9 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti } webrtc::PeerConnectionInterface::RTCOfferAnswerOptions offerOptions; - this->AddRef(); // CreateOffer will deref this when it's done. Without this, the callbacks never get called. - mPeerConnection->CreateOffer(this, offerOptions); - mPendingJobs--; + self->AddRef(); // CreateOffer will deref this when it's done. Without this, the callbacks never get called. + self->mPeerConnection->CreateOffer(self.get(), offerOptions); + self->mPendingJobs--; }); return true; @@ -1106,14 +1359,15 @@ void LLWebRTCPeerConnectionImpl::setMute(bool mute) mPendingJobs++; + webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this); mWebRTCImpl->PostSignalingTask( - [this, force_reset, enable]() + [self, force_reset, enable]() { - if (mPeerConnection) + if (self->mPeerConnection) { - auto senders = mPeerConnection->GetSenders(); + auto senders = self->mPeerConnection->GetSenders(); - RTC_LOG(LS_INFO) << __FUNCTION__ << (mMute ? "disabling" : "enabling") << " streams count " << senders.size(); + RTC_LOG(LS_INFO) << __FUNCTION__ << (self->mMute ? "disabling" : "enabling") << " streams count " << senders.size(); for (auto &sender : senders) { auto track = sender->track(); @@ -1129,7 +1383,7 @@ void LLWebRTCPeerConnectionImpl::setMute(bool mute) track->set_enabled(enable); } } - mPendingJobs--; + self->mPendingJobs--; } }); } @@ -1268,16 +1522,25 @@ void LLWebRTCPeerConnectionImpl::OnConnectionChange(webrtc::PeerConnectionInterf { case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected: { + // Audio is established now -- start playout for this connection. + // (Capture follows voice-enabled state, so it's already running and + // isn't touched here.) Doing playout here rather than at connection + // creation avoids running the output device with no decoded audio + // during the handshake (heard as a buzz). + mWebRTCImpl->startPlayout(); mPendingJobs++; - mWebRTCImpl->PostWorkerTask([this]() { - for (auto &observer : mSignalingObserverList) + webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this); + mWebRTCImpl->PostWorkerTask([self]() + { + for (auto &observer : self->mSignalingObserverList) { - observer->OnAudioEstablished(this); + observer->OnAudioEstablished(self.get()); } - mPendingJobs--; + self->mPendingJobs--; }); break; } + case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed: { for (auto &observer : mSignalingObserverList) @@ -1616,16 +1879,41 @@ void LLWebRTCPeerConnectionImpl::gatherConnectionStats() return; } - auto stats_callback = webrtc::make_ref_counted<LLStatsCollectorCallback>( - [this](const LLWebRTCStatsMap& generic_stats) + webrtc::scoped_refptr<LLWebRTCPeerConnectionImpl> self(this); + mWebRTCImpl->PostSignalingTask( + [self]() + { + if (!self->mPeerConnection + || self->mShuttingDown + || self->mPeerConnectionState != webrtc::PeerConnectionInterface::PeerConnectionState::kConnected + || self->mStatsRequestPending) // signaling thread only { - for (auto& observer : mSignalingObserverList) + return; + } + + self->mStatsRequestPending = true; + + auto stats_callback = webrtc::make_ref_counted<LLStatsCollectorCallback>( + [self](const LLWebRTCStatsMap& generic_stats) + { + self->mStatsRequestPending = false; + + // This can be delivered inline from PeerConnection::Close(), which + // flushes pending stats requests as it tears down. Don't call out + // to the observers in that case -- we're on our way out. + if (!self->mPeerConnection || self->mShuttingDown) + { + return; + } + + for (auto& observer : self->mSignalingObserverList) { observer->OnStatsDelivered(generic_stats); } }); - mPeerConnection->GetStats(stats_callback.get()); + self->mPeerConnection->GetStats(stats_callback.get()); + }); } LLWebRTCImpl * gWebRTCImpl = nullptr; diff --git a/indra/llwebrtc/llwebrtc.h b/indra/llwebrtc/llwebrtc.h index e76e708f0c..821400cfe8 100644 --- a/indra/llwebrtc/llwebrtc.h +++ b/indra/llwebrtc/llwebrtc.h @@ -153,6 +153,14 @@ class LLWebRTCDeviceInterface virtual void setCaptureDevice(const std::string& id) = 0; virtual void setRenderDevice(const std::string& id) = 0; + // Enable/disable the audio devices, set when voice is enabled/disabled. + // The capture (microphone) and playout (speaker) devices only run while this + // is enabled, so neither is held open when the user has voice off. While + // enabled, capture stays running across calls and mute/unmute so the AEC + // never cold-starts (no unmute hiss); playout still only runs when there's a + // connection to render. + virtual void setVoiceEnabled(bool enable) = 0; + // Device observers for device change callbacks. virtual void setDevicesObserver(LLWebRTCDevicesObserver *observer) = 0; virtual void unsetDevicesObserver(LLWebRTCDevicesObserver *observer) = 0; diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h index 2fd11b9b4f..71b63b8b76 100644 --- a/indra/llwebrtc/llwebrtc_impl.h +++ b/indra/llwebrtc/llwebrtc_impl.h @@ -180,7 +180,7 @@ private: class LLWebRTCAudioDeviceModule : public webrtc::AudioDeviceModule { public: - explicit LLWebRTCAudioDeviceModule(webrtc::scoped_refptr<webrtc::AudioDeviceModule> inner) : inner_(std::move(inner)), tuning_(false) + explicit LLWebRTCAudioDeviceModule(webrtc::scoped_refptr<webrtc::AudioDeviceModule> inner) : inner_(inner), tuning_(false) { RTC_CHECK(inner_); } @@ -197,9 +197,15 @@ public: } int32_t Init() override { return inner_->Init(); } - int32_t Terminate() override { return inner_->Terminate(); } + int32_t Terminate() override { + // libwebrtc attempts to terminate the adm when peer connections go to zero, but we don't want that, + // now that we're keeping the adm active throughout the session. + return 0; + } bool Initialized() const override { return inner_->Initialized(); } + int32_t ForceTerminate() { return inner_->Terminate(); } + // --- Device enumeration/selection (forward) --- int16_t PlayoutDevices() override { return inner_->PlayoutDevices(); } int16_t RecordingDevices() override { return inner_->RecordingDevices(); } @@ -325,30 +331,8 @@ public: // tuning microphone energy calculations float GetMicrophoneEnergy() { return audio_transport_.GetMicrophoneEnergy(); } void SetTuningMicGain(float gain) { audio_transport_.SetGain(gain); } - void SetTuning(bool tuning, bool mute) - { - tuning_ = tuning; - if (tuning) - { - inner_->InitRecording(); - inner_->StartRecording(); - inner_->StopPlayout(); - } - else - { - if (mute) - { - inner_->StopRecording(); - } - else - { - inner_->InitRecording(); - inner_->StartRecording(); - } - inner_->InitPlayout(); - inner_->StartPlayout(); - } - } + + void SetTuning(bool tuning, bool mute); protected: ~LLWebRTCAudioDeviceModule() override = default; @@ -442,7 +426,6 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO // void setAudioConfig(LLWebRTCDeviceInterface::AudioConfig config = LLWebRTCDeviceInterface::AudioConfig()) override; - void refreshDevices() override; void setDevicesObserver(LLWebRTCDevicesObserver *observer) override; @@ -451,6 +434,8 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO void setCaptureDevice(const std::string& id) override; void setRenderDevice(const std::string& id) override; + void setVoiceEnabled(bool enable) override; + void setTuningMode(bool enable) override; float getTuningAudioLevel() override; float getPeerConnectionAudioLevel() override; @@ -532,9 +517,23 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO LLWebRTCPeerConnectionInterface* newPeerConnection(); void freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection); + // Start playout once a connection's audio is established (playout is gated + // on there being a connection to render). Capture is not touched here -- + // it follows voice-enabled state, not connection state. Safe to call from + // any thread (work is posted to the worker thread). + void startPlayout(); + protected: + const webrtc::Environment mEnv; + void workerStartRecording(); + void workerStartPlayout(); void workerDeployDevices(); + // We always rely on WebRTC's internal (software APM) audio processing, so + // any platform/hardware AEC/AGC/NS must be kept disabled. + void workerDisableBuiltInAudioProcessing(); + + LLWebRTCLogSink* mLogSink; // The native webrtc threads @@ -547,10 +546,6 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO webrtc::scoped_refptr<webrtc::AudioProcessing> mAudioProcessingModule; - // more native webrtc stuff - std::unique_ptr<webrtc::TaskQueueFactory> mTaskQueueFactory; - - // Devices void updateDevices(); void deployDevices(); @@ -558,6 +553,10 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO webrtc::scoped_refptr<LLWebRTCAudioDeviceModule> mDeviceModule; std::vector<LLWebRTCDevicesObserver *> mVoiceDevicesObserverList; + bool mBuiltinNS; + bool mBuiltinAGC; + bool mBuiltinAEC; + // accessors in native webrtc for devices aren't apparently implemented yet. bool mTuningMode; std::string mRecordingDevice; @@ -567,6 +566,8 @@ class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceO LLWebRTCVoiceDeviceList mPlayoutDeviceList; bool mMute; + // Whether voice is enabled; gates whether the capture/playout devices run. + bool mVoiceEnabled; float mGain; LLCustomProcessorStatePtr mPeerCustomProcessor; @@ -590,11 +591,16 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface, { public: - LLWebRTCPeerConnectionImpl(); + LLWebRTCPeerConnectionImpl(const webrtc::Environment& env); ~LLWebRTCPeerConnectionImpl(); void init(LLWebRTCImpl * webrtc_impl); + // Posts closeOnSignalingThread() and returns immediately. void terminate(); + // The actual close. Signaling thread only. Callable directly (via a + // BlockingCall) when the caller needs the connection to be fully closed + // before it continues -- see LLWebRTCImpl::terminate(). + void closeOnSignalingThread(); virtual void AddRef() const override = 0; virtual webrtc::RefCountReleaseStatus Release() const override = 0; @@ -669,7 +675,7 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface, void gatherConnectionStats() override; protected: - + const webrtc::Environment mEnv; LLWebRTCImpl * mWebRTCImpl; webrtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> mPeerConnectionFactory; @@ -697,6 +703,14 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface, webrtc::PeerConnectionInterface::PeerConnectionState mPeerConnectionState; uint32_t mDisconnectCount; + // Accessed only on the WebRTC signaling thread. + bool mStatsRequestPending; + + // Set by closeOnSignalingThread() so that no new stats request (or other + // callback into the viewer) is issued while we're tearing down. + // Accessed only on the WebRTC signaling thread. + bool mShuttingDown; + std::atomic<int> mPendingJobs; }; diff --git a/indra/llwindow/llopenglview-objc.mm b/indra/llwindow/llopenglview-objc.mm index bdb5d8def0..3030169d58 100644 --- a/indra/llwindow/llopenglview-objc.mm +++ b/indra/llwindow/llopenglview-objc.mm @@ -226,12 +226,12 @@ attributedStringInfo getSegments(NSAttributedString *str) - (id) init { - return [self initWithFrame:[self bounds] withSamples:2 andVsync:TRUE]; + return [self initWithFrame:[self bounds] withSamples:0 andVsync:FALSE]; } - (id) initWithSamples:(NSUInteger)samples { - return [self initWithFrame:[self bounds] withSamples:samples andVsync:TRUE]; + return [self initWithFrame:[self bounds] withSamples:samples andVsync:FALSE]; } - (id) initWithSamples:(NSUInteger)samples andVsync:(BOOL)vsync @@ -247,8 +247,9 @@ attributedStringInfo getSegments(NSAttributedString *str) - (id) initWithFrame:(NSRect)frame withSamples:(NSUInteger)samples andVsync:(BOOL)vsync { - [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeURL]]; - [self initWithFrame:frame]; + NSRect fixedFrame = NSMakeRect(0, 0, NSWidth(frame), NSHeight(frame)); + [self initWithFrame:fixedFrame]; + [self setWantsBestResolutionOpenGLSurface:gHiDPISupport]; // Initialize with a default "safe" pixel format that will work with versions dating back to OS X 10.6. // Any specialized pixel formats, i.e. a core profile pixel format, should be initialized through rebuildContextWithFormat. @@ -260,7 +261,7 @@ attributedStringInfo getSegments(NSAttributedString *str) NSOpenGLPFAClosestPolicy, NSOpenGLPFAAccelerated, NSOpenGLPFADepthSize, 24, - NSOpenGLPFAColorSize, 32, + NSOpenGLPFAColorSize, 24, NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core, 0 }; @@ -271,8 +272,8 @@ attributedStringInfo getSegments(NSAttributedString *str) NSOpenGLPFAClosestPolicy, NSOpenGLPFAAccelerated, NSOpenGLPFAColorFloat, - NSOpenGLPFAColorSize, 64, NSOpenGLPFADepthSize, 24, + NSOpenGLPFAColorSize, 48, NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core, 0 }; @@ -327,9 +328,6 @@ attributedStringInfo getSegments(NSAttributedString *str) NSLog(@"Extended color space applied for HDR Display", nil); } - //for retina support - [self setWantsBestResolutionOpenGLSurface:gHiDPISupport]; - [self setOpenGLContext:glContext]; [glContext setView:self]; @@ -351,6 +349,8 @@ attributedStringInfo getSegments(NSAttributedString *str) GLint opacity = 1; [glContext setValues:&opacity forParameter:NSOpenGLCPSurfaceOpacity]; + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeURL]]; + return self; } diff --git a/indra/llwindow/llwindowcallbacks.cpp b/indra/llwindow/llwindowcallbacks.cpp index 7331f50ba0..4b804c82cc 100644 --- a/indra/llwindow/llwindowcallbacks.cpp +++ b/indra/llwindow/llwindowcallbacks.cpp @@ -190,7 +190,7 @@ bool LLWindowCallbacks::handleTimerEvent(LLWindow *window) return false; } -bool LLWindowCallbacks::handleDeviceChange(LLWindow *window) +bool LLWindowCallbacks::handleDeviceChange(LLWindow *window, const std::string& change_type) { return false; } diff --git a/indra/llwindow/llwindowcallbacks.h b/indra/llwindow/llwindowcallbacks.h index 59dcdd3ade..6d1990e92b 100644 --- a/indra/llwindow/llwindowcallbacks.h +++ b/indra/llwindow/llwindowcallbacks.h @@ -68,7 +68,7 @@ public: virtual void handleWindowUnblock(LLWindow *window); // window coming back after taking over CPU for a while virtual void handleDataCopy(LLWindow *window, S32 data_type, void *data); virtual bool handleTimerEvent(LLWindow *window); - virtual bool handleDeviceChange(LLWindow *window); + virtual bool handleDeviceChange(LLWindow *window, const std::string& change_type); virtual bool handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height); virtual bool handleDisplayChanged(); virtual bool handleWindowDidChangeScreen(LLWindow *window); diff --git a/indra/llwindow/llwindowmacosx-objc.h b/indra/llwindow/llwindowmacosx-objc.h index b302a705da..ec9afb1844 100644 --- a/indra/llwindow/llwindowmacosx-objc.h +++ b/indra/llwindow/llwindowmacosx-objc.h @@ -78,6 +78,8 @@ void initMainLoop(); void cleanupViewer(); void handleUrl(const char* url); void dispatchUrl(std::string url); +void startWatchdog(std::string_view state); +void stopWatchdog(); /* Defined in llwindowmacosx-objc.mm: */ int createNSApp(int argc, const char **argv); diff --git a/indra/llwindow/llwindowmacosx-objc.mm b/indra/llwindow/llwindowmacosx-objc.mm index d902a82a3c..85208a5ac3 100644 --- a/indra/llwindow/llwindowmacosx-objc.mm +++ b/indra/llwindow/llwindowmacosx-objc.mm @@ -223,8 +223,8 @@ OSErr setImageCursor(CursorRef ref) NSWindowRef createNSWindow(int x, int y, int width, int height) { LLNSWindow *window = [[LLNSWindow alloc]initWithContentRect:NSMakeRect(x, y, width, height) - styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable - backing:NSBackingStoreBuffered defer:NO]; + styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskFullSizeContentView + backing:NSBackingStoreBuffered defer:YES]; [window makeKeyAndOrderFront:nil]; [window setAcceptsMouseMovedEvents:TRUE]; [window setRestorable:FALSE]; // Viewer manages state from own settings @@ -347,7 +347,7 @@ void convertWindowToScreen(NSWindowRef window, float *coord) NSRect rect = NSMakeRect(coord[0], coord[1], 0, 0); rect = [(LLNSWindow*)window convertRectToScreen:rect]; - coord[0] = rect.origin.x; + coord[0] = rect.origin.x; coord[1] = [[NSScreen screens][0] frame].size.height - rect.origin.y; } diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp index 42250535f0..5a85887143 100644 --- a/indra/llwindow/llwindowmacosx.cpp +++ b/indra/llwindow/llwindowmacosx.cpp @@ -51,7 +51,7 @@ #include <IOKit/usb/IOUSBLib.h> extern bool gDebugWindowProc; -bool gHiDPISupport = true; +bool gHiDPISupport = false; bool gHDRDisplaySupport = false; const S32 BITS_PER_PIXEL = 32; @@ -761,7 +761,7 @@ bool LLWindowMacOSX::createContext(int x, int y, int width, int height, int bits kCGLPFAMultisample, kCGLPFASampleBuffers, static_cast<CGLPixelFormatAttribute>((mFSAASamples > 0 ? 1 : 0)), kCGLPFASamples, static_cast<CGLPixelFormatAttribute>(mFSAASamples), - kCGLPFAStencilSize, static_cast<CGLPixelFormatAttribute>(8), + //kCGLPFAStencilSize, static_cast<CGLPixelFormatAttribute>(8), kCGLPFADepthSize, static_cast<CGLPixelFormatAttribute>(24), kCGLPFAAlphaSize, static_cast<CGLPixelFormatAttribute>(8), kCGLPFAColorSize, static_cast<CGLPixelFormatAttribute>(24), @@ -939,9 +939,10 @@ bool LLWindowMacOSX::getVisible() if(mFullscreen) { result = true; - }if (mWindow) + } + if (mWindow) { - result = true; + result = true; } return(result); diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index dbdbfe390f..5793254ded 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -65,6 +65,7 @@ #include <utility> // std::pair #include <d3d9.h> +#include <d3d11.h> #include <dxgi1_4.h> #include <timeapi.h> @@ -115,7 +116,15 @@ static std::thread::id sMainThreadId; LPWSTR gIconResource = IDI_APPLICATION; LPWSTR gIconSmallResource = IDI_APPLICATION; -LPDIRECTINPUT8 gDirectInput8; + +namespace +{ + LPDIRECTINPUT8 gDirectInput8; + ID3D11Device* gD3D11Device = nullptr; + ID3D11DeviceContext* gD3D11Context = nullptr; + LUID gExpectedAdapterLUID; + HMODULE gD3D11Library; +} LLW32MsgCallback gAsyncMsgCallback = NULL; @@ -443,6 +452,16 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool } }); } + + // For mainWindowProc, it should not unpause watchdog if it was paused + void pingWindowTimeout(std::string_view state) + { + if (mWindowTimeout && mWindowTimeout->started()) + { + mWindowTimeout->setTimeout(WINDOW_TIMEOUT_SEC); + mWindowTimeout->ping(state); + } + } private: // These timeout related functions are strictly for the thread. void resumeTimeout(std::string_view state) @@ -508,6 +527,10 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks, //MAINT-516 -- force a load of opengl32.dll just in case windows went sideways LoadLibrary(L"opengl32.dll"); + // Request high-performance GPU before creating OpenGL context + // This increases probability of discrete GPU being used when + // the context is created. + requestHighPerformanceGPU(); if (mMaxCores != 0) { @@ -523,6 +546,8 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks, SetProcessAffinityMask(hProcess, mask); } + setThreadPriorityHigh(); + #if 0 // this is probably a bad idea, but keep it in your back pocket if you see what looks like // process deprioritization during profiles // force high thread priority @@ -545,41 +570,6 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks, } #endif -#if 0 // this is also probably a bad idea, but keep it in your back pocket for getting main thread off of background thread cores (see also LLThread::threadRun) - HANDLE hThread = GetCurrentThread(); - - SYSTEM_INFO sysInfo; - - GetSystemInfo(&sysInfo); - U32 core_count = sysInfo.dwNumberOfProcessors; - - if (max_cores != 0) - { - core_count = llmin(core_count, max_cores); - } - - if (hThread) - { - int priority = GetThreadPriority(hThread); - - if (priority < THREAD_PRIORITY_TIME_CRITICAL) - { - if (SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL)) - { - LL_INFOS() << "Set thread priority to THREAD_PRIORITY_TIME_CRITICAL" << LL_ENDL; - } - else - { - LL_INFOS() << "Failed to set thread priority: " << std::hex << GetLastError() << LL_ENDL; - } - - // tell main thread to prefer core 0 - SetThreadIdealProcessor(hThread, 0); - } - } -#endif - - mFSAASamples = fsaa_samples; mIconResource = gIconResource; mIconSmallResource = gIconSmallResource; @@ -1009,6 +999,7 @@ void LLWindowWin32::close() } mDragDrop->reset(); + clearHighPerformanceGPURequest(); // Go back to screen mode written in the registry. @@ -1073,6 +1064,52 @@ bool LLWindowWin32::isValid() return (mWindowHandle != NULL); } +void LLWindowWin32::setThreadPriorityHigh() +{ + // Threads start at normal priority. But this is our main window/rendering thread, + // even if window handle belongs to another thread. So we can raise its priority + // to ensure better responsiveness and less blocking by lack of resources. + HANDLE hThread = GetCurrentThread(); + if (hThread) + { + int priority = GetThreadPriority(hThread); + + if (priority == THREAD_PRIORITY_ERROR_RETURN) + { + LL_WARNS_ONCE("Window") << "Failed to get thread priority: " << std::hex << GetLastError() << LL_ENDL; + } + else if (priority > THREAD_PRIORITY_HIGHEST) + { + // At the moment nothing should be setting 'critical' priority, + // but if that happens for some reason, we don't want to mess with it. + LL_WARNS("Window") << "setThreadPriorityHigh ignored, priority was " << (S32)priority << LL_ENDL; + } + else if (priority != THREAD_PRIORITY_HIGHEST) + { + if (SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST)) + { + LL_DEBUGS("Window") << "Set thread priority to THREAD_PRIORITY_HIGHEST" << LL_ENDL; + } + else + { + LL_WARNS("Window") << "Failed to set thread priority: " << std::hex << GetLastError() << LL_ENDL; + } + } + } +} + +void LLWindowWin32::setThreadPriorityNormal() +{ + HANDLE hThread = GetCurrentThread(); + if (hThread) + { + if (!SetThreadPriority(hThread, THREAD_PRIORITY_NORMAL)) + { + LL_WARNS_ONCE("Window") << "Failed to set thread priority: " << std::hex << GetLastError() << LL_ENDL; + } + } +} + bool LLWindowWin32::getVisible() { return (mWindowHandle && IsWindowVisible(mWindowHandle)); @@ -2388,18 +2425,45 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_DEVICECHANGE: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DEVICECHANGE"); + window_imp->mWindowThread->pingWindowTimeout("WM_DEVICECHANGE"); + + // Log detailed device change information + std::string change_type = "UNKNOWN"; + switch (w_param) + { + case DBT_DEVICEARRIVAL: change_type = "DBT_DEVICEARRIVAL"; break; + case DBT_DEVICEREMOVECOMPLETE: change_type = "DBT_DEVICEREMOVECOMPLETE"; break; + case DBT_DEVNODES_CHANGED: change_type = "DBT_DEVNODES_CHANGED"; break; + case DBT_DEVICEQUERYREMOVE: change_type = "DBT_DEVICEQUERYREMOVE"; break; + case DBT_DEVICEQUERYREMOVEFAILED: change_type = "DBT_DEVICEQUERYREMOVEFAILED"; break; + case DBT_DEVICEREMOVEPENDING: change_type = "DBT_DEVICEREMOVEPENDING"; break; + case DBT_CONFIGCHANGED: change_type = "DBT_CONFIGCHANGED"; break; + } + if (w_param == DBT_DEVNODES_CHANGED || w_param == DBT_DEVICEARRIVAL) { - WINDOW_IMP_POST(window_imp->mCallbacks->handleDeviceChange(window_imp)); + WINDOW_IMP_POST(window_imp->mCallbacks->handleDeviceChange(window_imp, change_type)); return 1; } + else if (l_param) + { + const auto* hdr = reinterpret_cast<const DEV_BROADCAST_HDR*>(l_param); + if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) + { + // Might need to register for monitor device notifications + // to get this message when monitor is suspended or resumed. + // TODO: log monitor suspending and resuming. + LL_INFOS("Window") << "DEVICEINTERFACE: " << change_type << LL_ENDL; + } + } break; } case WM_PAINT: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_PAINT"); + window_imp->mWindowThread->pingWindowTimeout("WM_PAINT"); GetUpdateRect(window_imp->mWindowHandle, &update_rect, FALSE); update_width = update_rect.right - update_rect.left + 1; update_height = update_rect.bottom - update_rect.top + 1; @@ -2442,6 +2506,15 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ break; } + case WM_POWERBROADCAST: + { + // Might need to register for power broadcast interface + // Todo: log monitor suspending and resuming. + LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_POWERBROADCAST"); + LL_INFOS("Window") << "Received WM_POWERBROADCAST with wParam: 0x" << std::hex << (uintptr_t)w_param << " lParam: 0x" << (uintptr_t)l_param << std::dec << LL_ENDL; + break; + } + case WM_ACTIVATEAPP: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_ACTIVATEAPP"); @@ -2515,6 +2588,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_CLOSE: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_CLOSE"); + window_imp->mWindowThread->pingWindowTimeout("WM_CLOSE"); // todo: WM_CLOSE can be caused by user and by task manager, // distinguish these cases. // For now assume it is always user. @@ -2552,6 +2626,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ // Comes after WM_QUERYENDSESSION LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_ENDSESSION"); LL_INFOS("Window") << "Received WM_ENDSESSION with wParam: " << (U32)w_param << " lParam: " << (U32)l_param << LL_ENDL; + window_imp->mWindowThread->pingWindowTimeout("WM_ENDSESSION"); unsigned int end_session_flags = (U32)l_param; if (w_param == TRUE // if true, session is ending @@ -3043,19 +3118,31 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ // means that the window was un-minimized. if (w_param == SIZE_RESTORED && window_imp->mLastSizeWParam != SIZE_RESTORED) { - WINDOW_IMP_POST(window_imp->mCallbacks->handleActivate(window_imp, true)); + window_imp->post([=]() + { + window_imp->setThreadPriorityHigh(); + window_imp->mCallbacks->handleActivate(window_imp, true); + }); } // handle case of window being maximized from fully minimized state if (w_param == SIZE_MAXIMIZED && window_imp->mLastSizeWParam != SIZE_MAXIMIZED) { - WINDOW_IMP_POST(window_imp->mCallbacks->handleActivate(window_imp, true)); + window_imp->post([=]() + { + window_imp->setThreadPriorityHigh(); + window_imp->mCallbacks->handleActivate(window_imp, true); + }); } // Also handle the minimization case if (w_param == SIZE_MINIMIZED && window_imp->mLastSizeWParam != SIZE_MINIMIZED) { - WINDOW_IMP_POST(window_imp->mCallbacks->handleActivate(window_imp, false)); + window_imp->post([=]() + { + window_imp->setThreadPriorityNormal(); + window_imp->mCallbacks->handleActivate(window_imp, false); + }); } // Actually resize all of our views @@ -3075,6 +3162,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_DPICHANGED: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DPICHANGED"); + window_imp->mWindowThread->pingWindowTimeout("WM_DPICHANGED"); LPRECT lprc_new_scale; F32 new_scale = F32(LOWORD(w_param)) / F32(USER_DEFAULT_SCREEN_DPI); lprc_new_scale = (LPRECT)l_param; @@ -3095,7 +3183,9 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_DISPLAYCHANGE: { + window_imp->mWindowThread->pingWindowTimeout("WM_DISPLAYCHANGE"); WINDOW_IMP_POST(window_imp->mCallbacks->handleDisplayChanged()); + break; } case WM_SETFOCUS: @@ -3133,6 +3223,9 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_SETTINGCHANGE: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SETTINGCHANGE"); + // Can be called on OS user switching + LL_INFOS("Window") << "WM_SETTINGCHANGE, with wParam: 0x" << std::hex << (uintptr_t)w_param << " lParam: 0x" << (uintptr_t)l_param << std::dec << LL_ENDL; + window_imp->mWindowThread->pingWindowTimeout("WM_SETTINGCHANGE"); if (w_param == SPI_SETMOUSEVANISH) { if (!SystemParametersInfo(SPI_GETMOUSEVANISH, 0, &window_imp->mMouseVanish, 0)) @@ -4620,6 +4713,248 @@ void LLWindowWin32::setDPIAwareness() } } +void LLWindowWin32::requestHighPerformanceGPU() const +{ + // Try to load d3d11.dll and request high performance adapter + gD3D11Library = LoadLibraryA("d3d11.dll"); + if (gD3D11Library) + { + typedef HRESULT(WINAPI* PFN_D3D11_CREATE_DEVICE)( + IDXGIAdapter*, D3D_DRIVER_TYPE, HMODULE, UINT, + const D3D_FEATURE_LEVEL*, UINT, UINT, ID3D11Device**, + D3D_FEATURE_LEVEL*, ID3D11DeviceContext**); + + PFN_D3D11_CREATE_DEVICE pD3D11CreateDevice = + (PFN_D3D11_CREATE_DEVICE)GetProcAddress(gD3D11Library, "D3D11CreateDevice"); + + if (pD3D11CreateDevice) + { + // Try to enumerate adapters and select the best one + IDXGIFactory1* pFactory = nullptr; + IDXGIAdapter1* pSelectedAdapter = nullptr; + std::string selected_descr; + HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&pFactory); + + if (SUCCEEDED(hr) && pFactory) + { + IDXGIAdapter1* pAdapter = nullptr; + SIZE_T maxDedicatedMemory = 0; + UINT adapterIndex = 0; + S32 adapter_count = 0; + + // Enumerate all adapters and find the one with the most dedicated video memory + while (pFactory->EnumAdapters1(adapterIndex, &pAdapter) != DXGI_ERROR_NOT_FOUND) + { + DXGI_ADAPTER_DESC1 desc; + pAdapter->GetDesc1(&desc); + + std::wstring description_w(desc.Description); + std::string description = ll_convert_wide_to_string(description_w); + + + // Skip software adapters + if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) + { + LL_DEBUGS("Window") << "Adapter " << adapterIndex << ": " << description + << ", Dedicated VRAM: " << (desc.DedicatedVideoMemory / 1024 / 1024) << " MB" + << ", Vendor: 0x" << std::hex << desc.VendorId << std::dec + << ", Flags: " << desc.Flags << LL_ENDL; + } + // Skip Microsoft Basic Render Driver, it's a placeholder for missing drivers + else if (description.find("Microsoft Basic Render Driver") != std::string::npos) + { + // User is likely missing drivers, so log a warning. + // Don't consider this adapter as a valid selection. + LL_WARNS("Window") << "Adapter " << adapterIndex << ": " << description + << ", Dedicated VRAM: " << (desc.DedicatedVideoMemory / 1024 / 1024) << " MB" + << ", Vendor: 0x" << std::hex << desc.VendorId << std::dec + << ", Flags: " << desc.Flags << LL_ENDL; + } + else + { + LL_INFOS("Window") << "Adapter " << adapterIndex << ": " << description + << ", Dedicated VRAM: " << (desc.DedicatedVideoMemory / 1024 / 1024) << " MB" + << ", Vendor: 0x" << std::hex << desc.VendorId << std::dec + << ", Flags: " << desc.Flags << LL_ENDL; + + adapter_count++; + // Select adapter with most dedicated video memory (typically the discrete GPU) + if (desc.DedicatedVideoMemory > maxDedicatedMemory) + { + if (pSelectedAdapter) + { + pSelectedAdapter->Release(); + } + pSelectedAdapter = pAdapter; + pSelectedAdapter->AddRef(); + maxDedicatedMemory = desc.DedicatedVideoMemory; + gExpectedAdapterLUID = desc.AdapterLuid; + selected_descr = description; + } + } + + pAdapter->Release(); + adapterIndex++; + } + pFactory->Release(); + + if (adapter_count < 2) + { + // Only one adapter, no need to request high-performance GPU + if (pSelectedAdapter) + { + pSelectedAdapter->Release(); + } + gExpectedAdapterLUID = { 0, 0 }; + FreeLibrary(gD3D11Library); + gD3D11Library = nullptr; + return; + } + + LL_INFOS("Window") << "Selected as preferred adapter (highest VRAM): " << selected_descr << LL_ENDL; + } + + // Create a temporary device to ensure high-performance GPU is selected + // This initialization can help "wake up" the discrete GPU + D3D_FEATURE_LEVEL featureLevel; + D3D_FEATURE_LEVEL requestedLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1 }; + + bool adapterSelected = (pSelectedAdapter != nullptr); + if (adapterSelected) + { + hr = pD3D11CreateDevice( + pSelectedAdapter, + D3D_DRIVER_TYPE_UNKNOWN, + nullptr, + 0, + requestedLevels, + _countof(requestedLevels), + D3D11_SDK_VERSION, + &gD3D11Device, + &featureLevel, + &gD3D11Context + ); + pSelectedAdapter->Release(); + + if (!SUCCEEDED(hr)) + { + LL_WARNS("Window") << "D3D11 failed to use preffered adapter " << selected_descr << LL_ENDL; + gExpectedAdapterLUID = { 0, 0 }; + adapterSelected = false; + } + } + + if (!adapterSelected) + { + // Either failed to select or didn't find an adapter. + hr = pD3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_HARDWARE, + nullptr, + 0, + requestedLevels, + _countof(requestedLevels), + D3D11_SDK_VERSION, + &gD3D11Device, + &featureLevel, + &gD3D11Context + ); + if (!SUCCEEDED(hr)) + { + LL_WARNS("Window") << "D3D11 failed to use hardware adapter" << LL_ENDL; + FreeLibrary(gD3D11Library); + gD3D11Library = nullptr; + // These shouldn't be set, but make sure they are null. + gD3D11Device = nullptr; + gD3D11Context = nullptr; + } + } + } + else + { + LL_WARNS("Window") << "Failed to get D3D11CreateDevice function from d3d11.dll. High-performance GPU request failed." << LL_ENDL; + FreeLibrary(gD3D11Library); + gD3D11Library = nullptr; + } + } +} + +bool LLWindowWin32::detectGPUChange() const +{ + if (!gD3D11Device) + { + // Can't detect without D3D11 device + return false; + } + + if (gExpectedAdapterLUID.LowPart == 0 && gExpectedAdapterLUID.HighPart == 0) + { + // No specific adapter was selected, can't detect changes. + return false; + } + + IDXGIDevice* pDXGIDevice = nullptr; + HRESULT hr = gD3D11Device->QueryInterface(__uuidof(IDXGIDevice), (void**)&pDXGIDevice); + + if (SUCCEEDED(hr) && pDXGIDevice) + { + IDXGIAdapter* pCurrentAdapter = nullptr; + hr = pDXGIDevice->GetAdapter(&pCurrentAdapter); + + if (SUCCEEDED(hr) && pCurrentAdapter) + { + DXGI_ADAPTER_DESC desc; + pCurrentAdapter->GetDesc(&desc); + + std::wstring description_w(desc.Description); + + bool changed = false; + + // Check if LUID has changed + if (desc.AdapterLuid.LowPart != gExpectedAdapterLUID.LowPart || + desc.AdapterLuid.HighPart != gExpectedAdapterLUID.HighPart) + { + changed = true; + std::string current_gpu_name = ll_convert_wide_to_string(description_w); + LL_WARNS("Window") << "GPU change detected! Current adapter: " << current_gpu_name << LL_ENDL; + } + + pCurrentAdapter->Release(); + pDXGIDevice->Release(); + + return changed; + } + + if (pDXGIDevice) + { + pDXGIDevice->Release(); + } + } + + return false; +} + +void LLWindowWin32::clearHighPerformanceGPURequest() const +{ + detectGPUChange(); + gExpectedAdapterLUID = { 0, 0 }; + if (gD3D11Context) + { + gD3D11Context->Release(); + gD3D11Context = nullptr; + } + if (gD3D11Device) + { + gD3D11Device->Release(); + gD3D11Device = nullptr; + } + if (gD3D11Library) + { + FreeLibrary(gD3D11Library); + gD3D11Library = nullptr; + } +} + void* LLWindowWin32::getDirectInput8() { return &gDirectInput8; @@ -4648,6 +4983,12 @@ bool LLWindowWin32::getInputDevices(U32 device_type_filter, void LLWindowWin32::initWatchdog() { mWindowThread->initTimeout(); + + // Watchdog is effectively a 'login complete event', as the + // 'unstable' part is done and from now on we are tracking + // performance. + // No need to hold D3D11 context/device any more. + clearHighPerformanceGPURequest(); } F32 LLWindowWin32::getSystemUISize() @@ -4729,6 +5070,13 @@ inline LLWindowWin32::LLWindowWin32Thread::LLWindowWin32Thread() : LL::ThreadPool("Window Thread", 1, MAX_QUEUE_SIZE, false) { LL::ThreadPool::start(); + + // Set thread name for the window thread + // This will make it distinguishable in Visual Studio debugger + post([this]() + { + SetThreadDescription(GetCurrentThread(), L"LLWindowWin32 Thread"); + }); } /** @@ -4910,7 +5258,7 @@ void LLWindowWin32::LLWindowWin32Thread::run() } // Normally won't exist yet, but in case of re-init, make sure it's cleaned up - resumeTimeout("WindowThread"); + resumeTimeout("Window:WindowThread"); while (! getQueue().done()) { @@ -4921,23 +5269,25 @@ void LLWindowWin32::LLWindowWin32Thread::run() if (mWindowHandleThrd != 0) { - pingTimeout("messages"); MSG msg; BOOL status; if (mhDCThrd == 0) { + pingTimeout("Window:PeekMessage"); LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - PeekMessage"); logger.onChange("PeekMessage(", std::hex, mWindowHandleThrd, ")"); status = PeekMessage(&msg, mWindowHandleThrd, 0, 0, PM_REMOVE); } else { + pingTimeout("Window:GetMessage"); LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - GetMessage"); logger.always("GetMessage(", std::hex, mWindowHandleThrd, ")"); status = GetMessage(&msg, NULL, 0, 0); } if (status > 0) { + pingTimeout("Window:TranslateMessage"); logger.always("got MSG (", std::hex, msg.hwnd, ", ", msg.message, ", ", msg.wParam, ")"); TranslateMessage(&msg); @@ -4949,7 +5299,7 @@ void LLWindowWin32::LLWindowWin32Thread::run() { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - Function Queue"); - pingTimeout("queue"); + pingTimeout("Window:Queue"); logger.onChange("runPending()"); //process any pending functions getQueue().runPending(); diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index 8159092794..aab2635a34 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -148,6 +148,8 @@ protected: void initCursors(); HCURSOR loadColorCursor(LPCTSTR name); bool isValid(); + void setThreadPriorityHigh(); + void setThreadPriorityNormal(); void moveWindow(const LLCoordScreen& position,const LLCoordScreen& size); virtual LLSD getNativeKeyData(); @@ -170,6 +172,17 @@ protected: void handleCompositionMessage(U32 indexes); bool handleImeRequests(WPARAM request, LPARAM param, LRESULT *result); + // Additional function to request and hold a high-performance GPU on Windows 10+ + // + // Laptops can dynamically switch between integrated and discrete GPUs. + // The Viewer has gpu-specific optimizations, and this switching can cause problems and crashes. + // The login screen requires low performance, which can lead to the OS deciding to switch to the integrated GPU. + // To avoid this, we request and hold a high-performance GPU using A D3D11 context until login. + // For diagnostics, we also log GPU changes. + void requestHighPerformanceGPU() const; + bool detectGPUChange() const; + void clearHighPerformanceGPURequest() const; + protected: // // Platform specific methods diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index d42561d05e..81ac89695b 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -596,6 +596,7 @@ set(viewer_SOURCE_FILES llslurl.cpp llsnapshotlivepreview.cpp llspatialpartition.cpp + llstatslistener.cpp llspeakers.cpp llspeakingindicatormanager.cpp llsplitbutton.cpp @@ -732,7 +733,6 @@ set(viewer_SOURCE_FILES llvoicechannel.cpp llvoiceclient.cpp llvoicevisualizer.cpp - llvoicevivox.cpp llvoicewebrtc.cpp llvoinventorylistener.cpp llvopartgroup.cpp @@ -1280,6 +1280,7 @@ set(viewer_HEADER_FILES llsnapshotlivepreview.h llsnapshotmodel.h llspatialpartition.h + llstatslistener.h llspeakers.h llspeakingindicatormanager.h llsplitbutton.h @@ -1418,7 +1419,6 @@ set(viewer_HEADER_FILES llvoicechannel.h llvoiceclient.h llvoicevisualizer.h - llvoicevivox.h llvoicewebrtc.h llvoinventorylistener.h llvopartgroup.h @@ -1915,9 +1915,7 @@ if (WINDOWS) ${CMAKE_SOURCE_DIR}/../scripts/messages/message_template.msg ${SHARED_LIB_STAGING_DIR}/openjp2.dll ${SHARED_LIB_STAGING_DIR}/llwebrtc.dll - #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/SLVoice.exe #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/libsndfile-1.dll - #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/vivoxoal.dll ${AUTOBUILD_INSTALL_DIR}/ca-bundle.crt ${CMAKE_CURRENT_SOURCE_DIR}/licenses-win32.txt ${CMAKE_CURRENT_SOURCE_DIR}/featuretable.txt @@ -1928,18 +1926,6 @@ if (WINDOWS) media_plugin_example ) - if (ADDRESS_SIZE EQUAL 64) - list(APPEND COPY_INPUT_DEPENDENCIES - ${SHARED_LIB_STAGING_DIR}/vivoxsdk_x64.dll - ${SHARED_LIB_STAGING_DIR}/ortp_x64.dll - ) - else (ADDRESS_SIZE EQUAL 64) - list(APPEND COPY_INPUT_DEPENDENCIES - ${SHARED_LIB_STAGING_DIR}/vivoxsdk.dll - ${SHARED_LIB_STAGING_DIR}/ortp.dll - ) - endif (ADDRESS_SIZE EQUAL 64) - if (TARGET ll::discord_sdk) list(APPEND COPY_INPUT_DEPENDENCIES ${SHARED_LIB_STAGING_DIR}/discord_partner_sdk.dll diff --git a/indra/newview/Info-SecondLife.plist b/indra/newview/Info-SecondLife.plist index 82f63519db..81deab1055 100644 --- a/indra/newview/Info-SecondLife.plist +++ b/indra/newview/Info-SecondLife.plist @@ -38,6 +38,8 @@ <string>public.app-category.games</string> <key>NSHighResolutionCapable</key> <true/> + <key>NSLocalNetworkUsageDescription</key> + <string>Second Life uses WebRTC for voice chat, which may require local network access while establishing the voice connection.</string> <key>CFBundleDocumentTypes</key> <array> <dict> diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index b7397ce158..c4697fd566 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -26.2.0 +26.3.0 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 47506d9cb2..68efed0a67 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -6087,7 +6087,7 @@ <key>Type</key> <string>S32</string> <key>Value</key> - <integer>110</integer> + <integer>127</integer> </map> <key>NotificationChannelRightMargin</key> <map> @@ -8213,11 +8213,11 @@ <key>Comment</key> <string>If available free physical memory is below this value textures get agresively scaled down</string> <key>Persist</key> - <integer>1</integer> + <integer>0</integer> <key>Type</key> <string>U32</string> <key>Value</key> - <integer>256</integer> + <integer>512</integer> </map> <key>RenderLowMemMinDiscardIncrement</key> <map> @@ -11456,6 +11456,17 @@ <key>Value</key> <integer>1</integer> </map> + <key>ShowBlockedConvHistory</key> + <map> + <key>Comment</key> + <string>Specifies whether blocked agents will be shown in Call Log</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>ShowMatureEvents</key> <map> <key>Comment</key> @@ -13809,141 +13820,8 @@ <key>Value</key> <integer>1</integer> </map> - <key>VivoxAutoPostCrashDumps</key> - <map> - <key>Comment</key> - <string>If true, SLVoice will automatically send crash dumps directly to Vivox.</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>1</integer> - </map> - <key>VivoxDebugLevel</key> - <map> - <key>Comment</key> - <string>Logging level to use when launching the vivox daemon</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string>0</string> - </map> - <key>VivoxLogDirectory</key> - <map> - <key>Comment</key> - <string>Default log path is Application Support/SecondLife/logs specify alternate absolute path here.</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string></string> - </map> - <key>VivoxShutdownTimeout</key> - <map> - <key>Comment</key> - <string>shutdown timeout in miliseconds. The amount of time to wait for the service to shutdown gracefully after the last disconnect</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string>5</string> - </map> - <key>VivoxDebugSIPURIHostName</key> - <map> - <key>Comment</key> - <string>Hostname portion of vivox SIP URIs (empty string for the default).</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string></string> - </map> - <key>VivoxDebugVoiceAccountServerURI</key> - <map> - <key>Comment</key> - <string>URI to the vivox account management server (empty string for the default).</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string></string> - </map> - <key>VivoxVoiceHost</key> - <map> - <key>Comment</key> - <string>Client SLVoice host to connect to</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string>127.0.0.1</string> - </map> - <key>VivoxVoicePort</key> - <map> - <key>Comment</key> - <string>Client SLVoice port to connect to</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>U32</string> - <key>Value</key> - <integer>44125</integer> - </map> - - <key>VivoxVadAuto</key> - <map> - <key>Comment</key> - <string>A flag indicating if the automatic VAD is enabled (1) or disabled (0). The individual settings are ignored if the auto-mode is enabled</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>U32</string> - <key>Value</key> - <integer>0</integer> - </map> - <key>VivoxVadHangover</key> - <map> - <key>Comment</key> - <string>The time (in milliseconds) that it takes or the VAD to switch back to silence from speech mode after the last speech frame has been detected</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>U32</string> - <key>Value</key> - <integer>2000</integer> - </map> - <key>VivoxVadNoiseFloor</key> - <map> - <key>Comment</key> - <string>A dimensionless value between 0 and 20000 (default 576) that controls the maximum level at which the noise floor may be set at by the VAD's noise tracking</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>U32</string> - <key>Value</key> - <integer>576</integer> - </map> - <key>VivoxVadSensitivity</key> - <map> - <key>Comment</key> - <string>A dimensionless value between 0 and 100, indicating the 'sensitivity of the VAD'. Increasing this value corresponds to decreasing the sensitivity of the VAD and 0 is turned off altogether</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>U32</string> - <key>Value</key> - <integer>0</integer> - </map> - <key>VoiceCallsFriendsOnly</key> - <!-- deprecated (see SL-12871) --> + <key>VoiceCallsFriendsOnly</key> + <!-- deprecated (see SL-12871) --> <map> <key>Comment</key> <string>(Deprecated) Only accept voice calls from residents on your friends list</string> @@ -14020,17 +13898,6 @@ <key>Value</key> <integer>0</integer> </map> - <key>VoiceHost</key> - <map> - <key>Comment</key> - <string>Client SLVoice host to connect to</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string>127.0.0.1</string> - </map> <key>VoiceImageLevel0</key> <map> <key>Comment</key> @@ -14119,17 +13986,6 @@ <key>Value</key> <string>Default</string> </map> - <key>VoiceLogFile</key> - <map> - <key>Comment</key> - <string>Log file to use when launching the voice daemon</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string></string> - </map> <key>VoiceOutputAudioDevice</key> <map> <key>Comment</key> @@ -14152,17 +14008,6 @@ <key>Value</key> <integer>10</integer> </map> - <key>VoicePort</key> - <map> - <key>Comment</key> - <string>Client SLVoice port to connect to</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>U32</string> - <key>Value</key> - <integer>44125</integer> - </map> <key>VoiceEchoCancellation</key> <map> <key>Comment</key> diff --git a/indra/newview/app_settings/shaders/class1/deferred/CASF.glsl b/indra/newview/app_settings/shaders/class1/deferred/CASF.glsl index 8e12d09443..df58f68556 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/CASF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/CASF.glsl @@ -1171,12 +1171,12 @@ vec3 linear_to_srgb(vec3 cl); // - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage). // Burns 3 ops for conversion {shift,or,xor}. //============================================================================================================================== - AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} - AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} + AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000u));} + AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000u));} //------------------------------------------------------------------------------------------------------------------------------ // Just adjust high 16-bit value (useful when upper part of 32-bit word is a 16-bit float value). - AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} - AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} + AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000u));} + AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000u));} //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //_____________________________________________________________/\_______________________________________________________________ diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt index 396ece9dc1..6cbbb92f50 100644 --- a/indra/newview/featuretable_mac.txt +++ b/indra/newview/featuretable_mac.txt @@ -129,10 +129,10 @@ RenderTonemapType 1 1 RenderTonemapMix 1 0.7 RenderDisableVintageMode 1 0 RenderMaxTextureResolution 1 512 +//RenderReflectionProbeCount 1 1 RenderReflectionProbeCount 1 4 RenderReflectionProbeDrawDistance 1 16.0 - // // Medium Low Graphics Settings // @@ -174,6 +174,7 @@ RenderTonemapType 1 1 RenderTonemapMix 1 0.7 RenderDisableVintageMode 1 0 RenderMaxTextureResolution 1 1024 +//RenderReflectionProbeCount 1 32 RenderReflectionProbeCount 1 16 RenderReflectionProbeDrawDistance 1 24.0 diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi index 07ed0d0824..cf12be91c3 100644 --- a/indra/newview/installers/windows/installer_template.nsi +++ b/indra/newview/installers/windows/installer_template.nsi @@ -865,9 +865,6 @@ Delete "$INSTDIR\trial\*.html" Delete "$INSTDIR\newview.exe"
Delete "$INSTDIR\SecondLife.exe"
-# MAINT-3099 workaround - prevent these log files, if present, from causing a user alert
-Delete "$INSTDIR\VivoxVoiceService-*.log"
-
# Remove entire help directory
RMDir /r "$INSTDIR\help"
diff --git a/indra/newview/licenses-mac.txt b/indra/newview/licenses-mac.txt index b1336f1bc4..39c81da447 100644 --- a/indra/newview/licenses-mac.txt +++ b/indra/newview/licenses-mac.txt @@ -575,51 +575,6 @@ supporting the PNG file format in commercial products. If you use this source code in a product, acknowledgment is not required but would be appreciated. -************************************************************* -************************************************************* - -/** - * OpenAL cross platform audio library - * Copyright (C) 1999-2000 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -************************************************************* -************************************************************* - -Internet Software Consortium code - -/* This is from the BIND 4.9.4 release, modified to compile by itself */ -/* Copyright (c) 1996 by Internet Software Consortium. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS - * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE - * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR - * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS - * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS - * SOFTWARE. - */ - ============= meshoptimizer ============= diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index 5ddb87558a..73368c1bcc 100644 --- a/indra/newview/llagentlistener.cpp +++ b/indra/newview/llagentlistener.cpp @@ -43,6 +43,7 @@ #include "llviewernetwork.h" #include "llviewerobject.h" #include "llviewerobjectlist.h" +#include "llviewerjointattachment.h" #include "llviewerregion.h" #include "llvoavatarself.h" #include "llsdutil.h" @@ -195,6 +196,12 @@ LLAgentListener::LLAgentListener(LLAgent &agent) &LLAgentListener::getNearbyObjectsList, llsd::map("reply", LLSD())); + add("getAttachedObjectsList", + "Return attached objects list with information about each object\n" + "reply contains \"attachments\" result key", + &LLAgentListener::getAttachedObjectsList, + llsd::map("reply", LLSD())); + add("getAgentScreenPos", "Return screen position of the [\"avatar_id\"] avatar or own avatar if not specified\n" "reply contains \"x\", \"y\" coordinates and \"onscreen\" flag to indicate if it's actually in within the current window\n" @@ -772,6 +779,41 @@ void LLAgentListener::getNearbyObjectsList(LLSD const& event_data) } } +void LLAgentListener::getAttachedObjectsList(LLSD const& event_data) +{ + Response response(LLSD(), event_data); + response["attachments"] = LLSD::emptyArray(); + + if (!isAgentAvatarValid()) + { + return; + } + + for (const auto& [attachment_point_index, attachment_point] : gAgentAvatarp->mAttachmentPoints) + { + if (!attachment_point) + { + continue; + } + + for (const auto& attachment_object_ptr : attachment_point->mAttachedObjects) + { + if (LLViewerObject* attachment_object = attachment_object_ptr.get()) + { + response["attachments"].append(llsd::map( + "object_id", attachment_object->getID(), + "inventory_item_id", attachment_object->getAttachmentItemID(), + "name", attachment_object->getAttachmentItemName(), + "attachment_point", attachment_point->getName(), + "attachment_point_index", attachment_point_index, + "position", ll_sd_from_vector3(attachment_object->getPosition()), + "rotation", ll_sd_from_quaternion(attachment_object->getRotation()), + "is_temporary", attachment_object->isTempAttachment())); + } + } + } +} + void LLAgentListener::getAgentScreenPos(LLSD const& event_data) { Response response(LLSD(), event_data); diff --git a/indra/newview/llagentlistener.h b/indra/newview/llagentlistener.h index b5bea8c0bd..031fc75e46 100644 --- a/indra/newview/llagentlistener.h +++ b/indra/newview/llagentlistener.h @@ -68,6 +68,7 @@ private: void getID(LLSD const& event_data); void getNearbyAvatarsList(LLSD const& event_data); void getNearbyObjectsList(LLSD const& event_data); + void getAttachedObjectsList(LLSD const& event_data); void getAgentScreenPos(LLSD const& event_data); LLViewerObject * findObjectClosestTo( const LLVector3 & position, bool sit_target = false ) const; diff --git a/indra/newview/llappdelegate-objc.mm b/indra/newview/llappdelegate-objc.mm index 0b3d0355a2..d3aac5a43e 100644 --- a/indra/newview/llappdelegate-objc.mm +++ b/indra/newview/llappdelegate-objc.mm @@ -395,11 +395,13 @@ struct AttachmentInfo - (void)sendEvent:(NSEvent *)event { + startWatchdog("sendEvent"); // events are outside pumpMainLoop [super sendEvent:event]; if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) { [[self keyWindow] sendEvent:event]; } + stopWatchdog(); // leaving scope } @end diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 614d9fdeb1..9636db21c1 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -703,12 +703,12 @@ public: void onFetchCompletion(); bool isFetchCompleted(); bool isTimedOut(); + bool pollStopped(); void checkMissingWearables(); bool pollMissingWearables(); bool isMissingCompleted(); void recoverMissingWearable(LLWearableType::EType type); - void clearCOFLinksForMissingWearables(); void onWearableAssetFetch(LLViewerWearable *wearable); void onAllComplete(); @@ -717,7 +717,6 @@ public: found_list_t& getFoundList(); void eraseTypeToLink(LLWearableType::EType type); void eraseTypeToRecover(LLWearableType::EType type); - void setObjItems(const LLInventoryModel::item_array_t& items); void setGestItems(const LLInventoryModel::item_array_t& items); bool isMostRecent(); void handleLateArrivals(); @@ -727,7 +726,6 @@ public: private: found_list_t mFoundList; - LLInventoryModel::item_array_t mObjItems; LLInventoryModel::item_array_t mGestItems; typedef std::set<S32> type_set_t; type_set_t mTypesToRecover; @@ -804,11 +802,6 @@ void LLWearableHoldingPattern::eraseTypeToRecover(LLWearableType::EType type) mTypesToRecover.erase(type); } -void LLWearableHoldingPattern::setObjItems(const LLInventoryModel::item_array_t& items) -{ - mObjItems = items; -} - void LLWearableHoldingPattern::setGestItems(const LLInventoryModel::item_array_t& items) { mGestItems = items; @@ -914,55 +907,10 @@ void LLWearableHoldingPattern::onAllComplete() if (isAgentAvatarValid()) { - LL_DEBUGS("Avatar") << self_av_string() << "Updating " << mObjItems.size() << " attachments" << LL_ENDL; - LLAgentWearables::llvo_vec_t objects_to_remove; - LLAgentWearables::llvo_vec_t objects_to_retain; - LLInventoryModel::item_array_t items_to_add; - - LLAgentWearables::findAttachmentsAddRemoveInfo(mObjItems, - objects_to_remove, - objects_to_retain, - items_to_add); - - LL_DEBUGS("Avatar") << self_av_string() << "Removing " << objects_to_remove.size() - << " attachments" << LL_ENDL; - - // Here we remove the attachment pos overrides for *all* - // attachments, even those that are not being removed. This is - // needed to get joint positions all slammed down to their - // pre-attachment states. - gAgentAvatarp->clearAttachmentOverrides(); - - if (objects_to_remove.size() || items_to_add.size()) - { - LL_DEBUGS("Avatar") << "ATT will remove " << objects_to_remove.size() - << " and add " << items_to_add.size() << " items" << LL_ENDL; - } - - // Take off the attachments that will no longer be in the outfit. - LLAgentWearables::userRemoveMultipleAttachments(objects_to_remove); - // Update wearables. LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " updating agent wearables with " << mResolved << " wearable items " << LL_ENDL; LLAppearanceMgr::instance().updateAgentWearables(this); - - // Restore attachment pos overrides for the attachments that - // are remaining in the outfit. - for (LLAgentWearables::llvo_vec_t::iterator it = objects_to_retain.begin(); - it != objects_to_retain.end(); - ++it) - { - LLViewerObject *objectp = *it; - if (!objectp->isAnimatedObject()) - { - gAgentAvatarp->addAttachmentOverridesForObject(objectp); - } - } - - // Add new attachments to match those requested. - LL_DEBUGS("Avatar") << self_av_string() << "Adding " << items_to_add.size() << " attachments" << LL_ENDL; - LLAgentWearables::userAttachMultipleAttachments(items_to_add); } if (isFetchCompleted() && isMissingCompleted()) @@ -1000,6 +948,10 @@ bool LLWearableHoldingPattern::pollFetchCompletion() { // runway skip here? LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + + // If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves + doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollStopped, this)); + return true; } bool completed = isFetchCompleted(); @@ -1070,6 +1022,9 @@ void recovered_item_cb(const LLUUID& item_id, LLWearableType::EType type, LLView { // runway skip here? LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + + // If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves + return; } LL_DEBUGS("Avatar") << self_av_string() << "Recovered item for type " << type << LL_ENDL; @@ -1120,18 +1075,15 @@ bool LLWearableHoldingPattern::isMissingCompleted() return mTypesToLink.size()==0 && mTypesToRecover.size()==0; } -void LLWearableHoldingPattern::clearCOFLinksForMissingWearables() +bool LLWearableHoldingPattern::pollStopped() { - for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it) + // We have to keep on polling until we're sure that all callbacks have completed or they'll cause a crash + if (isFetchCompleted() && isMissingCompleted()) { - LLFoundData &data = *it; - if ((data.mWearableType < LLWearableType::WT_COUNT) && (!data.mWearable)) - { - // Wearable link that was never resolved; remove links to it from COF - LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " removing link for unresolved item " << data.mItemID.asString() << LL_ENDL; - LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID); - } + delete this; + return true; } + return false; } bool LLWearableHoldingPattern::pollMissingWearables() @@ -1140,6 +1092,10 @@ bool LLWearableHoldingPattern::pollMissingWearables() { // runway skip here? LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + + // If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves + doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollStopped, this)); + return true; } bool timed_out = isTimedOut(); @@ -1164,14 +1120,6 @@ bool LLWearableHoldingPattern::pollMissingWearables() gAgentAvatarp->debugWearablesLoaded(); - // BAP - if we don't call clearCOFLinksForMissingWearables() - // here, we won't have to add the link back in later if the - // wearable arrives late. This is to avoid corruption of - // wearable ordering info. Also has the effect of making - // unworn item links visible in the COF under some - // circumstances. - - //clearCOFLinksForMissingWearables(); onAllComplete(); } return done; @@ -1218,13 +1166,6 @@ void LLWearableHoldingPattern::handleLateArrivals() replaced_types.insert(data.mWearableType); - // BAP - if we didn't call - // clearCOFLinksForMissingWearables() earlier, we - // don't need to restore the link here. Fixes - // wearable ordering problems. - - // LLAppearanceMgr::instance().addCOFItemLink(data.mItemID,false); - // BAP failing this means inventory or asset server // are corrupted in a way we don't handle. llassert((data.mWearableType < LLWearableType::WT_COUNT) && (wearable->getType() == data.mWearableType)); @@ -2143,15 +2084,23 @@ void LLAppearanceMgr::purgeBaseOutfitLink(const LLUUID& category, LLPointer<LLIn // Keep the last N wearables of each type. For viewer 2.0, N is 1 for // both body parts and clothing items. void LLAppearanceMgr::filterWearableItems( - LLInventoryModel::item_array_t& items, S32 max_per_type, S32 max_total) + LLInventoryModel::item_array_t& items, S32 max_per_type, S32 max_total, bool skip_bodyparts) { // Restrict by max total items first. if ((max_total > 0) && (items.size() > max_total)) { - LLInventoryModel::item_array_t items_to_keep; - for (S32 i=0; i<max_total; i++) + LLInventoryModel::item_array_t items_to_keep; size_t non_body_kept = 0; + for (const auto& item : items) { - items_to_keep.push_back(items[i]); + if (skip_bodyparts && item.get() && item.get()->getType() == LLAssetType::AT_BODYPART) + { + items_to_keep.push_back(item); + } + else if (non_body_kept < max_total) + { + items_to_keep.push_back(item); + non_body_kept++; + } } items = items_to_keep; } @@ -2636,6 +2585,11 @@ void LLAppearanceMgr::updateAppearanceFromCOF(bool enforce_item_restrictions, remove_non_link_items(wear_items); remove_non_link_items(obj_items); remove_non_link_items(gest_items); + // Since we're following folder links we might have picked up new duplicates, or exceeded MAX_CLOTHING_LAYERS + removeDuplicateItems(wear_items); + removeDuplicateItems(obj_items); + removeDuplicateItems(gest_items); + filterWearableItems(wear_items, 0, LLAgentWearables::MAX_CLOTHING_LAYERS, true); dumpItemArray(wear_items,"asset_dump: wear_item"); dumpItemArray(obj_items,"asset_dump: obj_item"); @@ -2647,6 +2601,77 @@ void LLAppearanceMgr::updateAppearanceFromCOF(bool enforce_item_restrictions, << " descendent_count " << cof->getDescendentCount() << " viewer desc count " << cof->getViewerDescendentCount() << LL_ENDL; } + + // Update attachments to match those requested. + if (isAgentAvatarValid()) + { + // Include attachments which should be in COF but don't have their link created yet + std::set<LLUUID> pendingAttachments; + LLAttachmentsMgr::instance().getPendingAttachments(pendingAttachments); + for (const LLUUID& idAttachItem : pendingAttachments) + { + if ( !gAgentAvatarp->isWearingAttachment(idAttachItem) || isLinkedInCOF(idAttachItem) ) + { + LLAttachmentsMgr::instance().clearPendingAttachmentLink(idAttachItem); + continue; + } + + if (LLViewerInventoryItem* pAttachItem = gInventory.getItem(idAttachItem)) + { + obj_items.push_back(pAttachItem); + } + } + + LL_DEBUGS("Avatar") << self_av_string() << "Updating " << obj_items.size() << " attachments" << LL_ENDL; + LLAgentWearables::llvo_vec_t objects_to_remove; + LLAgentWearables::llvo_vec_t objects_to_retain; + LLInventoryModel::item_array_t items_to_add; + + LLAgentWearables::findAttachmentsAddRemoveInfo(obj_items, + objects_to_remove, + objects_to_retain, + items_to_add); + + LL_DEBUGS("Avatar") << self_av_string() << "Removing " << objects_to_remove.size() + << " attachments" << LL_ENDL; + + // Here we remove the attachment pos overrides for *all* + // attachments, even those that are not being removed. This is + // needed to get joint positions all slammed down to their + // pre-attachment states. + gAgentAvatarp->clearAttachmentOverrides(); + + if (objects_to_remove.size() || items_to_add.size()) + { + LL_DEBUGS("Avatar") << "ATT will remove " << objects_to_remove.size() + << " and add " << items_to_add.size() << " items" << LL_ENDL; + } + + // Take off the attachments that will no longer be in the outfit. + // (don't remove attachments until avatar is fully loaded - reduces random attaching/detaching/reattaching at log-on) + if (gAgentAvatarp->isFullyLoaded()) + { + LLAgentWearables::userRemoveMultipleAttachments(objects_to_remove); + } + + // Restore attachment pos overrides for the attachments that + // are remaining in the outfit. + for (LLAgentWearables::llvo_vec_t::iterator it = objects_to_retain.begin(); + it != objects_to_retain.end(); + ++it) + { + LLViewerObject *objectp = *it; + if (!objectp->isAnimatedObject()) + { + gAgentAvatarp->addAttachmentOverridesForObject(objectp); + } + } + + // Add new attachments to match those requested. + LL_DEBUGS("Avatar") << self_av_string() << "Adding " << items_to_add.size() << " attachments" << LL_ENDL; + LLAgentWearables::userAttachMultipleAttachments(items_to_add); + } + if(!wear_items.size()) { LLNotificationsUtil::add("CouldNotPutOnOutfit"); @@ -2661,7 +2686,6 @@ void LLAppearanceMgr::updateAppearanceFromCOF(bool enforce_item_restrictions, LLTimer hp_block_timer; LLWearableHoldingPattern* holder = new LLWearableHoldingPattern; - holder->setObjItems(obj_items); holder->setGestItems(gest_items); // Note: can't do normal iteration, because if all the @@ -4203,6 +4227,7 @@ void LLAppearanceMgr::removeItemsFromAvatar(const uuid_vec_t& ids_to_remove, nul continue; } removeCOFItemLinks(linked_item_id, cb); + LLAttachmentsMgr::instance().clearPendingAttachmentLink(linked_item_id); addDoomedTempAttachment(linked_item_id); } } diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index 131b6817ed..9d90be0004 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -151,6 +151,7 @@ public: // Attachment link management void unregisterAttachment(const LLUUID& item_id); void registerAttachment(const LLUUID& item_id); + bool getAttachmentInvLinkEnable() const { return mAttachmentInvLinkEnabled; } void setAttachmentInvLinkEnable(bool val); // Add COF link to individual item. @@ -249,7 +250,7 @@ private: private: - void filterWearableItems(LLInventoryModel::item_array_t& items, S32 max_per_type, S32 max_total); + void filterWearableItems(LLInventoryModel::item_array_t& items, S32 max_per_type, S32 max_total, bool skip_bodyparts = false); void getDescendentsOfAssetType(const LLUUID& category, LLInventoryModel::item_array_t& items, diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 4c0de50223..f0df9537fc 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -156,6 +156,7 @@ #include <boost/algorithm/string.hpp> #include <boost/regex.hpp> #include <boost/throw_exception.hpp> +#include <chrono> #if LL_WINDOWS # include <share.h> // For _SH_DENYWR in processMarkerFiles @@ -1316,7 +1317,11 @@ bool LLAppViewer::frame() { try { + const auto start = std::chrono::steady_clock::now(); ret = doFrame(); + const auto end = std::chrono::steady_clock::now(); + const U64 doframe_time_us = (U64)std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); + LLTrace::sample(LLStatViewer::DOFRAME_TIME_US, doframe_time_us); } catch (const LLContinueError&) { @@ -2208,6 +2213,8 @@ bool LLAppViewer::cleanup() // deleteSingleton() methods. LLSingletonBase::deleteAll(); + LLUICtrlFactory::deleteSingleton(); + LLSplashScreen::hide(); LL_INFOS() << "Goodbye!" << LL_ENDL; @@ -3045,14 +3052,20 @@ bool LLAppViewer::initConfiguration() if (mSecondInstance) { - // This is the second instance of SL. Mute voice, - // but make sure the setting is *not* persisted. - // Also see LLVivoxVoiceClient::voiceEnabled() + // This is the second concurrent instance of SL. + // Disable voice for this session only, user should + // be able to enable voice manually, after that it + // works the same way as on primary instance. LLControlVariable* enable_voice = gSavedSettings.getControl("EnableVoiceChat"); - if (enable_voice) + if (enable_voice && enable_voice->getValue().asBoolean()) { + LL_DEBUGS("AppInit") << "Disabling voice for this session only" << LL_ENDL; + // Will be saved as mValues[2] which does not get written to the file. + // This feels like a hack, but otherwise way too many controls have to + // be tracked manually instead of using xmls' control_name. const bool DO_NOT_PERSIST = false; - enable_voice->setValue(LLSD(false), DO_NOT_PERSIST); + LLSD::Boolean new_value = false; + enable_voice->setValue(new_value, DO_NOT_PERSIST); } } diff --git a/indra/newview/llappviewermacosx.cpp b/indra/newview/llappviewermacosx.cpp index b074c40c17..2bd9c8661a 100644 --- a/indra/newview/llappviewermacosx.cpp +++ b/indra/newview/llappviewermacosx.cpp @@ -135,6 +135,16 @@ void cleanupViewer() gViewerAppPtr = NULL; } +void startWatchdog(std::string_view state) +{ + gViewerAppPtr->resumeMainloopTimeout(state); +} + +void stopWatchdog() +{ + gViewerAppPtr->pauseMainloopTimeout(); +} + void clearDumpLogsDir() { if (!LLAppViewer::instance()->isSecondInstance()) diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 8975abffd7..afff5f2cf2 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -86,6 +86,7 @@ #include "BugSplat.h" #include "boost/json.hpp" // Boost.Json #include "llagent.h" // for agent location +#include "llmemory.h" #include "llstartup.h" #include "llviewerregion.h" #include "llvoavatarself.h" // for agent name @@ -185,6 +186,16 @@ namespace sBugSplatSender->setAttribute(WCSTR(L"VRAM"), WCSTR(STRINGIZE(gGLManager.mVRAM))); sBugSplatSender->setAttribute(WCSTR(L"RAM"), WCSTR(STRINGIZE(gSysMemory.getPhysicalMemoryKB().value()))); + const U32 avail_kb = LLMemory::getAvailableMemKB().value(); + if (avail_kb != U32_MAX) // filter out initial values, if one is not set, all are not set + { + // Memory usage at crash time (can be 1s obsolete) + sBugSplatSender->setAttribute(WCSTR(L"MemAllocatedKB"), WCSTR(std::to_string(LLMemory::getAllocatedMemKB().value()))); + sBugSplatSender->setAttribute(WCSTR(L"MemAvailableKB"), WCSTR(std::to_string(LLMemory::getAvailableMemKB().value()))); + sBugSplatSender->setAttribute(WCSTR(L"MemMaxPhysicalKB"), WCSTR(std::to_string(LLMemory::getMaxMemKB().value()))); + sBugSplatSender->setAttribute(WCSTR(L"MemAvailCommitMB"), WCSTR(std::to_string(LLMemory::getAvailableCommitMemMB().value()))); + } + if (gAgent.getRegion()) { // region location, when we have it @@ -197,6 +208,7 @@ namespace } LLAppViewer* app = LLAppViewer::instance(); + if (!app->isSecondInstance() && !app->errorMarkerExists()) { // If marker doesn't exist, create a marker with 'other' or 'logout' code for next launch diff --git a/indra/newview/llattachmentsmgr.cpp b/indra/newview/llattachmentsmgr.cpp index 73ac895d0d..51df71e4f5 100644 --- a/indra/newview/llattachmentsmgr.cpp +++ b/indra/newview/llattachmentsmgr.cpp @@ -42,10 +42,19 @@ const F32 MAX_ATTACHMENT_REQUEST_LIFETIME = 30.0F; const F32 MIN_RETRY_REQUEST_TIME = 5.0F; const F32 MAX_BAD_COF_TIME = 30.0F; +class LLRegisterAttachmentCallback : public LLRequestServerAppearanceUpdateOnDestroy +{ +public: + void fire(const LLUUID& item_id) override + { + LLAttachmentsMgr::instance().onRegisterAttachmentComplete(item_id); + LLRequestServerAppearanceUpdateOnDestroy::fire(item_id); + } +}; + LLAttachmentsMgr::LLAttachmentsMgr(): mAttachmentRequests("attach",MIN_RETRY_REQUEST_TIME), - mDetachRequests("detach",MIN_RETRY_REQUEST_TIME), - mQuestionableCOFLinks("badcof",MAX_BAD_COF_TIME) + mDetachRequests("detach",MIN_RETRY_REQUEST_TIME) { } @@ -80,6 +89,9 @@ void LLAttachmentsMgr::addAttachmentRequest(const LLUUID& item_id, void LLAttachmentsMgr::onAttachmentRequested(const LLUUID& item_id) { + if (item_id.isNull()) + return; + LLViewerInventoryItem *item = gInventory.getItem(item_id); LL_DEBUGS("Avatar") << "ATT attachment was requested " << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; @@ -113,8 +125,6 @@ void LLAttachmentsMgr::onIdle() expireOldDetachRequests(); - checkInvalidCOFLinks(); - spamStatusInfo(); } @@ -222,6 +232,11 @@ void LLAttachmentsMgr::linkRecentlyArrivedAttachments() { if (mRecentlyArrivedAttachments.size()) { + if (!LLAppearanceMgr::instance().getAttachmentInvLinkEnable()) + { + return; + } + // One or more attachments have arrived but have not yet been // processed for COF links if (mAttachmentRequests.empty()) @@ -268,17 +283,49 @@ void LLAttachmentsMgr::linkRecentlyArrivedAttachments() } if (ids_to_link.size()) { - LLPointer<LLInventoryCallback> cb = new LLRequestServerAppearanceUpdateOnDestroy(); - for (uuid_vec_t::const_iterator uuid_it = ids_to_link.begin(); - uuid_it != ids_to_link.end(); ++uuid_it) + LLPointer<LLInventoryCallback> cb = new LLRegisterAttachmentCallback(); + for (const LLUUID& id_item: ids_to_link) { - LLAppearanceMgr::instance().addCOFItemLink(*uuid_it, cb); + if (std::find(mPendingAttachLinks.begin(), mPendingAttachLinks.end(), id_item) == mPendingAttachLinks.end()) + { + LLAppearanceMgr::instance().addCOFItemLink(id_item, cb); + mPendingAttachLinks.insert(id_item); + } } } mRecentlyArrivedAttachments.clear(); } } +bool LLAttachmentsMgr::getPendingAttachments(std::set<LLUUID>& ids) const +{ + ids.clear(); + + // Returns the combined set of attachments that are pending link creation and those that currently have an ongoing link creation process. + set_union(mRecentlyArrivedAttachments.begin(), mRecentlyArrivedAttachments.end(), mPendingAttachLinks.begin(), mPendingAttachLinks.end(), std::inserter(ids, ids.begin())); + + return !ids.empty(); +} + +void LLAttachmentsMgr::clearPendingAttachmentLink(const LLUUID& idItem) +{ + mPendingAttachLinks.erase(idItem); +} + +void LLAttachmentsMgr::onRegisterAttachmentComplete(const LLUUID& id_item_link) +{ + if (const LLUUID& id_item = gInventory.getLinkedItemID(id_item_link); id_item != id_item_link) + { + clearPendingAttachmentLink(id_item); + + // It may have been detached already in which case we should remove the COF link + if ( isAgentAvatarValid() && !gAgentAvatarp->isWearingAttachment(id_item) ) + { + LLAppearanceMgr::instance().removeCOFItemLinks(id_item); + } + } +} + LLAttachmentsMgr::LLItemRequestTimes::LLItemRequestTimes(const std::string& op_name, F32 timeout): mOpName(op_name), mTimeout(timeout) @@ -407,6 +454,8 @@ void LLAttachmentsMgr::onDetachRequested(const LLUUID& inv_item_id) void LLAttachmentsMgr::onDetachCompleted(const LLUUID& inv_item_id) { + clearPendingAttachmentLink(inv_item_id); + LLTimer timer; LLInventoryItem *item = gInventory.getItem(inv_item_id); if (mDetachRequests.getTime(inv_item_id, timer)) @@ -428,10 +477,6 @@ void LLAttachmentsMgr::onDetachCompleted(const LLUUID& inv_item_id) { LL_DEBUGS("Avatar") << "ATT detach on shutdown for " << (item ? item->getName() : "UNKNOWN") << " " << inv_item_id << LL_ENDL; } - - LL_DEBUGS("Avatar") << "ATT detached item flagging as questionable for COF link checking " - << (item ? item->getName() : "UNKNOWN") << " id " << inv_item_id << LL_ENDL; - mQuestionableCOFLinks.addTime(inv_item_id); } bool LLAttachmentsMgr::isAttachmentStateComplete() const @@ -440,81 +485,8 @@ bool LLAttachmentsMgr::isAttachmentStateComplete() const && mAttachmentRequests.empty() && mDetachRequests.empty() && mRecentlyArrivedAttachments.empty() - && mQuestionableCOFLinks.empty(); -} - -// Check for attachments that are (a) linked in COF and (b) not -// attached to the avatar. This is a rotten function to have to -// include, because it runs the risk of either repeatedly spamming out -// COF link removals if they're failing for some reason, or getting -// into a tug of war with some other sequence of events that's in the -// process of adding the attachment in question. However, it's needed -// because we have no definitive source of authority for what things -// are actually supposed to be attached. Scripts, run on the server -// side, can remove an attachment without our expecting it. If this -// happens to an attachment that's just been added, then the COF link -// creation may still be in flight, and we will have to delete the -// link after it shows up. -// -// Note that we only flag items for possible link removal if they have -// been previously detached. This means that an attachment failure -// will leave the link in the COF, where it will hopefully resolve -// correctly on relog. -// -// See related: MAINT-5070, MAINT-4409 -// -void LLAttachmentsMgr::checkInvalidCOFLinks() -{ - if (!gInventory.isInventoryUsable() || mQuestionableCOFLinks.empty()) - { - return; - } - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(LLAppearanceMgr::instance().getCOF(), - cat_array,item_array,LLInventoryModel::EXCLUDE_TRASH); - for (S32 i=0; i<item_array.size(); i++) - { - const LLViewerInventoryItem* inv_item = item_array.at(i).get(); - const LLUUID& item_id = inv_item->getLinkedUUID(); - if (inv_item->getType() == LLAssetType::AT_OBJECT) - { - LLTimer timer; - bool is_flagged_questionable = mQuestionableCOFLinks.getTime(item_id,timer); - bool is_wearing_attachment = isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(item_id); - if (is_wearing_attachment && is_flagged_questionable) - { - LL_DEBUGS("Avatar") << "ATT was flagged questionable but is now " - << (is_wearing_attachment ? "attached " : "") - <<"removing flag after " - << timer.getElapsedTimeF32() << " item " - << inv_item->getName() << " id " << item_id << LL_ENDL; - mQuestionableCOFLinks.removeTime(item_id); - } - } - } - - for(LLItemRequestTimes::iterator it = mQuestionableCOFLinks.begin(); - it != mQuestionableCOFLinks.end(); ) - { - LLItemRequestTimes::iterator curr_it = it; - ++it; - const LLUUID& item_id = curr_it->first; - LLViewerInventoryItem *inv_item = gInventory.getItem(item_id); - if (curr_it->second.getElapsedTimeF32() > MAX_BAD_COF_TIME) - { - if (LLAppearanceMgr::instance().isLinkedInCOF(item_id)) - { - LL_DEBUGS("Avatar") << "ATT Linked in COF but not attached or requested, deleting link after " - << curr_it->second.getElapsedTimeF32() << " seconds for " - << (inv_item ? inv_item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; - LLAppearanceMgr::instance().removeCOFItemLinks(item_id); - } - mQuestionableCOFLinks.erase(curr_it); - continue; - } - } -} + && mPendingAttachLinks.empty(); + } void LLAttachmentsMgr::spamStatusInfo() { diff --git a/indra/newview/llattachmentsmgr.h b/indra/newview/llattachmentsmgr.h index de36253339..0f10da1377 100644 --- a/indra/newview/llattachmentsmgr.h +++ b/indra/newview/llattachmentsmgr.h @@ -85,10 +85,15 @@ public: void onDetachRequested(const LLUUID& inv_item_id); void onDetachCompleted(const LLUUID& inv_item_id); + void clearPendingAttachmentLink(const LLUUID& idItem); + bool getPendingAttachments(std::set<LLUUID>& ids) const; bool isAttachmentStateComplete() const; // [SL:KB] - Patch: Appearance-PhantomAttach | Checked: Catznip-5.0 void refreshAttachments(); +protected: + void onRegisterAttachmentComplete(const LLUUID& id_item_link); + friend class LLRegisterAttachmentCallback; private: @@ -112,7 +117,6 @@ private: void linkRecentlyArrivedAttachments(); void expireOldAttachmentRequests(); void expireOldDetachRequests(); - void checkInvalidCOFLinks(); void spamStatusInfo(); // Attachments that we are planning to rez but haven't requested from the server yet. @@ -127,9 +131,8 @@ private: // Attachments that have arrived but have not been linked in the COF yet. std::set<LLUUID> mRecentlyArrivedAttachments; LLTimer mCOFLinkBatchTimer; - - // Attachments that are linked in the COF but may be invalid. - LLItemRequestTimes mQuestionableCOFLinks; + // Attachments that have pending COF link creation + std::set<LLUUID> mPendingAttachLinks; }; #endif diff --git a/indra/newview/llblocklist.cpp b/indra/newview/llblocklist.cpp index 89516a8a84..05c882e3f1 100644 --- a/indra/newview/llblocklist.cpp +++ b/indra/newview/llblocklist.cpp @@ -48,6 +48,7 @@ LLBlockList::LLBlockList(const Params& p) LLMuteList::getInstance()->addObserver(this); mMuteListSize = static_cast<U32>(LLMuteList::getInstance()->getMutes().size()); + updateNoItemsCommentText(); // Set up context menu. LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; @@ -101,8 +102,35 @@ BlockListActionType LLBlockList::getCurrentMuteListActionType() return type; } +void LLBlockList::updateNoItemsCommentText() +{ + const LLMuteList* mute_list = LLMuteList::getInstance(); + if (!mute_list->isLoaded() && !mute_list->isFailed()) + { + setNoItemsCommentText(mLoadingItemsMsg); + } + else if (mute_list->isFailed()) + { + setNoItemsCommentText(mFailedItemsMsg); + } + else + { + updateNoItemsMessage(mNameFilter); + } +} + +void LLBlockList::onChange() +{ + // Something changed, not sure what so force a refresh. + mShouldAddAll = true; + mActionType = NONE; + updateNoItemsCommentText(); + setDirty(); +} + void LLBlockList::onChangeDetailed(const LLMute &mute) { + updateNoItemsCommentText(); mActionType = getCurrentMuteListActionType(); mCurItemId = mute.mID; @@ -197,6 +225,7 @@ void LLBlockList::addNewItem(const LLMute* mute) void LLBlockList::refresh() { + updateNoItemsCommentText(); bool have_filter = !mNameFilter.empty(); // save selection to restore it after list rebuilt @@ -208,6 +237,8 @@ void LLBlockList::refresh() clear(); createList(); mShouldAddAll = false; + // Full rebuild supersedes any queued incremental action. This ensures list consistency. + mActionType = NONE; } else { @@ -246,7 +277,9 @@ void LLBlockList::refresh() { LLBlockedListItem * curItem = dynamic_cast<LLBlockedListItem *> (*it); if(curItem) - { + { + // Refresh item text styling each pass so filtering keeps highlight in sync. + curItem->highlightName(mNameFilter); hideListItem(curItem, findInsensitive(curItem->getName(), mNameFilter)); } } diff --git a/indra/newview/llblocklist.h b/indra/newview/llblocklist.h index 64e8246f43..9266584256 100644 --- a/indra/newview/llblocklist.h +++ b/indra/newview/llblocklist.h @@ -58,7 +58,7 @@ public: LLToggleableMenu* getContextMenu() const { return mContextMenu.get(); } LLBlockedListItem* getBlockedItem() const; - virtual void onChange() { } + virtual void onChange(); virtual void onChangeDetailed(const LLMute& ); virtual void draw(); @@ -68,6 +68,8 @@ public: void refresh(); U32 getMuteListSize() { return mMuteListSize; } + void setLoadingItemsMsg(const std::string& msg) { mLoadingItemsMsg = msg; updateNoItemsCommentText(); } + void setFailedItemsMsg(const std::string& msg) { mFailedItemsMsg = msg; updateNoItemsCommentText(); } private: @@ -83,6 +85,7 @@ private: bool isMenuItemVisible(const LLSD& userdata); void toggleMute(U32 flags); void createList(); + void updateNoItemsCommentText(); BlockListActionType getCurrentMuteListActionType(); @@ -100,6 +103,8 @@ private: LLMute::EType mCurItemType; U32 mCurItemFlags; std::string mPrevNameFilter; + std::string mLoadingItemsMsg; + std::string mFailedItemsMsg; }; diff --git a/indra/newview/llchannelmanager.cpp b/indra/newview/llchannelmanager.cpp index 454991ab83..27e5263967 100644 --- a/indra/newview/llchannelmanager.cpp +++ b/indra/newview/llchannelmanager.cpp @@ -140,6 +140,7 @@ void LLChannelManager::onLoginCompleted() } else { + mStartUpToastInited = true; gViewerWindow->getRootView()->addChild(mStartUpChannel); // init channel's position and size diff --git a/indra/newview/llchannelmanager.h b/indra/newview/llchannelmanager.h index 4db7f32b10..75b3060e66 100644 --- a/indra/newview/llchannelmanager.h +++ b/indra/newview/llchannelmanager.h @@ -104,12 +104,15 @@ public: */ static LLNotificationsUI::LLScreenChannel* getNotificationScreenChannel(); + bool getStartUpToastInited() const { return mStartUpToastInited; } + std::vector<ChannelElem>& getChannelList() { return mChannelList;} private: LLScreenChannel* createChannel(LLScreenChannelBase::Params& p); LLScreenChannel* mStartUpChannel; + bool mStartUpToastInited{false}; std::vector<ChannelElem> mChannelList; }; diff --git a/indra/newview/llconversationloglist.cpp b/indra/newview/llconversationloglist.cpp index 65863f0a5e..e9c6df3ed8 100644 --- a/indra/newview/llconversationloglist.cpp +++ b/indra/newview/llconversationloglist.cpp @@ -45,6 +45,7 @@ LLConversationLogList::LLConversationLogList(const Params& p) mIsDirty(true) { LLConversationLog::instance().addObserver(this); + LLMuteList::instance().addObserver(this); // Set up context menu. LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; @@ -65,6 +66,15 @@ LLConversationLogList::LLConversationLogList(const Params& p) } mIsFriendsOnTop = gSavedSettings.getBOOL("SortFriendsFirst"); + LLControlVariable* cntrl_ptr = gSavedSettings.getControl("ShowBlockedConvHistory"); + if (cntrl_ptr) + { + mShowBlockedSignal = cntrl_ptr->getCommitSignal()->connect( + [this](LLControlVariable* control, const LLSD& value, const LLSD& previous) + { + setDirty(true); + }); + } } LLConversationLogList::~LLConversationLogList() @@ -74,6 +84,7 @@ LLConversationLogList::~LLConversationLogList() mContextMenu.get()->die(); } + LLMuteList::getInstance()->removeObserver(this); LLConversationLog::instance().removeObserver(this); } @@ -178,6 +189,44 @@ void LLConversationLogList::changed(const LLUUID& session_id, U32 mask) } } +// inherited from LLMuteListObserver +void LLConversationLogList::onChange() +{ +} + +// inherited from LLMuteListObserver +void LLConversationLogList::onChangeDetailed(const LLMute& mute) +{ + if (isDirty()) + { + // Going to refresh either way. + return; + } + + static LLCachedControl<bool> show_blocked(gSavedSettings, "ShowBlockedConvHistory", false); + if (!show_blocked()) + { + // Check if any conversation in the log has this participant + const std::vector<LLConversation>& conversations = LLConversationLog::instance().getConversations(); + + // Note sure if actually need this update. List can be rather large + // and rebuilding it on every event can be expensive. + // But 'mute' events are also rare, so maybe not a problem. + // There also might be a way to react only to relevant changes, + // instead of reacting to everything, like ignoring voice only mutes, + // which are not relevant for this log by themselves. + for (const LLConversation& conversation : conversations) + { + if (conversation.getParticipantID() == mute.mID) + { + setDirty(true); + break; + } + } + } + // else don't need to hide anything +} + void LLConversationLogList::addNewItem(const LLConversation* conversation) { LLConversationLogListItem* item = new LLConversationLogListItem(&*conversation); @@ -207,6 +256,9 @@ void LLConversationLogList::rebuildList() const std::vector<LLConversation>& conversations = log_instance.getConversations(); std::vector<LLConversation>::const_iterator iter = conversations.begin(); + LLMuteList* mutes = LLMuteList::getInstance(); + + static LLCachedControl<bool> show_blocked(gSavedSettings, "ShowBlockedConvHistory", false); for (; iter != conversations.end(); ++iter) { @@ -214,6 +266,11 @@ void LLConversationLogList::rebuildList() if (not_found) continue; + if (!show_blocked() && mutes->isMuted(iter->getParticipantID())) + { + continue; + } + addNewItem(&*iter); } @@ -517,8 +574,8 @@ bool LLConversationLogListNameComparator::doCompare(const LLConversationLogListI LLStringUtil::toUpper(name1); LLStringUtil::toUpper(name2); - bool friends_first = gSavedSettings.getBOOL("SortFriendsFirst"); - if (friends_first && (LLAvatarActions::isFriend(id1) ^ LLAvatarActions::isFriend(id2))) + static LLCachedControl<bool> sort_friends_first(gSavedSettings, "SortFriendsFirst", true); + if (sort_friends_first() && (LLAvatarActions::isFriend(id1) ^ LLAvatarActions::isFriend(id2))) { return LLAvatarActions::isFriend(id1); } @@ -533,8 +590,8 @@ bool LLConversationLogListDateComparator::doCompare(const LLConversationLogListI const LLUUID& id1 = conversation1->getConversation()->getParticipantID(); const LLUUID& id2 = conversation2->getConversation()->getParticipantID(); - bool friends_first = gSavedSettings.getBOOL("SortFriendsFirst"); - if (friends_first && (LLAvatarActions::isFriend(id1) ^ LLAvatarActions::isFriend(id2))) + static LLCachedControl<bool> sort_friends_first(gSavedSettings, "SortFriendsFirst", true); + if (sort_friends_first() && (LLAvatarActions::isFriend(id1) ^ LLAvatarActions::isFriend(id2))) { return LLAvatarActions::isFriend(id1); } diff --git a/indra/newview/llconversationloglist.h b/indra/newview/llconversationloglist.h index 6372f6c9a3..39f129c9f1 100644 --- a/indra/newview/llconversationloglist.h +++ b/indra/newview/llconversationloglist.h @@ -39,7 +39,7 @@ class LLConversationLogListItem; * it's always in actual state. */ -class LLConversationLogList: public LLFlatListViewEx, public LLConversationLogObserver +class LLConversationLogList: public LLFlatListViewEx, public LLConversationLogObserver, public LLMuteListObserver { LOG_CLASS(LLConversationLogList); public: @@ -76,6 +76,12 @@ public: virtual void changed(); virtual void changed(const LLUUID& session_id, U32 mask); + /** + * Changes from LLMuteListObserver + */ + virtual void onChange(); + virtual void onChangeDetailed(const LLMute& mute); + private: void setDirty(bool dirty = true) { mIsDirty = dirty; } @@ -104,6 +110,8 @@ private: bool mIsDirty; bool mIsFriendsOnTop; std::string mNameFilter; + + boost::signals2::scoped_connection mShowBlockedSignal; }; /** diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp index 2297fddf0c..117bfbf64c 100644 --- a/indra/newview/llconversationview.cpp +++ b/indra/newview/llconversationview.cpp @@ -217,6 +217,12 @@ bool LLConversationViewSession::postBuild() LLFolderViewItem::postBuild(); mItemPanel = LLUICtrlFactory::getInstance()->createFromFile<LLPanel>("panel_conversation_list_item.xml", NULL, LLPanel::child_registry_t::instance()); + + if (!mItemPanel) + { + LLError::LLUserWarningMsg::showMissingFiles(); + LL_ERRS() << "Failed to construct mItemPanel from panel_conversation_list_item.xml" << LL_ENDL; + } addChild(mItemPanel); mCallIconLayoutPanel = mItemPanel->getChild<LLPanel>("call_icon_panel"); diff --git a/indra/newview/lldebugview.cpp b/indra/newview/lldebugview.cpp index 53da9826ed..246a9d28e4 100644 --- a/indra/newview/lldebugview.cpp +++ b/indra/newview/lldebugview.cpp @@ -38,7 +38,6 @@ #include "llappviewer.h" #include "llsceneview.h" #include "llviewertexture.h" -#include "llfloaterreg.h" #include "llscenemonitor.h" // // Globals @@ -53,7 +52,6 @@ static LLDefaultChildRegistry::Register<LLDebugView> r("debug_view"); LLDebugView::LLDebugView(const LLDebugView::Params& p) : LLView(p), - mFastTimerView(NULL), mDebugConsolep(NULL), mFloaterSnapRegion(NULL) {} @@ -89,8 +87,6 @@ void LLDebugView::init() r.setLeftTopAndSize(25, rect.getHeight() - 50, (S32) (gViewerWindow->getWindowRectScaled().getWidth() * 0.75f), (S32) (gViewerWindow->getWindowRectScaled().getHeight() * 0.75f)); - mFastTimerView = dynamic_cast<LLFastTimerView*>(LLFloaterReg::getInstance("block_timers")); - gSceneView = new LLSceneView(r); gSceneView->setFollowsTop(); gSceneView->setFollowsLeft(); @@ -105,7 +101,7 @@ void LLDebugView::init() addChild(gSceneMonitorView); gSceneMonitorView->setRect(rect); - r.set(150, rect.getHeight() - 60, 820, 110); + r.set(150, rect.getHeight() - 60, 835, 110); LLTextureView::Params tvp; tvp.name("gTextureView"); tvp.rect(r); diff --git a/indra/newview/lldebugview.h b/indra/newview/lldebugview.h index 8fa2acc3c9..ca274d6f91 100644 --- a/indra/newview/lldebugview.h +++ b/indra/newview/lldebugview.h @@ -59,7 +59,6 @@ public: void setStatsVisible(bool visible); - LLFastTimerView* mFastTimerView; LLConsole* mDebugConsolep; LLView* mFloaterSnapRegion; }; diff --git a/indra/newview/lldrawpoolwater.cpp b/indra/newview/lldrawpoolwater.cpp index 7a974fe857..3cb5ad6f28 100644 --- a/indra/newview/lldrawpoolwater.cpp +++ b/indra/newview/lldrawpoolwater.cpp @@ -205,22 +205,22 @@ void LLDrawPoolWater::renderPostDeferred(S32 pass) if (tex_a && (!tex_b || (tex_a == tex_b))) { - shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_a); tex_a->setFilteringOption(filter_mode); + shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_a); blend_factor = 0; // only one tex provided, no blending } else if (tex_b && !tex_a) { - shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_b); tex_b->setFilteringOption(filter_mode); + shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_b); blend_factor = 0; // only one tex provided, no blending } else if (tex_b != tex_a) { - shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_a); tex_a->setFilteringOption(filter_mode); - shader->bindTexture(LLViewerShaderMgr::BUMP_MAP2, tex_b); tex_b->setFilteringOption(filter_mode); + shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_a); + shader->bindTexture(LLViewerShaderMgr::BUMP_MAP2, tex_b); } shader->bindTexture(LLShaderMgr::WATER_EXCLUSIONTEX, &gPipeline.mWaterExclusionMask); diff --git a/indra/newview/llexpandabletextbox.cpp b/indra/newview/llexpandabletextbox.cpp index 5c46eb9d80..41ae47b645 100644 --- a/indra/newview/llexpandabletextbox.cpp +++ b/indra/newview/llexpandabletextbox.cpp @@ -52,7 +52,7 @@ public: return copy; } - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) { // more label always spans width of text box if (num_chars == 0) diff --git a/indra/newview/llfavoritesbar.cpp b/indra/newview/llfavoritesbar.cpp index 98b3ca820b..b3408f7861 100644 --- a/indra/newview/llfavoritesbar.cpp +++ b/indra/newview/llfavoritesbar.cpp @@ -1375,6 +1375,11 @@ bool LLFavoritesBarCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) } void copy_slurl_to_clipboard_cb(std::string& slurl) { + if (slurl.empty()) + { + LLNotificationsUtil::add("LandmarkLocationUnknown"); + return; + } LLClipboard::instance().copyToClipboard(utf8str_to_wstring(slurl), 0, static_cast<S32>(slurl.size())); LLSD args; diff --git a/indra/newview/llfloaterchangeitemthumbnail.cpp b/indra/newview/llfloaterchangeitemthumbnail.cpp index fb31361fd9..914d2135ba 100644 --- a/indra/newview/llfloaterchangeitemthumbnail.cpp +++ b/indra/newview/llfloaterchangeitemthumbnail.cpp @@ -236,11 +236,13 @@ bool LLFloaterChangeItemThumbnail::handleDragAndDrop( if (cargo_type == DAD_TEXTURE) { LLInventoryItem *item = (LLInventoryItem *)cargo_data; - if (item->getAssetUUID().notNull()) + const LLUUID& asset_id = item->getAssetUUID(); + if (asset_id.notNull()) { if (drop) { - assignAndValidateAsset(item->getAssetUUID()); + LLPointer<LLViewerFetchedTexture> texturep = LLViewerTextureManager::getFetchedTexture(asset_id); + assignAndValidateTexture(asset_id, texturep); } *accept = ACCEPT_YES_SINGLE; @@ -708,6 +710,81 @@ void LLFloaterChangeItemThumbnail::assignAndValidateAsset(const LLUUID &asset_id } } } + +void LLFloaterChangeItemThumbnail::assignAndValidateTexture(const LLUUID& asset_id, LLPointer<LLViewerFetchedTexture> texturep) +{ + if (!texturep) + { + LL_WARNS() << "Image " << asset_id << " doesn't exist" << LL_ENDL; + return; + } + + if (texturep->isMissingAsset()) + { + LL_WARNS() << "Image " << asset_id << " is missing" << LL_ENDL; + return; + } + + if (texturep->getFullWidth() != texturep->getFullHeight()) + { + LLNotificationsUtil::add("ThumbnailDimentionsLimit"); + return; + } + + if (texturep->getFullWidth() < LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MIN + && texturep->getFullWidth() > 0) + { + LLNotificationsUtil::add("ThumbnailDimentionsLimit"); + return; + } + + if (texturep->getFullWidth() > LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MAX + || texturep->getFullWidth() == 0) + { + if (texturep->isFullyLoaded() + && (texturep->getRawImageLevel() == 0) + && (texturep->isRawImageValid())) + { + LLUUID task_id = mTaskId; + uuid_set_t inventory_ids = mItemList; + LLHandle<LLFloater> handle = getHandle(); + LLFloaterSimpleSnapshot::completion_t callback = + [inventory_ids, task_id, handle](const LLUUID& asset_id) + { + onUploadComplete(asset_id, task_id, inventory_ids, handle); + }; + LLFloaterSimpleSnapshot::uploadThumbnail(texturep->getRawImage(), + *mItemList.begin(), + mTaskId, + callback); + } + else + { + ImageLoadedData* data = new ImageLoadedData(); + data->mTaskId = mTaskId; + data->mItemIds = mItemList; + data->mThumbnailId = asset_id; + data->mFloaterHandle = getHandle(); + data->mSilent = false; + data->mTexturep = texturep; + + texturep->setBoostLevel(LLGLTexture::BOOST_PREVIEW); + texturep->setMinDiscardLevel(0); + texturep->setLoadedCallback(onFullImageLoaded, + 0, // Need best quality + true, + false, + (void*)data, + NULL, + false); + texturep->forceToSaveRawImage(0); + } + return; + } + + setThumbnailId(asset_id); +} + bool LLFloaterChangeItemThumbnail::validateAsset(const LLUUID &asset_id) { if (asset_id.isNull()) @@ -922,76 +999,8 @@ void LLFloaterChangeItemThumbnail::onTexturePickerCommit() } LLPointer<LLViewerFetchedTexture> texturep = LLViewerTextureManager::findFetchedTexture(asset_id, TEX_LIST_STANDARD); - if (!texturep) - { - LL_WARNS() << "Image " << asset_id << " doesn't exist" << LL_ENDL; - return; - } - - if (texturep->isMissingAsset()) - { - LL_WARNS() << "Image " << asset_id << " is missing" << LL_ENDL; - return; - } - - if (texturep->getFullWidth() != texturep->getFullHeight()) - { - LLNotificationsUtil::add("ThumbnailDimentionsLimit"); - return; - } - - if (texturep->getFullWidth() < LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MIN - && texturep->getFullWidth() > 0) - { - LLNotificationsUtil::add("ThumbnailDimentionsLimit"); - return; - } - - if (texturep->getFullWidth() > LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MAX - || texturep->getFullWidth() == 0) - { - if (texturep->isFullyLoaded() - && (texturep->getRawImageLevel() == 0) - && (texturep->isRawImageValid())) - { - LLUUID task_id = mTaskId; - uuid_set_t inventory_ids = mItemList; - LLHandle<LLFloater> handle = getHandle(); - LLFloaterSimpleSnapshot::completion_t callback = - [inventory_ids, task_id, handle](const LLUUID& asset_id) - { - onUploadComplete(asset_id, task_id, inventory_ids, handle); - }; - LLFloaterSimpleSnapshot::uploadThumbnail(texturep->getRawImage(), - *mItemList.begin(), - mTaskId, - callback); - } - else - { - ImageLoadedData* data = new ImageLoadedData(); - data->mTaskId = mTaskId; - data->mItemIds = mItemList; - data->mThumbnailId = asset_id; - data->mFloaterHandle = getHandle(); - data->mSilent = false; - data->mTexturep = texturep; - - texturep->setBoostLevel(LLGLTexture::BOOST_PREVIEW); - texturep->setMinDiscardLevel(0); - texturep->setLoadedCallback(onFullImageLoaded, - 0, // Need best quality - true, - false, - (void*)data, - NULL, - false); - texturep->forceToSaveRawImage(0); - } - return; - } - setThumbnailId(asset_id); + assignAndValidateTexture(asset_id, texturep); } } diff --git a/indra/newview/llfloaterchangeitemthumbnail.h b/indra/newview/llfloaterchangeitemthumbnail.h index 46f63801fe..980b46b298 100644 --- a/indra/newview/llfloaterchangeitemthumbnail.h +++ b/indra/newview/llfloaterchangeitemthumbnail.h @@ -82,7 +82,11 @@ private: static void onRemove(void*); static void onRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle<LLFloater> handle); + // As needed, attempts to load image then validates and assigns, does no converting. void assignAndValidateAsset(const LLUUID &asset_id, bool silent = false); + // As needed, attempts to load and convert(upload) image then validates and assigns. + void assignAndValidateTexture(const LLUUID& asset_id, LLPointer<LLViewerFetchedTexture> texturep); + static void onImageDataLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, diff --git a/indra/newview/llfloatercolorpicker.cpp b/indra/newview/llfloatercolorpicker.cpp index cd45093856..56e086502a 100644 --- a/indra/newview/llfloatercolorpicker.cpp +++ b/indra/newview/llfloatercolorpicker.cpp @@ -233,7 +233,15 @@ bool LLFloaterColorPicker::postBuild() childSetCommitCallback("sspin", onTextCommit, (void*)this ); childSetCommitCallback("lspin", onTextCommit, (void*)this ); - LLToolPipette::getInstance()->setToolSelectCallback(boost::bind(&LLFloaterColorPicker::onColorSelect, this, _1)); + mPipetteConnection = LLToolPipette::getInstance()->setToolSelectCallback( + [this](LLPointer<LLViewerObject> object, S32 te_index) + { + const LLTextureEntry* entry = object->getTE(te_index); + if (entry) + { + onColorSelect(entry->getColor()); + } + }); return true; } @@ -460,10 +468,10 @@ void LLFloaterColorPicker::onImmediateCheck( LLUICtrl* ctrl, void* data) } } -void LLFloaterColorPicker::onColorSelect( const LLTextureEntry& te ) +// From pipette +void LLFloaterColorPicker::onColorSelect( const LLColor4 &color ) { - // Pipete - selectCurRgb(te.getColor().mV[VRED], te.getColor().mV[VGREEN], te.getColor().mV[VBLUE]); + selectCurRgb(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE]); } void LLFloaterColorPicker::onMouseCaptureLost() diff --git a/indra/newview/llfloatercolorpicker.h b/indra/newview/llfloatercolorpicker.h index 5c27fffd08..af199d92a3 100644 --- a/indra/newview/llfloatercolorpicker.h +++ b/indra/newview/llfloatercolorpicker.h @@ -122,7 +122,7 @@ class LLFloaterColorPicker void onClickPipette ( ); static void onTextCommit ( LLUICtrl* ctrl, void* data ); static void onImmediateCheck ( LLUICtrl* ctrl, void* data ); - void onColorSelect( const class LLTextureEntry& te ); + void onColorSelect(const LLColor4& color); // from pipette private: // mutators for color values, can raise event to preview changes at object void selectCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn ); @@ -197,6 +197,7 @@ class LLFloaterColorPicker F32 mContextConeOutAlpha; F32 mContextConeFadeTime; + boost::signals2::scoped_connection mPipetteConnection; }; #endif // LL_LLFLOATERCOLORPICKER_H diff --git a/indra/newview/llfloaterconversationlog.cpp b/indra/newview/llfloaterconversationlog.cpp index 97399c9cf7..40e2d2bf1e 100644 --- a/indra/newview/llfloaterconversationlog.cpp +++ b/indra/newview/llfloaterconversationlog.cpp @@ -100,6 +100,10 @@ void LLFloaterConversationLog::onCustomAction (const LLSD& userdata) { mConversationLogList->toggleSortFriendsOnTop(); } + else if ("show_blocked" == command_name) + { + gSavedSettings.setBOOL("ShowBlockedConvHistory", !gSavedSettings.getBOOL("ShowBlockedConvHistory")); + } else if ("view_nearby_chat_history" == command_name) { LLFloaterReg::showInstance("preview_conversation", LLSD(LLUUID::null), true); @@ -129,6 +133,10 @@ bool LLFloaterConversationLog::isActionChecked(const LLSD& userdata) { return gSavedSettings.getBOOL("SortFriendsFirst"); } + else if ("show_blocked" == command_name) + { + return gSavedSettings.getBOOL("ShowBlockedConvHistory"); + } return false; } diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index 04327e30bd..f8f34ca12e 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -155,6 +155,11 @@ void LLFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const { // The general strategy when a session id is modified is to delete all related objects and create them anew. + // The conversation floater can be active while participant widgets are selected. + // Preserve that state separately so the new conversation widget can be selected + // after the server replaces an outgoing ad-hoc session id. + const bool was_active = old_session_id == getSelectedSession(); + // Note however that the LLFloaterIMSession has its session id updated through a call to sessionInitReplyReceived() // and do not need to be deleted and recreated (trying this creates loads of problems). We do need however to suppress // its related mSessions record as it's indexed with the wrong id. @@ -167,6 +172,11 @@ void LLFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const // Create a new conversation with the new id addConversationListItem(new_session_id, change_focus); LLFloaterIMSessionTab::addToHost(new_session_id); + + if (was_active) + { + selectConversationPair(new_session_id, true, false, true); + } } @@ -1734,7 +1744,7 @@ bool LLFloaterIMContainer::visibleContextMenuItem(const LLSD& userdata) void LLFloaterIMContainer::showConversation(const LLUUID& session_id) { setVisibleAndFrontmost(false); - selectConversationPair(session_id, true); + selectConversationPair(session_id, true, true, true); LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); if (session_floater) @@ -1780,13 +1790,13 @@ void LLFloaterIMContainer::selectNextConversationByID(const LLUUID& uuid) } // Synchronous select the conversation item and the conversation floater -bool LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater/*=true*/) +bool LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater/*=true*/, bool force_select_widget/*=false*/) { bool handled = true; LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); /* widget processing */ - if (select_widget && mConversationsRoot->getSelectedCount() <= 1) + if (select_widget && (force_select_widget || mConversationsRoot->getSelectedCount() <= 1)) { LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,session_id); if (widget && widget->getParentFolder()) @@ -1990,7 +2000,7 @@ bool LLFloaterIMContainer::removeConversationListItem(const LLUUID& uuid, bool c mConversationEventQueue.erase(uuid); // Don't let the focus fall IW, select and refocus on the first conversation in the list - if (change_focus && isInVisibleChain()) + if (change_focus && is_widget_selected && isInVisibleChain()) { setFocus(true); if (new_selection) diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h index 9f1690a9b9..4cb243aa64 100644 --- a/indra/newview/llfloaterimcontainer.h +++ b/indra/newview/llfloaterimcontainer.h @@ -74,7 +74,7 @@ public: void showConversation(const LLUUID& session_id); void selectConversation(const LLUUID& session_id); void selectNextConversationByID(const LLUUID& session_id); - bool selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater = true); + bool selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater = true, bool force_select_widget = false); void clearAllFlashStates(); bool selectAdjacentConversation(bool focus_selected); bool selectNextorPreviousConversation(bool select_next, bool focus_selected = true); diff --git a/indra/newview/llfloaterinspect.cpp b/indra/newview/llfloaterinspect.cpp index c0fe7ad896..163edf0426 100644 --- a/indra/newview/llfloaterinspect.cpp +++ b/indra/newview/llfloaterinspect.cpp @@ -100,6 +100,7 @@ void LLFloaterInspect::onOpen(const LLSD& key) LLSelectMgr::getInstance()->setForceSelection(forcesel); // restore previouis value mObjectSelection = LLSelectMgr::getInstance()->getSelection(); refresh(); + mDirty = false; } void LLFloaterInspect::onClickCreatorProfile() { diff --git a/indra/newview/llfloatermarketplace.cpp b/indra/newview/llfloatermarketplace.cpp index bb2378d864..a80546595c 100644 --- a/indra/newview/llfloatermarketplace.cpp +++ b/indra/newview/llfloatermarketplace.cpp @@ -101,6 +101,22 @@ void LLFloaterMarketplace::openMarketplaceURL(const std::string& url) // static bool LLFloaterMarketplace::isMarketplaceURL(const std::string& url) { + auto trimURL = [](std::string_view url) -> std::string_view + { + if (url.starts_with("https://")) + url.remove_prefix(8); + else if (url.starts_with("http://")) + url.remove_prefix(7); + + while (!url.empty() && url.back() == '/') + url.remove_suffix(1); + + return url; + }; + static LLCachedControl<std::string> marketplace_url(gSavedSettings, "MarketplaceURL", "https://marketplace.secondlife.com/"); - return url.starts_with(marketplace_url()); + std::string_view marketplace_url_trimmed = trimURL(marketplace_url()); + std::string_view url_trimmed = trimURL(url); + + return !marketplace_url_trimmed.empty() && url_trimmed.starts_with(marketplace_url_trimmed); } diff --git a/indra/newview/llfloatersimplesnapshot.cpp b/indra/newview/llfloatersimplesnapshot.cpp index 55b39d9193..4671f74d6d 100644 --- a/indra/newview/llfloatersimplesnapshot.cpp +++ b/indra/newview/llfloatersimplesnapshot.cpp @@ -54,87 +54,162 @@ void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, L { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("post_profile_image_coro", httpPolicy); + httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("post_thumbnail_image_coro", httpPolicy); LLCore::HttpRequest::ptr_t httpRequest = std::make_shared<LLCore::HttpRequest>(); LLCore::HttpHeaders::ptr_t httpHeaders; LLCore::HttpOptions::ptr_t httpOpts = std::make_shared<LLCore::HttpOptions>(); httpOpts->setFollowRedirects(true); - LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); + // Retry stage-2 upload by re-requesting a fresh one-time uploader capability (up to 3 attempts total) + const S32 MAX_UPLOAD_RETRIES = 2; + S32 upload_retry_count = 0; + LLUUID result_uuid; - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL; - return; - } - if (!result.has("uploader")) + while (upload_retry_count <= MAX_UPLOAD_RETRIES) { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL; - return; - } - std::string uploader_cap = result["uploader"].asString(); - if (uploader_cap.empty()) - { - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL; - return; - } + // Stage 1: Request uploader URL + LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); - // Upload the image + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared<LLCore::HttpRequest>(); - LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared<LLCore::HttpHeaders>(); - LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared<LLCore::HttpOptions>(); - S64 length; + if (!status) + { + LL_WARNS("Thumbnail") << "Failed to get uploader cap " << status.toString() << LL_ENDL; + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); + return; + } - { - llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); - if (!instream.is_open()) + if (!result.has("uploader")) { - LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL; + LL_WARNS("Thumbnail") << "Failed to get uploader cap, response contains no data." << LL_ENDL; + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); return; } - length = instream.tellg(); - } - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); // optional - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length)); // required! - uploaderhttpOpts->setFollowRedirects(true); + std::string uploader_cap = result["uploader"].asString(); + if (uploader_cap.empty()) + { + LL_WARNS("Thumbnail") << "Failed to get uploader cap, cap invalid." << LL_ENDL; + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); + return; + } - result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); + // Stage 2: Upload the image + LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared<LLCore::HttpRequest>(); + LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared<LLCore::HttpHeaders>(); + LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared<LLCore::HttpOptions>(); + S64 length; - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + { + llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); + if (!instream.is_open()) + { + LL_WARNS("Thumbnail") << "Failed to open file " << path_to_image << LL_ENDL; + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); + return; + } + length = instream.tellg(); + } - LL_DEBUGS("Thumbnail") << result << LL_ENDL; + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, std::to_string(length)); + uploaderhttpOpts->setFollowRedirects(true); - if (!status) - { - LL_WARNS("Thumbnail") << "Failed to upload image " << status.toString() << LL_ENDL; - return; - } + result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); - if (result["state"].asString() != "complete") - { - if (result.has("message")) - { - LL_WARNS("Thumbnail") << "Failed to upload image, state " << result["state"] << " message: " << result["message"] << LL_ENDL; - } - else + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + LL_DEBUGS("Thumbnail") << result << LL_ENDL; + + if (!status) { - LL_WARNS("Thumbnail") << "Failed to upload image " << result << LL_ENDL; + if (upload_retry_count < MAX_UPLOAD_RETRIES) + { + upload_retry_count++; + LL_WARNS("Thumbnail") << "Failed to upload image (attempt " << upload_retry_count + << " of " << (MAX_UPLOAD_RETRIES + 1) << "): " << status.toString() + << ", re-requesting uploader..." << LL_ENDL; + llcoro::suspendUntilTimeout(1.0f); + continue; + } + else + { + LL_WARNS("Thumbnail") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts: " << status.toString() << LL_ENDL; + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); + return; + } } - if (callback) + if (result["state"].asString() != "complete") { - callback(LLUUID()); + if (upload_retry_count < MAX_UPLOAD_RETRIES) + { + upload_retry_count++; + if (result.has("message")) + { + LL_WARNS("Thumbnail") << "Failed to upload image, state " << result["state"] + << " message: " << result["message"] << " (attempt " + << upload_retry_count << " of " << (MAX_UPLOAD_RETRIES + 1) + << "), re-requesting uploader..." << LL_ENDL; + } + else + { + LL_WARNS("Thumbnail") << "Failed to upload image (attempt " << upload_retry_count + << " of " << (MAX_UPLOAD_RETRIES + 1) + << "), re-requesting uploader..." << LL_ENDL; + } + llcoro::suspendUntilTimeout(1.0f); + continue; + } + else + { + if (result.has("message")) + { + LL_WARNS("Thumbnail") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts, state " << result["state"] + << " message: " << result["message"] << LL_ENDL; + } + else + { + LL_WARNS("Thumbnail") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts" << LL_ENDL; + } + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); + return; + } } - return; + + // Success! + result_uuid = result["new_asset"].asUUID(); + break; } if (first_data.has("category_id")) @@ -143,7 +218,7 @@ void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, L LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); if (cat) { - cat->setThumbnailUUID(result["new_asset"].asUUID()); + cat->setThumbnailUUID(result_uuid); } gInventory.addChangedMask(LLInventoryObserver::INTERNAL, cat_id); } @@ -153,16 +228,18 @@ void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, L LLViewerInventoryItem* item = gInventory.getItem(item_id); if (item) { - item->setThumbnailUUID(result["new_asset"].asUUID()); + item->setThumbnailUUID(result_uuid); } - // Are we supposed to get BulkUpdateInventory? gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id); } if (callback) { - callback(result["new_asset"].asUUID()); + callback(result_uuid); } + + // Cleanup + LLFile::remove(path_to_image); } ///---------------------------------------------------------------------------- diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp index 9366f1e65a..54d17be370 100644 --- a/indra/newview/llimprocessing.cpp +++ b/indra/newview/llimprocessing.cpp @@ -1486,6 +1486,7 @@ void LLIMProcessing::processNewMessage(LLUUID from_id, LLNotification::Params params("OfferFriendship"); params.substitutions = args; params.payload = payload; + params.offer_from_agent = true; LLPostponedNotification::add<LLPostponedOfferNotification>(params, from_id, false); } } @@ -1534,7 +1535,7 @@ void LLIMProcessing::requestOfflineMessages() && isAgentAvatarValid() && gAgent.getRegion() && gAgent.getRegion()->capabilitiesReceived() - && (LLMuteList::getInstance()->isLoaded() || LLMuteList::getInstance()->getLoadFailed())) + && LLMuteList::getInstance()->updateLoadState()) { std::string cap_url = gAgent.getRegionCapability("ReadOfflineMsgs"); diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index f3cef660f6..0a260b9916 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -91,8 +91,7 @@ const S32 XL8_PADDING = 3; // XL8_START_TAG.size() + XL8_END_TAG.size() const static U32 SESSION_INITIALIZATION_TIMEOUT = 30; // This enum corresponds to the sim's and adds P2P_CHAT_SESSION, -// as webrtc uses the multiagent chat mechanism for p2p calls, -// instead of relying on vivox calling. +// as webrtc uses the multiagent chat mechanism for p2p calls. // Don't change this without consulting a server developer. enum EMultiAgentChatSessionType { @@ -1945,12 +1944,6 @@ void LLIMModel::sendMessage(const std::string& utf8_text, info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id); U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE; - // Old call to send messages to SLim client, no longer supported. - //if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id))) - //{ - // // User is online through the OOW connector, but not with a regular viewer. Try to send the message via SLVoice. - // sent = LLVoiceClient::getInstance()->sendTextMessage(other_participant_id, utf8_text); - //} if(!sent) { @@ -3090,8 +3083,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload { if (type == IM_SESSION_P2P_INVITE) { - // decline p2p voice, either via the vivox-style call mechanism - // or via the webrtc-style "decline p2p" mechanism. + // decline p2p voice, via the webrtc-style "decline p2p" mechanism. LLVoiceP2PIncomingCallInterfacePtr call = LLVoiceClient::getInstance()->getIncomingCallInterface(payload["voice_channel_info"]); if (call) { diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 5d41cac5e1..7605887f6c 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -1912,6 +1912,11 @@ void LLItemBridge::doShowOnMap(LLLandmark* landmark) void copy_slurl_to_clipboard_callback_inv(const std::string& slurl) { + if (slurl.empty()) + { + LLNotificationsUtil::add("LandmarkLocationUnknown"); + return; + } gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); LLSD args; args["SLURL"] = slurl; diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 7522ea4907..a6fda946ef 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -634,12 +634,6 @@ bool get_is_item_worn(const LLUUID& id, const LLViewerInventoryItem* item) return false; } - // Consider the item as worn if it has links in COF. - if (LLAppearanceMgr::instance().isLinkedInCOF(id)) - { - return true; - } - switch(item->getType()) { case LLAssetType::AT_OBJECT: diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index a8bc3b361e..2657f11d0e 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -433,6 +433,8 @@ LLInventoryModel::LLInventoryModel() : // These are now ordered, keep them that way. mBacklinkMMap(), mIsAgentInvUsable(false), + mLibrarySkeletonLoadTime(0.f), + mAgentSkeletonLoadTime(0.f), mRootFolderID(), mLibraryRootFolderID(), mLibraryOwnerID(), @@ -2383,10 +2385,22 @@ void LLInventoryModel::cache( items, INCLUDE_TRASH, can_cache); + + if (categories.empty() && items.empty()) + { + LL_WARNS(LOG_INV) << "Nothing to cache for " << parent_folder_id << LL_ENDL; + return; + } + // Use temporary file to avoid potential conflicts with other // instances (even a 'read only' instance unzips into a file) std::string temp_file = gDirUtilp->getTempFilename(); - saveToFile(temp_file, categories, items); + if (!saveToFile(temp_file, categories, items)) + { + LL_WARNS(LOG_INV) << "Failed to save inventory cache for " << parent_folder_id << LL_ENDL; + LLFile::remove(temp_file); + return; + } std::string gzip_filename = getInvCacheAddres(agent_id); gzip_filename.append(".gz"); if(gzip_file(temp_file, gzip_filename)) @@ -2989,6 +3003,16 @@ bool LLInventoryModel::loadSkeleton( << " after " << timer.getElapsedTimeF32() << " seconds." << LL_ENDL; + const F32 elapsed = timer.getElapsedTimeF32(); + if (owner_id == mLibraryOwnerID) + { + mLibrarySkeletonLoadTime = elapsed; + } + else + { + mAgentSkeletonLoadTime = elapsed; + } + return rv; } @@ -3537,6 +3561,11 @@ bool LLInventoryModel::saveToFile(const std::string& filename, S32 cat_count = 0; for (auto& cat : categories) { + if (cat.isNull()) + { + LL_WARNS(LOG_INV) << "Skipping null category during inventory save" << LL_ENDL; + continue; + } if (cat->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) { LLSD sd; @@ -3551,6 +3580,11 @@ bool LLInventoryModel::saveToFile(const std::string& filename, auto it_count = items.size(); for (auto& item : items) { + if (item.isNull()) + { + LL_WARNS(LOG_INV) << "Skipping null item during inventory save" << LL_ENDL; + continue; + } LLSD sd; item->asLLSD(sd); item_array.append(sd); @@ -3567,6 +3601,12 @@ bool LLInventoryModel::saveToFile(const std::string& filename, LL_INFOS(LOG_INV) << "Inventory saved: " << (S32)cat_count << " categories, " << (S32)it_count << " items." << LL_ENDL; } + catch(std::bad_alloc&) + { + // We are quiting, so just log an error and move on. + LL_WARNS(LOG_INV) << "Failed to save inventory to cache due to memory allocation failure." << LL_ENDL; + return false; + } catch (...) { LOG_UNHANDLED_EXCEPTION(""); diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index 05ada9121a..3aaba69a60 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -158,8 +158,12 @@ protected: public: // The inventory model usage is sensitive to the initial construction of the model bool isInventoryUsable() const; + F32 getLibrarySkeletonLoadTime() const { return mLibrarySkeletonLoadTime; } + F32 getAgentSkeletonLoadTime() const { return mAgentSkeletonLoadTime; } private: bool mIsAgentInvUsable; // used to handle an invalid inventory state + F32 mLibrarySkeletonLoadTime; + F32 mAgentSkeletonLoadTime; // One-time initialization of HTTP system. void initHttpRequest(); diff --git a/indra/newview/llinventorymodelbackgroundfetch.cpp b/indra/newview/llinventorymodelbackgroundfetch.cpp index 82eefb50ac..b02b2e2eb9 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.cpp +++ b/indra/newview/llinventorymodelbackgroundfetch.cpp @@ -192,6 +192,8 @@ LLInventoryModelBackgroundFetch::LLInventoryModelBackgroundFetch(): mFetchCount(0), mLastFetchCount(0), mFetchFolderCount(0), + mInitialFetchDuration(0.f), + mInitialFetchDurationCaptured(false), mAllRecursiveFoldersFetched(false), mRecursiveInventoryFetchStarted(false), mRecursiveLibraryFetchStarted(false), @@ -199,6 +201,15 @@ LLInventoryModelBackgroundFetch::LLInventoryModelBackgroundFetch(): mMinTimeBetweenFetches(0.3f) {} +void LLInventoryModelBackgroundFetch::markFetchStarted() +{ + if (!mBackgroundFetchActive) + { + mFetchStartTimer.reset(); + } + mBackgroundFetchActive = true; +} + LLInventoryModelBackgroundFetch::~LLInventoryModelBackgroundFetch() { gIdleCallbacks.deleteFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, nullptr); @@ -289,7 +300,7 @@ void LLInventoryModelBackgroundFetch::start(const LLUUID& id, bool recursive) // it's a folder, do a bulk fetch LL_DEBUGS(LOG_INV) << "Start fetching category: " << id << ", recursive: " << recursive << LL_ENDL; - mBackgroundFetchActive = true; + markFetchStarted(); mFolderFetchActive = true; EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; if (id.isNull()) @@ -376,7 +387,7 @@ void LLInventoryModelBackgroundFetch::scheduleFolderFetch(const LLUUID& cat_id, { if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != cat_id) { - mBackgroundFetchActive = true; + markFetchStarted(); mFolderFetchActive = true; if (forced) @@ -403,7 +414,7 @@ void LLInventoryModelBackgroundFetch::scheduleItemFetch(const LLUUID& item_id, b { if (mFetchItemQueue.empty() || mFetchItemQueue.front().mUUID != item_id) { - mBackgroundFetchActive = true; + markFetchStarted(); if (forced) { // check if already requested @@ -447,7 +458,7 @@ void LLInventoryModelBackgroundFetch::fetchFolderAndLinks(const LLUUID& cat_id, }); // start idle loop to track completion - mBackgroundFetchActive = true; + markFetchStarted(); mFolderFetchActive = true; gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, nullptr); } @@ -489,14 +500,14 @@ void LLInventoryModelBackgroundFetch::fetchCOF(nullary_func_t callback) }); // start idle loop to track completion - mBackgroundFetchActive = true; + markFetchStarted(); mFolderFetchActive = true; gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, nullptr); } void LLInventoryModelBackgroundFetch::findLostItems() { - mBackgroundFetchActive = true; + markFetchStarted(); mFolderFetchActive = true; mFetchFolderQueue.emplace_back(LLUUID::null, FT_RECURSIVE); gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, nullptr); @@ -515,13 +526,18 @@ void LLInventoryModelBackgroundFetch::setAllFoldersFetched() mFolderFetchActive = false; if (isBulkFetchProcessingComplete()) { + if (mAllRecursiveFoldersFetched && !mInitialFetchDurationCaptured) + { + mInitialFetchDuration = mFetchStartTimer.getElapsedTimeF32(); + mInitialFetchDurationCaptured = true; + } mBackgroundFetchActive = false; } // For now only informs about initial fetch being done mFoldersFetchedSignal(); - LL_INFOS(LOG_INV) << "Inventory background fetch completed" << LL_ENDL; + LL_INFOS(LOG_INV) << "Inventory background fetch completed after: " << mFetchStartTimer.getElapsedTimeF32() << LL_ENDL; } boost::signals2::connection LLInventoryModelBackgroundFetch::setFetchCompletionCallback(folders_fetched_callback_t cb) @@ -843,6 +859,11 @@ void LLInventoryModelBackgroundFetch::bulkFetchViaAis() if (isBulkFetchProcessingComplete()) { + if (mAllRecursiveFoldersFetched && !mInitialFetchDurationCaptured) + { + mInitialFetchDuration = mFetchStartTimer.getElapsedTimeF32(); + mInitialFetchDurationCaptured = true; + } mBackgroundFetchActive = false; } } diff --git a/indra/newview/llinventorymodelbackgroundfetch.h b/indra/newview/llinventorymodelbackgroundfetch.h index ef6fa06e9f..6a5e0f66ff 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.h +++ b/indra/newview/llinventorymodelbackgroundfetch.h @@ -70,6 +70,7 @@ public: bool inventoryFetchStarted() const; bool inventoryFetchCompleted() const; bool inventoryFetchInProgress() const; + F32 getInitialFetchDuration() const { return mInitialFetchDuration; } void findLostItems(); void incrFetchCount(S32 fetching); @@ -120,6 +121,8 @@ protected: bool fetchQueueContainsNoDescendentsOf(const LLUUID& cat_id) const; private: + void markFetchStarted(); + bool mRecursiveInventoryFetchStarted; bool mRecursiveLibraryFetchStarted; bool mRecursiveMarketplaceFetchStarted; // AIS3 specific @@ -133,6 +136,9 @@ private: S32 mLastFetchCount; // for debug S32 mFetchFolderCount; + LLFrameTimer mFetchStartTimer; + F32 mInitialFetchDuration; + bool mInitialFetchDurationCaptured; LLFrameTimer mFetchTimer; F32 mMinTimeBetweenFetches; fetch_queue_t mFetchFolderQueue; diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index 21cf2a1837..2f0a52e78b 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -134,6 +134,7 @@ void LLLoginInstance::reconnect() { // Sort of like connect, only using the pre-existing // request params. + mAttemptComplete = false; std::vector<std::string> uris; LLGridManager::getInstance()->getLoginURIs(uris); mLoginModule->connect(uris.front(), mRequestData); diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp index c7b60b2fd5..4a808ba052 100644 --- a/indra/newview/llmediactrl.cpp +++ b/indra/newview/llmediactrl.cpp @@ -739,7 +739,7 @@ bool LLMediaCtrl::ensureMediaSourceExists() mMediaSource->setUsedInUI(true); mMediaSource->setHomeURL(mHomePageUrl, mHomePageMimeType); mMediaSource->setTarget(mTarget); - mMediaSource->setVisible( getVisible() ); + mMediaSource->setVisible( isInVisibleChain() ); mMediaSource->addObserver( this ); mMediaSource->setBackgroundColor( getBackgroundColor() ); mMediaSource->setTrustedBrowser(mTrusted); diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index b7bba02b9d..b72301c566 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -92,7 +92,17 @@ public: const LLUUID& invoice, const sparam_t& strings) { - LLMuteList::getInstance()->setLoaded(); + // We've gotten a message from the server that indicates our mute list is empty there. + + // First we want to make sure that if we had a cached mute list, we clear it assuming it is outdated. + LLMuteList* mute_list = LLMuteList::getInstance(); + if(mute_list->mLoadSource == LLMuteList::MLS_FALLBACK_CACHE && !mute_list->getMutes().empty()) + { + LL_WARNS() << "Our current mute list is not empty, but the server says that it should be. Is our cache outdated, or did our mutes get lost?" << LL_ENDL; + mute_list->clearCachedMutes(); + } + // Lastly we set the load state to loaded with the source of server empty. We are now in a clean and ready state. + mute_list->setLoaded(LLMuteList::MLS_SERVER_EMPTY); return true; } }; @@ -155,7 +165,9 @@ std::string LLMute::getDisplayType() const //----------------------------------------------------------------------------- LLMuteList::LLMuteList() : mLoadState(ML_INITIAL), - mRequestStartTime(0.f) + mLoadSource(MLS_NONE), + mRequestStartTime(0.f), + mTriedCacheFallback(false) { gGenericDispatcher.addHandler("emptymutelist", &sDispatchEmptyMuteList); @@ -178,6 +190,8 @@ LLMuteList::LLMuteList() : // but this way is just more convinient onAccountNameChanged(id, av_name.getUserName()); }); + // Register our region change callback handler early, we'll clean it up if/when we don't need it anymore. + mRegionChangedCallback = gAgent.addRegionChangedCallback(boost::bind(&LLMuteList::onRegionChanged, this)); } //----------------------------------------------------------------------------- @@ -191,6 +205,10 @@ LLMuteList::~LLMuteList() void LLMuteList::cleanupSingleton() { LLAvatarNameCache::getInstance()->setAccountNameChangedCallback(nullptr); + if (mRegionChangedCallback.connected()) + { + mRegionChangedCallback.disconnect(); + } } bool LLMuteList::isLinden(const std::string& name) @@ -210,9 +228,9 @@ bool LLMuteList::isLinden(const std::string& name) return last_name == "linden"; } -bool LLMuteList::getLoadFailed() const +bool LLMuteList::updateLoadState() { - if (mLoadState == ML_FAILED) + if (isLoaded() || isFailed()) { return true; } @@ -221,9 +239,83 @@ bool LLMuteList::getLoadFailed() const constexpr F64 WAIT_SECONDS = 30; if (mRequestStartTime + WAIT_SECONDS < LLTimer::getTotalSeconds()) { - return true; + LL_WARNS() << "Mute list request timed out; trying cache fallback once" << LL_ENDL; + tryLoadCacheFallback(gAgent.getID(), "request timeout"); + return isLoaded() || isFailed(); + } + } + return false; +} + +void LLMuteList::clearCachedMutes() +{ + mMutes.clear(); + mLegacyMutes.clear(); + LL_WARNS() << "Cached mutes cleared" << LL_ENDL; +} + +const char* LLMuteList::sourceToString(EMuteListSource source) +{ + switch (source) + { + case MLS_NONE: + return "none"; + case MLS_SERVER: + return "server"; + case MLS_SERVER_EMPTY: + return "server-empty"; + case MLS_SERVER_CACHE: + return "server-cached"; + case MLS_FALLBACK_CACHE: + return "fallback-cache"; + default: + return "unknown"; + } +} + +std::string LLMuteList::getCacheFilename(const LLUUID& agent_id) const +{ + std::string agent_id_string; + agent_id.toString(agent_id_string); + return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, agent_id_string) + ".cached_mute"; +} + +void LLMuteList::setFailed(const std::string& reason) +{ + mLoadState = ML_FAILED; + if (mLoadSource == MLS_NONE) + { + LL_WARNS() << "Mute list unavailable: " << reason << LL_ENDL; + } + else + { + LL_WARNS() << "Mute list unavailable: " << reason << " (last source=" << sourceToString(mLoadSource) << ")" << LL_ENDL; + } + notifyObservers(); +} + +bool LLMuteList::tryLoadCacheFallback(const LLUUID& agent_id, const std::string& reason) +{ + if (mTriedCacheFallback) + { + if (!isLoaded()) + { + setFailed("cache fallback already attempted before " + reason); } + return isLoaded(); } + + mTriedCacheFallback = true; + const std::string filename = getCacheFilename(agent_id); + LL_INFOS() << "Trying mute list cache fallback due to " << reason << ": " << filename << LL_ENDL; + + if (loadFromFile(filename, MLS_FALLBACK_CACHE)) + { + LL_WARNS() << "Loaded mute list from cache fallback due to " << reason << LL_ENDL; + return true; + } + + setFailed("cache fallback failed after " + reason); return false; } @@ -580,14 +672,14 @@ std::vector<LLMute> LLMuteList::getMutes() const //----------------------------------------------------------------------------- // loadFromFile() //----------------------------------------------------------------------------- -bool LLMuteList::loadFromFile(const std::string& filename) +bool LLMuteList::loadFromFile(const std::string& filename, EMuteListSource source) { LL_PROFILE_ZONE_SCOPED; if(!filename.size()) { LL_WARNS() << "Mute List Filename is Empty!" << LL_ENDL; - mLoadState = ML_FAILED; + setFailed("empty filename"); return false; } @@ -595,10 +687,13 @@ bool LLMuteList::loadFromFile(const std::string& filename) if (!fp) { LL_WARNS() << "Couldn't open mute list " << filename << LL_ENDL; - mLoadState = ML_FAILED; + setFailed("cannot open " + filename); return false; } + // Replace previous server-backed state so fallback can be superseded by authoritative data. + clearCachedMutes(); + // *NOTE: Changing the size of these buffers will require changes // in the scanf below. char id_buffer[MAX_STRING]; /*Flawfinder: ignore*/ @@ -627,7 +722,7 @@ bool LLMuteList::loadFromFile(const std::string& filename) } } fclose(fp); - setLoaded(); + setLoaded(source); // server does not maintain up-to date account names (not display names!) // in this list, so it falls to viewer. @@ -737,12 +832,16 @@ bool LLMuteList::isMuted(const std::string& username, U32 flags) const //----------------------------------------------------------------------------- void LLMuteList::requestFromServer(const LLUUID& agent_id) { - std::string agent_id_string; - std::string filename; - agent_id.toString(agent_id_string); - filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; + if(isLoadedFromServer()) + { + LL_WARNS() << "Blocked attempt to request mute list from server when already loaded from server!" << LL_ENDL; + return; + } + const std::string filename = getCacheFilename(agent_id); LLCRC crc; crc.update(filename); + mTriedCacheFallback = false; + mLoadSource = MLS_NONE; LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_MuteListRequest); @@ -755,17 +854,19 @@ void LLMuteList::requestFromServer(const LLUUID& agent_id) if (gDisconnected) { LL_WARNS() << "Trying to request mute list when disconnected!" << LL_ENDL; - mLoadState = ML_FAILED; + // Guard against potentially writing back to disk since we're not recovering our connection + mLoadState = ML_LOADED; + mLoadSource = MLS_FALLBACK_CACHE; return; } if (!gAgent.getRegion()) { LL_WARNS() << "No region for agent yet, skipping mute list request!" << LL_ENDL; - mLoadState = ML_FAILED; + tryLoadCacheFallback(agent_id, "no region for request"); return; } mLoadState = ML_REQUESTED; - mRequestStartTime = LLTimer::getElapsedSeconds(); + mRequestStartTime = LLTimer::getTotalSeconds(); // Double amount of retries due to this request happening during busy stage // Ideally this should be turned into a capability gMessageSystem->sendReliable(gAgent.getRegionHost(), LL_DEFAULT_RELIABLE_RETRIES * 2, true, LL_PING_BASED_TIMEOUT_DUMMY, NULL, NULL); @@ -777,15 +878,16 @@ void LLMuteList::requestFromServer(const LLUUID& agent_id) void LLMuteList::cache(const LLUUID& agent_id) { - // Write to disk even if empty. - if(isLoaded()) + // Write to disk even if empty, but never from degraded fallback state. + if (isLoaded() && mLoadSource != MLS_FALLBACK_CACHE) { - std::string agent_id_string; - std::string filename; - agent_id.toString(agent_id_string); - filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; + const std::string filename = getCacheFilename(agent_id); saveToFile(filename); } + else if (isLoaded()) + { + LL_WARNS() << "Skipping mute list cache write from fallback-only state" << LL_ENDL; + } } //----------------------------------------------------------------------------- @@ -795,11 +897,16 @@ void LLMuteList::cache(const LLUUID& agent_id) void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) { LL_INFOS() << "LLMuteList::processMuteListUpdate()" << LL_ENDL; + LLMuteList* mute_list = getInstance(); + if(mute_list->mTriedRegionChangeRetry) + { + LL_WARNS() << "Received mute list update after retrying region change; success!" << LL_ENDL; + } LLUUID agent_id; msg->getUUIDFast(_PREHASH_MuteData, _PREHASH_AgentID, agent_id); if(agent_id != gAgent.getID()) { - LL_WARNS() << "Got an mute list update for the wrong agent." << LL_ENDL; + LL_WARNS() << "Got a mute list update for the wrong agent." << LL_ENDL; return; } std::string unclean_filename; @@ -810,9 +917,8 @@ void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) LL_WARNS() << "Received empty mute list filename." << LL_ENDL; } - LLMuteList* mute_list = getInstance(); mute_list->mLoadState = ML_REQUESTED; - mute_list->mRequestStartTime = LLTimer::getElapsedSeconds(); + mute_list->mRequestStartTime = LLTimer::getTotalSeconds(); // Todo: Based of logs and testing, there is no callback // from server if file doesn't exist server side. @@ -831,12 +937,12 @@ void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) void LLMuteList::processUseCachedMuteList(LLMessageSystem* msg, void**) { LL_INFOS() << "LLMuteList::processUseCachedMuteList()" << LL_ENDL; - - std::string agent_id_string; - gAgent.getID().toString(agent_id_string); - std::string filename; - filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; - LLMuteList::getInstance()->loadFromFile(filename); + LLMuteList* mute_list = LLMuteList::getInstance(); + if(mute_list->mTriedRegionChangeRetry) + { + LL_WARNS() << "Received use cached mute list message after retrying region change; success!" << LL_ENDL; + } + mute_list->loadFromFile(mute_list->getCacheFilename(gAgent.getID()), MLS_SERVER_CACHE); } void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_status) @@ -845,13 +951,13 @@ void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_ if(local_filename_and_path && !local_filename_and_path->empty() && (error_code == 0)) { LL_INFOS() << "Received mute list from server" << LL_ENDL; - LLMuteList::getInstance()->loadFromFile(*local_filename_and_path); + LLMuteList::getInstance()->loadFromFile(*local_filename_and_path, MLS_SERVER); LLFile::remove(*local_filename_and_path); } else { LL_INFOS() << "LLMuteList xfer failed with code " << error_code << LL_ENDL; - LLMuteList::getInstance()->mLoadState = ML_FAILED; + LLMuteList::getInstance()->tryLoadCacheFallback(gAgent.getID(), "xfer failure"); } delete local_filename_and_path; } @@ -908,10 +1014,21 @@ void LLMuteList::removeObserver(LLMuteListObserver* observer) mObservers.erase(observer); } -void LLMuteList::setLoaded() +void LLMuteList::setLoaded(EMuteListSource source) { + if(isLoaded()) + { + LL_WARNS() << "Mute list was already loaded from " << sourceToString(mLoadSource) << ", switching to " << sourceToString(source) << LL_ENDL; + } mLoadState = ML_LOADED; + mLoadSource = source; notifyObservers(); + LL_INFOS() << "Mute list loaded from " << sourceToString(source) << LL_ENDL; + if(isLoadedFromServer() && mRegionChangedCallback.connected()) + { + LL_INFOS() << "Mute list loaded from server, disconnecting region change callback" << LL_ENDL; + mRegionChangedCallback.disconnect(); + } } void LLMuteList::notifyObservers() @@ -940,6 +1057,23 @@ void LLMuteList::notifyObserversDetailed(const LLMute& mute) } } +void LLMuteList::onRegionChanged() +{ + // If we are in a degraded state, either some protocol messages got lost between us and our login region, or our login region was having a bad day. + // Since the previous region might've been unable to provide our mute list, we can try to request it again from our new region. + // This is limited to one retry per session. + if(isLoadedDegraded() && !mTriedRegionChangeRetry) + { + if(mRegionChangedCallback.connected()) + { + mRegionChangedCallback.disconnect(); + } + LL_WARNS() << "Region changed while mute list is in degraded state, queueing a retry of the mute list request" << LL_ENDL; + mTriedRegionChangeRetry = true; + requestFromServer(gAgent.getID()); + } +} + LLRenderMuteList::LLRenderMuteList() {} diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h index b65fd61fcc..2781e9b177 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -31,6 +31,8 @@ #include "lluuid.h" #include "llextendedstatus.h" +#include <boost/signals2/connection.hpp> + class LLViewerObject; class LLMessageSystem; class LLMuteListObserver; @@ -82,6 +84,15 @@ class LLMuteList : public LLSingleton<LLMuteList> ML_LOADED, ML_FAILED, }; + + enum EMuteListSource + { + MLS_NONE, + MLS_SERVER, + MLS_SERVER_EMPTY, + MLS_SERVER_CACHE, + MLS_FALLBACK_CACHE, + }; public: // reasons for auto-unmuting a resident enum EAutoReason @@ -115,8 +126,17 @@ public: static bool isLinden(const std::string& name); - bool isLoaded() const { return mLoadState == ML_LOADED; } - bool getLoadFailed() const; + // Load state accessors. + bool isLoaded() const { return mLoadState == ML_LOADED; } // Loaded, but not necessarily from server. + bool isFailed() const { return mLoadState == ML_FAILED; } // Unable to load any mute list. Server did not reply. + // Loaded from server, which is the only source we consider authoritative. + bool isLoadedFromServer() const { return isLoaded() && (mLoadSource == MLS_SERVER || mLoadSource == MLS_SERVER_EMPTY); } + // Loaded, but from cache. Would be nice to upgrade to a server load from here if possible. + bool isLoadedDegraded() const { return isLoaded() && !isLoadedFromServer(); } + + // Advance the load state machine, trying cache fallback if necessary. + // Return value indicates mute list consumption readiness. + bool updateLoadState(); std::vector<LLMute> getMutes() const; @@ -126,11 +146,19 @@ public: // call this method on logout to save everything. void cache(const LLUUID& agent_id); + // Handler for region change event, used for server request retries if isLoadedDegraded() is true + void onRegionChanged(); + private: - bool loadFromFile(const std::string& filename); + void clearCachedMutes(); + bool loadFromFile(const std::string& filename, EMuteListSource source); bool saveToFile(const std::string& filename); + bool tryLoadCacheFallback(const LLUUID& agent_id, const std::string& reason); + void setFailed(const std::string& reason); + static const char* sourceToString(EMuteListSource source); + std::string getCacheFilename(const LLUUID& agent_id) const; - void setLoaded(); + void setLoaded(EMuteListSource source); void notifyObservers(); void notifyObserversDetailed(const LLMute &mute); @@ -177,7 +205,11 @@ private: observer_set_t mObservers; EMuteListState mLoadState; + EMuteListSource mLoadSource; F64 mRequestStartTime; + bool mTriedCacheFallback; + bool mTriedRegionChangeRetry; + boost::signals2::connection mRegionChangedCallback; friend class LLDispatchEmptyMuteList; }; diff --git a/indra/newview/llnotificationofferhandler.cpp b/indra/newview/llnotificationofferhandler.cpp index b65da28bda..27ef28f32a 100644 --- a/indra/newview/llnotificationofferhandler.cpp +++ b/indra/newview/llnotificationofferhandler.cpp @@ -36,6 +36,7 @@ #include "llscriptfloater.h" #include "llimview.h" #include "llnotificationsutil.h" +#include "llchannelmanager.h" #include <boost/regex.hpp> @@ -135,7 +136,13 @@ bool LLOfferHandler::processNotification(const LLNotificationPtr& notification, LLScreenChannel* channel = dynamic_cast<LLScreenChannel*>(mChannel.get()); if(channel) + { + if (LLChannelManager::getInstance()->getStartUpToastInited() && notification->getOfferFromAgent()) + { + LLChannelManager::getInstance()->onStartUpToastClose(); + } channel->addToast(p); + } } diff --git a/indra/newview/llpanelblockedlist.cpp b/indra/newview/llpanelblockedlist.cpp index 69f51b03b6..178f994beb 100644 --- a/indra/newview/llpanelblockedlist.cpp +++ b/indra/newview/llpanelblockedlist.cpp @@ -78,6 +78,10 @@ bool LLPanelBlockedList::postBuild() { mBlockedList = getChild<LLBlockList>("blocked"); mBlockedList->setCommitOnSelectionChange(true); + mBlockedList->setNoItemsMsg(getString("no_blocked")); + mBlockedList->setNoFilteredItemsMsg(getString("no_filtered_blocked")); + mBlockedList->setLoadingItemsMsg(getString("loading_blocked")); + mBlockedList->setFailedItemsMsg(getString("failed_blocked")); this->setVisibleCallback(boost::bind(&LLPanelBlockedList::removePicker, this)); switch (gSavedSettings.getU32("BlockPeopleSortOrder")) diff --git a/indra/newview/llpanelplaces.cpp b/indra/newview/llpanelplaces.cpp index 5435a79e16..5c866905d6 100644 --- a/indra/newview/llpanelplaces.cpp +++ b/indra/newview/llpanelplaces.cpp @@ -1356,6 +1356,12 @@ static bool is_agent_in_selected_parcel(LLParcel* parcel) static void onSLURLBuilt(std::string& slurl) { + if (slurl.empty()) + { + LLNotificationsUtil::add("LandmarkLocationUnknown"); + return; + } + LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); LLSD args; diff --git a/indra/newview/llpanelplacestab.cpp b/indra/newview/llpanelplacestab.cpp index 3916e34e31..e870ab7a2c 100644 --- a/indra/newview/llpanelplacestab.cpp +++ b/indra/newview/llpanelplacestab.cpp @@ -67,6 +67,12 @@ void LLPanelPlacesTab::onRegionResponse(const LLVector3d& landmark_global_pos, sl_url = ""; } + if (sl_url.empty()) + { + LLNotificationsUtil::add("LandmarkLocationUnknown"); + return; + } + LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(sl_url)); LLSD args; diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 758ca0b0fa..3d8e5575ae 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -113,77 +113,131 @@ LLUUID post_profile_image(std::string cap_url, const LLSD &first_data, std::stri LLCore::HttpOptions::ptr_t httpOpts = std::make_shared<LLCore::HttpOptions>(); httpOpts->setFollowRedirects(true); - LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); + // Retry stage-2 upload by re-requesting a fresh one-time uploader capability (up to 3 attempts total) + const S32 MAX_UPLOAD_RETRIES = 2; + S32 upload_retry_count = 0; + LLUUID result_uuid; - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL; - return LLUUID::null; - } - if (!result.has("uploader")) + while (upload_retry_count <= MAX_UPLOAD_RETRIES) { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL; - return LLUUID::null; - } - std::string uploader_cap = result["uploader"].asString(); - if (uploader_cap.empty()) - { - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL; - return LLUUID::null; - } + // Stage 1: Request uploader URL + LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); - // Upload the image - LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared<LLCore::HttpRequest>(); - LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared<LLCore::HttpHeaders>(); - LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared<LLCore::HttpOptions>(); - S64 length; + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - { - llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); - if (!instream.is_open()) + if (!status) { - LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL; + // todo: notification? + LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL; + return LLUUID::null; + } + + if (!result.has("uploader")) + { + // todo: notification? + LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL; return LLUUID::null; } - length = instream.tellg(); - } - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); // optional - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length)); // required! - uploaderhttpOpts->setFollowRedirects(true); + std::string uploader_cap = result["uploader"].asString(); + if (uploader_cap.empty()) + { + LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL; + return LLUUID::null; + } + + // Stage 2: Upload the image + LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared<LLCore::HttpRequest>(); + LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared<LLCore::HttpHeaders>(); + LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared<LLCore::HttpOptions>(); + S64 length; + + { + llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); + if (!instream.is_open()) + { + LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL; + return LLUUID::null; + } + length = instream.tellg(); + } - result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, std::to_string(length)); + uploaderhttpOpts->setFollowRedirects(true); - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); - LL_DEBUGS("AvatarProperties") << result << LL_ENDL; + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - if (!status) - { - LL_WARNS("AvatarProperties") << "Failed to upload image " << status.toString() << LL_ENDL; - return LLUUID::null; - } + LL_DEBUGS("AvatarProperties") << result << LL_ENDL; - if (result["state"].asString() != "complete") - { - if (result.has("message")) + if (!status) { - LL_WARNS("AvatarProperties") << "Failed to upload image, state " << result["state"] << " message: " << result["message"] << LL_ENDL; + if (upload_retry_count < MAX_UPLOAD_RETRIES) + { + upload_retry_count++; + LL_WARNS("AvatarProperties") << "Failed to upload image (attempt " << upload_retry_count + << " of " << (MAX_UPLOAD_RETRIES + 1) << "): " << status.toString() + << ", re-requesting uploader..." << LL_ENDL; + llcoro::suspendUntilTimeout(1.0f); + continue; + } + else + { + LL_WARNS("AvatarProperties") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts: " << status.toString() << LL_ENDL; + return LLUUID::null; + } } - else + + // Todo: should we really repeat if 'complete' not set? + if (result["state"].asString() != "complete") { - LL_WARNS("AvatarProperties") << "Failed to upload image " << result << LL_ENDL; + if (upload_retry_count < MAX_UPLOAD_RETRIES) + { + upload_retry_count++; + if (result.has("message")) + { + LL_WARNS("AvatarProperties") << "Failed to upload image, state " << result["state"] + << " message: " << result["message"] << " (attempt " + << upload_retry_count << " of " << (MAX_UPLOAD_RETRIES + 1) + << "), re-requesting uploader..." << LL_ENDL; + } + else + { + LL_WARNS("AvatarProperties") << "Failed to upload image (attempt " << upload_retry_count + << " of " << (MAX_UPLOAD_RETRIES + 1) + << "), re-requesting uploader..." << LL_ENDL; + } + llcoro::suspendUntilTimeout(1.0f); + continue; + } + else + { + if (result.has("message")) + { + LL_WARNS("AvatarProperties") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts, state " << result["state"] + << " message: " << result["message"] << LL_ENDL; + } + else + { + LL_WARNS("AvatarProperties") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts" << LL_ENDL; + } + return LLUUID::null; + } } - return LLUUID::null; + + // Success! + result_uuid = result["new_asset"].asUUID(); + break; } - return result["new_asset"].asUUID(); + return result_uuid; } enum EProfileImageType diff --git a/indra/newview/llpanelteleporthistory.cpp b/indra/newview/llpanelteleporthistory.cpp index bfdfa68e01..0fac70dbd2 100644 --- a/indra/newview/llpanelteleporthistory.cpp +++ b/indra/newview/llpanelteleporthistory.cpp @@ -1012,6 +1012,11 @@ LLFlatListView* LLTeleportHistoryPanel::getFlatListViewFromTab(LLAccordionCtrlTa void LLTeleportHistoryPanel::gotSLURLCallback(const std::string& slurl) { + if (slurl.empty()) + { + LLNotificationsUtil::add("LandmarkLocationUnknown"); + return; + } LLClipboard::instance().copyToClipboard(utf8str_to_wstring(slurl), 0, static_cast<S32>(slurl.size())); LLSD args; diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp index d8d6bcf5fd..5aaa53b732 100644 --- a/indra/newview/llpanelvoicedevicesettings.cpp +++ b/indra/newview/llpanelvoicedevicesettings.cpp @@ -338,8 +338,12 @@ void LLPanelVoiceDeviceSettings::initialize() // put voice client in "tuning" mode if (mUseTuningMode) { + // WebRTC tuning only affects the local audio device (mic-level + // monitoring and device selection); the peer connection stays up and + // its send/receive tracks are disabled for the duration. Unlike Vivox, + // there's no need to suspend (and tear down) the voice channel, which + // previously dropped the call and failed to reconnect on resume. LLVoiceClient::getInstance()->tuningStart(); - LLVoiceChannel::suspend(); } } @@ -348,7 +352,6 @@ void LLPanelVoiceDeviceSettings::cleanup() if (mUseTuningMode) { LLVoiceClient::getInstance()->tuningStop(); - LLVoiceChannel::resume(); } } diff --git a/indra/newview/llreflectionmap.cpp b/indra/newview/llreflectionmap.cpp index 7f5076bd56..f4f3e39f17 100644 --- a/indra/newview/llreflectionmap.cpp +++ b/indra/newview/llreflectionmap.cpp @@ -45,7 +45,8 @@ LLReflectionMap::~LLReflectionMap() { if (mOcclusionQuery) { - glDeleteQueries(1, &mOcclusionQuery); + gPipeline.mReflectionMapManager.recycleQuery(mOcclusionQuery); + mOcclusionQuery = 0; } } @@ -341,7 +342,7 @@ void LLReflectionMap::doOcclusion(const LLVector4a& eye) if (mOcclusionQuery == 0) { // no query was previously issued, allocate one and issue LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("rmdo - glGenQueries"); - glGenQueries(1, &mOcclusionQuery); + mOcclusionQuery = gPipeline.mReflectionMapManager.allocateQuery(); do_query = true; } else diff --git a/indra/newview/llreflectionmapmanager.cpp b/indra/newview/llreflectionmapmanager.cpp index aa48035aa8..4d8f56d0c0 100644 --- a/indra/newview/llreflectionmapmanager.cpp +++ b/indra/newview/llreflectionmapmanager.cpp @@ -574,6 +574,7 @@ void LLReflectionMapManager::refreshSettings() mRenderReflectionProbeLevel = gSavedSettings.getS32("RenderReflectionProbeLevel"); mRenderReflectionProbeCount = gSavedSettings.getU32("RenderReflectionProbeCount"); mRenderReflectionProbeDynamicAllocation = gSavedSettings.getS32("RenderReflectionProbeDynamicAllocation"); + cleanupQueryPool(); } LLReflectionMap* LLReflectionMapManager::addProbe(LLSpatialGroup* group) @@ -621,6 +622,25 @@ U32 LLReflectionMapManager::probeMemory() return (mDynamicProbeCount * 6 * (mProbeResolution * mProbeResolution) * 4) / 1024 / 1024 + (mDynamicProbeCount * 6 * (LL_IRRADIANCE_MAP_RESOLUTION * LL_IRRADIANCE_MAP_RESOLUTION) * 4) / 1024 / 1024; } +GLuint LLReflectionMapManager::allocateQuery() +{ + if (mQueryPool.empty()) + { + GLuint query; + glGenQueries(1, &query); + return query; + } + + GLuint query = mQueryPool.front(); + mQueryPool.pop_front(); + return query; +} + +void LLReflectionMapManager::recycleQuery(GLuint query) +{ + mQueryPool.push_back(query); +} + struct CompareProbeDepth { bool operator()(const LLReflectionMap* lhs, const LLReflectionMap* rhs) @@ -764,6 +784,7 @@ void LLReflectionMapManager::deleteProbe(U32 i) other->mNeighbors.erase(iter); } + // Probes are distance sorted, order matters. mProbes.erase(mProbes.begin() + i); } @@ -1700,10 +1721,23 @@ void LLReflectionMapManager::cleanup() glDeleteBuffers(1, &mUBO); mUBO = 0; + cleanupQueryPool(); + // note: also called on teleport (not just shutdown), so make sure we're in a good "starting" state initCubeFree(); } +void LLReflectionMapManager::cleanupQueryPool() +{ + if (!mQueryPool.empty()) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("cleanup query pool"); + std::vector<GLuint> queries(mQueryPool.begin(), mQueryPool.end()); + glDeleteQueries(static_cast<GLsizei>(queries.size()), queries.data()); + mQueryPool.clear(); + } +} + void LLReflectionMapManager::doOcclusion() { LLVector4a eye; diff --git a/indra/newview/llreflectionmapmanager.h b/indra/newview/llreflectionmapmanager.h index 337c4ca7fa..cfca9afcb4 100644 --- a/indra/newview/llreflectionmapmanager.h +++ b/indra/newview/llreflectionmapmanager.h @@ -106,6 +106,7 @@ public: // release any GL state void cleanup(); + void cleanupQueryPool(); // maintain reflection probes void update(); @@ -164,6 +165,10 @@ public: U32 probeCount(); U32 probeMemory(); + // glDeleteQueries is expensive, so we maintain a pool of queries + GLuint allocateQuery(); + void recycleQuery(GLuint query); + private: friend class LLPipeline; friend class LLHeroProbeManager; @@ -190,6 +195,8 @@ private: // bind UBO used for rendering void setUniforms(); + std::deque<GLuint> mQueryPool; + // render target for cube snapshots // used to generate mipmaps without doing a copy-to-texture LLRenderTarget mRenderTarget; diff --git a/indra/newview/llremoteparcelrequest.cpp b/indra/newview/llremoteparcelrequest.cpp index f89afd38ab..c1b33f313b 100644 --- a/indra/newview/llremoteparcelrequest.cpp +++ b/indra/newview/llremoteparcelrequest.cpp @@ -84,6 +84,7 @@ void LLRemoteParcelInfoProcessor::removeObserver(const LLUUID& parcel_id, LLRemo //static void LLRemoteParcelInfoProcessor::processParcelInfoReply(LLMessageSystem* msg, void**) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; LLParcelData parcel_data; msg->getUUID ("Data", "ParcelID", parcel_data.parcel_id); diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index 86c9818c4e..32cf1f3ca1 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -3054,7 +3054,6 @@ void LLSelectMgr::logNoOp(LLSelectNode* node, void *) // static void LLSelectMgr::logAttachmentRequest(LLSelectNode* node, void *) { - LLAttachmentsMgr::instance().onAttachmentRequested(node->mItemID); } // static diff --git a/indra/newview/llsidepanelinventory.cpp b/indra/newview/llsidepanelinventory.cpp index b48417bd71..623db82939 100644 --- a/indra/newview/llsidepanelinventory.cpp +++ b/indra/newview/llsidepanelinventory.cpp @@ -402,8 +402,6 @@ void LLSidepanelInventory::onOpen(const LLSD& key) gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", (U32)time_corrected()); } #endif - - gAgent.showLatestFeatureNotification("inventory"); } void LLSidepanelInventory::performActionOnSelection(const std::string &action) diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp index f079c70c6c..c75314825c 100644 --- a/indra/newview/llspeakers.cpp +++ b/indra/newview/llspeakers.cpp @@ -438,7 +438,7 @@ void LLSpeakerMgr::update(bool resort_ok) { if(speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL) { - // external speakers should be timed out when they leave the voice channel (since they only exist via SLVoice) + // external speakers should be timed out when they leave the voice channel speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; } else diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index e5c272a264..494a1b3378 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -1381,6 +1381,10 @@ bool idle_startup() { set_startup_status(0.30f, LLTrans::getString("LoginInitializingWorld"), gAgent.mMOTD); do_startup_frame(); + + // close login UI before world UI is initialized, if it is still visible + LLPanelLogin::closePanel(); + // We should have an agent id by this point. llassert(!(gAgentID == LLUUID::null)); @@ -3270,14 +3274,22 @@ void reset_login() if ( gViewerWindow ) { // Hide menus and normal buttons gViewerWindow->setNormalControlsVisible( false ); - gLoginMenuBarView->setVisible( true ); - gLoginMenuBarView->setEnabled( true ); + + if (gLoginMenuBarView) + { + gLoginMenuBarView->setVisible(true); + gLoginMenuBarView->setEnabled(true); + } + else + { + LL_WARNS("AppInit") << "gLoginMenuBarView not initialized" << LL_ENDL; + } } // Hide any other stuff LLFloaterReg::hideVisibleInstances(); - if (LLStartUp::getStartupState() > STATE_WORLD_INIT) + if (LLStartUp::getStartupState() > STATE_WORLD_INIT && gViewerWindow) { gViewerWindow->resetStatusBarContainer(); } diff --git a/indra/newview/llstatslistener.cpp b/indra/newview/llstatslistener.cpp new file mode 100644 index 0000000000..b0f8d54231 --- /dev/null +++ b/indra/newview/llstatslistener.cpp @@ -0,0 +1,200 @@ +/** + * @file llstatslistener.cpp + * @brief EventAPI interface for querying performance statistics + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2026, 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 "llstatslistener.h" + +#include "llinventorymodel.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llimagegl.h" +#include "llviewerstats.h" + +namespace +{ +template <typename STAT, typename EXTRACTOR> +LLSD collectPeriodArray(LLTrace::PeriodicRecording& recording, + size_t num_periods, + const STAT& stat, + EXTRACTOR extractor) +{ + LLSD values = LLSD::emptyArray(); + for (size_t i = 1; i <= num_periods; ++i) + { + LLTrace::Recording& period = recording.getPrevRecording(i); + if (period.hasValue(stat)) + { + values.append((F64)extractor(period, stat)); + } + } + return values; +} + +template <typename STAT, typename EXTRACTOR> +void setPeriodArray(LLSD& out, + const char* key, + LLTrace::Recording& last, + LLTrace::PeriodicRecording& recording, + size_t num_periods, + const STAT& stat, + EXTRACTOR extractor) +{ + if (last.hasValue(stat)) + { + out[key] = collectPeriodArray(recording, num_periods, stat, extractor); + } +} +} + +LLStatsListener::LLStatsListener() + : LLEventAPI("LLStats", "Query performance statistics") +{ + add("getPerfData", + "Get performance data from the frame recording buffer, plus texture memory\n" + "and inventory loading timing information.\n" + "Reply contains [\"stats\"] with nested group maps.", + &LLStatsListener::getPerfData, + llsd::map("reply", LLSD())); +} + +void LLStatsListener::getPerfData(LLSD const & evt) +{ + LLEventAPI::Response response(LLSD(), evt); + + // get_frame_recording() is a PeriodicRecording with 200 periods + LLTrace::PeriodicRecording& recording = LLTrace::get_frame_recording(); + LLTrace::Recording& last = recording.getLastRecording(); + + size_t num_periods = recording.getNumRecordedPeriods(); + F64 total_duration = recording.getDuration().value(); + + LLSD stats; + stats["total_periods_duration"] = total_duration; + stats["num_periods"] = (LLSD::Integer)num_periods; + + LLSD frametime; + + setPeriodArray(frametime, "fps", last, recording, num_periods, LLStatViewer::FPS, + [](LLTrace::Recording& period, const auto& stat) { return period.getPerSec(stat); }); + + setPeriodArray(frametime, "frame_time_ms", last, recording, num_periods, LLStatViewer::FRAMETIME, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + + setPeriodArray(frametime, "do_frame_time_us", last, recording, num_periods, LLStatViewer::DOFRAME_TIME_US, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + + setPeriodArray(frametime, "frame_time_jitter_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + + // Normalized jitter - scalar samples updated per frame + // report the current (last) value since they are session/period rolling stats + if (last.hasValue(LLStatViewer::NOTRMALIZED_FRAMETIME_JITTER_SESSION)) + { + frametime["normalized_jitter_session"] = last.getLastValue(LLStatViewer::NOTRMALIZED_FRAMETIME_JITTER_SESSION); + } + if (last.hasValue(LLStatViewer::NORMALIZED_FRAMTIME_JITTER_PERIOD)) + { + frametime["normalized_jitter_period"] = last.getLastValue(LLStatViewer::NORMALIZED_FRAMTIME_JITTER_PERIOD); + } + if (last.hasValue(LLStatViewer::NFTV)) + { + frametime["normalized_frametime_variation"] = last.getLastValue(LLStatViewer::NFTV); + } + + // Jitter event minute counters: running avg per minute and count in last completed minute + // These are already minute-window aggregates, so sending single value, not arrays + if (last.hasValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_PER_MINUTE)) + { + frametime["frame_time_jitter_events_per_minute"] = (F64)last.getLastValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_PER_MINUTE); + } + if (last.hasValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_LAST_MINUTE)) + { + frametime["frame_time_jitter_events_last_minute"] = (F64)last.getLastValue(LLStatViewer::FRAMETIME_JITTER_EVENTS_LAST_MINUTE); + } + + // Jitter percentiles / cumulative + setPeriodArray(frametime, "jitter_cumulative_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_CUMULATIVE, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + setPeriodArray(frametime, "jitter_99th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_99TH, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + setPeriodArray(frametime, "jitter_95th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_95TH, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + setPeriodArray(frametime, "jitter_stddev_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_STDDEV, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + + // Frame time percentiles + setPeriodArray(frametime, "frametime_99th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_99TH, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + setPeriodArray(frametime, "frametime_95th_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_95TH, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + setPeriodArray(frametime, "frametime_stddev_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_STDDEV, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + + // Frametime jitter event count + setPeriodArray(frametime, "frametime_jitter_events", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER_EVENTS, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + + stats["frametime"] = frametime; + + LLSD other; + + // Packet loss + setPeriodArray(other, "packet_loss_percent", last, recording, num_periods, LLStatViewer::PACKETS_LOST_PERCENT, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + + // Sim ping + setPeriodArray(other, "sim_ping_ms", last, recording, num_periods, LLStatViewer::SIM_PING, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + + // Triangle rendering + setPeriodArray(other, "ktris_per_frame", last, recording, num_periods, LLStatViewer::TRIANGLES_DRAWN, + [](LLTrace::Recording& period, const auto& stat) { return period.getSum(stat); }); + setPeriodArray(other, "ktris_per_sec", last, recording, num_periods, LLStatViewer::TRIANGLES_DRAWN, + [](LLTrace::Recording& period, const auto& stat) { return period.getPerSec(stat); }); + + // Object counts + setPeriodArray(other, "num_objects", last, recording, num_periods, LLStatViewer::NUM_OBJECTS, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + setPeriodArray(other, "num_active_objects", last, recording, num_periods, LLStatViewer::NUM_ACTIVE_OBJECTS, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + + stats["other"] = other; + + // texture memory usage + LLSD memory; + memory["texture_bytes_alloc_mb"] = (F64)LLImageGL::getTextureBytesAllocated() / 1024.0 / 512.0; + stats["memory"] = memory; + + LLSD inventory; + // library skeleton cache load duration + inventory["skeleton_load_time_library_seconds"] = gInventory.getLibrarySkeletonLoadTime(); + // agent skeleton cache load duration + inventory["skeleton_load_time_agent_seconds"] = gInventory.getAgentSkeletonLoadTime(); + // initial recursive inventory/library background fetch duration + inventory["initial_fetch_time_seconds"] = LLInventoryModelBackgroundFetch::instance().getInitialFetchDuration(); + inventory["fetch_completed"] = LLInventoryModelBackgroundFetch::instance().isEverythingFetched(); + stats["inventory"] = inventory; + + response["stats"] = stats; +} diff --git a/indra/newview/llstatslistener.h b/indra/newview/llstatslistener.h new file mode 100644 index 0000000000..77777034c7 --- /dev/null +++ b/indra/newview/llstatslistener.h @@ -0,0 +1,41 @@ +/** + * @file llstatslistener.h + * @brief EventAPI interface for querying performance statistics + * + * $LicenseInfo:firstyear=2026&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2026, 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_LLSTATSLISTENER_H +#define LL_LLSTATSLISTENER_H + +#include "lleventapi.h" + +class LLStatsListener : public LLEventAPI +{ +public: + LLStatsListener(); + +private: + void getPerfData(LLSD const & evt); +}; + +#endif // LL_LLSTATSLISTENER_H diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp index 52ec8c17c1..001c4a0449 100644 --- a/indra/newview/lltexturectrl.cpp +++ b/indra/newview/lltexturectrl.cpp @@ -285,7 +285,7 @@ void LLFloaterTexturePicker::setImageIDFromItem(const LLInventoryItem* itemp, bo void LLFloaterTexturePicker::setActive( bool active ) { - if (!active && getChild<LLUICtrl>("Pipette")->getValue().asBoolean()) + if (!active && mPipetteBtn->getValue().asBoolean()) { stopUsingPipette(); } @@ -658,7 +658,10 @@ bool LLFloaterTexturePicker::postBuild() mSavedFolderState.setApply(false); - LLToolPipette::getInstance()->setToolSelectCallback(boost::bind(&LLFloaterTexturePicker::onTextureSelect, this, _1)); + mPipetteConnection = LLToolPipette::getInstance()->setToolSelectCallback([this](LLPointer<LLViewerObject> object, S32 te_index) + { + onPipetteSelect(object, te_index); + }); getChild<LLComboBox>("l_bake_use_texture_combo_box")->setCommitCallback(onBakeTextureSelect, this); @@ -1069,7 +1072,7 @@ void LLFloaterTexturePicker::onBtnSelect(void* userdata) void LLFloaterTexturePicker::onBtnPipette() { - bool pipette_active = getChild<LLUICtrl>("Pipette")->getValue().asBoolean(); + bool pipette_active = mPipetteBtn->getValue().asBoolean(); pipette_active = !pipette_active; if (pipette_active) { @@ -1419,8 +1422,7 @@ void LLFloaterTexturePicker::changeMode() getChild<LLComboBox>("l_bake_use_texture_combo_box")->setVisible(index == PICKER_BAKE); getChild<LLCheckBoxCtrl>("hide_base_mesh_region")->setVisible(false);// index == 2); - bool pipette_visible = (index == PICKER_INVENTORY) - && (mInventoryPickType != PICK_MATERIAL); + bool pipette_visible = (index == PICKER_INVENTORY); mPipetteBtn->setVisible(pipette_visible); if (index == PICKER_BAKE) @@ -1560,15 +1562,8 @@ void LLFloaterTexturePicker::setInventoryPickType(EPickInventoryType type) refreshLocalList(); refreshInventoryFilter(); - if (mInventoryPickType == PICK_MATERIAL) - { - getChild<LLButton>("Pipette")->setVisible(false); - } - else - { - S32 index = mModeSelector->getValue().asInteger(); - getChild<LLButton>("Pipette")->setVisible(index == 0); - } + S32 index = mModeSelector->getValue().asInteger(); + mPipetteBtn->setVisible(index == 0); if (!mLabel.empty()) { @@ -1640,39 +1635,88 @@ void LLFloaterTexturePicker::onPickerCallback(const std::vector<std::string>& fi } } -void LLFloaterTexturePicker::onTextureSelect( const LLTextureEntry& te ) +void LLFloaterTexturePicker::onPipetteSelect(LLPointer<LLViewerObject>& object, S32 te_index) { - LLUUID inventory_item_id = findItemID(te.getID(), true); - if (inventory_item_id.notNull()) + if (mInventoryPickType == PICK_MATERIAL) { - LLToolPipette::getInstance()->setResult(true, ""); - if (mInventoryPickType == PICK_MATERIAL) - { - // tes have no data about material ids - // Plus gltf materials are layered with overrides, - // which mean that end result might have no id. - LL_WARNS() << "tes have no data about material ids" << LL_ENDL; - } - else + // Note: does not copy overrides! + LLUUID mat_id = object->getRenderMaterialID(te_index); + if (mat_id == BLANK_MATERIAL_ASSET_ID) { - setImageID(te.getID()); + // It's fine if blank material isn't in inventory, just set it + LLToolPipette::getInstance()->setResult(true, ""); + setImageID(mat_id); setTentative(false); - } - mNoCopyTextureSelected = false; - LLInventoryItem* itemp = gInventory.getItem(inventory_item_id); + mNoCopyTextureSelected = false; - if (itemp && !itemp->getPermissions().allowCopyBy(gAgent.getID())) + commitIfImmediateSet(); + } + else if (mat_id.isNull()) { - // no copy texture - mNoCopyTextureSelected = true; + LLToolPipette::getInstance()->setResult(false, LLTrans::getString("InventoryNoMaterial")); } + else + { + LLUUID inventory_item_id = findItemID(mat_id, true); + if (inventory_item_id.notNull()) + { + LLToolPipette::getInstance()->setResult(true, ""); + setImageID(mat_id); + setTentative(false); + + mNoCopyTextureSelected = false; + LLInventoryItem* itemp = gInventory.getItem(inventory_item_id); + + if (itemp && !itemp->getPermissions().allowCopyBy(gAgent.getID())) + { + // no copy texture + mNoCopyTextureSelected = true; + } - commitIfImmediateSet(); + commitIfImmediateSet(); + } + else + { + // Not in inventory, can't apply + LLToolPipette::getInstance()->setResult(false, LLTrans::getString("InventoryNoMaterial")); + } + } } else { - LLToolPipette::getInstance()->setResult(false, LLTrans::getString("InventoryNoTexture")); + const LLTextureEntry* entry = object->getTE(te_index); + if (!entry) + { + // Whatever was selected is not a face/TE, + // no texture to check, so do nothing. + // Should not be reachable, if you hit this, + // check what happens in pipette tool. + llassert(false); + return; + } + LLUUID inventory_item_id = findItemID(entry->getID(), true); + if (inventory_item_id.notNull()) + { + LLToolPipette::getInstance()->setResult(true, ""); + setImageID(entry->getID()); + setTentative(false); + + mNoCopyTextureSelected = false; + LLInventoryItem* itemp = gInventory.getItem(inventory_item_id); + + if (itemp && !itemp->getPermissions().allowCopyBy(gAgent.getID())) + { + // no copy texture + mNoCopyTextureSelected = true; + } + + commitIfImmediateSet(); + } + else + { + LLToolPipette::getInstance()->setResult(false, LLTrans::getString("InventoryNoTexture")); + } } } diff --git a/indra/newview/lltexturectrl.h b/indra/newview/lltexturectrl.h index e0060474ce..759711ebf7 100644 --- a/indra/newview/lltexturectrl.h +++ b/indra/newview/lltexturectrl.h @@ -374,7 +374,7 @@ public: static void onBtnNone(void* userdata); void onSelectionChange(const std::deque<LLFolderViewItem*> &items, bool user_action); static void onApplyImmediateCheck(LLUICtrl* ctrl, void* userdata); - void onTextureSelect(const LLTextureEntry& te); + void onPipetteSelect(LLPointer<LLViewerObject>& object, S32 te_index); static void onModeSelect(LLUICtrl* ctrl, void *userdata); static void onBtnAdd(void* userdata); @@ -457,6 +457,8 @@ private: set_image_asset_id_callback mSetImageAssetIDCallback; set_on_update_image_stats_callback mOnUpdateImageStatsCallback; + boost::signals2::scoped_connection mPipetteConnection; + bool mBakeTextureEnabled; bool mLocalTextureEnabled; diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index 8cbede8303..cbe2dac52a 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -250,7 +250,7 @@ void LLTextureBar::draw() gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); // Draw the progress bar. - S32 bar_width = 100; + S32 bar_width = 110; S32 bar_left = 260; left = bar_left; right = left + bar_width; diff --git a/indra/newview/lltoastnotifypanel.cpp b/indra/newview/lltoastnotifypanel.cpp index 0c4a9c81c2..e905a879cb 100644 --- a/indra/newview/lltoastnotifypanel.cpp +++ b/indra/newview/lltoastnotifypanel.cpp @@ -38,6 +38,7 @@ #include "llnotifications.h" #include "lluiconstants.h" #include "llrect.h" +#include "llstring.h" #include "lltrans.h" #include "llnotificationsutil.h" #include "llviewermessage.h" @@ -91,8 +92,24 @@ LLButton* LLToastNotifyPanel::createButton(const LLSD& form_element, bool is_opt std::string name = form_element["name"].asString(); std::string text = form_element["text"].asString(); bool make_small_btn = index == -1 || index == -2; // for block and ignore buttons in script dialog + + // Use Emoji font as exception if text contains red heart emoji (10084 U+2764) to ensure proper rendering + std::string font_name = mIsScriptDialog ? sFontScript : sFontDefault; + if (mIsScriptDialog) + { + LLWString wtext = utf8str_to_wstring(text); + for (llwchar ch : wtext) + { + if (ch == 0x2764) + { + font_name = "Emoji"; + break; + } + } + } + const LLFontGL* font = LLFontGL::getFont(LLFontDescriptor( - mIsScriptDialog ? sFontScript : sFontDefault, make_small_btn ? "Small" : "Medium", 0)); + font_name, make_small_btn ? "Small" : "Medium", 0)); p.name = name; p.label = text; p.tool_tip = text; @@ -284,7 +301,7 @@ void LLToastNotifyPanel::init( LLRect rect, bool show_images ) mIsScriptDialog = (notif_name == "ScriptDialog" || notif_name == "ScriptDialogGroup"); static LLCachedControl<S32> btn_width(gSavedSettings, "ToastButtonWidth", 90); - static LLCachedControl<S32> script_button_width(gSavedSettings, "ScriptToastButtonWidth", 110); + static LLCachedControl<S32> script_button_width(gSavedSettings, "ScriptToastButtonWidth", 127); mButtonWidth = mIsScriptDialog ? script_button_width : btn_width; bool is_content_trusted = (notif_name != "LoadWebPage"); diff --git a/indra/newview/lltoolmgr.cpp b/indra/newview/lltoolmgr.cpp index 07963a7bed..c7e6f774dc 100644 --- a/indra/newview/lltoolmgr.cpp +++ b/indra/newview/lltoolmgr.cpp @@ -57,6 +57,7 @@ #include "llviewerjoystick.h" #include "llviewermenu.h" #include "llviewerparcelmgr.h" +#include "lleventapi.h" // Used when app not active to avoid processing hover. @@ -69,6 +70,67 @@ LLToolset* gMouselookToolset = NULL; LLToolset* gFaceEditToolset = NULL; ///////////////////////////////////////////////////// +// LLToolMgrListener + +class LLToolMgrListener: public LLEventAPI +{ +public: + LLToolMgrListener(): + LLEventAPI("LLToolMgr", + "LLToolMgr listener for tool management operations") + { + add("openBuildFloater", + "Open the build floater by toggling build mode", + &LLToolMgrListener::openFloater); + add("selectTool", + "Select the specified tool by [\"tool_name\"]. Valid tool names: Focus, Move, Edit, Create, Land", + &LLToolMgrListener::selectTool); + } + +private: + void openFloater(const LLSD& event) const + { + LLToolMgr::getInstance()->toggleBuildMode(LLSD("build")); + } + + void selectTool(const LLSD& event) const + { + if (!event.has("tool_name")) + { + LL_WARNS() << "selectTool: called without tool_name" << LL_ENDL; + return; + } + + static const std::unordered_map<std::string, S32> tool_indices = { + {"focus", 1}, + {"move", 2}, + {"edit", 3}, + {"create", 4}, + {"land", 5} + }; + + const std::string tool_name = utf8str_tolower(event["tool_name"].asString()); + const auto it = tool_indices.find(tool_name); + if (it == tool_indices.end()) + { + LL_WARNS() << "selectTool: unknown tool_name: " << std::quoted(tool_name) << LL_ENDL; + return; + } + + LLToolset* current_toolset = LLToolMgr::getInstance()->getCurrentToolset(); + if (!current_toolset) + { + LL_WARNS() << "selectTool: no current toolset available" << LL_ENDL; + return; + } + + current_toolset->selectToolByIndex(it->second); + } +}; + +static LLToolMgrListener sToolMgrListener; + +///////////////////////////////////////////////////// // LLToolMgr LLToolMgr::LLToolMgr() diff --git a/indra/newview/lltoolpipette.cpp b/indra/newview/lltoolpipette.cpp index 9e3d435688..6ec25e9512 100644 --- a/indra/newview/lltoolpipette.cpp +++ b/indra/newview/lltoolpipette.cpp @@ -47,8 +47,8 @@ // LLToolPipette::LLToolPipette() -: LLTool(std::string("Pipette")), - mSuccess(true) +: LLTool(std::string("Pipette")) +, mSuccess(true) { } @@ -103,15 +103,6 @@ bool LLToolPipette::handleToolTip(S32 x, S32 y, MASK mask) return true; } -void LLToolPipette::setTextureEntry(const LLTextureEntry* entry) -{ - if (entry) - { - mTextureEntry = *entry; - mSignal(mTextureEntry); - } -} - void LLToolPipette::pickCallback(const LLPickInfo& pick_info) { LLViewerObject* hit_obj = pick_info.getObject(); @@ -120,12 +111,14 @@ void LLToolPipette::pickCallback(const LLPickInfo& pick_info) // if we clicked on a face of a valid prim, save off texture entry data if (hit_obj && hit_obj->getPCode() == LL_PCODE_VOLUME && - pick_info.mObjectFace != -1) + pick_info.mObjectFace != -1 && + hit_obj->getNumTEs() > pick_info.mObjectFace) { //TODO: this should highlight the selected face only LLSelectMgr::getInstance()->highlightObjectOnly(hit_obj); - const LLTextureEntry* entry = hit_obj->getTE(pick_info.mObjectFace); - LLToolPipette::getInstance()->setTextureEntry(entry); + + LLPointer<LLViewerObject> hit_obj_ptr = hit_obj; + LLToolPipette::getInstance()->mSignal(hit_obj_ptr, pick_info.mObjectFace); } } diff --git a/indra/newview/lltoolpipette.h b/indra/newview/lltoolpipette.h index 6c79674d76..7771f467e5 100644 --- a/indra/newview/lltoolpipette.h +++ b/indra/newview/lltoolpipette.h @@ -51,16 +51,13 @@ public: virtual bool handleHover(S32 x, S32 y, MASK mask) override; virtual bool handleToolTip(S32 x, S32 y, MASK mask) override; - // Note: Don't return connection; use boost::bind + boost::signals2::trackable to disconnect slots - typedef boost::signals2::signal<void (const LLTextureEntry& te)> signal_t; - void setToolSelectCallback(const signal_t::slot_type& cb) { mSignal.connect(cb); } + typedef boost::signals2::signal<void (LLPointer<LLViewerObject> obj, S32 te_index)> signal_t; + boost::signals2::connection setToolSelectCallback(const signal_t::slot_type& cb) { return mSignal.connect(cb); } void setResult(bool success, const std::string& msg); - void setTextureEntry(const LLTextureEntry* entry); static void pickCallback(const LLPickInfo& pick_info); protected: - LLTextureEntry mTextureEntry; signal_t mSignal; bool mSuccess; std::string mTooltipMsg; diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index beae71e7bf..d95560b306 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -37,6 +37,7 @@ #include "llui.h" // getRootView(), resolvePath() #include "lluictrl.h" #include "llerror.h" +#include "llcombobox.h" LLUIListener::LLUIListener(): @@ -55,6 +56,12 @@ LLUIListener::LLUIListener(): "current value as [\"value\"] reply.", &LLUIListener::getValue, LLSDMap("path", LLSD())("reply", LLSD())); + + add("setSelectedByValue", + "For the combobox identified by the path in [\"path\"] set selection by [\"value\"],\n" + "and return result as [\"reply\"]", + &LLUIListener::setSelectedByValue, + llsd::map("path", LLSD(), "value", LLSD(), "reply", LLSD())); } void LLUIListener::call(const LLSD& event) const @@ -99,3 +106,20 @@ void LLUIListener::getValue(const LLSD&event) const sendReply(reply, event); } + +void LLUIListener::setSelectedByValue(const LLSD& event) const +{ + Response response(LLSD(), event); + std::string path(event["path"]); + LLComboBox* combo_ctrl = dynamic_cast<LLComboBox*>(LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path)); + if (combo_ctrl) + { + response.setResponse(combo_ctrl->setSelectedByValue(event["value"], true)); + return; + } + else + { + LL_WARNS() << "Specified combobox doesn't exist: " << path << LL_ENDL; + } + response.setResponse(false); +} diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h index 70455c2c68..228e67ae26 100644 --- a/indra/newview/lluilistener.h +++ b/indra/newview/lluilistener.h @@ -42,6 +42,7 @@ public: private: void call(const LLSD& event) const; void getValue(const LLSD&event) const; + void setSelectedByValue(const LLSD& event) const; }; #endif /* ! defined(LL_LLUILISTENER_H) */ diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp index 6a667925b3..90eb977bba 100644 --- a/indra/newview/llvelopack.cpp +++ b/indra/newview/llvelopack.cpp @@ -833,7 +833,6 @@ static void on_first_run(void* p_user_data, const char* app_version) MultiByteToWideChar(CP_UTF8, 0, app_version, -1, &version[0], len); register_uninstall_info(install_dir, app_name, version); - // Drop install related settings // Unfortunately gDirUtilp is not initialized yet and it's shouldn't // be possible to change location of the settings. For now it's simpler diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp index 65a69acc88..46c76e2953 100644 --- a/indra/newview/llviewerassetupload.cpp +++ b/indra/newview/llviewerassetupload.cpp @@ -881,6 +881,7 @@ void LLViewerAssetUpload::AssetInventoryUploadCoproc(LLCoreHttpUtil::HttpCorouti if (uploadInfo->showUploadDialog()) { + // todo: localize this string std::string uploadMessage = "Uploading...\n\n"; uploadMessage.append(uploadInfo->getDisplayName()); LLUploadDialog::modalUploadDialog(uploadMessage); @@ -888,66 +889,90 @@ void LLViewerAssetUpload::AssetInventoryUploadCoproc(LLCoreHttpUtil::HttpCorouti LLSD body = uploadInfo->generatePostBody(); - result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOptions); + // Retry stage-2 upload by re-requesting a fresh one-time uploader capability (up to 3 attempts total) + const S32 MAX_UPLOAD_RETRIES = 2; + S32 upload_retry_count = 0; + LLCore::HttpStatus status; - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if ((!status) || (result.has("error"))) + while (upload_retry_count <= MAX_UPLOAD_RETRIES) { - HandleUploadError(status, result, uploadInfo); - if (uploadInfo->showUploadDialog()) - LLUploadDialog::modalUploadFinished(); - return; - } + // Stage 1: Request uploader URL + result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOptions); - std::string uploader = result["uploader"].asString(); - - bool success = false; - if (!uploader.empty() && uploadInfo->getAssetId().notNull()) - { - result = httpAdapter->postFileAndSuspend(httpRequest, uploader, uploadInfo->getAssetId(), uploadInfo->getAssetType(), httpOptions); - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - std::string ulstate = result["state"].asString(); - - if ((!status) || (ulstate != "complete")) + if ((!status) || (result.has("error"))) { HandleUploadError(status, result, uploadInfo); if (uploadInfo->showUploadDialog()) LLUploadDialog::modalUploadFinished(); return; } - if (!result.has("success")) + + std::string uploader = result["uploader"].asString(); + if (uploader.empty() || uploadInfo->getAssetId().isNull()) { - result["success"] = LLSD::Boolean((ulstate == "complete") && status); + LL_WARNS() << "No upload url provided. Nothing uploaded, responding with previous result." << LL_ENDL; + break; } - S32 uploadPrice = result["upload_price"].asInteger(); + // Stage 2: Upload to the uploader URL + result = httpAdapter->postFileAndSuspend(httpRequest, uploader, uploadInfo->getAssetId(), uploadInfo->getAssetType(), httpOptions); + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - if (uploadPrice > 0) - { - // this upload costed us L$, update our balance - // and display something saying that it cost L$ - LLStatusBar::sendMoneyBalanceRequest(); + std::string ulstate = result["state"].asString(); - LLSD args; - args["AMOUNT"] = llformat("%d", uploadPrice); - LLNotificationsUtil::add("UploadPayment", args); + if ((!status) || (ulstate != "complete")) + { + if (upload_retry_count < MAX_UPLOAD_RETRIES) + { + upload_retry_count++; + LL_WARNS() << "Upload to uploader failed (attempt " << upload_retry_count + << " of " << (MAX_UPLOAD_RETRIES + 1) << "), re-requesting uploader..." << LL_ENDL; + llcoro::suspendUntilTimeout(1.0f); + continue; + } + else + { + HandleUploadError(status, result, uploadInfo); + if (uploadInfo->showUploadDialog()) + LLUploadDialog::modalUploadFinished(); + return; + } } + + // Success! + break; } - else + + if (!result.has("success")) + { + result["success"] = LLSD::Boolean((result["state"].asString() == "complete") && status); + } + + S32 uploadPrice = result["upload_price"].asInteger(); + + if (uploadPrice > 0) { - LL_WARNS() << "No upload url provided. Nothing uploaded, responding with previous result." << LL_ENDL; + // this upload costed us L$, update our balance + // and display something saying that it cost L$ + LLStatusBar::sendMoneyBalanceRequest(); + + LLSD args; + args["AMOUNT"] = llformat("%d", uploadPrice); + LLNotificationsUtil::add("UploadPayment", args); } + LLUUID serverInventoryItem = uploadInfo->finishUpload(result); + bool succeeded = false; if (uploadInfo->showInventoryPanel()) { if (serverInventoryItem.notNull()) { - success = true; + succeeded = true; LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); @@ -973,7 +998,7 @@ void LLViewerAssetUpload::AssetInventoryUploadCoproc(LLCoreHttpUtil::HttpCorouti LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot"); if (uploadInfo->getAssetType() == LLAssetType::AT_TEXTURE && floater_snapshot && floater_snapshot->isShown()) { - floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", success).with("msg", "inventory"))); + floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", succeeded).with("msg", "inventory"))); } } diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 59dc4319e1..1b690fda9b 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -217,9 +217,10 @@ void display_update_camera() { final_far *= 0.5f; } - else if (LLViewerTexture::sDesiredDiscardBias > 2.f) + // When system memory is critically low or recovering, shrink draw distance. + else if (const F32 mem_factor = LLMemory::getSystemMemoryBudgetFactor(); mem_factor > 1.f) { - final_far = llmax(32.f, final_far / (LLViewerTexture::sDesiredDiscardBias - 1.f)); + final_far = llmax(32.f, final_far / mem_factor); } LLViewerCamera::getInstance()->setFar(final_far); LLVOAvatar::sRenderDistance = llclamp(final_far, 16.f, 256.f); diff --git a/indra/newview/llviewergenericmessage.cpp b/indra/newview/llviewergenericmessage.cpp index fd894a5997..f3a0f026b8 100644 --- a/indra/newview/llviewergenericmessage.cpp +++ b/indra/newview/llviewergenericmessage.cpp @@ -73,6 +73,7 @@ void send_generic_message(const std::string& method, void process_generic_message(LLMessageSystem* msg, void**) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; LLUUID agent_id; msg->getUUID("AgentData", "AgentID", agent_id); if (agent_id != gAgent.getID()) @@ -95,6 +96,7 @@ void process_generic_message(LLMessageSystem* msg, void**) void process_generic_streaming_message(LLMessageSystem* msg, void**) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; LLGenericStreamingMessage data; data.unpack(msg); switch (data.mMethod) @@ -110,6 +112,7 @@ void process_generic_streaming_message(LLMessageSystem* msg, void**) void process_large_generic_message(LLMessageSystem* msg, void**) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; LLUUID agent_id; msg->getUUID("AgentData", "AgentID", agent_id); if (agent_id != gAgent.getID()) diff --git a/indra/newview/llviewerjointattachment.cpp b/indra/newview/llviewerjointattachment.cpp index e5cf1bcb69..7942f7c4ea 100644 --- a/indra/newview/llviewerjointattachment.cpp +++ b/indra/newview/llviewerjointattachment.cpp @@ -171,7 +171,6 @@ void LLViewerJointAttachment::setupDrawable(LLViewerObject *object) //----------------------------------------------------------------------------- bool LLViewerJointAttachment::addObject(LLViewerObject* object) { -// object->extractAttachmentItemID(); // Same object reattached if (isObjectAttached(object)) diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index b491701a36..92bfc9e0f5 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -1758,6 +1758,7 @@ void LLViewerMediaImpl::createMediaSource() ////////////////////////////////////////////////////////////////////////////////////////// void LLViewerMediaImpl::destroyMediaSource() { + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; mNeedsNewTexture = true; // Tell the viewer media texture it's no longer active @@ -2645,19 +2646,30 @@ void LLViewerMediaImpl::navigateTo(const std::string& url, const std::string& mi } ////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::navigateInternal() +void LLViewerMediaImpl::navigateInternal(bool should_log) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; // Helpful to have media urls in log file. Shouldn't be spammy. { // Do not log the query parts LLURI u(mMediaURL); std::string sanitized_url = (u.query().empty() ? mMediaURL : u.scheme() + "://" + u.authority() + u.path()); - LL_INFOS() << "media id= " << mTextureId << " url=" << sanitized_url << ", mime_type=" << mMimeType << LL_ENDL; + if (should_log) + { + LL_INFOS("Media") << "media id= " << mTextureId << " url=" << sanitized_url << ", mime_type=" << mMimeType << LL_ENDL; + } + else + { + LL_DEBUGS("Media") << "media id= " << mTextureId << " url=" << sanitized_url << ", mime_type=" << mMimeType << LL_ENDL; + } } if(mNavigateSuspended) { - LL_WARNS() << "Deferring navigate." << LL_ENDL; + if (should_log || !mNavigateSuspendedDeferred) + { + LL_WARNS() << "Deferring navigate." << LL_ENDL; + } mNavigateSuspendedDeferred = true; return; } @@ -2665,7 +2677,13 @@ void LLViewerMediaImpl::navigateInternal() if (!mMimeProbe.expired()) { - LL_WARNS() << "MIME type probe already in progress -- bailing out." << LL_ENDL; + if (should_log) + { + // media periodically suspends and unsuspends (should_log == false), + // unsuspend calls this function, it's epxected that sometimes + // unsuspend will be attempted while a probe is in flight. + LL_WARNS() << "MIME type probe already in progress -- bailing out." << LL_ENDL; + } return; } @@ -2726,10 +2744,14 @@ void LLViewerMediaImpl::navigateInternal() { loadURI(); } - else + else if (should_log) { LL_WARNS("Media") << "Couldn't navigate to: " << mMediaURL << " as there is no media type for: " << mMimeType << LL_ENDL; } + else + { + LL_DEBUGS("Media") << "Couldn't navigate to: " << mMediaURL << " as there is no media type for: " << mMimeType << LL_ENDL; + } } void LLViewerMediaImpl::mimeDiscoveryCoro(std::string url) @@ -3995,7 +4017,7 @@ void LLViewerMediaImpl::setNavigateSuspended(bool suspend) if(mNavigateSuspendedDeferred) { mNavigateSuspendedDeferred = false; - navigateInternal(); + navigateInternal(false /*suspend happens periodically, don't log*/); } } } diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h index 1fc5bbc9e0..c840fbb72c 100644 --- a/indra/newview/llviewermedia.h +++ b/indra/newview/llviewermedia.h @@ -251,7 +251,7 @@ public: void navigateHome(); void unload(); void navigateTo(const std::string& url, const std::string& mime_type = "", bool rediscover_type = false, bool server_request = false, bool clean_browser = false); - void navigateInternal(); + void navigateInternal(bool should_log = true); void navigateStop(); bool handleKeyHere(KEY key, MASK mask); bool handleKeyUpHere(KEY key, MASK mask); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 1b1d703f38..6dc87e9aa3 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -131,7 +131,6 @@ #include "llviewerstatsrecorder.h" #include "llvlcomposition.h" #include "llvoavatarself.h" -#include "llvoicevivox.h" #include "llworld.h" #include "llworldmap.h" #include "pipeline.h" diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 1c6544f3f9..26d40436e9 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -40,6 +40,7 @@ #include "llinventorydefines.h" #include "lllslconstants.h" #include "llmaterialtable.h" +#include "llmemory.h" #include "llregionhandle.h" #include "llsd.h" #include "llsdserialize.h" @@ -3391,11 +3392,12 @@ void send_agent_update(bool force_send, bool send_reliable) static F32 last_draw_disatance_step = 1024; F32 memory_limited_draw_distance = gAgentCamera.mDrawDistance; - - if (LLViewerTexture::isSystemMemoryCritical()) + const F32 mem_factor = LLMemory::getSystemMemoryBudgetFactor(); + if (mem_factor > 1.f) { - // If we are low on memory, reduce requested draw distance - memory_limited_draw_distance = llmax(gAgentCamera.mDrawDistance / LLViewerTexture::getSystemMemoryBudgetFactor(), gAgentCamera.mDrawDistance / 2.f); + // We are critically low on memory or recovering, + // limit requested draw distance + memory_limited_draw_distance = llmax(gAgentCamera.mDrawDistance / mem_factor, gAgentCamera.mDrawDistance / 2.f); } if (tp_state == LLAgent::TELEPORT_ARRIVING || LLStartUp::getStartupState() < STATE_MISC) @@ -3574,6 +3576,7 @@ extern U32Bits gObjectData; void process_object_update(LLMessageSystem *mesgsys, void **user_data) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; // Update the data counters if (mesgsys->getReceiveCompressedSize()) { @@ -3595,6 +3598,7 @@ void process_object_update(LLMessageSystem *mesgsys, void **user_data) void process_compressed_object_update(LLMessageSystem *mesgsys, void **user_data) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; // Update the data counters if (mesgsys->getReceiveCompressedSize()) { @@ -3616,6 +3620,7 @@ void process_compressed_object_update(LLMessageSystem *mesgsys, void **user_data void process_cached_object_update(LLMessageSystem *mesgsys, void **user_data) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; // Update the data counters if (mesgsys->getReceiveCompressedSize()) { @@ -3633,6 +3638,7 @@ void process_cached_object_update(LLMessageSystem *mesgsys, void **user_data) void process_terse_object_update_improved(LLMessageSystem *mesgsys, void **user_data) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; if (mesgsys->getReceiveCompressedSize()) { gObjectData += (U32Bytes)mesgsys->getReceiveCompressedSize(); @@ -3992,6 +3998,7 @@ void process_sim_stats(LLMessageSystem *msg, void **user_data) void process_avatar_animation(LLMessageSystem *mesgsys, void **user_data) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; LLUUID animation_id; LLUUID uuid; S32 anim_sequence_id; @@ -4103,6 +4110,7 @@ void process_avatar_animation(LLMessageSystem *mesgsys, void **user_data) void process_object_animation(LLMessageSystem *mesgsys, void **user_data) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; LLUUID animation_id; LLUUID uuid; S32 anim_sequence_id; @@ -4168,6 +4176,7 @@ void process_object_animation(LLMessageSystem *mesgsys, void **user_data) void process_avatar_appearance(LLMessageSystem *mesgsys, void **user_data) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; LLUUID uuid; mesgsys->getUUIDFast(_PREHASH_Sender, _PREHASH_ID, uuid); @@ -5701,6 +5710,7 @@ void process_script_experience_details(const LLSD& experience_details, LLSD args void process_script_question(LLMessageSystem *msg, void **user_data) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; // *TODO: Translate owner name -> [FIRST] [LAST] LLHost sender = msg->getSender(); diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 607ffe32ee..7f51ad1065 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -1530,7 +1530,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, U16 param_type; S32 param_size; dp.unpackU16(param_type, "param_type"); - dp.unpackBinaryData(param_block, param_size, "param_data"); + dp.unpackBinaryData(param_block, MAX_OBJECT_PARAMS_SIZE, param_size, "param_data"); //LL_INFOS() << "Param type: " << param_type << ", Size: " << param_size << LL_ENDL; LLDataPackerBinaryBuffer dp2(param_block, param_size); unpackParameterEntry(param_type, &dp2); @@ -1787,7 +1787,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, dp->unpackU32(size, "ScratchPadSize"); delete [] mData; mData = new U8[size]; - dp->unpackBinaryData((U8 *)mData, sp_size, "PartData"); + dp->unpackBinaryData((U8 *)mData, size, sp_size, "PartData"); } else { @@ -1860,7 +1860,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, U16 param_type; S32 param_size; dp->unpackU16(param_type, "param_type"); - dp->unpackBinaryData(param_block, param_size, "param_data"); + dp->unpackBinaryData(param_block, MAX_OBJECT_PARAMS_SIZE, param_size, "param_data"); //LL_INFOS() << "Param type: " << param_type << ", Size: " << param_size << LL_ENDL; LLDataPackerBinaryBuffer dp2(param_block, param_size); unpackParameterEntry(param_type, &dp2); @@ -3211,6 +3211,7 @@ S32 LLFilenameAndTask::sCount = 0; // static void LLViewerObject::processTaskInv(LLMessageSystem* msg, void** user_data) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; LLUUID task_id; msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_TaskID, task_id); LLViewerObject* object = gObjectList.findObject(task_id); diff --git a/indra/newview/llvieweroctree.cpp b/indra/newview/llvieweroctree.cpp index a1c2e0f9ff..a43e4b99f9 100644 --- a/indra/newview/llvieweroctree.cpp +++ b/indra/newview/llvieweroctree.cpp @@ -1143,6 +1143,8 @@ void LLOcclusionCullingGroup::checkOcclusion() GLuint query_result; // Will be # samples drawn, or a boolean depending on mHasOcclusionQuery2 (both are type GLuint) { LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("co - query result"); + // Ping watchdog before a blocking GL_QUERY_RESULT GPU call + LLAppViewer::instance()->pingMainloopTimeout("Display:CullQueryResult"); glGetQueryObjectuiv(mOcclusionQuery[LLViewerCamera::sCurCameraID], GL_QUERY_RESULT, &query_result); } #if LL_TRACK_PENDING_OCCLUSION_QUERIES diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index f31befd1ab..dd02cca50a 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1768,6 +1768,11 @@ void LLViewerRegion::killInvisibleObjects(F32 max_time) if(iter == mImpl->mActiveSet.end()) { iter = mImpl->mActiveSet.begin(); + if (iter == mImpl->mActiveSet.end()) + { + // Set became empty + break; + } } if((*iter)->getParentID() > 0) { diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 8312bf9e74..f84c2b8530 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -62,7 +62,6 @@ #include "llsdserialize.h" #include "llsdutil.h" #include "llcorehttputil.h" -#include "llvoicevivox.h" #include "llinventorymodel.h" #include "lluiusage.h" #include "lltranslate.h" @@ -233,6 +232,8 @@ LLTrace::SampleStatHandle<U32> FRAMETIME_JITTER_EVENTS("frametimeevents", "Numbe FRAMETIME_JITTER_EVENTS_PER_MINUTE("frametimeeventspm", "Average number of frametime events per minute."), FRAMETIME_JITTER_EVENTS_LAST_MINUTE("frametimeeventslastmin", "Number of frametime events in the last minute."); +LLTrace::SampleStatHandle<U64> DOFRAME_TIME_US("doframetimeus", "doFrame wall time in microseconds."); + LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION("normalizedframetimejitter", "Normalized frametime jitter over the session."); LLTrace::SampleStatHandle<F64> NFTV("nftv", "Normalized frametime variation."); LLTrace::SampleStatHandle<F64> NORMALIZED_FRAMTIME_JITTER_PERIOD("normalizedframetimejitterperiod", "Normalized frametime jitter over the last 5 seconds."); @@ -810,8 +811,6 @@ void send_viewer_stats(bool include_preferences) body["ui"] = LLUIUsage::instance().asLLSD(); - body["stats"]["voice"] = LLVoiceVivoxStats::getInstance()->read(); - // Misc stats, two strings and two ints // These are not expecticed to persist across multiple releases // Comment any changes with your name and the expected release revision diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h index 8ec0dd0024..7da74e716b 100644 --- a/indra/newview/llviewerstats.h +++ b/indra/newview/llviewerstats.h @@ -209,29 +209,44 @@ extern SimMeasurement<F64Megabytes > SIM_PHYSICS_MEM; extern LLTrace::SampleStatHandle<F64Milliseconds > FRAMETIME_JITTER, - SIM_PING; + FRAMETIME, + SIM_PING, + FRAMETIME_JITTER_99TH, + FRAMETIME_JITTER_95TH, + FRAMETIME_99TH, + FRAMETIME_95TH, + FRAMETIME_JITTER_CUMULATIVE, + FRAMETIME_JITTER_STDDEV, + FRAMETIME_STDDEV; + +extern LLTrace::SampleStatHandle<U32> FRAMETIME_JITTER_EVENTS, + FRAMETIME_JITTER_EVENTS_PER_MINUTE, + FRAMETIME_JITTER_EVENTS_LAST_MINUTE; + +extern LLTrace::SampleStatHandle<U64> DOFRAME_TIME_US; + +extern LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION; +extern LLTrace::SampleStatHandle<F64> NFTV; +extern LLTrace::SampleStatHandle<F64> NORMALIZED_FRAMTIME_JITTER_PERIOD; extern LLTrace::EventStatHandle<LLUnit<F64, LLUnits::Meters> > AGENT_POSITION_SNAP; extern LLTrace::EventStatHandle<> LOADING_WEARABLES_LONG_DELAY; extern LLTrace::EventStatHandle<F64Milliseconds > REGION_CROSSING_TIME, - FRAME_STACKTIME, - UPDATE_STACKTIME, - NETWORK_STACKTIME, - IMAGE_STACKTIME, - REBUILD_STACKTIME, - RENDER_STACKTIME; + FRAME_STACKTIME, + UPDATE_STACKTIME, + NETWORK_STACKTIME, + IMAGE_STACKTIME, + REBUILD_STACKTIME, + RENDER_STACKTIME; extern LLTrace::EventStatHandle<F64Seconds > AVATAR_EDIT_TIME, - TOOLBOX_TIME, - MOUSELOOK_TIME; + TOOLBOX_TIME, + MOUSELOOK_TIME; extern LLTrace::EventStatHandle<LLUnit<F32, LLUnits::Percent> > OBJECT_CACHE_HIT_RATE; -extern LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION; -extern LLTrace::SampleStatHandle<F64> NORMALIZED_FRAMTIME_JITTER_PERIOD; - extern LLTrace::SampleStatHandle<U32> WEBRTC_PACKETS_IN_LOST, WEBRTC_PACKETS_IN_RECEIVED, WEBRTC_PACKETS_OUT_SENT, WEBRTC_PACKETS_OUT_LOST; extern LLTrace::SampleStatHandle<F32> WEBRTC_JITTER_OUT, WEBRTC_JITTER_IN, WEBRTC_LATENCY, WEBRTC_UPLOAD_BANDWIDTH, WEBRTC_JITTER_BUFFER; diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp index 210cd62d6f..95e34b4df1 100644 --- a/indra/newview/llviewertexteditor.cpp +++ b/indra/newview/llviewertexteditor.cpp @@ -189,7 +189,12 @@ public: return new LLEmbeddedItemSegment(mStart, mImage, mItem, *editor); } - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) + { + return getSegmentDimensionsF32(first_char, num_chars, width, height); + } + + inline bool getSegmentDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { if (num_chars == 0) { @@ -213,8 +218,9 @@ public: } else { - S32 width, height; - getDimensions(mStart, 1, width, height); + F32 width; + S32 height; + getSegmentDimensionsF32(mStart, 1, width, height); if (width > num_pixels) { return 0; diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 05b71b65b2..f31aa8e6c5 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -533,7 +533,7 @@ void LLViewerTexture::updateClass() if (is_sys_low) { // Not having system memory is more serious, so discard harder - sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.5f * getSystemMemoryBudgetFactor()); + sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.5f * LLMemory::getSystemMemoryBudgetFactor()); } else { @@ -679,20 +679,6 @@ bool LLViewerTexture::isSystemMemoryCritical() return getFreeSystemMemory() < get_render_free_main_memory_treshold() / 2; } -F32 LLViewerTexture::getSystemMemoryBudgetFactor() -{ - const S32Megabytes MIN_FREE_MAIN_MEMORY(get_render_free_main_memory_treshold() / 2); - S32 free_budget = (S32Megabytes)getFreeSystemMemory() - MIN_FREE_MAIN_MEMORY; - if (free_budget < 0) - { - // Leave some padding, otherwise we will crash out of memory before hitting factor 2. - const S32Megabytes PAD_BUFFER(32); - // Result should range from 1 at 0 free budget to 2 at -224 free budget, 2.14 at -256MB - return 1.f - free_budget / (MIN_FREE_MAIN_MEMORY - PAD_BUFFER); - } - return 1.f; -} - //end of static functions //------------------------------------------------------------------------------------------- const U32 LLViewerTexture::sCurrentFileVersion = 1; diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index 2937651995..20aaa8a058 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -116,7 +116,6 @@ public: static void updateClass(); static bool isSystemMemoryLow(); static bool isSystemMemoryCritical(); - static F32 getSystemMemoryBudgetFactor(); LLViewerTexture(bool usemipmaps = true); LLViewerTexture(const LLUUID& id, bool usemipmaps) ; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index d9516de89d..e77c14a53d 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -1106,7 +1106,17 @@ F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) while (!mCreateTextureList.empty()) { - LLViewerFetchedTexture* imagep = mCreateTextureList.front(); + // Hold a smart pointer to keep the texture alive throughout processing, + // even if side effects (e.g. pipeline rebuilds, GL operations) indirectly + // cause other references to be released. (see: #5426) + LLPointer<LLViewerFetchedTexture> imagep = mCreateTextureList.front(); + mCreateTextureList.pop(); + + if (!imagep) + { + continue; + } + llassert(imagep->mCreatePending); // desired discard may change while an image is being decoded. If the texture in VRAM is sufficient @@ -1132,8 +1142,6 @@ F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) imagep->scaleDown(); } - mCreateTextureList.pop(); - if (create_timer.getElapsedTimeF32() > max_time) { break; @@ -1155,10 +1163,11 @@ F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) // give time to downscaling first -- if mDownScaleQueue is not empty, we're running out of memory and need // to free up memory by discarding off screen textures quickly + S32 min_count = (S32)mCreateTextureList.size() / 20 + 3; // do at least 5 and make sure we don't get too far behind even if it violates // the time limit. If we don't downscale quickly the viewer will hit swap and may // freeze. - S32 min_count = (S32)mCreateTextureList.size() / 20 + 3; + //S32 min_count = (S32)mCreateTextureList.size() / 20 + 5; create_timer.reset(); while (!mDownScaleQueue.empty()) @@ -1204,8 +1213,18 @@ F32 LLViewerTextureList::updateImagesLoadingFastCache(F32 max_time) LLTimer timer; image_list_t::iterator enditer = mFastCacheList.begin(); { - // prelock fast cache mutex to avoid waiting multiple times. - LLMutexLock cache_lock(LLAppViewer::getTextureCache()->getFastCacheMutex()); + // Prelock fast cache mutex to avoid waiting multiple times. + LLMutexTrylock fast_cache_lock(LLAppViewer::getTextureCache()->getFastCacheMutex()); + if (!fast_cache_lock.isLocked()) + { + // Cache is busy, skip this update cycle to avoid blocking the main thread. + // + // Generally fast cache operations are brief and rare in comparison to writing + // main texture body, but if disk is busy, it can get stuck for multiple + // seconds, waiting for that long is not practical. + // But some variant of a timed try lock for 0.1ms or less might be optimal. + return 0.0f; + } for (image_list_t::iterator iter = mFastCacheList.begin(); iter != mFastCacheList.end();) { diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index c95a3c9578..1183cc70ef 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -203,6 +203,7 @@ #include "llwindowlistener.h" #include "llviewerwindowlistener.h" +#include "llstatslistener.h" #include "llcleanup.h" #if LL_WINDOWS @@ -1776,7 +1777,7 @@ bool LLViewerWindow::handleTimerEvent(LLWindow *window) return false; } -bool LLViewerWindow::handleDeviceChange(LLWindow *window) +bool LLViewerWindow::handleDeviceChange(LLWindow *window, const std::string& change_type) { // give a chance to use a joystick after startup (hot-plugging) if (!LLViewerJoystick::getInstance()->isJoystickInitialized() ) @@ -1784,6 +1785,10 @@ bool LLViewerWindow::handleDeviceChange(LLWindow *window) LLViewerJoystick::getInstance()->init(true); return true; } + else + { + LL_INFOS("Window") << "Device change event: " << change_type << LL_ENDL; + } return false; } @@ -1805,6 +1810,7 @@ bool LLViewerWindow::handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 bool LLViewerWindow::handleDisplayChanged() { + LL_INFOS("Window") << "Display change event" << LL_ENDL; LLFontGL::sResolutionGeneration++; return false; } @@ -1875,8 +1881,8 @@ LLViewerWindow::LLViewerWindow(const Params& p) mToolStored( NULL ), mHideCursorPermanent( false ), mCursorHidden(false), - mResDirty(false), - mStatesDirty(false), + mResDirty(true), + mStatesDirty(true), mProgressView(NULL) { // gKeyboard is still NULL, so it doesn't do LLWindowListener any good to @@ -1886,6 +1892,7 @@ LLViewerWindow::LLViewerWindow(const Params& p) LLWindowListener::KeyboardGetter getter = [](){ return gKeyboard; }; mWindowListener = std::make_unique<LLWindowListener>(this, getter); mViewerWindowListener = std::make_unique<LLViewerWindowListener>(this); + mStatsListener = std::make_unique<LLStatsListener>(); mSystemChannel.reset(new LLNotificationChannel("System", "Visible", LLNotificationFilters::includeEverything)); mCommunicationChannel.reset(new LLCommunicationChannel("Communication", "Visible")); @@ -6016,6 +6023,11 @@ void LLViewerWindow::checkSettings() // We want to update the resolution AFTER the states getting refreshed not before. if (mResDirty) { + LLCoordWindow size; + mWindow->getSize(&size); + mWindowRectRaw.set(0, size.mY, size.mX, 0); + mWindowRectScaled.set(0, ll_round((F32)size.mY / mDisplayScale.mV[VY]), ll_round((F32)size.mX / mDisplayScale.mV[VX]), 0); + reshape(getWindowWidthRaw(), getWindowHeightRaw()); mResDirty = false; } diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index ec28a3fc4a..c748f051dd 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -66,6 +66,7 @@ class LLWindow; class LLRootView; class LLWindowListener; class LLViewerWindowListener; +class LLStatsListener; class LLVOPartGroup; class LLPopupView; class LLCubeMap; @@ -230,7 +231,7 @@ public: /*virtual*/ void handleWindowUnblock(LLWindow *window); /*virtual*/ void handleDataCopy(LLWindow *window, S32 data_type, void *data); /*virtual*/ bool handleTimerEvent(LLWindow *window); - /*virtual*/ bool handleDeviceChange(LLWindow *window); + /*virtual*/ bool handleDeviceChange(LLWindow *window, const std::string& change_type); /*virtual*/ bool handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height); /*virtual*/ bool handleDisplayChanged(); /*virtual*/ bool handleWindowDidChangeScreen(LLWindow *window); @@ -563,6 +564,7 @@ private: std::unique_ptr<LLWindowListener> mWindowListener; std::unique_ptr<LLViewerWindowListener> mViewerWindowListener; + std::unique_ptr<LLStatsListener> mStatsListener; // Object temporarily hovered over while dragging LLPointer<LLViewerObject> mDragHoveredObject; diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index f0fa1d782e..0bdcb535a8 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -6111,6 +6111,7 @@ const LLUUID& LLVOAvatar::getStepSound() const //----------------------------------------------------------------------------- void LLVOAvatar::processAnimationStateChanges() { + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; if ( isAnyAnimationSignaled(AGENT_WALK_ANIMS, NUM_AGENT_WALK_ANIMS) ) { startMotion(ANIM_AGENT_WALK_ADJUST); @@ -7702,7 +7703,8 @@ const LLViewerJointAttachment *LLVOAvatar::attachObject(LLViewerObject *viewer_o updateAttachmentOverrides(); } - updateVisualComplexity(); + // Inform complexity logic to do partial update. + markAttachmentComplexityDirty(viewer_object->getID()); if (viewer_object->isSelected()) { @@ -8006,7 +8008,7 @@ bool LLVOAvatar::detachObject(LLViewerObject *viewer_object) if (attachment->isObjectAttached(viewer_object)) { - updateVisualComplexity(); + markAttachmentComplexityDirty(viewer_object->getID(), true); bool is_animated_object = viewer_object->isAnimatedObject(); cleanupAttachedMesh(viewer_object); @@ -8433,6 +8435,7 @@ void LLVOAvatar::updateRezzedStatusTimers(S32 rez_status) selfStopPhase("wear_inventory_category", false); selfStopPhase("process_initial_wearables_update", false); + // Start a complexity update. updateVisualComplexity(); } } @@ -8708,6 +8711,10 @@ bool LLVOAvatar::processFullyLoadedChange(bool loading) if (changed && isSelf()) { + // Agent's own avatar doesn't track bakes the same way as other avatars. + // So just update here, on cloud removal. + markBodyPartsComplexityDirty(); + // to know about outfit switching LLAvatarRenderNotifier::getInstance()->updateNotificationState(); } @@ -8724,7 +8731,7 @@ bool LLVOAvatar::processFullyLoadedChange(bool loading) bool LLVOAvatar::isFullyLoaded() const { - return (mRenderUnloadedAvatar || mFullyLoaded); + return (mRenderUnloadedAvatar && !isSelf()) || mFullyLoaded; } bool LLVOAvatar::hasFirstFullAttachmentData() const @@ -9368,6 +9375,7 @@ void LLVOAvatar::releaseComponentTextures() { // Regression case of messaging system. Expected 21 textures, received 20. last texture is not valid so set to default setTETexture(TEX_HAIR_BAKED, IMG_DEFAULT_AVATAR); + markBodyPartsComplexityDirty(); } } @@ -9385,6 +9393,7 @@ void LLVOAvatar::releaseComponentTextures() { const U8 te = (ETextureIndex)bakedDicEntry->mLocalTextures[texture]; setTETexture(te, IMG_DEFAULT_AVATAR); + markBodyPartsComplexityDirty(); } } } @@ -10270,6 +10279,7 @@ void LLVOAvatar::onBakedTextureMasksLoaded( bool success, LLViewerFetchedTexture LL_INFOS() << "unexpected image id: " << id << LL_ENDL; } self->dirtyMesh(); + self->markBodyPartsComplexityDirty(); } else { @@ -10302,6 +10312,10 @@ void LLVOAvatar::onInitialBakedTextureLoaded( bool success, LLViewerFetchedTextu } if (final || !success ) { + if (selfp) + { + selfp->markBodyPartsComplexityDirty(); + } delete avatar_idp; } } @@ -10324,6 +10338,7 @@ void LLVOAvatar::onBakedTextureLoaded(bool success, if (selfp && !success) { selfp->removeMissingBakedTextures(); + selfp->markBodyPartsComplexityDirty(); } if( final || !success ) @@ -10334,6 +10349,7 @@ void LLVOAvatar::onBakedTextureLoaded(bool success, if( selfp && success && final ) { selfp->useBakedTexture( id ); + selfp->markBodyPartsComplexityDirty(); } } @@ -11284,10 +11300,384 @@ void LLVOAvatar::idleUpdateDebugInfo() void LLVOAvatar::updateVisualComplexity() { LL_DEBUGS("AvatarRender") << "avatar " << getID() << " appearance changed" << LL_ENDL; - // Set the cache time to in the past so it's updated ASAP + // Trigger cache recalculation on next idle update. + // Will recalculate stale, missing data and control avatar. + mVisualComplexityStale = true; +} + +void LLVOAvatar::calculateAttachmentComplexity(LLViewerObject* attached_object, + const F32 max_attachment_complexity, + ComplexityComponent& cache) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + cache.reset(); + + if (!attached_object + || attached_object->isDead()) + { + return; + } + + accountRenderComplexityForObject( + attached_object, + max_attachment_complexity, + cache.textures, + cache.render_cost, + cache.triangle_count, + cache.est_triangle_count, + cache.surface_area, + cache.hud_complexity, + cache.object_complexity + ); + + cache.last_update_time = LLFrameTimer::getTotalSeconds(); + cache.needs_update = false; +} + +void LLVOAvatar::calculateBodyPartsComplexity(ComplexityComponent& cache) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + cache.reset(); + + // Body parts have a fixed cost + // This represents the base avatar mesh (eyes, hair, shape, skin, etc.) + cache.render_cost = calculateBodyPartsComplexity(); + + // For more accurate body part complexity, could enumerate mesh LODs here + // For now, using a constant cost as in the original implementation + + cache.last_update_time = LLFrameTimer::getTotalSeconds(); + cache.needs_update = false; +} + +bool LLVOAvatar::shouldUpdateComplexityComponent(const ComplexityComponent& component) const +{ + if (component.needs_update) + { + return true; + } + + constexpr F32 CACHE_LIFETIME_SECONDS = 30.0; // Todo: should be indefinite, until something actually changes + F64 current_time = LLFrameTimer::getTotalSeconds(); + return (current_time - component.last_update_time) > CACHE_LIFETIME_SECONDS; +} + +bool LLVOAvatar::calculateControlAvatarComplexity(ComplexityComponent& cache, const F32 max_attachment_complexity) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + cache.reset(); + + if (!isControlAvatar()) + { + return false; + } + + LLControlAvatar* control_av = dynamic_cast<LLControlAvatar*>(this); + if (!control_av) + { + return false; + } + + LLVOVolume* volp = control_av->mRootVolp; + if (!volp || volp->isAttachment()) + { + return false; + } + + accountRenderComplexityForObject( + volp, + max_attachment_complexity, + cache.textures, + cache.render_cost, + cache.triangle_count, + cache.est_triangle_count, + cache.surface_area, + cache.hud_complexity, + cache.object_complexity + ); + + // todo: store 'expires' time instead or make it indefinite? + cache.last_update_time = LLFrameTimer::getTotalSeconds(); + cache.needs_update = false; + + return true; +} + +void LLVOAvatar::accumulateComplexityComponent(const ComplexityComponent& component, + U32& total_cost, + hud_complexity_list_t& hud_list, + object_complexity_list_t& object_list) +{ + total_cost += component.render_cost; + mAttachmentSurfaceArea += component.surface_area; + mAttachmentVisibleTriangleCount += component.triangle_count; + mAttachmentEstTriangleCount += component.est_triangle_count; + + // Add HUD/object complexity info if present + if (component.hud_complexity.objectId.notNull()) + { + hud_list.push_back(component.hud_complexity); + } + if (component.object_complexity.objectId.notNull()) + { + object_list.push_back(component.object_complexity); + } +} + +void LLVOAvatar::markAttachmentComplexityDirty(const LLUUID& object_id, bool force_reset_attachment) +{ + // Mark the cache entry if it exists + complexity_cache_map_t::iterator it = mComplexityCache.find(object_id); + if (it != mComplexityCache.end()) + { + if (force_reset_attachment) + { + // Object was detached. + // Force reset it in case it lingers in gObjectList for some reason (ex: dropped to world). + it->second.reset(); + } + it->second.needs_update = true; + } + + // Launch update process if not already scheduled + // It will add any missing attachments. mVisualComplexityStale = true; } +void LLVOAvatar::markBodyPartsComplexityDirty() +{ + mBodyPartsComplexity.needs_update = true; + + // Launch update process if not already scheduled + mVisualComplexityStale = true; +} + +void LLVOAvatar::performPartialComplexityUpdate(const F32 max_attachment_complexity) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + // Update any attachments marked as dirty + // Todo: might want to limit time or count here and defer the rest + // till next run. In such a case will need to make sure + // mVisualComplexityStale remains true. + + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (!attachment || !attachment->getValid()) + { + continue; + } + + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + if (attached_object && !attached_object->isDead()) + { + LLUUID object_id = attached_object->getID(); + ComplexityComponent& cache = mComplexityCache[object_id]; + + // Update if cache is stale or a new entry. + if (shouldUpdateComplexityComponent(cache)) + { + calculateAttachmentComplexity(attached_object, max_attachment_complexity, cache); + } + } + } + } + + // Update body parts if stale + if (shouldUpdateComplexityComponent(mBodyPartsComplexity)) + { + calculateBodyPartsComplexity(mBodyPartsComplexity); + } +} + +// Calculations for mVisualComplexity value +// Call rate is flexible, can be once in 20, can be once in 200 frames, +// depends on priority and known cost of an avatar in question. +void LLVOAvatar::calculateUpdateRenderComplexity() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + // **************************************************************** + // This calculation should not be modified by third party viewers, + // since it is used to limit rendering and should be uniform for + // everyone. If you have suggested improvements, submit them to + // the official viewer for consideration. + // **************************************************************** + + if (!mVisualComplexityStale) + { + return; + } + + // Get the attachment complexity limit + static LLCachedControl<F32> max_complexity_setting(gSavedSettings, "MaxAttachmentComplexity"); + F32 max_attachment_complexity = max_complexity_setting; + max_attachment_complexity = llmax(max_attachment_complexity, DEFAULT_MAX_ATTACHMENT_COMPLEXITY); + + // Update complexity for any dirty attachments or body parts. + // + // Todo: Limit this by time or count and continue later as + // doing everything in one go can be very expensive (multiple ms) + // Note that calculateUpdateRenderComplexity() can be launched once + // per 200 frames. Limiting it by time or count runs the risk of + // already checked attachments getting stale on last_update_time, + // thus function will keep running indefinetely. + performPartialComplexityUpdate(max_attachment_complexity); + + // Reset per-run counters + mAttachmentSurfaceArea = 0.f; + mAttachmentVisibleTriangleCount = 0; + mAttachmentEstTriangleCount = 0.f; + + U32 total_cost = 0; + hud_complexity_list_t hud_complexity_list; + object_complexity_list_t object_complexity_list; + + // Calculate and accumulate control avatar complexity if applicable + // For now this is on each run. + // Todo: See if mControlAvatarComplexity.needs_update is applicable here. + if (calculateControlAvatarComplexity(mControlAvatarComplexity, max_attachment_complexity)) + { + accumulateComplexityComponent( + mControlAvatarComplexity, + total_cost, + hud_complexity_list, + object_complexity_list); + } + + // Accumulate body parts complexity + accumulateComplexityComponent(mBodyPartsComplexity, total_cost, hud_complexity_list, object_complexity_list); + + // Accumulate all attachment complexity from cache + // Clean up cache entries for attachments that no longer exist + std::vector<LLUUID> to_remove; + + for (complexity_cache_map_t::iterator cache_iter = mComplexityCache.begin(); + cache_iter != mComplexityCache.end(); ++cache_iter) + { + const LLUUID& object_id = cache_iter->first; + + // Verify object still exists + LLViewerObject* obj = gObjectList.findObject(object_id); + if (!obj + || obj->isDead() + || !obj->isAttachment()) + { + to_remove.push_back(object_id); + continue; + } + + // Accumulate this attachment's complexity + accumulateComplexityComponent(cache_iter->second, total_cost, + hud_complexity_list, object_complexity_list); + } + + // Remove stale cache entries + for (std::vector<LLUUID>::iterator it = to_remove.begin(); it != to_remove.end(); ++it) + { + mComplexityCache.erase(*it); + } + + if (total_cost != mVisualComplexity) + { + LL_DEBUGS("AvatarRender") << "Avatar " << getID() + << " complexity updated was " << mVisualComplexity << " now " << total_cost + << " reported " << mReportedVisualComplexity + << LL_ENDL; + } + else + { + LL_DEBUGS("AvatarRender") << "Avatar " << getID() + << " complexity updated no change " << mVisualComplexity + << " reported " << mReportedVisualComplexity + << LL_ENDL; + } + + // Store results + mVisualComplexity = total_cost; + + // Call the reporting function with the aggregated lists + processComplexityCostChange(hud_complexity_list, object_complexity_list); + + // Stop processing until something changes + mVisualComplexityStale = false; +} + +U32 LLVOAvatar::calculateBodyPartsComplexity() +{ + constexpr U32 COMPLEXITY_BODY_PART_COST = 200; + U32 cost = 0; + for (U8 baked_index = 0; baked_index < BAKED_NUM_INDICES; baked_index++) + { + const LLAvatarAppearanceDictionary::BakedEntry* baked_dict + = LLAvatarAppearance::getDictionary()->getBakedTexture((EBakedTextureIndex)baked_index); + ETextureIndex tex_index = baked_dict->mTextureIndex; + if ((tex_index != TEX_SKIRT_BAKED) || (isWearingWearableType(LLWearableType::WT_SKIRT))) + { + // Same as isTextureVisible(), but doesn't account for isSelf to ensure identical numbers for all avatars + if (isIndexLocalTexture(tex_index)) + { + if (isTextureDefined(tex_index, 0)) + { + cost += COMPLEXITY_BODY_PART_COST; + } + } + else + { + // baked textures can use TE images directly + if (isTextureDefined(tex_index) + && (getTEImage(tex_index)->getID() != IMG_INVISIBLE || LLDrawPoolAlpha::sShowDebugAlpha)) + { + cost += COMPLEXITY_BODY_PART_COST; + } + } + } + } + LL_DEBUGS("ARCdetail") << "Avatar body parts complexity: " << cost << LL_ENDL; + return cost; +} + +void LLVOAvatar::processComplexityCostChange(const hud_complexity_list_t &hud_complexity_list, const object_complexity_list_t &object_complexity_list) +{ + static LLCachedControl<U32> show_my_complexity_changes(gSavedSettings, "ShowMyComplexityChanges", 20); + + if (isSelf() && show_my_complexity_changes) + { + // Avatar complexity + LLAvatarRenderNotifier::getInstance()->updateNotificationAgent(mVisualComplexity); + LLAvatarRenderNotifier::getInstance()->setObjectComplexityList(object_complexity_list); + // HUD complexity + LLHUDRenderNotifier::getInstance()->updateNotificationHUD(hud_complexity_list); + } + + //schedule an update to ART next frame if needed + if (LLPerfStats::tunables.userAutoTuneEnabled && + LLPerfStats::tunables.userFPSTuningStrategy != LLPerfStats::TUNE_SCENE_ONLY && + !isVisuallyMuted()) + { + const LLUUID id = getID(); // <== use id to make sure this avatar didn't get deleted between frames + LL::WorkQueue::getInstance("mainloop")->post([id]() + { + LLViewerObject* obj = gObjectList.findObject(id); + if (obj + && !obj->isDead() + && obj->isAvatar() + && obj->mDrawable) + { + LLVOAvatar* avatar = (LLVOAvatar*)obj; + gPipeline.profileAvatar(avatar); + } + }); + } +} // Account for the complexity of a single top-level object associated // with an avatar. This will be either an attached object or an animated @@ -11297,15 +11687,18 @@ void LLVOAvatar::accountRenderComplexityForObject( const F32 max_attachment_complexity, LLVOVolume::texture_cost_t& textures, U32& cost, - hud_complexity_list_t& hud_complexity_list, - object_complexity_list_t& object_complexity_list) + U32& visible_triangle_count, + F32& est_triangle_count, + F32& surface_area, + LLHUDComplexity& hud_object_complexity, + LLObjectComplexity& object_complexity) { LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; if (attached_object && !attached_object->isHUDAttachment()) { - mAttachmentVisibleTriangleCount += attached_object->recursiveGetTriangleCount(); - mAttachmentEstTriangleCount += attached_object->recursiveGetEstTrianglesMax(); - mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea(); + visible_triangle_count += attached_object->recursiveGetTriangleCount(); + est_triangle_count += attached_object->recursiveGetEstTrianglesMax(); + surface_area += attached_object->recursiveGetScaledSurfaceArea(); textures.clear(); const LLDrawable* drawable = attached_object->mDrawable; @@ -11360,11 +11753,9 @@ void LLVOAvatar::accountRenderComplexityForObject( if (isSelf()) { - LLObjectComplexity object_complexity; object_complexity.objectName = attached_object->getAttachmentItemName(); object_complexity.objectId = attached_object->getAttachmentItemID(); object_complexity.objectCost = (U32)attachment_total_cost; - object_complexity_list.push_back(object_complexity); } } } @@ -11376,13 +11767,12 @@ void LLVOAvatar::accountRenderComplexityForObject( && attached_object->mDrawable) { textures.clear(); - mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea(); + surface_area += attached_object->recursiveGetScaledSurfaceArea(); const LLVOVolume* volume = attached_object->mDrawable->getVOVolume(); if (volume) { bool is_rigged_mesh = volume->isRiggedMeshFast(); - LLHUDComplexity hud_object_complexity; hud_object_complexity.objectName = attached_object->getAttachmentItemName(); hud_object_complexity.objectId = attached_object->getAttachmentItemID(); std::string joint_name; @@ -11437,145 +11827,6 @@ void LLVOAvatar::accountRenderComplexityForObject( } } } - hud_complexity_list.push_back(hud_object_complexity); - } - } -} - -// Calculations for mVisualComplexity value -void LLVOAvatar::calculateUpdateRenderComplexity() -{ - /***************************************************************** - * This calculation should not be modified by third party viewers, - * since it is used to limit rendering and should be uniform for - * everyone. If you have suggested improvements, submit them to - * the official viewer for consideration. - *****************************************************************/ - if (mVisualComplexityStale) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - static const U32 COMPLEXITY_BODY_PART_COST = 200; - static LLCachedControl<F32> max_complexity_setting(gSavedSettings, "MaxAttachmentComplexity"); - F32 max_attachment_complexity = max_complexity_setting; - max_attachment_complexity = llmax(max_attachment_complexity, DEFAULT_MAX_ATTACHMENT_COMPLEXITY); - - // Diagnostic list of all textures on our avatar - static std::unordered_set<const LLViewerTexture*> all_textures; - - U32 cost = VISUAL_COMPLEXITY_UNKNOWN; - LLVOVolume::texture_cost_t textures; - hud_complexity_list_t hud_complexity_list; - object_complexity_list_t object_complexity_list; - - for (U8 baked_index = 0; baked_index < BAKED_NUM_INDICES; baked_index++) - { - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict - = LLAvatarAppearance::getDictionary()->getBakedTexture((EBakedTextureIndex)baked_index); - ETextureIndex tex_index = baked_dict->mTextureIndex; - if ((tex_index != TEX_SKIRT_BAKED) || (isWearingWearableType(LLWearableType::WT_SKIRT))) - { - // Same as isTextureVisible(), but doesn't account for isSelf to ensure identical numbers for all avatars - if (isIndexLocalTexture(tex_index)) - { - if (isTextureDefined(tex_index, 0)) - { - cost += COMPLEXITY_BODY_PART_COST; - } - } - else - { - // baked textures can use TE images directly - if (isTextureDefined(tex_index) - && (getTEImage(tex_index)->getID() != IMG_INVISIBLE || LLDrawPoolAlpha::sShowDebugAlpha)) - { - cost += COMPLEXITY_BODY_PART_COST; - } - } - } - } - LL_DEBUGS("ARCdetail") << "Avatar body parts complexity: " << cost << LL_ENDL; - - mAttachmentVisibleTriangleCount = 0; - mAttachmentEstTriangleCount = 0.f; - mAttachmentSurfaceArea = 0.f; - - // A standalone animated object needs to be accounted for - // using its associated volume. Attached animated objects - // will be covered by the subsequent loop over attachments. - LLControlAvatar *control_av = dynamic_cast<LLControlAvatar*>(this); - if (control_av) - { - LLVOVolume *volp = control_av->mRootVolp; - if (volp && !volp->isAttachment()) - { - accountRenderComplexityForObject(volp, max_attachment_complexity, - textures, cost, hud_complexity_list, object_complexity_list); - } - } - - // Account for complexity of all attachments. - for (attachment_map_t::const_iterator attachment_point = mAttachmentPoints.begin(); - attachment_point != mAttachmentPoints.end(); - ++attachment_point) - { - LLViewerJointAttachment* attachment = attachment_point->second; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* attached_object = attachment_iter->get(); - accountRenderComplexityForObject(attached_object, max_attachment_complexity, - textures, cost, hud_complexity_list, object_complexity_list); - } - } - - if ( cost != mVisualComplexity ) - { - LL_DEBUGS("AvatarRender") << "Avatar "<< getID() - << " complexity updated was " << mVisualComplexity << " now " << cost - << " reported " << mReportedVisualComplexity - << LL_ENDL; - } - else - { - LL_DEBUGS("AvatarRender") << "Avatar "<< getID() - << " complexity updated no change " << mVisualComplexity - << " reported " << mReportedVisualComplexity - << LL_ENDL; - } - mVisualComplexity = cost; - mVisualComplexityStale = false; - - static LLCachedControl<U32> show_my_complexity_changes(gSavedSettings, "ShowMyComplexityChanges", 20); - - if (isSelf() && show_my_complexity_changes) - { - // Avatar complexity - LLAvatarRenderNotifier::getInstance()->updateNotificationAgent(mVisualComplexity); - LLAvatarRenderNotifier::getInstance()->setObjectComplexityList(object_complexity_list); - // HUD complexity - LLHUDRenderNotifier::getInstance()->updateNotificationHUD(hud_complexity_list); - } - - //schedule an update to ART next frame if needed - if (LLPerfStats::tunables.userAutoTuneEnabled && - LLPerfStats::tunables.userFPSTuningStrategy != LLPerfStats::TUNE_SCENE_ONLY && - !isVisuallyMuted()) - { - const LLUUID id = getID(); // <== use id to make sure this avatar didn't get deleted between frames - LL::WorkQueue::getInstance("mainloop")->post([id]() - { - LLViewerObject* obj = gObjectList.findObject(id); - if (obj - && !obj->isDead() - && obj->isAvatar() - && obj->mDrawable) - { - LLVOAvatar* avatar = (LLVOAvatar*)obj; - gPipeline.profileAvatar(avatar); - } - }); } } } diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 0c3b82c886..cc2a083794 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -309,11 +309,17 @@ public: const F32 max_attachment_complexity, LLVOVolume::texture_cost_t& textures, U32& cost, - hud_complexity_list_t& hud_complexity_list, - object_complexity_list_t& object_complexity_list); + U32& visible_triangle_count, + F32& est_triangle_count, + F32& surface_area, + LLHUDComplexity& hud_object_complexity, + LLObjectComplexity& object_complexity); void calculateUpdateRenderComplexity(); static const U32 VISUAL_COMPLEXITY_UNKNOWN; void updateVisualComplexity(); + // Mark that an attachment needs complexity recalculation + void markAttachmentComplexityDirty(const LLUUID& object_id, bool force_reset_attachment = false); + void markBodyPartsComplexityDirty(); void placeProfileQuery(); void readProfileQuery(S32 retries); @@ -588,12 +594,6 @@ private: // CPU render time in ms F32 mCPURenderTime = 0.f; - // the isTooComplex method uses these mutable values to avoid recalculating too frequently - // DEPRECATED -- obsolete avatar render cost values - mutable U32 mVisualComplexity; - mutable bool mVisualComplexityStale; - U32 mReportedVisualComplexity; // from other viewers through the simulator - mutable bool mCachedInMuteList; mutable F64 mCachedMuteListUpdateTime; mutable bool mCachedInBuddyList = false; @@ -602,6 +602,81 @@ private: VisualMuteSettings mVisuallyMuteSetting; // Always or never visually mute this AV //-------------------------------------------------------------------- + // Complexity calculation and caching + //-------------------------------------------------------------------- + +private: + // Structure to cache complexity metrics for individual attachments or components + struct ComplexityComponent + { + U32 render_cost; + U32 triangle_count; + F32 surface_area; + F32 est_triangle_count; + LLVOVolume::texture_cost_t textures; + F64 last_update_time; + bool needs_update; + + // For tracking HUD and object lists + LLHUDComplexity hud_complexity; + LLObjectComplexity object_complexity; + + ComplexityComponent() + : render_cost(0) + , triangle_count(0) + , surface_area(0.f) + , est_triangle_count(0.f) + , last_update_time(0.0) + , needs_update(true) + { + } + + void reset() + { + render_cost = 0; + triangle_count = 0; + surface_area = 0.f; + est_triangle_count = 0.f; + textures.clear(); + hud_complexity.reset(); + object_complexity.reset(); + needs_update = true; + } + }; + + void calculateAttachmentComplexity(LLViewerObject* attached_object, + const F32 max_attachment_complexity, + ComplexityComponent& cache); + void calculateBodyPartsComplexity(ComplexityComponent& cache); + U32 calculateBodyPartsComplexity(); + + // return true, if a valid control avatar. + bool calculateControlAvatarComplexity(ComplexityComponent& cache, const F32 max_attachment_complexity); + + void accumulateComplexityComponent(const ComplexityComponent& component, + U32& total_cost, + hud_complexity_list_t& hud_list, + object_complexity_list_t& object_list); + + bool shouldUpdateComplexityComponent(const ComplexityComponent& component) const; + void performPartialComplexityUpdate(const F32 max_attachment_complexity); + + void processComplexityCostChange(const hud_complexity_list_t &hud_complexity_list, const object_complexity_list_t &object_complexity_list); + + // Todo: probably safe to store by local instead of global id + // since they should be unique to this avatar, but local id might be not known. + typedef std::map<LLUUID, ComplexityComponent> complexity_cache_map_t; + complexity_cache_map_t mComplexityCache; // Cache per-attachment complexity + ComplexityComponent mBodyPartsComplexity; // Cache for body parts (mesh, eyes, hair, etc) + ComplexityComponent mControlAvatarComplexity; // Cache for animated object control avatar + + // the isTooComplex method uses these mutable values to avoid recalculating too frequently + // DEPRECATED -- obsolete avatar render cost values + mutable U32 mVisualComplexity; + mutable bool mVisualComplexityStale; + U32 mReportedVisualComplexity; // from other viewers through the simulator + + //-------------------------------------------------------------------- // animated object status //-------------------------------------------------------------------- public: @@ -637,7 +712,6 @@ private: // Shadowing //-------------------------------------------------------------------- public: - void updateShadowFaces(); LLDrawable* mShadow; private: LLFace* mShadow0Facep; diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp index 5d456b1a19..e513a3813f 100644 --- a/indra/newview/llvocache.cpp +++ b/indra/newview/llvocache.cpp @@ -32,6 +32,7 @@ #include "lldrawable.h" #include "llviewerregion.h" #include "llagentcamera.h" +#include "llmemory.h" #include "llsdserialize.h" #include "llworld.h" // For LLWorld::getInstance() //static variables @@ -488,11 +489,12 @@ void LLVOCacheEntry::updateDebugSettings() static const F32 MIN_RADIUS = 1.0f; F32 draw_radius = gAgentCamera.mDrawDistance; - if (LLViewerTexture::isSystemMemoryCritical()) + const F32 mem_factor = LLMemory::getSystemMemoryBudgetFactor(); + if (mem_factor > 1.f) { // Factor is intended to go from 1.0 to 2.0 // For safety cap reduction at 50%, we don't want to go below half of draw distance - draw_radius = llmax(draw_radius / LLViewerTexture::getSystemMemoryBudgetFactor(), draw_radius / 2.f); + draw_radius = llmax(draw_radius / mem_factor, draw_radius / 2.f); } const F32 clamped_min_radius = llclamp((F32) min_radius, MIN_RADIUS, draw_radius); // [1, mDrawDistance] sNearRadius = MIN_RADIUS + ((clamped_min_radius - MIN_RADIUS) * adjust_factor); diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 4fd0a8e2b8..95b6bb4227 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -25,8 +25,10 @@ */ #include "llvoiceclient.h" -#include "llvoicevivox.h" -#if !__FreeBSD__ && !_M_ARM64 +#if __FreeBSD__ || _M_ARM64 +#include "llviewerregion.h" +#include "llmutelist.h" +#else #include "llvoicewebrtc.h" #endif #include "llviewernetwork.h" @@ -119,17 +121,13 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv LLVoiceModuleInterface *getVoiceModule(const std::string &voice_server_type) { - if (voice_server_type == VIVOX_VOICE_SERVER_TYPE || voice_server_type.empty()) - { - return (LLVoiceModuleInterface *) LLVivoxVoiceClient::getInstance(); - } #if !__FreeBSD__ && !_M_ARM64 - else if (voice_server_type == WEBRTC_VOICE_SERVER_TYPE) + if (voice_server_type == WEBRTC_VOICE_SERVER_TYPE) { return (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance(); } -#endif else +#endif { LLNotificationsUtil::add("VoiceVersionMismatch"); return nullptr; @@ -173,7 +171,6 @@ void LLVoiceClient::init(LLPumpIO *pump) #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->init(pump); #endif - LLVivoxVoiceClient::getInstance()->init(pump); } void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) @@ -186,16 +183,17 @@ void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &age #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->userAuthorized(user_id, agentID); #endif - LLVivoxVoiceClient::getInstance()->userAuthorized(user_id, agentID); } void LLVoiceClient::handleSimulatorFeaturesReceived(const LLSD &simulatorFeatures) { std::string voiceServerType = simulatorFeatures["VoiceServerType"].asString(); +#if !__FreeBSD__ && !_M_ARM64 if (voiceServerType.empty()) { - voiceServerType = VIVOX_VOICE_SERVER_TYPE; + voiceServerType = WEBRTC_VOICE_SERVER_TYPE; } +#endif if (mSpatialVoiceModule && !mNonSpatialVoiceModule) { @@ -298,21 +296,16 @@ void LLVoiceClient::setHidden(bool hidden) #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->setHidden(hidden); #endif - LLVivoxVoiceClient::getInstance()->setHidden(hidden); } void LLVoiceClient::terminate() { #if !__FreeBSD__ && !_M_ARM64 - if (LLVivoxVoiceClient::instanceExists()) + if (LLWebRTCVoiceClient::instanceExists()) { LLWebRTCVoiceClient::getInstance()->terminate(); } #endif - if (LLVivoxVoiceClient::instanceExists()) - { - LLVivoxVoiceClient::getInstance()->terminate(); - } mSpatialVoiceModule = NULL; m_servicePump = NULL; @@ -350,7 +343,6 @@ void LLVoiceClient::updateSettings() #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->updateSettings(); #endif - LLVivoxVoiceClient::getInstance()->updateSettings(); } //-------------------------------------------------- @@ -361,7 +353,6 @@ void LLVoiceClient::tuningStart() #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->tuningStart(); #endif - LLVivoxVoiceClient::getInstance()->tuningStart(); } void LLVoiceClient::tuningStop() @@ -369,15 +360,14 @@ void LLVoiceClient::tuningStop() #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->tuningStop(); #endif - LLVivoxVoiceClient::getInstance()->tuningStop(); } bool LLVoiceClient::inTuningMode() { -#if !__FreeBSD__ && !_M_ARM64 - return LLWebRTCVoiceClient::getInstance()->inTuningMode(); +#if __FreeBSD__ || _M_ARM64 + return false; #else - return LLVivoxVoiceClient::getInstance()->inTuningMode(); + return LLWebRTCVoiceClient::getInstance()->inTuningMode(); #endif } @@ -385,8 +375,6 @@ void LLVoiceClient::tuningSetMicVolume(float volume) { #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->tuningSetMicVolume(volume); -#else - LLVivoxVoiceClient::getInstance()->tuningSetMicVolume(volume); #endif } @@ -394,17 +382,15 @@ void LLVoiceClient::tuningSetSpeakerVolume(float volume) { #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->tuningSetSpeakerVolume(volume); -#else - LLVivoxVoiceClient::getInstance()->tuningSetSpeakerVolume(volume); #endif } float LLVoiceClient::tuningGetEnergy(void) { -#if !__FreeBSD__ && !_M_ARM64 - return LLWebRTCVoiceClient::getInstance()->tuningGetEnergy(); +#if __FreeBSD__ || _M_ARM64 + return 0.0f; #else - return LLVivoxVoiceClient::getInstance()->tuningGetEnergy(); + return LLWebRTCVoiceClient::getInstance()->tuningGetEnergy(); #endif } @@ -413,19 +399,19 @@ float LLVoiceClient::tuningGetEnergy(void) bool LLVoiceClient::deviceSettingsAvailable() { -#if !__FreeBSD__ && !_M_ARM64 - return LLWebRTCVoiceClient::getInstance()->deviceSettingsAvailable(); +#if __FreeBSD__ || _M_ARM64 + return false; #else - return LLVivoxVoiceClient::getInstance()->deviceSettingsAvailable(); + return LLWebRTCVoiceClient::getInstance()->deviceSettingsAvailable(); #endif } bool LLVoiceClient::deviceSettingsUpdated() { -#if !__FreeBSD__ && !_M_ARM64 - return LLWebRTCVoiceClient::getInstance()->deviceSettingsUpdated(); +#if __FreeBSD__ || _M_ARM64 + return false; #else - return LLVivoxVoiceClient::getInstance()->deviceSettingsUpdated(); + return LLWebRTCVoiceClient::getInstance()->deviceSettingsUpdated(); #endif } @@ -433,14 +419,11 @@ void LLVoiceClient::refreshDeviceLists(bool clearCurrentList) { #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->refreshDeviceLists(clearCurrentList); -#else - LLVivoxVoiceClient::getInstance()->refreshDeviceLists(clearCurrentList); #endif } void LLVoiceClient::setCaptureDevice(const std::string& name) { - LLVivoxVoiceClient::getInstance()->setCaptureDevice(name); #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->setCaptureDevice(name); #endif @@ -448,7 +431,6 @@ void LLVoiceClient::setCaptureDevice(const std::string& name) void LLVoiceClient::setRenderDevice(const std::string& name) { - LLVivoxVoiceClient::getInstance()->setRenderDevice(name); #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->setRenderDevice(name); #endif @@ -456,20 +438,22 @@ void LLVoiceClient::setRenderDevice(const std::string& name) const LLVoiceDeviceList& LLVoiceClient::getCaptureDevices() { -#if !__FreeBSD__ && !_M_ARM64 - return LLWebRTCVoiceClient::getInstance()->getCaptureDevices(); +#if __FreeBSD__ || _M_ARM64 + static LLVoiceDeviceList devices; + return devices; #else - return LLVivoxVoiceClient::getInstance()->getCaptureDevices(); + return LLWebRTCVoiceClient::getInstance()->getCaptureDevices(); #endif } const LLVoiceDeviceList& LLVoiceClient::getRenderDevices() { -#if !__FreeBSD__ && !_M_ARM64 - return LLWebRTCVoiceClient::getInstance()->getRenderDevices(); +#if __FreeBSD__ || _M_ARM64 + static LLVoiceDeviceList devices; + return devices; #else - return LLVivoxVoiceClient::getInstance()->getRenderDevices(); + return LLWebRTCVoiceClient::getInstance()->getRenderDevices(); #endif } @@ -482,17 +466,15 @@ void LLVoiceClient::getParticipantList(std::set<LLUUID> &participants) const #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->getParticipantList(participants); #endif - LLVivoxVoiceClient::getInstance()->getParticipantList(participants); } bool LLVoiceClient::isParticipant(const LLUUID &speaker_id) const { -#if !__FreeBSD__ && !_M_ARM64 - return LLWebRTCVoiceClient::getInstance()->isParticipant(speaker_id) || +#if __FreeBSD__ || _M_ARM64 + return false; #else - return + return LLWebRTCVoiceClient::getInstance()->isParticipant(speaker_id); #endif - LLVivoxVoiceClient::getInstance()->isParticipant(speaker_id); } @@ -584,22 +566,20 @@ void LLVoiceClient::activateSpatialChannel(bool activate) bool LLVoiceClient::isCurrentChannel(const LLSD& channelInfo) { -#if !__FreeBSD__ && !_M_ARM64 - return LLWebRTCVoiceClient::getInstance()->isCurrentChannel(channelInfo) || +#if __FreeBSD__ || _M_ARM64 + return false; #else - return + return LLWebRTCVoiceClient::getInstance()->isCurrentChannel(channelInfo); #endif - LLVivoxVoiceClient::getInstance()->isCurrentChannel(channelInfo); } bool LLVoiceClient::compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) { -#if !__FreeBSD__ && !_M_ARM64 - return LLWebRTCVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2) || +#if __FreeBSD__ || _M_ARM64 + return false; #else - return + return LLWebRTCVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2); #endif - LLVivoxVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2); } LLVoiceP2PIncomingCallInterfacePtr LLVoiceClient::getIncomingCallInterface(const LLSD& voice_call_info) @@ -624,12 +604,14 @@ LLVoiceP2POutgoingCallInterface *LLVoiceClient::getOutgoingCallInterface(const L LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion(); voice_server_type = versionInfo.internalVoiceServerType; } +#if !__FreeBSD__ && !_M_ARM64 if (voiceChannelInfo.has("voice_server_type") && voiceChannelInfo["voice_server_type"] != voice_server_type) { // there's a mismatch between what the peer is offering and what our server - // can handle, so downgrade to vivox - voice_server_type = VIVOX_VOICE_SERVER_TYPE; + // can handle, so default to webrtc + voice_server_type = WEBRTC_VOICE_SERVER_TYPE; } +#endif LLVoiceModuleInterface *module = getVoiceModule(voice_server_type); return dynamic_cast<LLVoiceP2POutgoingCallInterface *>(module); } @@ -643,7 +625,6 @@ void LLVoiceClient::setVoiceVolume(F32 volume) #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->setVoiceVolume(volume); #endif - LLVivoxVoiceClient::getInstance()->setVoiceVolume(volume); } void LLVoiceClient::setMicGain(F32 gain) @@ -651,7 +632,6 @@ void LLVoiceClient::setMicGain(F32 gain) #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->setMicGain(gain); #endif - LLVivoxVoiceClient::getInstance()->setMicGain(gain); } @@ -703,10 +683,6 @@ void LLVoiceClient::setVoiceEnabled(bool enabled) LLWebRTCVoiceClient::getInstance()->setVoiceEnabled(enabled); } #endif - if (LLVivoxVoiceClient::instanceExists()) - { - LLVivoxVoiceClient::getInstance()->setVoiceEnabled(enabled); - } } void LLVoiceClient::updateMicMuteLogic() @@ -728,7 +704,6 @@ void LLVoiceClient::updateMicMuteLogic() #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->setMuteMic(new_mic_mute); #endif - LLVivoxVoiceClient::getInstance()->setMuteMic(new_mic_mute); } void LLVoiceClient::setMuteMic(bool muted) @@ -825,25 +800,20 @@ bool LLVoiceClient::getVoiceEnabled(const LLUUID& id) const std::string LLVoiceClient::getDisplayName(const LLUUID& id) const { -#if !__FreeBSD__ && !_M_ARM64 - std::string result = LLWebRTCVoiceClient::getInstance()->getDisplayName(id); - if (result.empty()) - { - result = LLVivoxVoiceClient::getInstance()->getDisplayName(id); - } - return result; +#if __FreeBSD__ || _M_ARM64 + std::string result; #else - return LLVivoxVoiceClient::getInstance()->getDisplayName(id); + std::string result = LLWebRTCVoiceClient::getInstance()->getDisplayName(id); #endif + return result; } bool LLVoiceClient::isVoiceWorking() const { -#if !__FreeBSD__ && !_M_ARM64 - return LLVivoxVoiceClient::getInstance()->isVoiceWorking() || - LLWebRTCVoiceClient::getInstance()->isVoiceWorking(); +#if __FreeBSD__ || _M_ARM64 + return false; #else - return LLVivoxVoiceClient::getInstance()->isVoiceWorking(); + return LLWebRTCVoiceClient::getInstance()->isVoiceWorking(); #endif } @@ -859,33 +829,30 @@ bool LLVoiceClient::isOnlineSIP(const LLUUID& id) bool LLVoiceClient::getIsSpeaking(const LLUUID& id) { -#if !__FreeBSD__ && !_M_ARM64 - return LLWebRTCVoiceClient::getInstance()->getIsSpeaking(id) || +#if __FreeBSD__ || _M_ARM64 + return false; #else - return + return LLWebRTCVoiceClient::getInstance()->getIsSpeaking(id); #endif - LLVivoxVoiceClient::getInstance()->getIsSpeaking(id); } bool LLVoiceClient::getIsModeratorMuted(const LLUUID& id) { // don't bother worrying about p2p calls, as // p2p calls don't have mute. -#if !__FreeBSD__ && !_M_ARM64 - return LLWebRTCVoiceClient::getInstance()->getIsModeratorMuted(id) || +#if __FreeBSD__ || _M_ARM64 + return false; #else - return + return LLWebRTCVoiceClient::getInstance()->getIsModeratorMuted(id); #endif - LLVivoxVoiceClient::getInstance()->getIsModeratorMuted(id); } F32 LLVoiceClient::getCurrentPower(const LLUUID& id) { -#if !__FreeBSD__ && !_M_ARM64 - return std::fmax(LLVivoxVoiceClient::getInstance()->getCurrentPower(id), - LLWebRTCVoiceClient::getInstance()->getCurrentPower(id)); +#if __FreeBSD__ || _M_ARM64 + return 0.0f; #else - return LLVivoxVoiceClient::getInstance()->getCurrentPower(id); + return LLWebRTCVoiceClient::getInstance()->getCurrentPower(id); #endif } @@ -898,10 +865,10 @@ bool LLVoiceClient::getOnMuteList(const LLUUID& id) F32 LLVoiceClient::getUserVolume(const LLUUID& id) { -#if !__FreeBSD__ && !_M_ARM64 - return std::fmax(LLVivoxVoiceClient::getInstance()->getUserVolume(id), LLWebRTCVoiceClient::getInstance()->getUserVolume(id)); +#if __FreeBSD__ || _M_ARM64 + return 0.0f; #else - return LLVivoxVoiceClient::getInstance()->getUserVolume(id); + return LLWebRTCVoiceClient::getInstance()->getUserVolume(id); #endif } @@ -910,7 +877,6 @@ void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->setUserVolume(id, volume); #endif - LLVivoxVoiceClient::getInstance()->setUserVolume(id, volume); } //-------------------------------------------------- @@ -918,7 +884,6 @@ void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) { - LLVivoxVoiceClient::getInstance()->addObserver(observer); #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->addObserver(observer); #endif @@ -926,10 +891,6 @@ void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) { - if (LLVivoxVoiceClient::instanceExists()) - { - LLVivoxVoiceClient::getInstance()->removeObserver(observer); - } #if !__FreeBSD__ && !_M_ARM64 if (LLWebRTCVoiceClient::instanceExists()) { @@ -940,7 +901,6 @@ void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) void LLVoiceClient::addObserver(LLFriendObserver* observer) { - LLVivoxVoiceClient::getInstance()->addObserver(observer); #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->addObserver(observer); #endif @@ -948,10 +908,6 @@ void LLVoiceClient::addObserver(LLFriendObserver* observer) void LLVoiceClient::removeObserver(LLFriendObserver* observer) { - if (LLVivoxVoiceClient::instanceExists()) - { - LLVivoxVoiceClient::getInstance()->removeObserver(observer); - } #if !__FreeBSD__ && !_M_ARM64 if (LLWebRTCVoiceClient::instanceExists()) { @@ -962,7 +918,6 @@ void LLVoiceClient::removeObserver(LLFriendObserver* observer) void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) { - LLVivoxVoiceClient::getInstance()->addObserver(observer); #if !__FreeBSD__ && !_M_ARM64 LLWebRTCVoiceClient::getInstance()->addObserver(observer); #endif @@ -970,10 +925,6 @@ void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) { - if (LLVivoxVoiceClient::instanceExists()) - { - LLVivoxVoiceClient::getInstance()->removeObserver(observer); - } #if !__FreeBSD__ && !_M_ARM64 if (LLWebRTCVoiceClient::instanceExists()) { @@ -982,22 +933,6 @@ void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) #endif } -std::string LLVoiceClient::sipURIFromID(const LLUUID &id) const -{ - if (mNonSpatialVoiceModule) - { - return mNonSpatialVoiceModule->sipURIFromID(id); - } - else if (mSpatialVoiceModule) - { - return mSpatialVoiceModule->sipURIFromID(id); - } - else - { - return std::string(); - } -} - LLSD LLVoiceClient::getP2PChannelInfoTemplate(const LLUUID& id) const { if (mNonSpatialVoiceModule) @@ -1030,7 +965,7 @@ class LLViewerRequiredVoiceVersion : public LLHTTPNode const LLSD& context, const LLSD& input) const { - std::string voice_server_type = "vivox"; + std::string voice_server_type = "webrtc"; if (input.has("body") && input["body"].has("voice_server_type")) { voice_server_type = input["body"]["voice_server_type"].asString(); @@ -1038,17 +973,13 @@ class LLViewerRequiredVoiceVersion : public LLHTTPNode LLVoiceModuleInterface *voiceModule = NULL; - if (voice_server_type == "vivox" || voice_server_type.empty()) - { - voiceModule = (LLVoiceModuleInterface *) LLVivoxVoiceClient::getInstance(); - } #if !__FreeBSD__ && !_M_ARM64 - else if (voice_server_type == "webrtc") + if (voice_server_type == "webrtc" || voice_server_type.empty()) { voiceModule = (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance(); } -#endif else +#endif { LL_WARNS("Voice") << "Unknown voice server type " << voice_server_type << LL_ENDL; if (!sAlertedUser) diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h index 788ea3b3b3..70bce055f9 100644 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -106,7 +106,7 @@ struct LLVoiceVersionInfo /// @class LLVoiceP2POutgoingCallInterface /// @brief Outgoing call interface /// -/// For providers that support P2P signaling (vivox) +/// For providers that support P2P signaling ///////////////////////////////// class LLVoiceP2POutgoingCallInterface @@ -121,7 +121,7 @@ class LLVoiceP2POutgoingCallInterface /// @class LLVoiceP2PIncomingCallInterface /// @brief Incoming call interface /// -/// For providers that support P2P signaling (vivox) +/// For providers that support P2P signaling ///////////////////////////////// class LLVoiceP2PIncomingCallInterface { @@ -180,7 +180,6 @@ public: virtual bool deviceSettingsAvailable()=0; virtual bool deviceSettingsUpdated() = 0; - // Requery the vivox daemon for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed // (use this if you want to know when it's done). // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. @@ -280,7 +279,6 @@ public: virtual void removeObserver(LLVoiceClientParticipantObserver* observer)=0; //@} - virtual std::string sipURIFromID(const LLUUID &id) const=0; virtual LLSD getP2PChannelInfoTemplate(const LLUUID& id) const=0; //@} @@ -384,7 +382,7 @@ public: bool deviceSettingsAvailable(); bool deviceSettingsUpdated(); // returns true when the device list has been updated recently. - // Requery the vivox daemon for the current list of input/output devices. + // Requery the voice driver for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed // (use this if you want to know when it's done). // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. @@ -488,7 +486,6 @@ public: static void addObserver(LLVoiceClientParticipantObserver* observer); static void removeObserver(LLVoiceClientParticipantObserver* observer); - std::string sipURIFromID(const LLUUID &id) const; LLSD getP2PChannelInfoTemplate(const LLUUID& id) const; ////////////////////////// @@ -513,7 +510,7 @@ protected: LLVoiceModuleInterface* mSpatialVoiceModule; LLVoiceModuleInterface* mNonSpatialVoiceModule; - LLSD mSpatialCredentials; // used to store spatial credentials for vivox + LLSD mSpatialCredentials; // used to store spatial credentials for the voice subsystem // so they're available when the region voice // server is retrieved. LLPumpIO *m_servicePump; diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp deleted file mode 100644 index f32bdfb75c..0000000000 --- a/indra/newview/llvoicevivox.cpp +++ /dev/null @@ -1,7935 +0,0 @@ - /** - * @file LLVivoxVoiceClient.cpp - * @brief Implementation of LLVivoxVoiceClient class which is the interface to the voice client process. - * - * $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$ - */ - -#include "llviewerprecompiledheaders.h" -#include <algorithm> -#include "llvoicevivox.h" - -#include "llsdutil.h" - -// Linden library includes -#include "llavatarnamecache.h" -#include "llvoavatarself.h" -#include "llbufferstream.h" -#include "llfile.h" -#include "llmenugl.h" -#if 1 -# include "expat.h" -#else -# include "expat/expat.h" -#endif -#include "llcallbacklist.h" -#include "llviewerregion.h" -#include "llviewernetwork.h" // for gGridChoice -#include "llbase64.h" -#include "llviewercontrol.h" -#include "llappviewer.h" // for gDisconnected, gDisableVoice -#include "llprocess.h" - -// Viewer includes -#include "llmutelist.h" // to check for muted avatars -#include "llagent.h" -#include "llcachename.h" -#include "llimview.h" // for LLIMMgr -#include "llparcel.h" -#include "llviewerparcelmgr.h" -#include "llfirstuse.h" -#include "llspeakers.h" -#include "lltrans.h" -#include "llviewerwindow.h" -#include "llviewercamera.h" -#include "llversioninfo.h" - -#include "llviewernetwork.h" -#include "llnotificationsutil.h" - -#include "llcorehttputil.h" -#include "lleventfilter.h" - -#include "stringize.h" - -// for base64 decoding -#include "apr_base64.h" - -#define USE_SESSION_GROUPS 0 -#define VX_NULL_POSITION -2147483648.0 /*The Silence*/ - -extern LLMenuBarGL* gMenuBarView; -extern void handle_voice_morphing_subscribe(); - -const std::string VIVOX_VOICE_SERVER_TYPE = "vivox"; - -namespace { - const F32 VOLUME_SCALE_VIVOX = 0.01f; - - const F32 SPEAKING_TIMEOUT = 1.f; - - static const std::string VISIBLE_VOICE_SERVER_TYPE = "Vivox"; - - // Don't retry connecting to the daemon more frequently than this: - const F32 DAEMON_CONNECT_THROTTLE_SECONDS = 1.0f; - const int DAEMON_CONNECT_RETRY_MAX = 3; - - // Don't send positional updates more frequently than this: - const F32 UPDATE_THROTTLE_SECONDS = 0.5f; - - // Timeout for connection to Vivox - const F32 CONNECT_ATTEMPT_TIMEOUT = 300.0f; - const F32 CONNECT_DNS_TIMEOUT = 5.0f; - const int CONNECT_RETRY_MAX = 3; - - const F32 LOGIN_ATTEMPT_TIMEOUT = 30.0f; - const F32 LOGOUT_ATTEMPT_TIMEOUT = 5.0f; - const int LOGIN_RETRY_MAX = 3; - - const F32 PROVISION_RETRY_TIMEOUT = 2.0; - const int PROVISION_RETRY_MAX = 5; - - // Cosine of a "trivially" small angle - const F32 FOUR_DEGREES = 4.0f * (F_PI / 180.0f); - const F32 MINUSCULE_ANGLE_COS = (F32) cos(0.5f * FOUR_DEGREES); - - const F32 SESSION_JOIN_TIMEOUT = 30.0f; - - // Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine() - // which is treated as normal. The is the number of frames to wait for a channel join before giving up. This was changed - // from the original count of 50 for two reason. Modern PCs have higher frame rates and sometimes the SLVoice process - // backs up processing join requests. There is a log statement that records when channel joins take longer than 100 frames. - const int MAX_NORMAL_JOINING_SPATIAL_NUM = 1500; - - // How often to check for expired voice fonts in seconds - const F32 VOICE_FONT_EXPIRY_INTERVAL = 10.f; - // Time of day at which Vivox expires voice font subscriptions. - // Used to replace the time portion of received expiry timestamps. - static const std::string VOICE_FONT_EXPIRY_TIME = "T05:00:00Z"; - - // Maximum length of capture buffer recordings in seconds. - const F32 CAPTURE_BUFFER_MAX_TIME = 10.f; - - const int ERROR_VIVOX_OBJECT_NOT_FOUND = 1001; - const int ERROR_VIVOX_NOT_LOGGED_IN = 1007; -} - -static int scale_mic_volume(float volume) -{ - // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 - return 30 + (int)(volume * 20.0f); -} - -static int scale_speaker_volume(float volume) -{ - // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 - return 30 + (int)(volume * 40.0f); - -} - - -/////////////////////////////////////////////////////////////////////////////////////////////// - -class LLVivoxVoiceClientMuteListObserver : public LLMuteListObserver -{ - /* virtual */ void onChange() { LLVivoxVoiceClient::getInstance()->muteListChanged();} -}; - - -void LLVoiceVivoxStats::reset() -{ - mStartTime = -1.0f; - mConnectCycles = 0; - mConnectTime = -1.0f; - mConnectAttempts = 0; - mProvisionTime = -1.0f; - mProvisionAttempts = 0; - mEstablishTime = -1.0f; - mEstablishAttempts = 0; -} - -LLVoiceVivoxStats::LLVoiceVivoxStats() -{ - reset(); -} - -LLVoiceVivoxStats::~LLVoiceVivoxStats() -{ -} - -void LLVoiceVivoxStats::connectionAttemptStart() -{ - if (!mConnectAttempts) - { - mStartTime = LLTimer::getTotalTime(); - mConnectCycles++; - } - mConnectAttempts++; -} - -void LLVoiceVivoxStats::connectionAttemptEnd(bool success) -{ - if ( success ) - { - mConnectTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -void LLVoiceVivoxStats::provisionAttemptStart() -{ - if (!mProvisionAttempts) - { - mStartTime = LLTimer::getTotalTime(); - } - mProvisionAttempts++; -} - -void LLVoiceVivoxStats::provisionAttemptEnd(bool success) -{ - if ( success ) - { - mProvisionTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -void LLVoiceVivoxStats::establishAttemptStart() -{ - if (!mEstablishAttempts) - { - mStartTime = LLTimer::getTotalTime(); - } - mEstablishAttempts++; -} - -void LLVoiceVivoxStats::establishAttemptEnd(bool success) -{ - if ( success ) - { - mEstablishTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -LLSD LLVoiceVivoxStats::read() -{ - LLSD stats(LLSD::emptyMap()); - - stats["connect_cycles"] = LLSD::Integer(mConnectCycles); - stats["connect_attempts"] = LLSD::Integer(mConnectAttempts); - stats["connect_time"] = LLSD::Real(mConnectTime); - - stats["provision_attempts"] = LLSD::Integer(mProvisionAttempts); - stats["provision_time"] = LLSD::Real(mProvisionTime); - - stats["establish_attempts"] = LLSD::Integer(mEstablishAttempts); - stats["establish_time"] = LLSD::Real(mEstablishTime); - - return stats; -} - -static LLVivoxVoiceClientMuteListObserver mutelist_listener; -static bool sMuteListListener_listening = false; - -/////////////////////////////////////////////////////////////////////////////////////////////// -static LLProcessPtr sGatewayPtr; -static LLEventStream sGatewayPump("VivoxDaemonPump", true); - -static bool isGatewayRunning() -{ - return sGatewayPtr && sGatewayPtr->isRunning(); -} - -static void killGateway() -{ - if (sGatewayPtr) - { - LL_DEBUGS("Voice") << "SLVoice " << sGatewayPtr->getStatusString() << LL_ENDL; - - sGatewayPump.stopListening("VivoxDaemonPump"); - sGatewayPtr->kill(__FUNCTION__); - sGatewayPtr=NULL; - } - else - { - LL_DEBUGS("Voice") << "no gateway" << LL_ENDL; - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////// - -bool LLVivoxVoiceClient::sShuttingDown = false; -bool LLVivoxVoiceClient::sConnected = false; -LLPumpIO *LLVivoxVoiceClient::sPump = nullptr; - -LLVivoxVoiceClient::LLVivoxVoiceClient() : - mWriteOffset(0), - mHidden(true), - mSessionTerminateRequested(false), - mRelogRequested(false), - mTerminateDaemon(false), - mSpatialJoiningNum(0), - - mTuningMode(false), - mTuningEnergy(0.0f), - mTuningMicVolume(0), - mTuningMicVolumeDirty(true), - mTuningSpeakerVolume(50), // Set to 50 so the user can hear himself when he sets his mic volume - mTuningSpeakerVolumeDirty(true), - mDevicesListUpdated(false), - - mAudioSession(), // TBD - should be NULL - mNextAudioSession(), - - mCurrentParcelLocalID(0), - mConnectorEstablished(false), - mAccountLoggedIn(false), - mNumberOfAliases(0), - mCommandCookie(0), - mLoginRetryCount(0), - - mBuddyListMapPopulated(false), - mBlockRulesListReceived(false), - mAutoAcceptRulesListReceived(false), - - mCaptureDeviceDirty(false), - mRenderDeviceDirty(false), - mSpatialCoordsDirty(false), - mIsInitialized(false), - - mMuteMic(false), - mMuteMicDirty(false), - mFriendsListDirty(true), - - mEarLocation(0), - mSpeakerVolumeDirty(true), - mSpeakerMuteDirty(true), - mMicVolume(0), - mMicVolumeDirty(true), - - mVoiceEnabled(false), - mProcessChannels(false), - mWriteInProgress(false), - - mVoiceFontsReceived(false), - mVoiceFontsNew(false), - mVoiceFontListDirty(false), - - mCaptureBufferMode(false), - mCaptureBufferRecording(false), - mCaptureBufferRecorded(false), - mCaptureBufferPlaying(false), - mShutdownComplete(true), - mPlayRequestCount(0), - - mAvatarNameCacheConnection(), - mIsInTuningMode(false), - mIsInChannel(false), - mIsJoiningSession(false), - mIsWaitingForFonts(false), - mIsLoggingIn(false), - mIsLoggedIn(false), - mIsProcessingChannels(false), - mIsCoroutineActive(false), - mVivoxPump("vivoxClientPump") -{ - sShuttingDown = false; - sConnected = false; - sPump = nullptr; - - mSpeakerVolume = scale_speaker_volume(0); - - mVoiceVersion.serverVersion = ""; - mVoiceVersion.voiceServerType = VISIBLE_VOICE_SERVER_TYPE; - mVoiceVersion.internalVoiceServerType = VIVOX_VOICE_SERVER_TYPE; - mVoiceVersion.majorVersion = 1; - mVoiceVersion.minorVersion = 0; - mVoiceVersion.mBuildVersion = ""; - mVoiceVersion.serverVersion = ""; - - // gMuteListp isn't set up at this point, so we defer this until later. -// gMuteListp->addObserver(&mutelist_listener); - - -#if LL_DARWIN || LL_LINUX - // HACK: THIS DOES NOT BELONG HERE - // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us. - // This should cause us to ignore SIGPIPE and handle the error through proper channels. - // This should really be set up elsewhere. Where should it go? - signal(SIGPIPE, SIG_IGN); - - // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes. - // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that. - signal(SIGCHLD, SIG_IGN); -#endif - - - gIdleCallbacks.addFunction(idle, this); -} - -//--------------------------------------------------- - -LLVivoxVoiceClient::~LLVivoxVoiceClient() -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - sShuttingDown = true; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::init(LLPumpIO *pump) -{ - // constructor will set up LLVoiceClient::getInstance() - sPump = pump; - -// LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", -// boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - -} - -void LLVivoxVoiceClient::terminate() -{ - if (sShuttingDown) - { - return; - } - - // needs to be done manually here since we will not get another pass in - // coroutines... that mechanism is long since gone. - if (mIsLoggedIn) - { - logoutOfVivox(false); - } - - if(sConnected) - { - breakVoiceConnection(false); - sConnected = false; - } - else - { - mRelogRequested = false; - killGateway(); - } - - sShuttingDown = true; - sPump = NULL; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::cleanUp() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - deleteAllSessions(); - deleteAllVoiceFonts(); - deleteVoiceFontTemplates(); - LL_DEBUGS("Voice") << "exiting" << LL_ENDL; -} - -//--------------------------------------------------- - -const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion() -{ - return mVoiceVersion; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::updateSettings() -{ - setVoiceEnabled(LLVoiceClient::getInstance()->voiceEnabled()); - setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); - - std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); - setCaptureDevice(inputDevice); - std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); - setRenderDevice(outputDevice); - F32 mic_level = gSavedSettings.getF32("AudioLevelMic"); - setMicGain(mic_level); -} - -///////////////////////////// -// utility functions - -bool LLVivoxVoiceClient::writeString(const std::string &str) -{ - bool result = false; - LL_DEBUGS("LowVoice") << "sending:\n" << str << LL_ENDL; - - if(sConnected) - { - apr_status_t err; - apr_size_t size = (apr_size_t)str.size(); - apr_size_t written = size; - - //MARK: Turn this on to log outgoing XML - // LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; - - // check return code - sockets will fail (broken, etc.) - err = apr_socket_send( - mSocket->getSocket(), - (const char*)str.data(), - &written); - - if(err == 0 && written == size) - { - // Success. - result = true; - } - else if (err == 0 && written != size) { - // Did a short write, log it for now - LL_WARNS("Voice") << ") short write on socket sending data to vivox daemon." << "Sent " << written << "bytes instead of " << size <<LL_ENDL; - } - else if(APR_STATUS_IS_EAGAIN(err)) - { - char buf[MAX_STRING]; - LL_WARNS("Voice") << "EAGAIN error " << err << " (" << apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL; - } - else - { - // Assume any socket error means something bad. For now, just close the socket. - char buf[MAX_STRING]; - LL_WARNS("Voice") << "apr error " << err << " ("<< apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL; - daemonDied(); - } - } - - return result; -} - - -///////////////////////////// -// session control messages -void LLVivoxVoiceClient::connectorCreate() -{ - std::ostringstream stream; - std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); - - // Transition to stateConnectorStarted when the connector handle comes back. - std::string vivoxLogLevel = gSavedSettings.getString("VivoxDebugLevel"); - if ( vivoxLogLevel.empty() ) - { - vivoxLogLevel = "0"; - } - LL_DEBUGS("Voice") << "creating connector with log level " << vivoxLogLevel << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">" - << "<ClientName>V2 SDK</ClientName>" - << "<AccountManagementServer>" << mVoiceAccountServerURI << "</AccountManagementServer>" - << "<Mode>Normal</Mode>" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Logging>" - << "<Folder>" << logdir << "</Folder>" - << "<FileNamePrefix>Connector</FileNamePrefix>" - << "<FileNameSuffix>.log</FileNameSuffix>" - << "<LogLevel>" << vivoxLogLevel << "</LogLevel>" - << "</Logging>" - << "<Application>" << LLVersionInfo::instance().getChannel() << " " << LLVersionInfo::instance().getVersion() << "</Application>" - //<< "<Application></Application>" //Name can cause problems per vivox. - << "<MaxCalls>12</MaxCalls>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::connectorShutdown() -{ - if(mConnectorEstablished) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.InitiateShutdown.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "</Request>" - << "\n\n\n"; - - mShutdownComplete = false; - mConnectorEstablished = false; - - writeString(stream.str()); - } - else - { - mShutdownComplete = true; - } -} - -void LLVivoxVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) -{ - - mAccountDisplayName = user_id; - - LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL; - - mAccountName = nameFromID(agentID); -} - -void LLVivoxVoiceClient::setLoginInfo( - const std::string& account_name, - const std::string& password, - const std::string& voice_sip_uri_hostname, - const std::string& voice_account_server_uri) -{ - mVoiceSIPURIHostName = voice_sip_uri_hostname; - mVoiceAccountServerURI = voice_account_server_uri; - - if(mAccountLoggedIn) - { - // Already logged in. - LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; - - // Don't process another login. - return; - } - else if ( account_name != mAccountName ) - { - LL_WARNS("Voice") << "Mismatched account name! " << account_name - << " instead of " << mAccountName << LL_ENDL; - } - else - { - mAccountPassword = password; - } - - std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName"); - - if( !debugSIPURIHostName.empty() ) - { - LL_INFOS("Voice") << "Overriding account server based on VivoxDebugSIPURIHostName: " - << debugSIPURIHostName << LL_ENDL; - mVoiceSIPURIHostName = debugSIPURIHostName; - } - - if( mVoiceSIPURIHostName.empty() ) - { - // we have an empty account server name - // so we fall back to hardcoded defaults - - if(LLGridManager::getInstance()->isInProductionGrid()) - { - // Use the release account server - mVoiceSIPURIHostName = "bhr.vivox.com"; - } - else - { - // Use the development account server - mVoiceSIPURIHostName = "bhd.vivox.com"; - } - LL_INFOS("Voice") << "Defaulting SIP URI host: " - << mVoiceSIPURIHostName << LL_ENDL; - - } - - std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI"); - - if( !debugAccountServerURI.empty() ) - { - LL_INFOS("Voice") << "Overriding account server based on VivoxDebugVoiceAccountServerURI: " - << debugAccountServerURI << LL_ENDL; - mVoiceAccountServerURI = debugAccountServerURI; - } - - if( mVoiceAccountServerURI.empty() ) - { - // If the account server URI isn't specified, construct it from the SIP URI hostname - mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; - LL_INFOS("Voice") << "Inferring account server based on SIP URI Host name: " - << mVoiceAccountServerURI << LL_ENDL; - } -} - -void LLVivoxVoiceClient::idle(void* user_data) -{ -} - -//========================================================================= -// the following are methods to support the coroutine implementation of the -// voice connection and processing. They should only be called in the context -// of a coroutine. -// -// - -typedef enum e_voice_control_coro_state -{ - VOICE_STATE_ERROR = -1, - VOICE_STATE_DONE = 0, - VOICE_STATE_TP_WAIT, // entry point - VOICE_STATE_START_DAEMON, - VOICE_STATE_PROVISION_ACCOUNT, - VOICE_STATE_START_SESSION, - VOICE_STATE_SESSION_RETRY, - VOICE_STATE_SESSION_ESTABLISHED, - VOICE_STATE_WAIT_FOR_CHANNEL, - VOICE_STATE_DISCONNECT, - VOICE_STATE_WAIT_FOR_EXIT, -} EVoiceControlCoroState; - -void LLVivoxVoiceClient::voiceControlCoro() -{ - int state = 0; - try - { - // state is passed as a reference instead of being - // a member due to unresolved issues with coroutine - // surviving longer than LLVivoxVoiceClient - voiceControlStateMachine(state); - } - catch (const LLCoros::Stop&) - { - LL_DEBUGS("LLVivoxVoiceClient") << "Received a shutdown exception" << LL_ENDL; - } - catch (const LLContinueError&) - { - LOG_UNHANDLED_EXCEPTION("LLVivoxVoiceClient"); - } - catch (...) - { - // Ideally for Windows need to log SEH exception instead or to set SEH - // handlers but bugsplat shows local variables for windows, which should - // be enough - LL_WARNS("Voice") << "voiceControlStateMachine crashed in state " << state << LL_ENDL; - throw; - } -} - -void LLVivoxVoiceClient::voiceControlStateMachine(S32 &coro_state) -{ - if (sShuttingDown) - { - return; - } - - LL_DEBUGS("Voice") << "starting" << LL_ENDL; - mIsCoroutineActive = true; - LLCoros::set_consuming(true); - - U32 retry = 0; - - coro_state = VOICE_STATE_TP_WAIT; - - do - { - if (sShuttingDown) - { - // Vivox singleton performed the exit, logged out, - // cleaned sockets, gateway and no longer cares - // about state of coroutine, so just stop - return; - } - - switch (coro_state) - { - case VOICE_STATE_TP_WAIT: - // starting point for voice - if (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE) - { - LL_DEBUGS("Voice") << "Suspending voiceControlCoro() momentarily for teleport. Tuning: " << mTuningMode << ". Relog: " << mRelogRequested << LL_ENDL; - llcoro::suspendUntilTimeout(1.0); - } - else - { - coro_state = VOICE_STATE_START_DAEMON; - } - break; - - case VOICE_STATE_START_DAEMON: - LL_DEBUGS("Voice") << "Launching daemon" << LL_ENDL; - LLVoiceVivoxStats::getInstance()->reset(); - if (startAndLaunchDaemon()) - { - coro_state = VOICE_STATE_PROVISION_ACCOUNT; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_PROVISION_ACCOUNT: - if (provisionVoiceAccount()) - { - coro_state = VOICE_STATE_START_SESSION; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_START_SESSION: - if (establishVoiceConnection()) - { - coro_state = VOICE_STATE_SESSION_ESTABLISHED; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_SESSION_RETRY: - giveUp(); // cleans sockets and session - if (mRelogRequested) - { - // We failed to connect, give it a bit time before retrying. - retry++; - F32 full_delay = llmin(5.f * (F32)retry, 60.f); - F32 current_delay = 0.f; - LL_INFOS("Voice") << "Voice failed to establish session after " << retry - << " tries. Will attempt to reconnect in " << full_delay - << " seconds" << LL_ENDL; - while (current_delay < full_delay && !sShuttingDown) - { - // Assuming that a second has passed is not accurate, - // but we don't need accurancy here, just to make sure - // that some time passed and not to outlive voice itself - current_delay++; - llcoro::suspendUntilTimeout(1.f); - } - coro_state = VOICE_STATE_WAIT_FOR_EXIT; - } - else - { - coro_state = VOICE_STATE_DONE; - } - break; - - case VOICE_STATE_SESSION_ESTABLISHED: - { - // enable/disable the automatic VAD and explicitly set the initial values of - // the VAD variables ourselves when it is off - see SL-15072 for more details - // note: we set the other parameters too even if the auto VAD is on which is ok - unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); - unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover"); - unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor"); - unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity"); - setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity); - - // watch for changes to the VAD settings via Debug Settings UI and act on them accordingly - gSavedSettings.getControl("VivoxVadAuto")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadHangover")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadNoiseFloor")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadSensitivity")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - - if (mTuningMode) - { - performMicTuning(); - } - - coro_state = VOICE_STATE_WAIT_FOR_CHANNEL; - } - break; - - case VOICE_STATE_WAIT_FOR_CHANNEL: - waitForChannel(); // todo: split into more states like login/fonts - coro_state = VOICE_STATE_DISCONNECT; - break; - - case VOICE_STATE_DISCONNECT: - LL_DEBUGS("Voice") << "lost channel RelogRequested=" << mRelogRequested << LL_ENDL; - endAndDisconnectSession(); - retry = 0; // Connected without issues - coro_state = VOICE_STATE_WAIT_FOR_EXIT; - break; - - case VOICE_STATE_WAIT_FOR_EXIT: - if (isGatewayRunning()) - { - LL_INFOS("Voice") << "waiting for SLVoice to exit" << LL_ENDL; - llcoro::suspendUntilTimeout(1.0); - } - else if (mRelogRequested && mVoiceEnabled) - { - LL_INFOS("Voice") << "will attempt to reconnect to voice" << LL_ENDL; - coro_state = VOICE_STATE_TP_WAIT; - } - else - { - coro_state = VOICE_STATE_DONE; - } - break; - - case VOICE_STATE_DONE: - break; - } - } while (coro_state > 0); - - if (sShuttingDown) - { - // LLVivoxVoiceClient might be already dead - return; - } - - mIsCoroutineActive = false; - LL_INFOS("Voice") << "exiting" << LL_ENDL; -} - -bool LLVivoxVoiceClient::endAndDisconnectSession() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - breakVoiceConnection(true); - - killGateway(); - - return true; -} - -bool LLVivoxVoiceClient::callbackEndDaemon(const LLSD& data) -{ - if (!sShuttingDown && mVoiceEnabled) - { - LL_WARNS("Voice") << "SLVoice terminated " << ll_stream_notation_sd(data) << LL_ENDL; - terminateAudioSession(false); - closeSocket(); - cleanUp(); - LLVoiceClient::getInstance()->setUserPTTState(false); - gAgent.setVoiceConnected(false); - mRelogRequested = true; - } - sGatewayPump.stopListening("VivoxDaemonPump"); - return false; -} - -bool LLVivoxVoiceClient::startAndLaunchDaemon() -{ - //--------------------------------------------------------------------- - if (!LLVoiceClient::getInstance()->voiceEnabled()) - { - // Voice is locked out, we must not launch the vivox daemon. - LL_WARNS("Voice") << "voice disabled; not starting daemon" << LL_ENDL; - return false; - } - - if (!isGatewayRunning()) - { -#ifndef VIVOXDAEMON_REMOTEHOST - // Launch the voice daemon -#if LL_WINDOWS - // On windows use exe (not work or RO) directory - std::string exe_path = gDirUtilp->getExecutableDir(); - gDirUtilp->append(exe_path, "SLVoice.exe"); -#elif LL_DARWIN - // On MAC use resource directory - std::string exe_path = gDirUtilp->getAppRODataDir(); - gDirUtilp->append(exe_path, "SLVoice"); -#else - std::string exe_path = gDirUtilp->getExecutableDir(); - gDirUtilp->append(exe_path, "SLVoice.exe"); -#endif - // See if the vivox executable exists - llstat s; - if (!LLFile::stat(exe_path, &s)) - { - // vivox executable exists. Build the command line and launch the daemon. - LLProcess::Params params; -#if LL_LINUX || __FreeBSD__ -#if __FreeBSD__ - params.envs.add("WINEARCH=win32"); - params.envs.add("WINEPREFIX=~/.i386-wine-pkg"); -#endif // __FreeBSD__ - params.executable = "wine"; - params.args.add(exe_path); -#else - params.executable = exe_path; -#endif // LL_LINUX || __FreeBSD__ - - // VOICE-88: Cycle through [portbase..portbase+portrange) on - // successive tries because attempting to relaunch (after manually - // disabling and then re-enabling voice) with the same port can - // cause SLVoice's bind() call to fail with EADDRINUSE. We expect - // that eventually the OS will time out previous ports, which is - // why we cycle instead of incrementing indefinitely. - - static LLCachedControl<U32> portbase(gSavedSettings, "VivoxVoicePort"); - static LLCachedControl<std::string> host(gSavedSettings, "VivoxVoiceHost"); - static LLCachedControl<std::string> loglevel(gSavedSettings, "VivoxDebugLevel"); - static LLCachedControl<std::string> log_folder(gSavedSettings, "VivoxLogDirectory"); - static LLCachedControl<std::string> shutdown_timeout(gSavedSettings, "VivoxShutdownTimeout"); - static const U32 portrange = 100; - static U32 portoffset = 0; - U32 port = 0; - - if (LLAppViewer::instance()->isSecondInstance()) - { - // Ideally need to know amount of instances and - // to increment instance_offset on EADDRINUSE. - // But for now just use rand - static U32 instance_offset = portrange * ll_rand(20); - port = portbase + portoffset + instance_offset; - } - else - { - // leave main thread with exclusive port set - port = portbase + portoffset; - } - portoffset = (portoffset + 1) % portrange; - params.args.add("-i"); - params.args.add(STRINGIZE(host() << ':' << port)); - - params.args.add("-ll"); - if (loglevel().empty()) - { - params.args.add("0"); - } - else - { - params.args.add(loglevel); - } - - params.args.add("-lf"); - if (log_folder().empty()) - { - params.args.add(gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "")); - } - else - { - params.args.add(log_folder); - } - - // set log file basename and .log - params.args.add("-lp"); - params.args.add("SLVoice"); - params.args.add("-ls"); - params.args.add(".log"); - - // rotate any existing log - std::string new_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.log"); - std::string old_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.old"); - if (gDirUtilp->fileExists(new_log)) - { - LLFile::rename(new_log, old_log); - } - - if (!shutdown_timeout().empty()) - { - params.args.add("-st"); - params.args.add(shutdown_timeout); - } - params.cwd = gDirUtilp->getAppRODataDir(); - -# ifdef VIVOX_HANDLE_ARGS - params.args.add("-ah"); - params.args.add(LLVivoxSecurity::getInstance()->accountHandle()); - - params.args.add("-ch"); - params.args.add(LLVivoxSecurity::getInstance()->connectorHandle()); -# endif // VIVOX_HANDLE_ARGS - - params.postend = sGatewayPump.getName(); - sGatewayPump.listen("VivoxDaemonPump", boost::bind(&LLVivoxVoiceClient::callbackEndDaemon, this, _1)); - - LL_INFOS("Voice") << "Launching SLVoice" << LL_ENDL; - LL_DEBUGS("Voice") << "SLVoice params " << params << LL_ENDL; - - sGatewayPtr = LLProcess::create(params); - - mDaemonHost = LLHost(host().c_str(), port); - } - else - { - LL_WARNS("Voice") << exe_path << " not found." << LL_ENDL; - return false; - } -#else - // SLIM SDK: port changed from 44124 to 44125. - // We can connect to a client gateway running on another host. This is useful for testing. - // To do this, launch the gateway on a nearby host like this: - // vivox-gw.exe -p tcp -i 0.0.0.0:44125 - // and put that host's IP address here. - mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost"), gSavedSettings.getU32("VivoxVoicePort")); -#endif - - // Dirty the states we'll need to sync with the daemon when it comes up. - mMuteMicDirty = true; - mMicVolumeDirty = true; - mSpeakerVolumeDirty = true; - mSpeakerMuteDirty = true; - // These only need to be set if they're not default (i.e. empty string). - mCaptureDeviceDirty = !mCaptureDevice.empty(); - mRenderDeviceDirty = !mRenderDevice.empty(); - - mMainSessionGroupHandle.clear(); - } - else - { - LL_DEBUGS("Voice") << " gateway running; not attempting to start" << LL_ENDL; - } - - //--------------------------------------------------------------------- - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << LL_ENDL; - - int retryCount(0); - LLVoiceVivoxStats::getInstance()->reset(); - while (!sConnected && !sShuttingDown && retryCount++ <= DAEMON_CONNECT_RETRY_MAX) - { - LLVoiceVivoxStats::getInstance()->connectionAttemptStart(); - LL_DEBUGS("Voice") << "Attempting to connect to vivox daemon: " << mDaemonHost << LL_ENDL; - closeSocket(); - if (!mSocket) - { - mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); - } - - sConnected = mSocket->blockingConnect(mDaemonHost); - LLVoiceVivoxStats::getInstance()->connectionAttemptEnd(sConnected); - if (!sConnected) - { - llcoro::suspendUntilTimeout(DAEMON_CONNECT_THROTTLE_SECONDS); - } - } - - //--------------------------------------------------------------------- - if (sShuttingDown && !sConnected) - { - return false; - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - while (!sPump && !sShuttingDown) - { // Can't use the pump until we have it available. - llcoro::suspendUntilNextFrame(); - } - - if (sShuttingDown) - { - return false; - } - - // MBW -- Note to self: pumps and pipes examples in - // indra/test/io.cpp - // indra/test/llpipeutil.{cpp|h} - - // Attach the pumps and pipes - - LLPumpIO::chain_t readChain; - - readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket))); - readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser())); - - - sPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); - - - //--------------------------------------------------------------------- - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - // Initial devices query - getCaptureDevicesSendMessage(); - getRenderDevicesSendMessage(); - - mLoginRetryCount = 0; - - return true; -} - -bool LLVivoxVoiceClient::provisionVoiceAccount() -{ - LL_INFOS("Voice") << "Provisioning voice account." << LL_ENDL; - - while ((!gAgent.getRegion() || !gAgent.getRegion()->capabilitiesReceived()) && !sShuttingDown) - { - LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; - // *TODO* Pump a message for wake up. - llcoro::suspend(); - } - - if (sShuttingDown) - { - return false; - } - - std::string url = gAgent.getRegionCapability("ProvisionVoiceAccountRequest"); - - LL_DEBUGS("Voice") << "region ready for voice provisioning; url=" << url << LL_ENDL; - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("voiceAccountProvision", httpPolicy); - LLCore::HttpRequest::ptr_t httpRequest = std::make_shared<LLCore::HttpRequest>(); - LLCore::HttpOptions::ptr_t httpOpts = std::make_shared<LLCore::HttpOptions>(); - int retryCount(0); - - LLSD result; - bool provisioned = false; - do - { - LLVoiceVivoxStats::getInstance()->provisionAttemptStart(); - LLSD body; - body["voice_server_type"] = "vivox"; - result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts); - - if (sShuttingDown) - { - return false; - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (status == LLCore::HttpStatus(404)) - { - F32 timeout = pow(PROVISION_RETRY_TIMEOUT, static_cast<float>(retryCount)); - LL_WARNS("Voice") << "Provision CAP 404. Retrying in " << timeout << " seconds. Retries: " << (S32)retryCount << LL_ENDL; - llcoro::suspendUntilTimeout(timeout); - - if (sShuttingDown) - { - return false; - } - } - else if (!status) - { - LL_WARNS("Voice") << "Unable to provision voice account." << LL_ENDL; - LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(false); - return false; - } - else - { - provisioned = true; - } - } while (!provisioned && ++retryCount <= PROVISION_RETRY_MAX && !sShuttingDown); - - if (sShuttingDown && !provisioned) - { - return false; - } - - LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(provisioned); - if (!provisioned) - { - LL_WARNS("Voice") << "Could not access voice provision cap after " << retryCount << " attempts." << LL_ENDL; - return false; - } - LL_DEBUGS("Voice") << "Voice Provision Result." << result << LL_ENDL; - std::string voiceSipUriHostname; - std::string voiceAccountServerUri; - std::string voiceUserName = result["username"].asString(); - std::string voicePassword = result["password"].asString(); - - if (result.has("voice_sip_uri_hostname")) - { - voiceSipUriHostname = result["voice_sip_uri_hostname"].asString(); - } - - // this key is actually misnamed -- it will be an entire URI, not just a hostname. - if (result.has("voice_account_server_name")) - { - voiceAccountServerUri = result["voice_account_server_name"].asString(); - } - - LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response" - << " user " << (voiceUserName.empty() ? "not set" : "set") - << " password " << (voicePassword.empty() ? "not set" : "set") - << " sip uri " << voiceSipUriHostname - << " account uri " << voiceAccountServerUri - << LL_ENDL; - - setLoginInfo(voiceUserName, voicePassword, voiceSipUriHostname, voiceAccountServerUri); - - return true; -} - -bool LLVivoxVoiceClient::establishVoiceConnection() -{ - if (!mVoiceEnabled && mIsInitialized) - { - LL_WARNS("Voice") << "cannot establish connection; enabled "<<mVoiceEnabled<<" initialized "<<mIsInitialized<<LL_ENDL; - return false; - } - - if (sShuttingDown) - { - return false; - } - - LLSD result; - bool connected(false); - bool giving_up(false); - int retries = 0; - LL_INFOS("Voice") << "Requesting connection to voice service" << LL_ENDL; - - LLVoiceVivoxStats::getInstance()->establishAttemptStart(); - connectorCreate(); - do - { - result = llcoro::suspendUntilEventOn(mVivoxPump); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - if (result.has("connector")) - { - connected = LLSD::Boolean(result["connector"]); - LLVoiceVivoxStats::getInstance()->establishAttemptEnd(connected); - if (!connected) - { - if (result.has("retry") && ++retries <= CONNECT_RETRY_MAX && !sShuttingDown) - { - F32 timeout = (F32)LLSD::Real(result["retry"]); - timeout *= retries; - LL_INFOS("Voice") << "Retry connection to voice service in " << timeout << " seconds" << LL_ENDL; - llcoro::suspendUntilTimeout(timeout); - - if (mVoiceEnabled) // user may have switched it off - { - // try again - LLVoiceVivoxStats::getInstance()->establishAttemptStart(); - connectorCreate(); - } - else - { - // stop if they've turned off voice - giving_up = true; - } - } - else - { - giving_up=true; - } - } - } - LL_DEBUGS("Voice") << (connected ? "" : "not ") << "connected, " - << (giving_up ? "" : "not ") << "giving up" - << LL_ENDL; - } while (!connected && !giving_up && !sShuttingDown); - - if (giving_up) - { - LLSD args; - args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority(); - LLNotificationsUtil::add("NoVoiceConnect", args); - } - - return connected; -} - -bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait) -{ - LL_DEBUGS("Voice") << "( wait=" << corowait << ")" << LL_ENDL; - bool retval(true); - - mShutdownComplete = false; - connectorShutdown(); - - if (corowait) - { - LLSD timeoutResult(LLSDMap("connector", "timeout")); - - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - retval = result.has("connector"); - } - else - { - mRelogRequested = false; //stop the control coro - // If we are not doing a corowait then we must sleep until the connector has responded - // otherwise we may very well close the socket too early. -#if LL_WINDOWS - if (!mShutdownComplete) - { - // The situation that brings us here is a call from ::terminate() - // At this point message system is already down so we can't wait for - // the message, yet we need to receive "connector shutdown response". - // Either wait a bit and emulate it or check gMessageSystem for specific message - _sleep(1000); - if (sConnected) - { - sConnected = false; - LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); - mVivoxPump.post(vivoxevent); - } - mShutdownComplete = true; - } -#endif - } - - LL_DEBUGS("Voice") << "closing SLVoice socket" << LL_ENDL; - closeSocket(); // Need to do this now -- bad things happen if the destructor does it later. - cleanUp(); - sConnected = false; - - return retval; -} - -bool LLVivoxVoiceClient::loginToVivox() -{ - LLSD timeoutResult(LLSDMap("login", "timeout")); - - int loginRetryCount(0); - - bool response_ok(false); - bool account_login(false); - bool send_login(true); - - do - { - mIsLoggingIn = true; - if (send_login) - { - loginSendMessage(); - send_login = false; - } - - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - if (result.has("login")) - { - std::string loginresp = result["login"]; - - if (((loginresp == "retry") || (loginresp == "timeout")) && !sShuttingDown) - { - LL_WARNS("Voice") << "login failed with status '" << loginresp << "' " - << " count " << loginRetryCount << "/" << LOGIN_RETRY_MAX - << LL_ENDL; - if (++loginRetryCount > LOGIN_RETRY_MAX) - { - // We've run out of retries - tell the user - LL_WARNS("Voice") << "too many login retries (" << loginRetryCount << "); giving up." << LL_ENDL; - LLSD args; - args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority(); - mTerminateDaemon = true; - LLNotificationsUtil::add("NoVoiceConnect", args); - - mIsLoggingIn = false; - return false; - } - response_ok = false; - account_login = false; - send_login = true; - - // an exponential backoff gets too long too quickly; stretch it out, but not too much - F32 timeout = loginRetryCount * LOGIN_ATTEMPT_TIMEOUT; - - // tell the user there is a problem - LL_WARNS("Voice") << "login " << loginresp << " will retry login in " << timeout << " seconds." << LL_ENDL; - - if (!sShuttingDown) - { - // Todo: this is way to long, viewer can get stuck waiting during shutdown - // either make it listen to pump or split in smaller waits with checks for shutdown - llcoro::suspendUntilTimeout(timeout); - } - } - else if (loginresp == "failed") - { - mIsLoggingIn = false; - return false; - } - else if (loginresp == "response_ok") - { - response_ok = true; - } - else if (loginresp == "account_login") - { - account_login = true; - } - else if (sShuttingDown) - { - mIsLoggingIn = false; - return false; - } - } - - } while ((!response_ok || !account_login) && !sShuttingDown); - - if (sShuttingDown) - { - return false; - } - - mRelogRequested = false; - mIsLoggedIn = true; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); - - // Set up the mute list observer if it hasn't been set up already. - if ((!sMuteListListener_listening)) - { - LLMuteList::getInstance()->addObserver(&mutelist_listener); - sMuteListListener_listening = true; - } - - // Set the initial state of mic mute, local speaker volume, etc. - sendLocalAudioUpdates(); - mIsLoggingIn = false; - - return true; -} - -void LLVivoxVoiceClient::logoutOfVivox(bool wait) -{ - if (mIsLoggedIn) - { - // Ensure that we'll re-request provisioning before logging in again - mAccountPassword.clear(); - mVoiceAccountServerURI.clear(); - - logoutSendMessage(); - - if (wait) - { - LLSD timeoutResult(LLSDMap("logout", "timeout")); - LLSD result; - - do - { - LL_DEBUGS("Voice") - << "waiting for logout response on " - << mVivoxPump.getName() - << LL_ENDL; - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - break; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - // Don't get confused by prior queued events -- note that it's - // very important that mVivoxPump is an LLEventMailDrop, which - // does queue events. - } while (! result["logout"]); - } - else - { - LL_DEBUGS("Voice") << "not waiting for logout" << LL_ENDL; - } - - mIsLoggedIn = false; - } -} - - -bool LLVivoxVoiceClient::retrieveVoiceFonts() -{ - // Request the set of available voice fonts. - refreshVoiceEffectLists(true); - - mIsWaitingForFonts = true; - LLSD result; - do - { - result = llcoro::suspendUntilEventOn(mVivoxPump); - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("voice_fonts")) - break; - } while (true); - mIsWaitingForFonts = false; - - mVoiceFontExpiryTimer.start(); - mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); - - return result["voice_fonts"].asBoolean(); -} - -bool LLVivoxVoiceClient::requestParcelVoiceInfo() -{ - //_INFOS("Voice") << "Requesting voice info for Parcel" << LL_ENDL; - - LLViewerRegion * region = gAgent.getRegion(); - if (region == NULL || !region->capabilitiesReceived()) - { - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not yet available, deferring" << LL_ENDL; - return false; - } - - // grab the cap. - std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest"); - if (url.empty()) - { - // Region dosn't have the cap. Stop probing. - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not available in this region" << LL_ENDL; - return false; - } - - // update the parcel - checkParcelChanged(true); - - LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter = std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("parcelVoiceInfoRequest", httpPolicy); - LLCore::HttpRequest::ptr_t httpRequest = std::make_shared<LLCore::HttpRequest>(); - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD()); - - if (sShuttingDown) - { - return false; - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - // if a terminate request has been received, - // bail and go to the stateSessionTerminated - // state. If the cap request is still pending, - // the responder will check to see if we've moved - // to a new session and won't change any state. - LL_DEBUGS("Voice") << "terminate requested " << mSessionTerminateRequested - << " enabled " << mVoiceEnabled - << " initialized " << mIsInitialized - << LL_ENDL; - terminateAudioSession(true); - return false; - } - - if ((!status) || (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized))) - { - if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - LL_WARNS("Voice") << "Session terminated." << LL_ENDL; - } - - LL_WARNS("Voice") << "No voice on parcel" << LL_ENDL; - sessionTerminate(); - return false; - } - - if (!result.has("voice_credentials")) - { - if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) - { - LL_WARNS("Voice") << "No voice credentials" << LL_ENDL; - } - else - { - LL_DEBUGS("Voice") << "No voice credentials" << LL_ENDL; - } - } - - // set the spatial channel. If no voice credentials or uri are - // available, then we simply drop out of voice spatially. - return setSpatialChannel(result["voice_credentials"]); -} - -bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) -{ - mIsJoiningSession = true; - - sessionStatePtr_t oldSession = mAudioSession; - - LL_INFOS("Voice") << "Adding or joining voice session " << nextSession->mHandle << LL_ENDL; - - mAudioSession = nextSession; - if (!mAudioSession || !mAudioSession->mReconnect) - { - mNextAudioSession.reset(); - } - - // The old session may now need to be deleted. - reapSession(oldSession); - - if (mAudioSession) - { - if (!mAudioSession->mHandle.empty()) - { - // Connect to a session by session handle - - sessionMediaConnectSendMessage(mAudioSession); - } - else - { - // Connect to a session by URI - sessionCreateSendMessage(mAudioSession, true, false); - } - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); - - llcoro::suspend(); - - if (sShuttingDown) - { - return false; - } - - LLSD result; - - if (mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM) - { - // Notify observers to let them know there is problem with voice - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - LL_WARNS() << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << LL_ENDL; - } - - // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for - // example for p2p many times while waiting for response, so it can't be used to detect errors - if (mAudioSession && mAudioSession->mIsSpatial) - { - mSpatialJoiningNum++; - } - - if (!mVoiceEnabled && mIsInitialized) - { - LL_DEBUGS("Voice") << "Voice no longer enabled. Exiting" - << " enabled " << mVoiceEnabled - << " initialized " << mIsInitialized - << LL_ENDL; - mIsJoiningSession = false; - // User bailed out during connect -- jump straight to teardown. - terminateAudioSession(true); - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - return false; - } - else if (mSessionTerminateRequested) - { - LL_DEBUGS("Voice") << "Terminate requested" << LL_ENDL; - if (mAudioSession && !mAudioSession->mHandle.empty()) - { - // Only allow direct exits from this state in p2p calls (for cancelling an invite). - // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. - if (mAudioSession->mIsP2P) - { - terminateAudioSession(true); - mIsJoiningSession = false; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - return false; - } - } - } - - bool added(true); - bool joined(false); - - LLSD timeoutResult(LLSDMap("session", "timeout")); - - // We are about to start a whole new session. Anything that MIGHT still be in our - // maildrop is going to be stale and cause us much wailing and gnashing of teeth. - // Just flush it all out and start new. - mVivoxPump.discard(); - - // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4 - // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck. - // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. - // This is a cheap way to make sure both have happened before proceeding. - do - { - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, SESSION_JOIN_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_INFOS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("session")) - { - if (!mAudioSession) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initialized." << LL_ENDL; - continue; - } - if (result.has("handle") && result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - - std::string message = result["session"].asString(); - - if ((message == "added") || (message == "created")) - { - added = true; - } - else if (message == "joined") - { - joined = true; - } - else if ((message == "failed") || (message == "removed") || (message == "timeout")) - { // we will get a removed message if a voice call is declined. - LL_INFOS("Voice") << "Result:" << result << LL_ENDL; - if (message == "failed") - { - int reason = result["reason"].asInteger(); - LL_WARNS("Voice") << "Add and join failed for reason " << reason << LL_ENDL; - - if ( (reason == ERROR_VIVOX_NOT_LOGGED_IN) - || (reason == ERROR_VIVOX_OBJECT_NOT_FOUND)) - { - LL_DEBUGS("Voice") << "Requesting reprovision and login." << LL_ENDL; - requestRelog(); - } - } - else - { - LL_WARNS("Voice") << "session '" << message << "' " - << LL_ENDL; - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - mIsJoiningSession = false; - return false; - } - } - } while (!added || !joined); - - mIsJoiningSession = false; - - if (mSpatialJoiningNum > 100) - { - LL_WARNS("Voice") << "There seems to be problem with connecting to a voice channel. Frames to join were " << mSpatialJoiningNum << LL_ENDL; - } - - mSpatialJoiningNum = 0; - - // Events that need to happen when a session is joined could go here. - // send an initial positional information immediately upon joining. - // - // do an initial update for position and the camera position, then send a - // positional update. - updatePosition(); - enforceTether(); - - // Dirty state that may need to be sync'ed with the daemon. - mMuteMicDirty = true; - mSpeakerVolumeDirty = true; - mSpatialCoordsDirty = true; - - sendPositionAndVolumeUpdate(); - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); - - return true; -} - -bool LLVivoxVoiceClient::terminateAudioSession(bool wait) -{ - - if (mAudioSession) - { - LL_INFOS("Voice") << "terminateAudioSession(" << wait << ") Terminating current voice session " << mAudioSession->mHandle << LL_ENDL; - - if (mIsLoggedIn) - { - if (!mAudioSession->mHandle.empty()) - { - -#if RECORD_EVERYTHING - // HACK: for testing only - // Save looped recording - std::string savepath("/tmp/vivoxrecording"); - { - time_t now = time(NULL); - const size_t BUF_SIZE = 64; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - - strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); - savepath += time_str; - } - recordingLoopSave(savepath); -#endif - - sessionMediaDisconnectSendMessage(mAudioSession); - - if (wait) - { - LLSD result; - do - { - LLSD timeoutResult(LLSDMap("session", "timeout")); - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("session")) - { - if (result.has("handle")) - { - if (result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - } - - std::string message = result["session"].asString(); - if (message == "removed" || message == "timeout") - break; - } - } while (true); - - } - } - else - { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "Session " << mAudioSession->mHandle << " already terminated by logout." << LL_ENDL; - } - - sessionStatePtr_t oldSession = mAudioSession; - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); // needs mAudioSession for uri - mAudioSession.reset(); - - // The old session may now need to be deleted. - reapSession(oldSession); - } - else - { - LL_WARNS("Voice") << "terminateAudioSession(" << wait << ") with NULL mAudioSession" << LL_ENDL; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - } - - - // Always reset the terminate request flag when we get here. - // Some slower PCs have a race condition where they can switch to an incoming P2P call faster than the state machine leaves - // the region chat. - mSessionTerminateRequested = false; - - bool status=((mVoiceEnabled || !mIsInitialized) && !mRelogRequested && !sShuttingDown); - LL_DEBUGS("Voice") << "exiting" - << " VoiceEnabled " << mVoiceEnabled - << " IsInitialized " << mIsInitialized - << " RelogRequested " << mRelogRequested - << " ShuttingDown " << (sShuttingDown ? "True" : "False") - << " returning " << status - << LL_ENDL; - return status; -} - - -typedef enum e_voice_wait_for_channel_state -{ - VOICE_CHANNEL_STATE_LOGIN = 0, // entry point - VOICE_CHANNEL_STATE_CHECK_EFFECTS, - VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING, - VOICE_CHANNEL_STATE_PROCESS_CHANNEL, - VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY, - VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK, - VOICE_CHANNEL_STATE_LOGOUT, - VOICE_CHANNEL_STATE_RELOG, - VOICE_CHANNEL_STATE_DONE, -} EVoiceWaitForChannelState; - -bool LLVivoxVoiceClient::waitForChannel() -{ - LL_INFOS("Voice") << "Waiting for channel" << LL_ENDL; - - EVoiceWaitForChannelState state = VOICE_CHANNEL_STATE_LOGIN; - - do - { - if (sShuttingDown) - { - // terminate() forcefully disconects voice, no need for cleanup - return false; - } - - switch (state) - { - case VOICE_CHANNEL_STATE_LOGIN: - if (!loginToVivox()) - { - return false; - } - state = VOICE_CHANNEL_STATE_CHECK_EFFECTS; - break; - - case VOICE_CHANNEL_STATE_CHECK_EFFECTS: - if (LLVoiceClient::instance().getVoiceEffectEnabled()) - { - retrieveVoiceFonts(); - - if (sShuttingDown) - { - return false; - } - - // Request the set of available voice fonts. - refreshVoiceEffectLists(false); - } - -#if USE_SESSION_GROUPS - // Rider: This code is completely unchanged from the original state machine - // It does not seem to be in active use... but I'd rather not rip it out. - // create the main session group - setState(stateCreatingSessionGroup); - sessionGroupCreateSendMessage(); -#endif - - state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; - break; - - case VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING: - llcoro::suspend(); - state = VOICE_CHANNEL_STATE_PROCESS_CHANNEL; - break; - - case VOICE_CHANNEL_STATE_PROCESS_CHANNEL: - if (mTuningMode) - { - performMicTuning(); - } - else if (mCaptureBufferMode) - { - recordingAndPlaybackMode(); - } - else if (mProcessChannels && ((mNextAudioSession == NULL) || checkParcelChanged())) - { - // the parcel is changed, or we have no pending audio sessions, - // so try to request the parcel voice info - // if we have the cap, we move to the appropriate state - requestParcelVoiceInfo(); //suspends for http reply - } - else if (sessionNeedsRelog(mNextAudioSession)) - { - LL_INFOS("Voice") << "Session requesting reprovision and login." << LL_ENDL; - requestRelog(); - break; - } - else if (mNextAudioSession) - { - if (!mNextAudioSession->mIsP2P && !mProcessChannels) - { - llcoro::suspend(); - break; - } - - try - { - sessionStatePtr_t joinSession = mNextAudioSession; - mNextAudioSession.reset(); - mIsProcessingChannels = true; - if (!runSession(joinSession)) //suspends - { - mIsProcessingChannels = false; - LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL; - break; - } - else - { - mIsProcessingChannels = false; - LL_DEBUGS("Voice") - << "runSession returned true to inner loop" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - } - } - catch (const LLCoros::Stop&) - { - LL_DEBUGS("LLVivoxVoiceClient") << "Received a shutdown exception" << LL_ENDL; - } - catch (const LLContinueError&) - { - LOG_UNHANDLED_EXCEPTION("LLVivoxVoiceClient"); - } - catch (...) - { - // Ideally for Windows need to log SEH exception instead or to set SEH - // handlers but bugsplat shows local variables for windows, which should - // be enough - LL_WARNS("Voice") << "voiceControlStateMachine crashed in state VOICE_CHANNEL_STATE_PROCESS_CHANNEL" - << " mRelogRequested " << mRelogRequested - << " mVoiceEnabled " << mVoiceEnabled - << " mIsProcessingChannels " << mIsProcessingChannels - << " mProcessChannels " << mProcessChannels - << LL_ENDL; - throw; - } - } - - state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY; - break; - - case VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY: - if (!mNextAudioSession) - { - llcoro::suspendUntilTimeout(1.0); - } - state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK; - break; - - case VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK: - if (mVoiceEnabled && !mRelogRequested) - { - state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; - break; - } - else - { - mIsProcessingChannels = false; - LL_DEBUGS("Voice") - << "leaving inner waitForChannel loop" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - state = VOICE_CHANNEL_STATE_LOGOUT; - break; - } - - case VOICE_CHANNEL_STATE_LOGOUT: - logoutOfVivox(true /*bool wait*/); - if (mRelogRequested) - { - state = VOICE_CHANNEL_STATE_RELOG; - } - else - { - state = VOICE_CHANNEL_STATE_DONE; - } - break; - - case VOICE_CHANNEL_STATE_RELOG: - LL_DEBUGS("Voice") << "Relog Requested, restarting provisioning" << LL_ENDL; - if (!provisionVoiceAccount()) - { - if (sShuttingDown) - { - return false; - } - LL_WARNS("Voice") << "provisioning voice failed; giving up" << LL_ENDL; - giveUp(); - return false; - } - if (mVoiceEnabled && mRelogRequested && isGatewayRunning()) - { - state = VOICE_CHANNEL_STATE_LOGIN; - } - else - { - state = VOICE_CHANNEL_STATE_DONE; - } - break; - case VOICE_CHANNEL_STATE_DONE: - LL_DEBUGS("Voice") - << "exiting" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - return !sShuttingDown; - } - } while (true); -} - -bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) -{ - LL_INFOS("Voice") << "running new voice session " << session->mHandle << LL_ENDL; - - if (sShuttingDown || !mProcessChannels) - { - return false; - } - - bool joined_session = addAndJoinSession(session); - - if (!joined_session) - { - notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); - - if (mSessionTerminateRequested) - { - LL_DEBUGS("Voice") << "runSession terminate requested " << LL_ENDL; - terminateAudioSession(true); - } - // if a relog has been requested then addAndJoineSession - // failed in a spectacular way and we need to back out. - // If this is not the case then we were simply trying to - // make a call and the other party rejected it. - return !mRelogRequested; - } - - notifyParticipantObservers(); - notifyVoiceFontObservers(); - - LLSD timeoutEvent(LLSDMap("timeout", LLSD::Boolean(true))); - - mIsInChannel = true; - mMuteMicDirty = true; - - while (!sShuttingDown - && mVoiceEnabled - && isGatewayRunning() - && !mSessionTerminateRequested - && !mTuningMode - && mProcessChannels) - { - sendCaptureAndRenderDevices(); // suspends - - if (sShuttingDown) - { - return false; - } - - if (mSessionTerminateRequested) - { - break; - } - - if (mAudioSession && mAudioSession->mParticipantsChanged) - { - mAudioSession->mParticipantsChanged = false; - notifyParticipantObservers(); - } - - if (!inSpatialChannel()) - { - // When in a non-spatial channel, never send positional updates. - mSpatialCoordsDirty = false; - } - else - { - updatePosition(); - - if (checkParcelChanged()) - { - // *RIDER: I think I can just return here if the parcel has changed - // and grab the new voice channel from the outside loop. - // - // if the parcel has changed, attempted to request the - // cap for the parcel voice info. If we can't request it - // then we don't have the cap URL so we do nothing and will - // recheck next time around - if (requestParcelVoiceInfo()) // suspends - { // The parcel voice URI has changed.. break out and reconnect. - break; - } - - if (sShuttingDown) - { - return false; - } - } - // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) - enforceTether(); - } - sendPositionAndVolumeUpdate(); - - // Do notifications for expiring Voice Fonts. - if (mVoiceFontExpiryTimer.hasExpired()) - { - expireVoiceFonts(); - mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); - } - - // send any requests to adjust mic and speaker settings if they have changed - sendLocalAudioUpdates(); - - mIsInitialized = true; - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, UPDATE_THROTTLE_SECONDS, timeoutEvent); - - if (sShuttingDown) - { - return false; - } - - if (!result.has("timeout")) // logging the timeout event spams the log - { - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } - if (result.has("session")) - { - if (result.has("handle")) - { - if (!mAudioSession) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initiated." << LL_ENDL; - continue; - } - if (result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - } - - std::string message = result["session"]; - - if (message == "removed") - { - LL_DEBUGS("Voice") << "session removed" << LL_ENDL; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - break; - } - } - else if (result.has("login")) - { - std::string message = result["login"]; - if (message == "account_logout") - { - LL_DEBUGS("Voice") << "logged out" << LL_ENDL; - mIsLoggedIn = false; - mRelogRequested = true; - break; - } - } - } - - if (sShuttingDown) - { - return false; - } - - mIsInChannel = false; - LL_DEBUGS("Voice") << "terminating at end of runSession" << LL_ENDL; - terminateAudioSession(true); - - return true; -} - -void LLVivoxVoiceClient::sendCaptureAndRenderDevices() -{ - if (mCaptureDeviceDirty || mRenderDeviceDirty) - { - std::ostringstream stream; - - buildSetCaptureDevice(stream); - buildSetRenderDevice(stream); - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } -} - -void LLVivoxVoiceClient::recordingAndPlaybackMode() -{ - LL_INFOS("Voice") << "In voice capture/playback mode." << LL_ENDL; - - while (true) - { - LLSD command; - do - { - command = llcoro::suspendUntilEventOn(mVivoxPump); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(command) << LL_ENDL; - } while (!command.has("recplay")); - - if (command["recplay"].asString() == "quit") - { - mCaptureBufferMode = false; - break; - } - else if (command["recplay"].asString() == "record") - { - voiceRecordBuffer(); - } - else if (command["recplay"].asString() == "playback") - { - voicePlaybackBuffer(); - } - } - - LL_INFOS("Voice") << "Leaving capture/playback mode." << LL_ENDL; - mCaptureBufferRecording = false; - mCaptureBufferRecorded = false; - mCaptureBufferPlaying = false; - - return; -} - -int LLVivoxVoiceClient::voiceRecordBuffer() -{ - LLSD timeoutResult(LLSDMap("recplay", "stop")); - - LL_INFOS("Voice") << "Recording voice buffer" << LL_ENDL; - - LLSD result; - - captureBufferRecordStartSendMessage(); - notifyVoiceFontObservers(); - - do - { - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } while (!result.has("recplay")); - - mCaptureBufferRecorded = true; - - captureBufferRecordStopSendMessage(); - mCaptureBufferRecording = false; - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - return true; - /*TODO expand return to move directly into play*/ -} - -int LLVivoxVoiceClient::voicePlaybackBuffer() -{ - LLSD timeoutResult(LLSDMap("recplay", "stop")); - - LL_INFOS("Voice") << "Playing voice buffer" << LL_ENDL; - - LLSD result; - - do - { - captureBufferPlayStartSendMessage(mPreviewVoiceFont); - - // Store the voice font being previewed, so that we know to restart if it changes. - mPreviewVoiceFontLast = mPreviewVoiceFont; - - do - { - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } while (!result.has("recplay")); - - if (result["recplay"] == "playback") - continue; // restart playback... May be a font change. - - break; - } while (true); - - // Stop playing. - captureBufferPlayStopSendMessage(); - mCaptureBufferPlaying = false; - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - return true; -} - - -bool LLVivoxVoiceClient::performMicTuning() -{ - LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL; - mIsInTuningMode = true; - - while (mTuningMode && !sShuttingDown) - { - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } - - mIsInTuningMode = false; - - //--------------------------------------------------------------------- - return true; -} - -//========================================================================= - -void LLVivoxVoiceClient::closeSocket(void) -{ - mSocket.reset(); - sConnected = false; - mConnectorEstablished = false; - mAccountLoggedIn = false; -} - -void LLVivoxVoiceClient::loginSendMessage() -{ - std::ostringstream stream; - - bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps"); - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Login.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<AccountName>" << mAccountName << "</AccountName>" - << "<AccountPassword>" << mAccountPassword << "</AccountPassword>" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>" - << "<EnableBuddiesAndPresence>false</EnableBuddiesAndPresence>" - << "<EnablePresencePersistence>0</EnablePresencePersistence>" - << "<BuddyManagementMode>Application</BuddyManagementMode>" - << "<ParticipantPropertyFrequency>5</ParticipantPropertyFrequency>" - << (autoPostCrashDumps?"<AutopostCrashDumps>true</AutopostCrashDumps>":"") - << "</Request>\n\n\n"; - - LL_INFOS("Voice") << "Attempting voice login" << LL_ENDL; - writeString(stream.str()); -} - -void LLVivoxVoiceClient::logout() -{ - // Ensure that we'll re-request provisioning before logging in again - mAccountPassword.clear(); - mVoiceAccountServerURI.clear(); - - logoutSendMessage(); -} - -void LLVivoxVoiceClient::logoutSendMessage() -{ - if(mAccountLoggedIn) - { - LL_INFOS("Voice") << "Attempting voice logout" << LL_ENDL; - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Logout.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "</Request>" - << "\n\n\n"; - - mAccountLoggedIn = false; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionGroupCreateSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Create.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "<Type>Normal</Type>" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI - << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")" - << LL_ENDL; - - session->mCreateInProgress = true; - if(startAudio) - { - session->mMediaConnectInProgress = true; - } - - std::ostringstream stream; - stream - << "<Request requestId=\"" << session->mSIPURI << "\" action=\"Session.Create.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "<URI>" << session->mSIPURI << "</URI>"; - - static const std::string allowed_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~"; - - if(!session->mHash.empty()) - { - stream - << "<Password>" << LLURI::escape(session->mHash, allowed_chars) << "</Password>" - << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>"; - } - - stream - << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>" - << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>" - << "<Name>" << mChannelName << "</Name>" - << "</Request>\n\n\n"; - LL_WARNS("Voice") << "Session.Create: " << stream.str() << LL_ENDL; - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText) -{ - LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL; - - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL; - - session->mCreateInProgress = true; - if(startAudio) - { - session->mMediaConnectInProgress = true; - } - - std::string password; - if(!session->mHash.empty()) - { - static const std::string allowed_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~" - ; - password = LLURI::escape(session->mHash, allowed_chars); - } - - std::ostringstream stream; - stream - << "<Request requestId=\"" << session->mSIPURI << "\" action=\"SessionGroup.AddSession.1\">" - << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" - << "<URI>" << session->mSIPURI << "</URI>" - << "<Name>" << mChannelName << "</Name>" - << "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>" - << "<ConnectText>" << (startText?"true":"false") << "</ConnectText>" - << "<Password>" << password << "</Password>" - << "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>" - << "</Request>\n\n\n" - ; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionMediaConnectSendMessage(const sessionStatePtr_t &session) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "Connecting audio to session handle: " << session->mHandle - << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")" - << LL_ENDL; - - session->mMediaConnectInProgress = true; - - std::ostringstream stream; - - stream - << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.MediaConnect.1\">" - << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "<Media>Audio</Media>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionTextConnectSendMessage(const sessionStatePtr_t &session) -{ - LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL; - - std::ostringstream stream; - - stream - << "<Request requestId=\"" << session->mHandle << "\" action=\"Session.TextConnect.1\">" - << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionTerminate() -{ - mSessionTerminateRequested = true; -} - -void LLVivoxVoiceClient::requestRelog() -{ - mSessionTerminateRequested = true; - mRelogRequested = true; -} - - -void LLVivoxVoiceClient::leaveAudioSession() -{ - if(mAudioSession) - { - LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL; - - if(!mAudioSession->mHandle.empty()) - { - -#if RECORD_EVERYTHING - // HACK: for testing only - // Save looped recording - std::string savepath("/tmp/vivoxrecording"); - { - time_t now = time(NULL); - const size_t BUF_SIZE = 64; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - - strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); - savepath += time_str; - } - recordingLoopSave(savepath); -#endif - - sessionMediaDisconnectSendMessage(mAudioSession); - } - else - { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "called with no active session" << LL_ENDL; - } - sessionTerminate(); -} - -void LLVivoxVoiceClient::sessionTerminateSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - - sessionGroupTerminateSendMessage(session); - return; - /* - LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - */ -} - -void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Terminate.1\">" - << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - sessionGroupTerminateSendMessage(session); - return; - /* - LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.MediaDisconnect.1\">" - << "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "<Media>Audio</Media>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - */ - -} - - -void LLVivoxVoiceClient::getCaptureDevicesSendMessage() -{ - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetCaptureDevices.1\">" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::getRenderDevicesSendMessage() -{ - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetRenderDevices.1\">" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::clearCaptureDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mCaptureDevices.clear(); -} - -void LLVivoxVoiceClient::addCaptureDevice(const LLVoiceDevice& device) -{ - LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; - mCaptureDevices.push_back(device); -} - -LLVoiceDeviceList& LLVivoxVoiceClient::getCaptureDevices() -{ - return mCaptureDevices; -} - -void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) -{ - if(name == "Default") - { - if(!mCaptureDevice.empty()) - { - mCaptureDevice.clear(); - mCaptureDeviceDirty = true; - } - } - else - { - if(mCaptureDevice != name) - { - mCaptureDevice = name; - mCaptureDeviceDirty = true; - } - } -} -void LLVivoxVoiceClient::setDevicesListUpdated(bool state) -{ - mDevicesListUpdated = state; -} - -void LLVivoxVoiceClient::clearRenderDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mRenderDevices.clear(); -} - -void LLVivoxVoiceClient::addRenderDevice(const LLVoiceDevice& device) -{ - LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; - mRenderDevices.push_back(device); -} - -LLVoiceDeviceList& LLVivoxVoiceClient::getRenderDevices() -{ - return mRenderDevices; -} - -void LLVivoxVoiceClient::setRenderDevice(const std::string& name) -{ - if(name == "Default") - { - if(!mRenderDevice.empty()) - { - mRenderDevice.clear(); - mRenderDeviceDirty = true; - } - } - else - { - if(mRenderDevice != name) - { - mRenderDevice = name; - mRenderDeviceDirty = true; - } - } - -} - -void LLVivoxVoiceClient::tuningStart() -{ - LL_DEBUGS("Voice") << "Starting tuning" << LL_ENDL; - mTuningMode = true; - if (!mIsCoroutineActive) - { - LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", - boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - } - else if (mIsInChannel) - { - LL_DEBUGS("Voice") << "no channel" << LL_ENDL; - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::tuningStop() -{ - mTuningMode = false; - // force a renegotiation. - mCurrentParcelLocalID = 0; - mCurrentRegionName = ""; -} - -bool LLVivoxVoiceClient::inTuningMode() -{ - return mIsInTuningMode; -} - -void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) -{ - mTuningAudioFile = name; - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStart.1\">" - << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" - << "<Loop>" << (loop?"1":"0") << "</Loop>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningRenderStopSendMessage() -{ - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">" - << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int loop) -{ - LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL; - - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">" - << "<Duration>-1</Duration>" - << "<LoopToRenderDevice>" << loop << "</LoopToRenderDevice>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningCaptureStopSendMessage() -{ - LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL; - - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">" - << "</Request>\n\n\n"; - - writeString(stream.str()); - - mTuningEnergy = 0.0f; -} - -void LLVivoxVoiceClient::tuningSetMicVolume(float volume) -{ - int scaled_volume = scale_mic_volume(volume); - - if(scaled_volume != mTuningMicVolume) - { - mTuningMicVolume = scaled_volume; - mTuningMicVolumeDirty = true; - } -} - -void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume) -{ - int scaled_volume = scale_speaker_volume(volume); - - if(scaled_volume != mTuningSpeakerVolume) - { - mTuningSpeakerVolume = scaled_volume; - mTuningSpeakerVolumeDirty = true; - } -} - -float LLVivoxVoiceClient::tuningGetEnergy(void) -{ - return mTuningEnergy; -} - -bool LLVivoxVoiceClient::deviceSettingsAvailable() -{ - bool result = true; - - if(!sConnected) - result = false; - - if(mRenderDevices.empty()) - result = false; - - return result; -} -bool LLVivoxVoiceClient::deviceSettingsUpdated() -{ - bool updated = mDevicesListUpdated; - if (mDevicesListUpdated) - { - // a hot swap event or a polling of the audio devices has been parsed since the last redraw of the input and output device panel. - mDevicesListUpdated = false; // toggle the setting - } - return updated; -} - -void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList) -{ - if(clearCurrentList) - { - clearCaptureDevices(); - clearRenderDevices(); - } - getCaptureDevicesSendMessage(); - getRenderDevicesSendMessage(); -} - -void LLVivoxVoiceClient::daemonDied() -{ - // The daemon died, so the connection is gone. Reset everything and start over. - LL_WARNS("Voice") << "Connection to vivox daemon lost. Resetting state."<< LL_ENDL; - - //TODO: Try to relaunch the daemon -} - -void LLVivoxVoiceClient::giveUp() -{ - // All has failed. Clean up and stop trying. - LL_WARNS("Voice") << "Terminating Voice Service" << LL_ENDL; - closeSocket(); - cleanUp(); -} - -static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel) -{ - F32 nat[3], nup[3], nl[3]; // the new at, up, left vectors and the new position and velocity -// F32 nvel[3]; - F64 npos[3]; - - // The original XML command was sent like this: - /* - << "<Position>" - << "<X>" << pos[VX] << "</X>" - << "<Y>" << pos[VZ] << "</Y>" - << "<Z>" << pos[VY] << "</Z>" - << "</Position>" - << "<Velocity>" - << "<X>" << mAvatarVelocity[VX] << "</X>" - << "<Y>" << mAvatarVelocity[VZ] << "</Y>" - << "<Z>" << mAvatarVelocity[VY] << "</Z>" - << "</Velocity>" - << "<AtOrientation>" - << "<X>" << l.mV[VX] << "</X>" - << "<Y>" << u.mV[VX] << "</Y>" - << "<Z>" << a.mV[VX] << "</Z>" - << "</AtOrientation>" - << "<UpOrientation>" - << "<X>" << l.mV[VZ] << "</X>" - << "<Y>" << u.mV[VY] << "</Y>" - << "<Z>" << a.mV[VZ] << "</Z>" - << "</UpOrientation>" - << "<LeftOrientation>" - << "<X>" << l.mV [VY] << "</X>" - << "<Y>" << u.mV [VZ] << "</Y>" - << "<Z>" << a.mV [VY] << "</Z>" - << "</LeftOrientation>"; - */ - -#if 1 - // This was the original transform done when building the XML command - nat[0] = left.mV[VX]; - nat[1] = up.mV[VX]; - nat[2] = at.mV[VX]; - - nup[0] = left.mV[VZ]; - nup[1] = up.mV[VY]; - nup[2] = at.mV[VZ]; - - nl[0] = left.mV[VY]; - nl[1] = up.mV[VZ]; - nl[2] = at.mV[VY]; - - npos[0] = pos.mdV[VX]; - npos[1] = pos.mdV[VZ]; - npos[2] = pos.mdV[VY]; - -// nvel[0] = vel.mV[VX]; -// nvel[1] = vel.mV[VZ]; -// nvel[2] = vel.mV[VY]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } - - // This was the original transform done in the SDK - nat[0] = at.mV[2]; - nat[1] = 0; // y component of at vector is always 0, this was up[2] - nat[2] = -1 * left.mV[2]; - - // We override whatever the application gives us - nup[0] = 0; // x component of up vector is always 0 - nup[1] = 1; // y component of up vector is always 1 - nup[2] = 0; // z component of up vector is always 0 - - nl[0] = at.mV[0]; - nl[1] = 0; // y component of left vector is always zero, this was up[0] - nl[2] = -1 * left.mV[0]; - - npos[2] = pos.mdV[2] * -1.0; - npos[1] = pos.mdV[1]; - npos[0] = pos.mdV[0]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } -#else - // This is the compose of the two transforms (at least, that's what I'm trying for) - nat[0] = at.mV[VX]; - nat[1] = 0; // y component of at vector is always 0, this was up[2] - nat[2] = -1 * up.mV[VZ]; - - // We override whatever the application gives us - nup[0] = 0; // x component of up vector is always 0 - nup[1] = 1; // y component of up vector is always 1 - nup[2] = 0; // z component of up vector is always 0 - - nl[0] = left.mV[VX]; - nl[1] = 0; // y component of left vector is always zero, this was up[0] - nl[2] = -1 * left.mV[VY]; - - npos[0] = pos.mdV[VX]; - npos[1] = pos.mdV[VZ]; - npos[2] = pos.mdV[VY] * -1.0; - - nvel[0] = vel.mV[VX]; - nvel[1] = vel.mV[VZ]; - nvel[2] = vel.mV[VY]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } - -#endif -} - -void LLVivoxVoiceClient::setHidden(bool hidden) -{ - mHidden = hidden; - - if (mHidden && inSpatialChannel()) - { - // get out of the channel entirely - leaveAudioSession(); - } - else - { - sendPositionAndVolumeUpdate(); - } -} - -void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) -{ - std::ostringstream stream; - - if (mSpatialCoordsDirty && inSpatialChannel()) - { - LLVector3 l, u, a, vel; - LLVector3d pos; - - mSpatialCoordsDirty = false; - - // Always send both speaker and listener positions together. - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">" - << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"; - - stream << "<SpeakerPosition>"; - - LLMatrix3 avatarRot = mAvatarRot.getMatrix3(); - -// LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL; - l = avatarRot.getLeftRow(); - u = avatarRot.getUpRow(); - a = avatarRot.getFwdRow(); - - pos = mAvatarPosition; - vel = mAvatarVelocity; - - // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore. - // The old transform is replicated by this function. - oldSDKTransform(l, u, a, pos, vel); - - if (mHidden) - { - for (int i=0;i<3;++i) - { - pos.mdV[i] = VX_NULL_POSITION; - } - } - - stream - << "<Position>" - << "<X>" << pos.mdV[VX] << "</X>" - << "<Y>" << pos.mdV[VY] << "</Y>" - << "<Z>" << pos.mdV[VZ] << "</Z>" - << "</Position>" - << "<Velocity>" - << "<X>" << vel.mV[VX] << "</X>" - << "<Y>" << vel.mV[VY] << "</Y>" - << "<Z>" << vel.mV[VZ] << "</Z>" - << "</Velocity>" - << "<AtOrientation>" - << "<X>" << a.mV[VX] << "</X>" - << "<Y>" << a.mV[VY] << "</Y>" - << "<Z>" << a.mV[VZ] << "</Z>" - << "</AtOrientation>" - << "<UpOrientation>" - << "<X>" << u.mV[VX] << "</X>" - << "<Y>" << u.mV[VY] << "</Y>" - << "<Z>" << u.mV[VZ] << "</Z>" - << "</UpOrientation>" - << "<LeftOrientation>" - << "<X>" << l.mV [VX] << "</X>" - << "<Y>" << l.mV [VY] << "</Y>" - << "<Z>" << l.mV [VZ] << "</Z>" - << "</LeftOrientation>" - ; - - stream << "</SpeakerPosition>"; - - stream << "<ListenerPosition>"; - - LLVector3d earPosition; - LLVector3 earVelocity; - LLMatrix3 earRot; - - switch(mEarLocation) - { - case earLocCamera: - default: - earPosition = mCameraPosition; - earVelocity = mCameraVelocity; - earRot = mCameraRot; - break; - - case earLocAvatar: - earPosition = mAvatarPosition; - earVelocity = mAvatarVelocity; - earRot = avatarRot; - break; - - case earLocMixed: - earPosition = mAvatarPosition; - earVelocity = mAvatarVelocity; - earRot = mCameraRot; - break; - } - - l = earRot.getLeftRow(); - u = earRot.getUpRow(); - a = earRot.getFwdRow(); - - pos = earPosition; - vel = earVelocity; - - - oldSDKTransform(l, u, a, pos, vel); - - if (mHidden) - { - for (int i=0;i<3;++i) - { - pos.mdV[i] = VX_NULL_POSITION; - } - } - - stream - << "<Position>" - << "<X>" << pos.mdV[VX] << "</X>" - << "<Y>" << pos.mdV[VY] << "</Y>" - << "<Z>" << pos.mdV[VZ] << "</Z>" - << "</Position>" - << "<Velocity>" - << "<X>" << vel.mV[VX] << "</X>" - << "<Y>" << vel.mV[VY] << "</Y>" - << "<Z>" << vel.mV[VZ] << "</Z>" - << "</Velocity>" - << "<AtOrientation>" - << "<X>" << a.mV[VX] << "</X>" - << "<Y>" << a.mV[VY] << "</Y>" - << "<Z>" << a.mV[VZ] << "</Z>" - << "</AtOrientation>" - << "<UpOrientation>" - << "<X>" << u.mV[VX] << "</X>" - << "<Y>" << u.mV[VY] << "</Y>" - << "<Z>" << u.mV[VZ] << "</Z>" - << "</UpOrientation>" - << "<LeftOrientation>" - << "<X>" << l.mV [VX] << "</X>" - << "<Y>" << l.mV [VY] << "</Y>" - << "<Z>" << l.mV [VZ] << "</Z>" - << "</LeftOrientation>" - ; - - stream << "</ListenerPosition>"; - - stream << "<ReqDispositionType>1</ReqDispositionType>"; //do not generate responses for update requests - stream << "</Request>\n\n\n"; - } - - if(mAudioSession && (mAudioSession->mVolumeDirty || mAudioSession->mMuteDirty)) - { - participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - - mAudioSession->mVolumeDirty = false; - mAudioSession->mMuteDirty = false; - - for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) - { - participantStatePtr_t p(iter->second); - - if(p->mVolumeDirty) - { - // Can't set volume/mute for yourself - if(!p->mIsSelf) - { - // scale from the range 0.0-1.0 to vivox volume in the range 0-100 - S32 volume = ll_round(p->mVolume / VOLUME_SCALE_VIVOX); - bool mute = p->mOnMuteList; - - if(mute) - { - // SetParticipantMuteForMe doesn't work in p2p sessions. - // If we want the user to be muted, set their volume to 0 as well. - // This isn't perfect, but it will at least reduce their volume to a minimum. - volume = 0; - // Mark the current volume level as set to prevent incoming events - // changing it to 0, so that we can return to it when unmuting. - p->mVolumeSet = true; - } - - if(volume == 0) - { - mute = true; - } - - LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL; - - // SLIM SDK: Send both volume and mute commands. - - // Send a "volume for me" command for the user. - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">" - << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>" - << "<ParticipantURI>" << p->mURI << "</ParticipantURI>" - << "<Volume>" << volume << "</Volume>" - << "</Request>\n\n\n"; - - if(!mAudioSession->mIsP2P) - { - // Send a "mute for me" command for the user - // Doesn't work in P2P sessions - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantMuteForMe.1\">" - << "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>" - << "<ParticipantURI>" << p->mURI << "</ParticipantURI>" - << "<Mute>" << (mute?"1":"0") << "</Mute>" - << "<Scope>Audio</Scope>" - << "</Request>\n\n\n"; - } - } - - p->mVolumeDirty = false; - } - } - } - - std::string update(stream.str()); - if(!update.empty()) - { - LL_DEBUGS("VoiceUpdate") << "sending update " << update << LL_ENDL; - writeString(update); - } - -} - -void LLVivoxVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) -{ - if(mCaptureDeviceDirty) - { - LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">" - << "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>" - << "</Request>" - << "\n\n\n"; - - mCaptureDeviceDirty = false; - } -} - -void LLVivoxVoiceClient::buildSetRenderDevice(std::ostringstream &stream) -{ - if(mRenderDeviceDirty) - { - LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetRenderDevice.1\">" - << "<RenderDeviceSpecifier>" << mRenderDevice << "</RenderDeviceSpecifier>" - << "</Request>" - << "\n\n\n"; - mRenderDeviceDirty = false; - } -} - -void LLVivoxVoiceClient::sendLocalAudioUpdates() -{ - // Check all of the dirty states and then send messages to those needing to be changed. - // Tuningmode hands its own mute settings. - std::ostringstream stream; - - if (mMuteMicDirty && !mTuningMode) - { - mMuteMicDirty = false; - - // Send a local mute command. - - LL_INFOS("Voice") << "Sending MuteLocalMic command with parameter " << (mMuteMic ? "true" : "false") << LL_ENDL; - - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>" << (mMuteMic ? "true" : "false") << "</Value>" - << "</Request>\n\n\n"; - - } - - if (mSpeakerMuteDirty && !mTuningMode) - { - const char *muteval = ((mSpeakerVolume <= scale_speaker_volume(0)) ? "true" : "false"); - - mSpeakerMuteDirty = false; - - LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL; - - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalSpeaker.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>" << muteval << "</Value>" - << "</Request>\n\n\n"; - - } - - if (mSpeakerVolumeDirty) - { - mSpeakerVolumeDirty = false; - - LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL; - - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalSpeakerVolume.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>" << mSpeakerVolume << "</Value>" - << "</Request>\n\n\n"; - - } - - if (mMicVolumeDirty) - { - mMicVolumeDirty = false; - - LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL; - - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalMicVolume.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>" << mMicVolume << "</Value>" - << "</Request>\n\n\n"; - } - - - if (!stream.str().empty()) - { - writeString(stream.str()); - } -} - -/** - * Because of the recurring voice cutout issues (SL-15072) we are going to try - * to disable the automatic VAD (Voice Activity Detection) and set the associated - * parameters directly. We will expose them via Debug Settings and that should - * let us iterate on a collection of values that work for us. Hopefully! - * - * From the VIVOX Docs: - * - * VadAuto: A flag indicating if the automatic VAD is enabled (1) or disabled (0) - * - * VadHangover: The time (in milliseconds) that it takes - * for the VAD to switch back to silence from speech mode after the last speech - * frame has been detected. - * - * VadNoiseFloor: A dimensionless value between 0 and - * 20000 (default 576) that controls the maximum level at which the noise floor - * may be set at by the VAD's noise tracking. Too low of a value will make noise - * tracking ineffective (A value of 0 disables noise tracking and the VAD then - * relies purely on the sensitivity property). Too high of a value will make - * long speech classifiable as noise. - * - * VadSensitivity: A dimensionless value between 0 and - * 100, indicating the 'sensitivity of the VAD'. Increasing this value corresponds - * to decreasing the sensitivity of the VAD (i.e. '0' is most sensitive, - * while 100 is 'least sensitive') - */ -void LLVivoxVoiceClient::setupVADParams(unsigned int vad_auto, - unsigned int vad_hangover, - unsigned int vad_noise_floor, - unsigned int vad_sensitivity) -{ - std::ostringstream stream; - - LL_INFOS("Voice") << "Setting the automatic VAD to " - << (vad_auto ? "True" : "False") - << " and discrete values to" - << " VadHangover = " << vad_hangover - << ", VadSensitivity = " << vad_sensitivity - << ", VadNoiseFloor = " << vad_noise_floor - << LL_ENDL; - - // Create a request to set the VAD parameters: - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetVadProperties.1\">" - << "<VadAuto>" << vad_auto << "</VadAuto>" - << "<VadHangover>" << vad_hangover << "</VadHangover>" - << "<VadSensitivity>" << vad_sensitivity << "</VadSensitivity>" - << "<VadNoiseFloor>" << vad_noise_floor << "</VadNoiseFloor>" - << "</Request>\n\n\n"; - - if (!stream.str().empty()) - { - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::onVADSettingsChange() -{ - // pick up the VAD variables (one of which was changed) - unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); - unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover"); - unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor"); - unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity"); - - // build a VAD params change request and send it to SLVoice - setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity); -} - -///////////////////////////// -// Response/Event handlers - -void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID) -{ - LLSD result = LLSD::emptyMap(); - - if(statusCode == 0) - { - // Connector created, move forward. - if (connectorHandle == LLVivoxSecurity::getInstance()->connectorHandle()) - { - LL_INFOS("Voice") << "Voice connector succeeded, Vivox SDK version is " << versionID << " connector handle " << connectorHandle << LL_ENDL; - mVoiceVersion.serverVersion = versionID; - mConnectorEstablished = true; - mTerminateDaemon = false; - - result["connector"] = LLSD::Boolean(true); - } - else - { - // This shouldn't happen - we are somehow out of sync with SLVoice - // or possibly there are two things trying to run SLVoice at once - // or someone is trying to hack into it. - LL_WARNS("Voice") << "Connector returned wrong handle " - << "(" << connectorHandle << ")" - << " expected (" << LLVivoxSecurity::getInstance()->connectorHandle() << ")" - << LL_ENDL; - result["connector"] = LLSD::Boolean(false); - // Give up. - mTerminateDaemon = true; - } - } - else if (statusCode == 10028) // web request timeout prior to login - { - // this is usually fatal, but a long timeout might work - result["connector"] = LLSD::Boolean(false); - result["retry"] = LLSD::Real(CONNECT_ATTEMPT_TIMEOUT); - - LL_WARNS("Voice") << "Voice connection failed" << LL_ENDL; - } - else if (statusCode == 10006) // name resolution failure - a shorter retry may work - { - // some networks have slower DNS, but a short timeout might let it catch up - result["connector"] = LLSD::Boolean(false); - result["retry"] = LLSD::Real(CONNECT_DNS_TIMEOUT); - - LL_WARNS("Voice") << "Voice connection DNS lookup failed" << LL_ENDL; - } - else // unknown failure - give up - { - LL_WARNS("Voice") << "Voice connection failure ("<< statusCode << "): " << statusString << LL_ENDL; - mTerminateDaemon = true; - result["connector"] = LLSD::Boolean(false); - } - - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases) -{ - LLSD result = LLSD::emptyMap(); - - LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; - - // Status code of 20200 means "bad password". We may want to special-case that at some point. - - if ( statusCode == HTTP_UNAUTHORIZED ) - { - // Login failure which is probably caused by the delay after a user's password being updated. - LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - result["login"] = LLSD::String("retry"); - } - else if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - result["login"] = LLSD::String("failed"); - } - else - { - // Login succeeded, move forward. - mAccountLoggedIn = true; - mNumberOfAliases = numberOfAliases; - result["login"] = LLSD::String("response_ok"); - } - - mVivoxPump.post(result); - -} - -void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ - sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - - if(session) - { - session->mCreateInProgress = false; - } - - if(statusCode != 0) - { - LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) - { - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - if(session == mAudioSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "failed") - ("reason", LLSD::Integer(statusCode))); - - mVivoxPump.post(vivoxevent); - } - else - { - reapSession(session); - } - } - } - else - { - LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL; - if(session) - { - setSessionHandle(session, sessionHandle); - } - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "created")); - - mVivoxPump.post(vivoxevent); - } -} - -void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ - sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - - if(session) - { - session->mCreateInProgress = false; - } - - if(statusCode != 0) - { - LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) - { - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - if(session == mAudioSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "failed")); - - mVivoxPump.post(vivoxevent); - } - else - { - reapSession(session); - } - } - } - else - { - LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL; - if(session) - { - setSessionHandle(session, sessionHandle); - } - - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "added")); - - mVivoxPump.post(vivoxevent); - - } -} - -void LLVivoxVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString) -{ - sessionStatePtr_t session(findSession(requestId)); - // 1026 is session already has media, somehow mediaconnect was called twice on the same session. - // set the session info to reflect that the user is already connected. - if (statusCode == 1026) - { - session->mVoiceActive = true; - session->mMediaConnectInProgress = false; - session->mMediaStreamState = streamStateConnected; - //session->mTextStreamState = streamStateConnected; - session->mErrorStatusCode = 0; - } - else if (statusCode != 0) - { - LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL; - if (session) - { - session->mMediaConnectInProgress = false; - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - } - } - else - { - LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL; - } -} - -void LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusString) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL; - // Should this ever fail? do we care if it does? - } - LLSD vivoxevent(LLSDMap("logout", LLSD::Boolean(true))); - - mVivoxPump.post(vivoxevent); -} - -void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL; - // Should this ever fail? do we care if it does? - } - - sConnected = false; - mShutdownComplete = true; - - LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); - - mVivoxPump.post(vivoxevent); -} - -void LLVivoxVoiceClient::sessionAddedEvent( - std::string &uriString, - std::string &alias, - std::string &sessionHandle, - std::string &sessionGroupHandle, - bool isChannel, - bool incoming, - std::string &nameString, - std::string &applicationString) -{ - sessionStatePtr_t session; - - LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL; - - session = addSession(uriString, sessionHandle); - if(session) - { - session->mGroupHandle = sessionGroupHandle; - session->mIsChannel = isChannel; - session->mIncoming = incoming; - session->mAlias = alias; - - // Generate a caller UUID -- don't need to do this for channels - if(!session->mIsChannel) - { - if(IDFromName(session->mSIPURI, session->mCallerID)) - { - // Normal URI(base64-encoded UUID) - } - else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID)) - { - // Wrong URI, but an alias is available. Stash the incoming URI as an alternate - session->mAlternateSIPURI = session->mSIPURI; - - // and generate a proper URI from the ID. - setSessionURI(session, sipURIFromID(session->mCallerID)); - } - else - { - LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL; - session->mCallerID.generate(session->mSIPURI); - session->mSynthesizedCallerID = true; - - // Can't look up the name in this case -- we have to extract it from the URI. - std::string namePortion = nameFromsipURI(session->mSIPURI); - if(namePortion.empty()) - { - // Didn't seem to be a SIP URI, just use the whole provided name. - namePortion = nameString; - } - - // Some incoming names may be separated with an underscore instead of a space. Fix this. - LLStringUtil::replaceChar(namePortion, '_', ' '); - - // Act like we just finished resolving the name (this stores it in all the right places) - avatarNameResolved(session->mCallerID, namePortion); - } - - LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL; - - if(!session->mSynthesizedCallerID) - { - // If we got here, we don't have a proper name. Initiate a lookup. - lookupName(session->mCallerID); - } - } - } -} - -void LLVivoxVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle) -{ - LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL; - -#if USE_SESSION_GROUPS - if(mMainSessionGroupHandle.empty()) - { - // This is the first (i.e. "main") session group. Save its handle. - mMainSessionGroupHandle = sessionGroupHandle; - } - else - { - LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL; - } -#endif -} - -void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) -{ - LL_DEBUGS("Voice") << "Joined Audio Session" << LL_ENDL; - if(mAudioSession != session) - { - sessionStatePtr_t oldSession = mAudioSession; - - mAudioSession = session; - - // The old session may now need to be deleted. - reapSession(oldSession); - } - - // This is the session we're joining. - if(mIsJoiningSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle)) - ("session", "joined")); - - mVivoxPump.post(vivoxevent); - - // Add the current user as a participant here. - participantStatePtr_t participant(session->addParticipant(sipURIFromName(mAccountName))); - if(participant) - { - participant->mIsSelf = true; - lookupName(participant->mAvatarID); - - LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - } - - if(!session->mIsChannel) - { - // this is a p2p session. Make sure the other end is added as a participant. - participantStatePtr_t participant(session->addParticipant(session->mSIPURI)); - if(participant) - { - if(participant->mAvatarIDValid) - { - lookupName(participant->mAvatarID); - } - else if(!session->mName.empty()) - { - participant->mDisplayName = session->mName; - avatarNameResolved(participant->mAvatarID, session->mName); - } - - // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here? - LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - } - } - } -} - -void LLVivoxVoiceClient::sessionRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle) -{ - LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL; - - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - leftAudioSession(session); - - // This message invalidates the session's handle. Set it to empty. - clearSessionHandle(session); - - // This also means that the session's session group is now empty. - // Terminate the session group so it doesn't leak. - sessionGroupTerminateSendMessage(session); - - // Reset the media state (we now have no info) - session->mMediaStreamState = streamStateUnknown; - //session->mTextStreamState = streamStateUnknown; - - // Conditionally delete the session - reapSession(session); - } - else - { - // Already reaped this session. - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL; - } - -} - -void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session) -{ - if(session) - { - - if(session->mCreateInProgress) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL; - } - else if(session->mMediaConnectInProgress) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL; - } - else if(session == mAudioSession) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL; - } - else if(session == mNextAudioSession) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL; - } - else - { - // We don't have a reason to keep tracking this session, so just delete it. - LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL; - deleteSession(session); - } - } - else - { -// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL; - } -} - -// Returns true if the session seems to indicate we've moved to a region on a different voice server -bool LLVivoxVoiceClient::sessionNeedsRelog(const sessionStatePtr_t &session) -{ - bool result = false; - - if(session) - { - // Only make this check for spatial channels (so it won't happen for group or p2p calls) - if(session->mIsSpatial) - { - std::string::size_type atsign; - - atsign = session->mSIPURI.find("@"); - - if(atsign != std::string::npos) - { - std::string urihost = session->mSIPURI.substr(atsign + 1); - if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str())) - { - // The hostname in this URI is different from what we expect. This probably means we need to relog. - - // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of - // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator. - - result = true; - } - } - } - } - - return result; -} - -void LLVivoxVoiceClient::leftAudioSession(const sessionStatePtr_t &session) -{ - if (mAudioSession == session) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle)) - ("session", "removed")); - - mVivoxPump.post(vivoxevent); - } -} - -void LLVivoxVoiceClient::accountLoginStateChangeEvent( - std::string &accountHandle, - int statusCode, - std::string &statusString, - int state) -{ - LLSD levent = LLSD::emptyMap(); - - /* - According to Mike S., status codes for this event are: - login_state_logged_out=0, - login_state_logged_in = 1, - login_state_logging_in = 2, - login_state_logging_out = 3, - login_state_resetting = 4, - login_state_error=100 - */ - - LL_DEBUGS("Voice") << "state change event: " << state << LL_ENDL; - switch(state) - { - case 1: - levent["login"] = LLSD::String("account_login"); - - mVivoxPump.post(levent); - break; - case 2: - break; - - case 3: - levent["login"] = LLSD::String("account_loggingOut"); - - mVivoxPump.post(levent); - break; - - case 4: - break; - - case 100: - LL_WARNS("Voice") << "account state event error" << LL_ENDL; - break; - - case 0: - levent["login"] = LLSD::String("account_logout"); - - mVivoxPump.post(levent); - break; - - default: - //Used to be a commented out warning - LL_WARNS("Voice") << "unknown account state event: " << state << LL_ENDL; - break; - } -} - -void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType) -{ - LLSD result; - - if (mediaCompletionType == "AuxBufferAudioCapture") - { - mCaptureBufferRecording = false; - result["recplay"] = "end"; - } - else if (mediaCompletionType == "AuxBufferAudioRender") - { - // Ignore all but the last stop event - if (--mPlayRequestCount <= 0) - { - mCaptureBufferPlaying = false; - result["recplay"] = "end"; -// result["recplay"] = "done"; - } - } - else - { - LL_WARNS("Voice") << "Unknown MediaCompletionType: " << mediaCompletionType << LL_ENDL; - } - - if (!result.isUndefined()) - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::mediaStreamUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - int statusCode, - std::string &statusString, - int state, - bool incoming) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - - LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL; - - if(session) - { - // We know about this session - - // Save the state for later use - session->mMediaStreamState = state; - - switch(statusCode) - { - case 0: - case HTTP_OK: - // generic success - // Don't change the saved error code (it may have been set elsewhere) - break; - default: - // save the status code for later - session->mErrorStatusCode = statusCode; - break; - } - - switch(state) - { - case streamStateDisconnecting: - case streamStateIdle: - // Standard "left audio session", Vivox state 'disconnected' - session->mVoiceActive = false; - session->mMediaConnectInProgress = false; - leftAudioSession(session); - break; - - case streamStateConnected: - session->mVoiceActive = true; - session->mMediaConnectInProgress = false; - joinedAudioSession(session); - case streamStateConnecting: // do nothing, but prevents a warning getting into the logs. - break; - - case streamStateRinging: - if(incoming) - { - // Send the voice chat invite to the GUI layer - // TODO: Question: Should we correlate with the mute list here? - session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID); - session->mVoiceInvitePending = true; - if(session->mName.empty()) - { - lookupName(session->mCallerID); - } - else - { - // Act like we just finished resolving the name - avatarNameResolved(session->mCallerID, session->mName); - } - } - break; - - default: - LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; - break; - - } - - } - else - { - // session disconnectintg and disconnected events arriving after we have already left the session. - LL_DEBUGS("Voice") << "session " << sessionHandle << " not found"<< LL_ENDL; - } -} - -void LLVivoxVoiceClient::participantAddedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString, - std::string &displayNameString, - int participantType) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->addParticipant(uriString)); - if(participant) - { - participant->mAccountName = nameString; - - LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - - if(participant->mAvatarIDValid) - { - // Initiate a lookup - lookupName(participant->mAvatarID); - } - else - { - // If we don't have a valid avatar UUID, we need to fill in the display name to make the active speakers floater work. - std::string namePortion = nameFromsipURI(uriString); - if(namePortion.empty()) - { - // Problem with the SIP URI, fall back to the display name - namePortion = displayNameString; - } - if(namePortion.empty()) - { - // Problems with both of the above, fall back to the account name - namePortion = nameString; - } - - // Set the display name (which is a hint to the active speakers window not to do its own lookup) - participant->mDisplayName = namePortion; - avatarNameResolved(participant->mAvatarID, namePortion); - } - } - } -} - -void LLVivoxVoiceClient::participantRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - if(participant) - { - session->removeParticipant(participant); - } - else - { - LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL; - } - } - else - { - // a late arriving event on a session we have already left. - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; - } -} - - -void LLVivoxVoiceClient::participantUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - bool isModeratorMuted, - bool isSpeaking, - int volume, - F32 energy) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - - if(participant) - { - //LL_INFOS("Voice") << "Participant Update for " << participant->mDisplayName << LL_ENDL; - - participant->mIsSpeaking = isSpeaking; - participant->mIsModeratorMuted = isModeratorMuted; - - // SLIM SDK: convert range: ensure that energy is set to zero if is_speaking is false - if (isSpeaking) - { - participant->mSpeakingTimeout.reset(); - participant->mPower = energy; - } - else - { - participant->mPower = 0.0f; - } - - // Ignore incoming volume level if it has been explicitly set, or there - // is a volume or mute change pending. - if ( !participant->mVolumeSet && !participant->mVolumeDirty) - { - participant->mVolume = (F32)volume * VOLUME_SCALE_VIVOX; - } - - // *HACK: mantipov: added while working on EXT-3544 - /* - Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE - LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER. - - participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted - Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug. - Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates. - - But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post() - voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager - and event is not fired. - - So, we have to call LLSpeakerMgr::update() here. - */ - LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel(); - - // ignore session ID of local chat - if (voice_cnl && voice_cnl->getSessionID().notNull()) - { - LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID()); - if (speaker_manager) - { - speaker_manager->update(true); - - // also initialize voice moderate_mode depend on Agent's participant. See EXT-6937. - // *TODO: remove once a way to request the current voice channel moderation mode is implemented. - if (gAgent.getID() == participant->mAvatarID) - { - speaker_manager->initVoiceModerateMode(); - } - } - } - } - else - { - LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; - } -} - -void LLVivoxVoiceClient::messageEvent( - std::string &sessionHandle, - std::string &uriString, - std::string &alias, - std::string &messageHeader, - std::string &messageBody, - std::string &applicationString) -{ - LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL; -// LL_DEBUGS("Voice") << " header " << messageHeader << ", body: \n" << messageBody << LL_ENDL; - - LL_INFOS("Voice") << "Vivox raw message:" << std::endl << messageBody << LL_ENDL; - - if(messageHeader.find(HTTP_CONTENT_TEXT_HTML) != std::string::npos) - { - std::string message; - - { - const std::string startMarker = "<body"; - const std::string startMarker2 = ">"; - const std::string endMarker = "</body>"; - const std::string startSpan = "<span"; - const std::string endSpan = "</span>"; - std::string::size_type start; - std::string::size_type end; - - // Default to displaying the raw string, so the message gets through. - message = messageBody; - - // Find the actual message text within the XML fragment - start = messageBody.find(startMarker); - start = messageBody.find(startMarker2, start); - end = messageBody.find(endMarker); - - if(start != std::string::npos) - { - start += startMarker2.size(); - - if(end != std::string::npos) - end -= start; - - message.assign(messageBody, start, end); - } - else - { - // Didn't find a <body>, try looking for a <span> instead. - start = messageBody.find(startSpan); - start = messageBody.find(startMarker2, start); - end = messageBody.find(endSpan); - - if(start != std::string::npos) - { - start += startMarker2.size(); - - if(end != std::string::npos) - end -= start; - - message.assign(messageBody, start, end); - } - } - } - -// LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL; - - // strip formatting tags - { - std::string::size_type start; - std::string::size_type end; - - while((start = message.find('<')) != std::string::npos) - { - if((end = message.find('>', start + 1)) != std::string::npos) - { - // Strip out the tag - message.erase(start, (end + 1) - start); - } - else - { - // Avoid an infinite loop - break; - } - } - } - - // Decode ampersand-escaped chars - { - std::string::size_type mark = 0; - - // The text may contain text encoded with <, >, and & - mark = 0; - while((mark = message.find("<", mark)) != std::string::npos) - { - message.replace(mark, 4, "<"); - mark += 1; - } - - mark = 0; - while((mark = message.find(">", mark)) != std::string::npos) - { - message.replace(mark, 4, ">"); - mark += 1; - } - - mark = 0; - while((mark = message.find("&", mark)) != std::string::npos) - { - message.replace(mark, 5, "&"); - mark += 1; - } - } - - // strip leading/trailing whitespace (since we always seem to get a couple newlines) - LLStringUtil::trim(message); - -// LL_DEBUGS("Voice") << " stripped message = \n" << message << LL_ENDL; - - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - bool is_do_not_disturb = gAgent.isDoNotDisturb(); - bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat); - bool is_linden = LLMuteList::isLinden(session->mName); - LLChat chat; - - chat.mMuted = is_muted && !is_linden; - - if(!chat.mMuted) - { - chat.mFromID = session->mCallerID; - chat.mFromName = session->mName; - chat.mSourceType = CHAT_SOURCE_AGENT; - - if(is_do_not_disturb && !is_linden) - { - // TODO: Question: Return do not disturb mode response here? Or maybe when session is started instead? - } - - LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL; - LLIMMgr::getInstance()->addMessage(session->mIMSessionID, - session->mCallerID, - session->mName.c_str(), - message.c_str(), - false, - LLStringUtil::null, // default arg - IM_NOTHING_SPECIAL, // default arg - 0, // default arg - LLUUID::null, // default arg - LLVector3::zero); // default arg - } - } - } -} - -void LLVivoxVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - if(participant) - { - if (!stricmp(notificationType.c_str(), "Typing")) - { - // Other end started typing - // TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart(). - // It requires some info for the message, which we don't have here. - } - else if (!stricmp(notificationType.c_str(), "NotTyping")) - { - // Other end stopped typing - // TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop(). - // It requires some info for the message, which we don't have here. - } - else - { - LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL; - } -} - -void LLVivoxVoiceClient::voiceServiceConnectionStateChangedEvent(int statusCode, std::string &statusString, std::string &build_id) -{ - // We don't generally need to process this. However, one occurence is when we first connect, and so it is the - // earliest opportunity to learn what we're connected to. - if (statusCode) - { - LL_WARNS("Voice") << "VoiceServiceConnectionStateChangedEvent statusCode: " << statusCode << - "statusString: " << statusString << LL_ENDL; - return; - } - if (build_id.empty()) - { - return; - } - mVoiceVersion.mBuildVersion = build_id; -} - -void LLVivoxVoiceClient::auxAudioPropertiesEvent(F32 energy) -{ - LL_DEBUGS("VoiceEnergy") << "got energy " << energy << LL_ENDL; - mTuningEnergy = energy; -} - -void LLVivoxVoiceClient::muteListChanged() -{ - // The user's mute list has been updated. Go through the current participant list and sync it with the mute list. - if(mAudioSession) - { - participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - - for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) - { - participantStatePtr_t p(iter->second); - - // Check to see if this participant is on the mute list already - if(p->updateMuteState()) - mAudioSession->mVolumeDirty = true; - } - } -} - -///////////////////////////// -// Managing list of participants -LLVivoxVoiceClient::participantState::participantState(const std::string &uri) : - mURI(uri), - mPTT(false), - mIsSpeaking(false), - mIsModeratorMuted(false), - mLastSpokeTimestamp(0.f), - mPower(0.f), - mVolume(LLVoiceClient::VOLUME_DEFAULT), - mUserVolume(0), - mOnMuteList(false), - mVolumeSet(false), - mVolumeDirty(false), - mAvatarIDValid(false), - mIsSelf(false) -{ -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addParticipant(const std::string &uri) -{ - participantStatePtr_t result; - bool useAlternateURI = false; - - // Note: this is mostly the body of LLVivoxVoiceClient::sessionState::findParticipant(), but since we need to know if it - // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here. - { - participantMap::iterator iter = mParticipantsByURI.find(uri); - - if(iter == mParticipantsByURI.end()) - { - if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) - { - // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. - // Use mSIPURI instead, since it will be properly encoded. - iter = mParticipantsByURI.find(mSIPURI); - useAlternateURI = true; - } - } - - if(iter != mParticipantsByURI.end()) - { - result = iter->second; - } - } - - if(!result) - { - // participant isn't already in one list or the other. - result = std::make_shared<participantState>(useAlternateURI?mSIPURI:uri); - mParticipantsByURI.insert(participantMap::value_type(result->mURI, result)); - mParticipantsChanged = true; - - // Try to do a reverse transform on the URI to get the GUID back. - { - LLUUID id; - if(LLVivoxVoiceClient::getInstance()->IDFromName(result->mURI, id)) - { - result->mAvatarIDValid = true; - result->mAvatarID = id; - } - else - { - // Create a UUID by hashing the URI, but do NOT set mAvatarIDValid. - // This indicates that the ID will not be in the name cache. - result->mAvatarID.generate(uri); - } - } - - if(result->updateMuteState()) - { - mMuteDirty = true; - } - - mParticipantsByUUID.insert(participantUUIDMap::value_type(result->mAvatarID, result)); - - if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume)) - { - result->mVolumeDirty = true; - mVolumeDirty = true; - } - - LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL; - } - - return result; -} - -bool LLVivoxVoiceClient::participantState::updateMuteState() -{ - bool result = false; - - bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); - if(mOnMuteList != isMuted) - { - mOnMuteList = isMuted; - mVolumeDirty = true; - result = true; - } - return result; -} - -bool LLVivoxVoiceClient::participantState::isAvatar() -{ - return mAvatarIDValid; -} - -void LLVivoxVoiceClient::sessionState::removeParticipant(const LLVivoxVoiceClient::participantStatePtr_t &participant) -{ - if(participant) - { - participantMap::iterator iter = mParticipantsByURI.find(participant->mURI); - participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(participant->mAvatarID); - - LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; - - if(iter == mParticipantsByURI.end()) - { - LL_WARNS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL; - } - else if(iter2 == mParticipantsByUUID.end()) - { - LL_WARNS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL; - } - else if(iter->second != iter2->second) - { - LL_WARNS("Voice") << "Internal error: participant mismatch!" << LL_ENDL; - } - else - { - mParticipantsByURI.erase(iter); - mParticipantsByUUID.erase(iter2); - - mParticipantsChanged = true; - } - } -} - -void LLVivoxVoiceClient::sessionState::removeAllParticipants() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - - while(!mParticipantsByURI.empty()) - { - removeParticipant(mParticipantsByURI.begin()->second); - } - - if(!mParticipantsByUUID.empty()) - { - LL_WARNS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL; - } -} - -/*static*/ -void LLVivoxVoiceClient::sessionState::VerifySessions() -{ - std::set<wptr_t>::iterator it = mSession.begin(); - while (it != mSession.end()) - { - if ((*it).expired()) - { - LL_WARNS("Voice") << "Expired session found! removing" << LL_ENDL; - it = mSession.erase(it); - } - else - ++it; - } -} - - -void LLVivoxVoiceClient::getParticipantList(std::set<LLUUID> &participants) -{ - if(mProcessChannels && mAudioSession) - { - for(participantUUIDMap::iterator iter = mAudioSession->mParticipantsByUUID.begin(); - iter != mAudioSession->mParticipantsByUUID.end(); - iter++) - { - participants.insert(iter->first); - } - } -} - -bool LLVivoxVoiceClient::isParticipant(const LLUUID &speaker_id) -{ - if(mProcessChannels && mAudioSession) - { - return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end()); - } - return false; -} - - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipant(const std::string &uri) -{ - participantStatePtr_t result; - - participantMap::iterator iter = mParticipantsByURI.find(uri); - - if(iter == mParticipantsByURI.end()) - { - if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) - { - // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. - // Look up the other URI - iter = mParticipantsByURI.find(mSIPURI); - } - } - - if(iter != mParticipantsByURI.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipantByID(const LLUUID& id) -{ - participantStatePtr_t result; - participantUUIDMap::iterator iter = mParticipantsByUUID.find(id); - - if(iter != mParticipantsByUUID.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::findParticipantByID(const LLUUID& id) -{ - participantStatePtr_t result; - - if(mAudioSession) - { - result = mAudioSession->findParticipantByID(id); - } - - return result; -} - - - -// Check for parcel boundary crossing -bool LLVivoxVoiceClient::checkParcelChanged(bool update) -{ - LLViewerRegion *region = gAgent.getRegion(); - LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - - if(region && parcel) - { - S32 parcelLocalID = parcel->getLocalID(); - std::string regionName = region->getName(); - - // LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << LL_ENDL; - - // The region name starts out empty and gets filled in later. - // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes. - // If either is empty, wait for the next time around. - if(!regionName.empty()) - { - if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName)) - { - // We have changed parcels. Initiate a parcel channel lookup. - if (update) - { - mCurrentParcelLocalID = parcelLocalID; - mCurrentRegionName = regionName; - } - return true; - } - } - } - return false; -} - -bool LLVivoxVoiceClient::switchChannel( - std::string uri, - bool spatial, - bool no_reconnect, - bool is_p2p, - std::string hash) -{ - bool needsSwitch = !mIsInChannel; - - if (mIsInChannel) - { - if (mSessionTerminateRequested) - { - // If a terminate has been requested, we need to compare against where the URI we're already headed to. - if(mNextAudioSession) - { - if (mNextAudioSession->mSIPURI != uri) - { - needsSwitch = true; - } - } - else - { - // mNextAudioSession is null -- this probably means we're on our way back to spatial. - if(!uri.empty()) - { - // We do want to process a switch in this case. - needsSwitch = true; - } - } - } - else - { - // Otherwise, compare against the URI we're in now. - if(mAudioSession) - { - if(mAudioSession->mSIPURI != uri) - { - needsSwitch = true; - } - } - else - { - if(!uri.empty()) - { - // mAudioSession is null -- it's not clear what case would cause this. - // For now, log it as a warning and see if it ever crops up. - LL_WARNS("Voice") << "No current audio session... Forcing switch" << LL_ENDL; - needsSwitch = true; - } - } - } - } - - if(needsSwitch) - { - if(uri.empty()) - { - // Leave any channel we may be in - LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL; - - sessionStatePtr_t oldSession = mNextAudioSession; - mNextAudioSession.reset(); - - // The old session may now need to be deleted. - reapSession(oldSession); - - // If voice was on, turn it off - if (LLVoiceClient::getInstance()->getUserPTTState()) - { - LLVoiceClient::getInstance()->setUserPTTState(false); - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - } - else - { - LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL; - - mNextAudioSession = addSession(uri); - mNextAudioSession->mHash = hash; - mNextAudioSession->mIsSpatial = spatial; - mNextAudioSession->mReconnect = !no_reconnect; - mNextAudioSession->mIsP2P = is_p2p; - } - - if (mIsInChannel) - { - // If we're already in a channel, or if we're joining one, terminate - // so we can rejoin with the new session data. - sessionTerminate(); - } - } - - return needsSwitch; -} - -void LLVivoxVoiceClient::joinSession(const sessionStatePtr_t &session) -{ - mNextAudioSession = session; - - if (mIsInChannel) - { - // If we're already in a channel, or if we're joining one, terminate - // so we can rejoin with the new session data. - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::setNonSpatialChannel(const LLSD& channelInfo, bool notify_on_first_join, bool hangup_on_last_leave) -{ - switchChannel(channelInfo["channel_uri"].asString(), false, false, false, channelInfo["channel_credentials"].asString()); -} - -bool LLVivoxVoiceClient::setSpatialChannel(const LLSD& channelInfo) -{ - mSpatialSessionURI = channelInfo["channel_uri"].asString(); - mSpatialSessionCredentials = channelInfo["channel_credentials"].asString(); - if (!mProcessChannels) - { - // we're not even processing channels (another provider is) so - // save the credentials aside and exit - return false; - } - - LL_DEBUGS("Voice") << "got spatial channel uri: \"" << mSpatialSessionURI << "\"" << LL_ENDL; - - if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) - { - // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. - LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL; - return false; - } - else - { - return switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); - } -} - -void LLVivoxVoiceClient::callUser(const LLUUID &uuid) -{ - std::string userURI = sipURIFromID(uuid); - mProcessChannels = true; - - switchChannel(userURI, false, true, true); -} - -void LLVivoxVoiceClient::hangup() { leaveChannel(); } - - -LLVoiceP2PIncomingCallInterfacePtr LLVivoxVoiceClient::getIncomingCallInterface(const LLSD &voice_call_info) -{ - return std::make_shared<LLVivoxVoiceP2PIncomingCall>(voice_call_info); -} - -bool LLVivoxVoiceClient::answerInvite(const std::string &sessionHandle) -{ - // this is only ever used to answer incoming p2p call invites. - - sessionStatePtr_t session(findSession(sessionHandle)); - if (session) - { - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - mProcessChannels = true; - joinSession(session); - return true; - } - - return false; -} - -void LLVivoxVoiceClient::declineInvite(const std::string &sessionHandle) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if (session) - { - sessionMediaDisconnectSendMessage(session); - } -} - -bool LLVivoxVoiceClient::isVoiceWorking() const -{ - - //Added stateSessionTerminated state to avoid problems with call in parcels with disabled voice (EXT-4758) - // Condition with joining spatial num was added to take into account possible problems with connection to voice - // server(EXT-4313). See bug descriptions and comments for MAX_NORMAL_JOINING_SPATIAL_NUM for more info. - return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && mIsLoggedIn; -} - -// Returns true if the indicated participant in the current audio session is really an SL avatar. -// Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls. -bool LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id) -{ - bool result = true; - sessionStatePtr_t session(findSession(id)); - - if(session) - { - // this is a p2p session with the indicated caller, or the session with the specified UUID. - if(session->mSynthesizedCallerID) - result = false; - } - else - { - // Didn't find a matching session -- check the current audio session for a matching participant - if(mAudioSession) - { - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->isAvatar(); - } - } - } - - return result; -} - -// Returns true if calling back the session URI after the session has closed is possible. -// Currently this will be false only for PSTN P2P calls. -bool LLVivoxVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) -{ - bool result = true; - sessionStatePtr_t session(findSession(session_id)); - - if(session != NULL) - { - result = session->isCallBackPossible(); - } - - return result; -} - -// Returns true if the session can accept text IM's. -// Currently this will be false only for PSTN P2P calls. -bool LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id) -{ - bool result = true; - sessionStatePtr_t session(findSession(session_id)); - - if(session != NULL) - { - result = session->isTextIMPossible(); - } - - return result; -} - -void LLVivoxVoiceClient::leaveNonSpatialChannel() -{ - LL_DEBUGS("Voice") << "Request to leave spacial channel." << LL_ENDL; - - // Make sure we don't rejoin the current session. - sessionStatePtr_t oldNextSession(mNextAudioSession); - mNextAudioSession.reset(); - - // Most likely this will still be the current session at this point, but check it anyway. - reapSession(oldNextSession); - - verifySessionState(); - - sessionTerminate(); -} - -void LLVivoxVoiceClient::processChannels(bool process) -{ - mCurrentParcelLocalID = -1; - mCurrentRegionName.clear(); - mProcessChannels = process; -} - -bool LLVivoxVoiceClient::isCurrentChannel(const LLSD &channelInfo) -{ - if (!mProcessChannels - || (channelInfo.has("voice_server_type") && channelInfo["voice_server_type"].asString() != VIVOX_VOICE_SERVER_TYPE) - || mSessionTerminateRequested) - { - return false; - } - // favor the next audio session, as that's the one we're bringing up. - sessionStatePtr_t session = mNextAudioSession; - if (!session) - { - session = mAudioSession; - } - if (session) - { - if (!channelInfo["session_handle"].asString().empty()) - { - return session->mHandle == channelInfo["session_handle"].asString(); - } - return channelInfo["channel_uri"].asString() == session->mSIPURI; - } - return false; -} - -bool LLVivoxVoiceClient::compareChannels(const LLSD& channelInfo1, const LLSD& channelInfo2) -{ - return (!channelInfo1.has("voice_server_type") || (channelInfo1["voice_server_type"] == VIVOX_VOICE_SERVER_TYPE)) && - (!channelInfo2.has("voice_server_type") || (channelInfo2["voice_server_type"] == VIVOX_VOICE_SERVER_TYPE)) && - (channelInfo1["channel_uri"] == channelInfo2["channel_uri"]); -} - -bool LLVivoxVoiceClient::inProximalChannel() -{ - bool result = false; - - if (mIsInChannel && !mSessionTerminateRequested) - { - result = inSpatialChannel(); - } - - return result; -} - -std::string LLVivoxVoiceClient::sipURIFromID(const LLUUID &id) const -{ - std::string result; - result = "sip:"; - result += nameFromID(id); - result += "@"; - result += mVoiceSIPURIHostName; - - return result; -} - -LLSD LLVivoxVoiceClient::getP2PChannelInfoTemplate(const LLUUID& id) const -{ - LLSD result; - result["channel_uri"] = sipURIFromID(id); - result["voice_server_type"] = VIVOX_VOICE_SERVER_TYPE; - return result; -} - -std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) -{ - std::string result; - if(avatar) - { - result = "sip:"; - result += nameFromID(avatar->getID()); - result += "@"; - result += mVoiceSIPURIHostName; - } - - return result; -} - -std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid) const -{ - std::string result; - - if (uuid.isNull()) { - //VIVOX, the uuid emtpy look for the mURIString and return that instead. - //result.assign(uuid.mURIStringName); - LLStringUtil::replaceChar(result, '_', ' '); - return result; - } - // Prepending this apparently prevents conflicts with reserved names inside the vivox code. - result = "x"; - - // Base64 encode and replace the pieces of base64 that are less compatible - // with e-mail local-parts. - // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" - result += LLBase64::encode(uuid.mData, UUID_BYTES); - LLStringUtil::replaceChar(result, '+', '-'); - LLStringUtil::replaceChar(result, '/', '_'); - - // If you need to transform a GUID to this form on the macOS command line, this will do so: - // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') - - // The reverse transform can be done with: - // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p - - return result; -} - -bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) -{ - bool result = false; - - // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com" - // If it is, convert to a bare name before doing the transform. - std::string name = nameFromsipURI(inName); - - // Doesn't look like a SIP URI, assume it's an actual name. - if(name.empty()) - name = inName; - - // This will only work if the name is of the proper form. - // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: - // "xFnPP04IpREWNkuw1cOXlhw==" - - if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) - { - // The name appears to have the right form. - - // Reverse the transforms done by nameFromID - std::string temp = name; - LLStringUtil::replaceChar(temp, '-', '+'); - LLStringUtil::replaceChar(temp, '_', '/'); - - U8 rawuuid[UUID_BYTES + 1]; - int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); - if(len == UUID_BYTES) - { - // The decode succeeded. Stuff the bits into the result's UUID - memcpy(uuid.mData, rawuuid, UUID_BYTES); - result = true; - } - } - - if(!result) - { - // VIVOX: not a standard account name, just copy the URI name mURIString field - // and hope for the best. bpj - uuid.setNull(); // VIVOX, set the uuid field to nulls - } - - return result; -} - -std::string LLVivoxVoiceClient::sipURIFromName(std::string &name) -{ - std::string result; - result = "sip:"; - result += name; - result += "@"; - result += mVoiceSIPURIHostName; - -// LLStringUtil::toLower(result); - - return result; -} - -std::string LLVivoxVoiceClient::nameFromsipURI(const std::string &uri) -{ - std::string result; - - std::string::size_type sipOffset, atOffset; - sipOffset = uri.find("sip:"); - atOffset = uri.find("@"); - if((sipOffset != std::string::npos) && (atOffset != std::string::npos)) - { - result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4)); - } - - return result; -} - -bool LLVivoxVoiceClient::inSpatialChannel(void) -{ - bool result = false; - - if(mAudioSession) - { - result = mAudioSession->mIsSpatial; - } - - return result; -} - - -LLSD LLVivoxVoiceClient::getAudioSessionChannelInfo() -{ - LLSD result; - - if (mAudioSession) - { - result = mAudioSession->getVoiceChannelInfo(); - } - - return result; -} - -std::string LLVivoxVoiceClient::getAudioSessionHandle() -{ - std::string result; - - if(mAudioSession) - result = mAudioSession->mHandle; - - return result; -} - - -///////////////////////////// -// Sending updates of current state - -void LLVivoxVoiceClient::enforceTether(void) -{ - LLVector3d tethered = mCameraRequestedPosition; - - // constrain 'tethered' to within 50m of mAvatarPosition. - { - F32 max_dist = 50.0f; - LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition; - F32 camera_distance = (F32)camera_offset.magVec(); - if(camera_distance > max_dist) - { - tethered = mAvatarPosition + - (max_dist / camera_distance) * camera_offset; - } - } - - if(dist_vec_squared(mCameraPosition, tethered) > 0.01) - { - mCameraPosition = tethered; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::updatePosition(void) -{ - - LLViewerRegion *region = gAgent.getRegion(); - if(region && isAgentAvatarValid()) - { - LLMatrix3 rot; - LLVector3d pos; - LLQuaternion qrot; - - // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here... - // They're currently always set to zero. - - // Send the current camera position to the voice code - rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); - pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); - - LLVivoxVoiceClient::getInstance()->setCameraPosition( - pos, // position - LLVector3::zero, // velocity - rot); // rotation matrix - - // Send the current avatar position to the voice code - qrot = gAgentAvatarp->getRootJoint()->getWorldRotation(); - pos = gAgentAvatarp->getPositionGlobal(); - - // TODO: Can we get the head offset from outside the LLVOAvatar? - // pos += LLVector3d(mHeadOffset); - pos += LLVector3d(0.f, 0.f, 1.f); - - LLVivoxVoiceClient::getInstance()->setAvatarPosition( - pos, // position - LLVector3::zero, // velocity - qrot); // rotation matrix - } -} - -void LLVivoxVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) -{ - mCameraRequestedPosition = position; - - if(mCameraVelocity != velocity) - { - mCameraVelocity = velocity; - mSpatialCoordsDirty = true; - } - - if(mCameraRot != rot) - { - mCameraRot = rot; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot) -{ - if(dist_vec_squared(mAvatarPosition, position) > 0.01) - { - mAvatarPosition = position; - mSpatialCoordsDirty = true; - } - - if(mAvatarVelocity != velocity) - { - mAvatarVelocity = velocity; - mSpatialCoordsDirty = true; - } - - // If the two rotations are not exactly equal test their dot product - // to get the cos of the angle between them. - // If it is too small, don't update. - F32 rot_cos_diff = llabs(dot(mAvatarRot, rot)); - if ((mAvatarRot != rot) && (rot_cos_diff < MINUSCULE_ANGLE_COS)) - { - mAvatarRot = rot; - mSpatialCoordsDirty = true; - } -} - -bool LLVivoxVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) -{ - bool result = false; - - if(region) - { - name = region->getName(); - } - - if(!name.empty()) - result = true; - - return result; -} - -void LLVivoxVoiceClient::leaveChannel(void) -{ - if (mIsInChannel) - { - LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL; - mChannelName.clear(); - } - sessionTerminate(); -} - -void LLVivoxVoiceClient::setMuteMic(bool muted) -{ - if(mMuteMic != muted) - { - mMuteMic = muted; - mMuteMicDirty = true; - } -} - -void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) -{ - LL_DEBUGS("Voice") - << "( " << (enabled ? "enabled" : "disabled") << " )" - << " was "<< (mVoiceEnabled ? "enabled" : "disabled") - << " coro "<< (mIsCoroutineActive ? "active" : "inactive") - << LL_ENDL; - - if (enabled != mVoiceEnabled) - { - // TODO: Refactor this so we don't call into LLVoiceChannel, but simply - // use the status observer - mVoiceEnabled = enabled; - LLVoiceClientStatusObserver::EStatusType status; - - if (enabled) - { - LL_DEBUGS("Voice") << "enabling" << LL_ENDL; - LLVoiceChannel::getCurrentVoiceChannel()->activate(); - status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED; - - if (!mIsCoroutineActive) - { - LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", - boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - } - else - { - LL_DEBUGS("Voice") << "coro should be active.. not launching" << LL_ENDL; - } - } - else - { - // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it. - LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); - gAgent.setVoiceConnected(false); - status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED; - mCurrentParcelLocalID = -1; - mCurrentRegionName.clear(); - } - - notifyStatusObservers(status); - } - else - { - LL_DEBUGS("Voice") << " no-op" << LL_ENDL; - } -} - - -void LLVivoxVoiceClient::setEarLocation(S32 loc) -{ - if(mEarLocation != loc) - { - LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; - - mEarLocation = loc; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::setVoiceVolume(F32 volume) -{ - int scaled_volume = scale_speaker_volume(volume); - - if(scaled_volume != mSpeakerVolume) - { - int min_volume = scale_speaker_volume(0); - if((scaled_volume == min_volume) || (mSpeakerVolume == min_volume)) - { - mSpeakerMuteDirty = true; - } - - mSpeakerVolume = scaled_volume; - mSpeakerVolumeDirty = true; - } -} - -void LLVivoxVoiceClient::setMicGain(F32 volume) -{ - int scaled_volume = scale_mic_volume(volume); - - if(scaled_volume != mMicVolume) - { - mMicVolume = scaled_volume; - mMicVolumeDirty = true; - } -} - -///////////////////////////// -// Accessors for data related to nearby speakers - -std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id) -{ - std::string result; - if (mProcessChannels) - { - participantStatePtr_t participant(findParticipantByID(id)); - if (participant) - { - result = participant->mDisplayName; - } - } - - return result; -} - - - -bool LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id) -{ - bool result = false; - if (mProcessChannels) - { - participantStatePtr_t participant(findParticipantByID(id)); - if (participant) - { - if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) - { - participant->mIsSpeaking = false; - } - result = participant->mIsSpeaking; - } - } - - return result; -} - -bool LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id) -{ - bool result = false; - if (!mProcessChannels) - { - return false; - } - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mIsModeratorMuted; - } - - return result; -} - -F32 LLVivoxVoiceClient::getCurrentPower(const LLUUID& id) -{ - F32 result = 0; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mPower; - } - - return result; -} - - - -bool LLVivoxVoiceClient::getUsingPTT(const LLUUID& id) -{ - bool result = false; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - // I'm not sure what the semantics of this should be. - // Does "using PTT" mean they're configured with a push-to-talk button? - // For now, we know there's no PTT mechanism in place, so nobody is using it. - } - - return result; -} - -// External accessors. -F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id) -{ - // Minimum volume will be returned for users with voice disabled - F32 result = LLVoiceClient::VOLUME_MIN; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mVolume; - - // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. - // LL_DEBUGS("Voice") << "mVolume = " << result << " for " << id << LL_ENDL; - } - - return result; -} - -void LLVivoxVoiceClient::setUserVolume(const LLUUID& id, F32 volume) -{ - if(mAudioSession) - { - participantStatePtr_t participant(findParticipantByID(id)); - if (participant && !participant->mIsSelf) - { - if (!is_approx_equal(volume, LLVoiceClient::VOLUME_DEFAULT)) - { - // Store this volume setting for future sessions if it has been - // changed from the default - LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume); - } - else - { - // Remove stored volume setting if it is returned to the default - LLSpeakerVolumeStorage::getInstance()->removeSpeakerVolume(id); - } - - participant->mVolume = llclamp(volume, LLVoiceClient::VOLUME_MIN, LLVoiceClient::VOLUME_MAX); - participant->mVolumeDirty = true; - mAudioSession->mVolumeDirty = true; - } - } -} - -std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id) -{ - std::string result; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mGroupID; - } - - return result; -} - -void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; - - if(!mMainSessionGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" - << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Start</RecordingControlType>" - << "<DeltaFramesPerControlFrame>" << deltaFramesPerControlFrame << "</DeltaFramesPerControlFrame>" - << "<Filename>" << "" << "</Filename>" - << "<EnableAudioRecordingEvents>false</EnableAudioRecordingEvents>" - << "<LoopModeDurationSeconds>" << seconds << "</LoopModeDurationSeconds>" - << "</Request>\n\n\n"; - - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::recordingLoopSave(const std::string& filename) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" - << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Flush</RecordingControlType>" - << "<Filename>" << filename << "</Filename>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::recordingStop() -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">" - << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Stop</RecordingControlType>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackStart(const std::string& filename) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">" - << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Start</RecordingControlType>" - << "<Filename>" << filename << "</Filename>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackStop() -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">" - << "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>" - << "<RecordingControlType>Stop</RecordingControlType>" - << "</Request>\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackSetPaused(bool paused) -{ - // TODO: Implement once Vivox gives me a sample -} - -void LLVivoxVoiceClient::filePlaybackSetMode(bool vox, float speed) -{ - // TODO: Implement once Vivox gives me a sample -} - -//------------------------------------------------------------------------ -std::set<LLVivoxVoiceClient::sessionState::wptr_t, std::owner_less<LLVivoxVoiceClient::sessionState::wptr_t>> LLVivoxVoiceClient::sessionState::mSession; - - -LLVivoxVoiceClient::sessionState::sessionState() : - mErrorStatusCode(0), - mMediaStreamState(streamStateUnknown), - mCreateInProgress(false), - mMediaConnectInProgress(false), - mVoiceInvitePending(false), - mTextInvitePending(false), - mSynthesizedCallerID(false), - mIsChannel(false), - mIsSpatial(false), - mIsP2P(false), - mIncoming(false), - mVoiceActive(false), - mReconnect(false), - mVolumeDirty(false), - mMuteDirty(false), - mParticipantsChanged(false) -{ -} - -LLSD LLVivoxVoiceClient::sessionState::getVoiceChannelInfo() -{ - LLSD result; - - result["voice_server_type"] = VIVOX_VOICE_SERVER_TYPE; - result["channel_credentials"] = mHash; - result["channel_uri"] = mSIPURI; - result["session_handle"] = mHandle; - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::createSession() -{ - sessionState::ptr_t ptr(new sessionState()); - - std::pair<std::set<wptr_t>::iterator, bool> result = mSession.insert(ptr); - - if (result.second) - ptr->mMyIterator = result.first; - - return ptr; -} - -LLVivoxVoiceClient::sessionState::~sessionState() -{ - LL_INFOS("Voice") << "Destroying session handle=" << mHandle << " SIP=" << mSIPURI << LL_ENDL; - if (mMyIterator != mSession.end()) - mSession.erase(mMyIterator); - - removeAllParticipants(); -} - -bool LLVivoxVoiceClient::sessionState::isCallBackPossible() -{ - // This may change to be explicitly specified by vivox in the future... - // Currently, only PSTN P2P calls cannot be returned. - // Conveniently, this is also the only case where we synthesize a caller UUID. - return !mSynthesizedCallerID; -} - -bool LLVivoxVoiceClient::sessionState::isTextIMPossible() -{ - // This may change to be explicitly specified by vivox in the future... - return !mSynthesizedCallerID; -} - - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByHandle(const std::string &handle) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByHandle, _1, handle)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchCreatingSessionByURI(const std::string &uri) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCreatingURI, _1, uri)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByURI(const std::string &uri) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testBySIPOrAlterateURI, _1, uri)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByParticipant(const LLUUID &participant_id) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set<wptr_t>::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCallerId, _1, participant_id)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -void LLVivoxVoiceClient::sessionState::for_each(sessionFunc_t func) -{ - std::for_each(mSession.begin(), mSession.end(), boost::bind(for_eachPredicate, _1, func)); -} - -// simple test predicates. -// *TODO: These should be made into lambdas when we can pull the trigger on newer C++ features. -bool LLVivoxVoiceClient::sessionState::testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle) -{ - ptr_t aLock(a.lock()); - - return aLock ? aLock->mHandle == handle : false; -} - -bool LLVivoxVoiceClient::sessionState::testByCreatingURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri) -{ - ptr_t aLock(a.lock()); - - return aLock ? (aLock->mCreateInProgress && (aLock->mSIPURI == uri)) : false; -} - -bool LLVivoxVoiceClient::sessionState::testBySIPOrAlterateURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri) -{ - ptr_t aLock(a.lock()); - - return aLock ? ((aLock->mSIPURI == uri) || (aLock->mAlternateSIPURI == uri)) : false; -} - - -bool LLVivoxVoiceClient::sessionState::testByCallerId(const LLVivoxVoiceClient::sessionState::wptr_t &a, LLUUID participantId) -{ - ptr_t aLock(a.lock()); - - return aLock ? ((aLock->mCallerID == participantId) || (aLock->mIMSessionID == participantId)) : false; -} - -/*static*/ -void LLVivoxVoiceClient::sessionState::for_eachPredicate(const LLVivoxVoiceClient::sessionState::wptr_t &a, sessionFunc_t func) -{ - ptr_t aLock(a.lock()); - - if (aLock) - func(aLock); - else - { - LL_WARNS("Voice") << "Stale handle in session map!" << LL_ENDL; - } -} - - - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const std::string &handle) -{ - sessionStatePtr_t result; - sessionMap::iterator iter = mSessionsByHandle.find(handle); - if(iter != mSessionsByHandle.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) -{ - sessionStatePtr_t result = sessionState::matchCreatingSessionByURI(uri); - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const LLUUID &participant_id) -{ - sessionStatePtr_t result = sessionState::matchSessionByParticipant(participant_id); - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std::string &uri, const std::string &handle) -{ - sessionStatePtr_t result; - - if(handle.empty()) - { - // No handle supplied. - // Check whether there's already a session with this URI - result = sessionState::matchSessionByURI(uri); - } - else // (!handle.empty()) - { - // Check for an existing session with this handle - sessionMap::iterator iter = mSessionsByHandle.find(handle); - - if(iter != mSessionsByHandle.end()) - { - result = iter->second; - } - } - - if(!result) - { - // No existing session found. - - LL_DEBUGS("Voice") << "adding new session: handle \"" << handle << "\" URI " << uri << LL_ENDL; - result = sessionState::createSession(); - result->mSIPURI = uri; - result->mHandle = handle; - - if (LLVoiceClient::instance().getVoiceEffectEnabled()) - { - result->mVoiceFontID = LLVoiceClient::instance().getVoiceEffectDefault(); - } - - if(!result->mHandle.empty()) - { - // *TODO: Rider: This concerns me. There is a path (via switchChannel) where - // we do not track the session. In theory this means that we could end up with - // a mAuidoSession that does not match the session tracked in mSessionsByHandle - mSessionsByHandle.insert(sessionMap::value_type(result->mHandle, result)); - } - } - else - { - // Found an existing session - - if(uri != result->mSIPURI) - { - // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL; - setSessionURI(result, uri); - } - - if(handle != result->mHandle) - { - if(handle.empty()) - { - // There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break. - LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL; - } - else - { - // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL; - setSessionHandle(result, handle); - } - } - - LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL; - } - - verifySessionState(); - - return result; -} - -void LLVivoxVoiceClient::clearSessionHandle(const sessionStatePtr_t &session) -{ - if (session) - { - if (!session->mHandle.empty()) - { - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if (iter != mSessionsByHandle.end()) - { - mSessionsByHandle.erase(iter); - } - } - else - { - LL_WARNS("Voice") << "Session has empty handle!" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "Attempt to clear NULL session!" << LL_ENDL; - } - -} - -void LLVivoxVoiceClient::setSessionHandle(const sessionStatePtr_t &session, const std::string &handle) -{ - // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly. - - if(!session->mHandle.empty()) - { - // Remove session from the map if it should have been there. - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_WARNS("Voice") << "Internal error: session mismatch! Session may have been duplicated. Removing version in map." << LL_ENDL; - } - - mSessionsByHandle.erase(iter); - } - else - { - LL_WARNS("Voice") << "Attempt to remove session with handle " << session->mHandle << " not found in map!" << LL_ENDL; - } - } - - session->mHandle = handle; - - if(!handle.empty()) - { - mSessionsByHandle.insert(sessionMap::value_type(session->mHandle, session)); - } - - verifySessionState(); -} - -void LLVivoxVoiceClient::setSessionURI(const sessionStatePtr_t &session, const std::string &uri) -{ - // There used to be a map of session URIs to sessions, which made this complex.... - session->mSIPURI = uri; - - verifySessionState(); -} - -void LLVivoxVoiceClient::deleteSession(const sessionStatePtr_t &session) -{ - // Remove the session from the handle map - if(!session->mHandle.empty()) - { - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_WARNS("Voice") << "Internal error: session mismatch, removing session in map." << LL_ENDL; - } - mSessionsByHandle.erase(iter); - } - } - - // At this point, the session should be unhooked from all lists and all state should be consistent. - verifySessionState(); - - // If this is the current audio session, clean up the pointer which will soon be dangling. - if(mAudioSession == session) - { - mAudioSession.reset(); - } - - // ditto for the next audio session - if(mNextAudioSession == session) - { - mNextAudioSession.reset(); - } - -} - -void LLVivoxVoiceClient::deleteAllSessions() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - while (!mSessionsByHandle.empty()) - { - const sessionStatePtr_t session = mSessionsByHandle.begin()->second; - deleteSession(session); - } - -} - -void LLVivoxVoiceClient::verifySessionState(void) -{ - LL_DEBUGS("Voice") << "Sessions in handle map=" << mSessionsByHandle.size() << LL_ENDL; - sessionState::VerifySessions(); -} - -void LLVivoxVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) -{ - mParticipantObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) -{ - mParticipantObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyParticipantObservers() -{ - for (observer_set_t::iterator it = mParticipantObservers.begin(); - it != mParticipantObservers.end(); - ) - { - LLVoiceClientParticipantObserver* observer = *it; - observer->onParticipantsChanged(); - // In case onParticipantsChanged() deleted an entry. - it = mParticipantObservers.upper_bound(observer); - } -} - -void LLVivoxVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) -{ - mStatusObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) -{ - mStatusObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) -{ - LL_DEBUGS("Voice") << "( " << LLVoiceClientStatusObserver::status2string(status) << " )" - << " mAudioSession=" << mAudioSession - << LL_ENDL; - - if(mAudioSession) - { - if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) - { - switch(mAudioSession->mErrorStatusCode) - { - case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; - case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; - case 20715: - //invalid channel, we may be using a set of poorly cached - //info - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - case 1009: - //invalid username and password - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - } - - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - } - else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL) - { - switch(mAudioSession->mErrorStatusCode) - { - case HTTP_NOT_FOUND: // NOT_FOUND - // *TODO: Should this be 503? - case 480: // TEMPORARILY_UNAVAILABLE - case HTTP_REQUEST_TIME_OUT: // REQUEST_TIMEOUT - // call failed because other user was not available - // treat this as an error case - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - break; - } - } - } - - LLSD channel_info = getAudioSessionChannelInfo(); - LL_DEBUGS("Voice") - << " " << LLVoiceClientStatusObserver::status2string(status) - << ", session channelInfo " << channel_info - << ", proximal is " << inSpatialChannel() - << LL_ENDL; - - if (!mProcessChannels) - { - // we're not processing...another voice module is. - // so nobody wants to hear from us. - return; - } - for (status_observer_set_t::iterator it = mStatusObservers.begin(); - it != mStatusObservers.end(); - ) - { - LLVoiceClientStatusObserver* observer = *it; - observer->onChange(status, channel_info, inSpatialChannel()); - // In case onError() deleted an entry. - it = mStatusObservers.upper_bound(observer); - } - - // skipped to avoid speak button blinking - if ( status != LLVoiceClientStatusObserver::STATUS_JOINING - && status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL - && status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED) - { - bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); - - LL_WARNS("Voice") << "Setting voice connected " << (voice_status ? "True" : "False") << LL_ENDL; - gAgent.setVoiceConnected(voice_status); - - if (voice_status) - { - LLAppViewer::instance()->postToMainCoro([=]() { LLFirstUse::speak(true); }); - } - } -} - -void LLVivoxVoiceClient::addObserver(LLFriendObserver* observer) -{ - mFriendObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLFriendObserver* observer) -{ - mFriendObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyFriendObservers() -{ - for (friend_observer_set_t::iterator it = mFriendObservers.begin(); - it != mFriendObservers.end(); - ) - { - LLFriendObserver* observer = *it; - it++; - // The only friend-related thing we notify on is online/offline transitions. - observer->changed(LLFriendObserver::ONLINE); - } -} - -void LLVivoxVoiceClient::lookupName(const LLUUID &id) -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLVivoxVoiceClient::onAvatarNameCache, this, _1, _2)); -} - -void LLVivoxVoiceClient::onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - std::string display_name = av_name.getDisplayName(); - avatarNameResolved(agent_id, display_name); -} - -void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name) -{ - participantStatePtr_t participant(session->findParticipantByID(id)); - if (participant) - { - // Found -- fill in the name - participant->mAccountName = name; - // and post a "participants updated" message to listeners later. - session->mParticipantsChanged = true; - } - - // Check whether this is a p2p session whose caller name just resolved - if (session->mCallerID == id) - { - // this session's "caller ID" just resolved. Fill in the name. - session->mName = name; - if (session->mTextInvitePending) - { - session->mTextInvitePending = false; - - // We don't need to call LLIMMgr::getInstance()->addP2PSession() here. The first incoming message will create the panel. - } - if (session->mVoiceInvitePending) - { - session->mVoiceInvitePending = false; - LLIMMgr::getInstance()->inviteToSession( - session->mIMSessionID, - session->mName, - session->mCallerID, - session->mName, - IM_SESSION_P2P_INVITE, - LLIMMgr::INVITATION_TYPE_VOICE, - session->getVoiceChannelInfo()); - } - } -} - -void LLVivoxVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) -{ - sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name)); -} - -bool LLVivoxVoiceClient::setVoiceEffect(const LLUUID& id) -{ - if (!mAudioSession) - { - return false; - } - - if (!id.isNull()) - { - if (mVoiceFontMap.empty()) - { - LL_DEBUGS("Voice") << "Voice fonts not available." << LL_ENDL; - return false; - } - else if (mVoiceFontMap.find(id) == mVoiceFontMap.end()) - { - LL_DEBUGS("Voice") << "Invalid voice font " << id << LL_ENDL; - return false; - } - } - - // *TODO: Check for expired fonts? - mAudioSession->mVoiceFontID = id; - - // *TODO: Separate voice font defaults for spatial chat and IM? - gSavedPerAccountSettings.setString("VoiceEffectDefault", id.asString()); - - sessionSetVoiceFontSendMessage(mAudioSession); - notifyVoiceFontObservers(); - - return true; -} - -const LLUUID LLVivoxVoiceClient::getVoiceEffect() -{ - return mAudioSession ? mAudioSession->mVoiceFontID : LLUUID::null; -} - -LLSD LLVivoxVoiceClient::getVoiceEffectProperties(const LLUUID& id) -{ - LLSD sd; - - voice_font_map_t::iterator iter = mVoiceFontMap.find(id); - if (iter != mVoiceFontMap.end()) - { - sd["template_only"] = false; - } - else - { - // Voice effect is not in the voice font map, see if there is a template - iter = mVoiceFontTemplateMap.find(id); - if (iter == mVoiceFontTemplateMap.end()) - { - LL_WARNS("Voice") << "Voice effect " << id << "not found." << LL_ENDL; - return sd; - } - sd["template_only"] = true; - } - - voiceFontEntry *font = iter->second; - sd["name"] = font->mName; - sd["expiry_date"] = font->mExpirationDate; - sd["is_new"] = font->mIsNew; - - return sd; -} - -LLVivoxVoiceClient::voiceFontEntry::voiceFontEntry(LLUUID& id) : - mID(id), - mFontIndex(0), - mFontType(VOICE_FONT_TYPE_NONE), - mFontStatus(VOICE_FONT_STATUS_NONE), - mIsNew(false) -{ - mExpiryTimer.stop(); -} - -LLVivoxVoiceClient::voiceFontEntry::~voiceFontEntry() -{ -} - -void LLVivoxVoiceClient::refreshVoiceEffectLists(bool clear_lists) -{ - if (clear_lists) - { - mVoiceFontsReceived = false; - deleteAllVoiceFonts(); - deleteVoiceFontTemplates(); - } - - accountGetSessionFontsSendMessage(); - accountGetTemplateFontsSendMessage(); -} - -const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectList() const -{ - return mVoiceFontList; -} - -const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectTemplateList() const -{ - return mVoiceFontTemplateList; -} - -void LLVivoxVoiceClient::addVoiceFont(const S32 font_index, - const std::string &name, - const std::string &description, - const LLDate &expiration_date, - bool has_expired, - const S32 font_type, - const S32 font_status, - const bool template_font) -{ - // Vivox SessionFontIDs are not guaranteed to remain the same between - // sessions or grids so use a UUID for the name. - - // If received name is not a UUID, fudge one by hashing the name and type. - LLUUID font_id; - if (LLUUID::validate(name)) - { - font_id = LLUUID(name); - } - else - { - font_id.generate(STRINGIZE(font_type << ":" << name)); - } - - voiceFontEntry *font = NULL; - - voice_font_map_t& font_map = template_font ? mVoiceFontTemplateMap : mVoiceFontMap; - voice_effect_list_t& font_list = template_font ? mVoiceFontTemplateList : mVoiceFontList; - - // Check whether we've seen this font before. - voice_font_map_t::iterator iter = font_map.find(font_id); - bool new_font = (iter == font_map.end()); - - // Override the has_expired flag if we have passed the expiration_date as a double check. - if (expiration_date.secondsSinceEpoch() < (LLDate::now().secondsSinceEpoch() + VOICE_FONT_EXPIRY_INTERVAL)) - { - has_expired = true; - } - - if (has_expired) - { - LL_DEBUGS("VoiceFont") << "Expired " << (template_font ? "Template " : "") - << expiration_date.asString() << " " << font_id - << " (" << font_index << ") " << name << LL_ENDL; - - // Remove existing session fonts that have expired since we last saw them. - if (!new_font && !template_font) - { - deleteVoiceFont(font_id); - } - return; - } - - if (new_font) - { - // If it is a new font create a new entry. - font = new voiceFontEntry(font_id); - } - else - { - // Not a new font, update the existing entry - font = iter->second; - } - - if (font) - { - font->mFontIndex = font_index; - // Use the description for the human readable name if available, as the - // "name" may be a UUID. - font->mName = description.empty() ? name : description; - font->mFontType = font_type; - font->mFontStatus = font_status; - - // If the font is new or the expiration date has changed the expiry timers need updating. - if (!template_font && (new_font || font->mExpirationDate != expiration_date)) - { - font->mExpirationDate = expiration_date; - - // Set the expiry timer to trigger a notification when the voice font can no longer be used. - font->mExpiryTimer.start(); - font->mExpiryTimer.setExpiryAt(expiration_date.secondsSinceEpoch() - VOICE_FONT_EXPIRY_INTERVAL); - - // Only flag new session fonts after the first time we have fetched the list. - if (mVoiceFontsReceived) - { - font->mIsNew = true; - mVoiceFontsNew = true; - } - } - - LL_DEBUGS("VoiceFont") << (template_font ? "Template " : "") - << font->mExpirationDate.asString() << " " << font->mID - << " (" << font->mFontIndex << ") " << name << LL_ENDL; - - if (new_font) - { - font_map.insert(voice_font_map_t::value_type(font->mID, font)); - font_list.insert(voice_effect_list_t::value_type(font->mName, font->mID)); - } - - mVoiceFontListDirty = true; - - // Debugging stuff - - if (font_type < VOICE_FONT_TYPE_NONE || font_type >= VOICE_FONT_TYPE_UNKNOWN) - { - LL_WARNS("VoiceFont") << "Unknown voice font type: " << font_type << LL_ENDL; - } - if (font_status < VOICE_FONT_STATUS_NONE || font_status >= VOICE_FONT_STATUS_UNKNOWN) - { - LL_WARNS("VoiceFont") << "Unknown voice font status: " << font_status << LL_ENDL; - } - } -} - -void LLVivoxVoiceClient::expireVoiceFonts() -{ - // *TODO: If we are selling voice fonts in packs, there are probably - // going to be a number of fonts with the same expiration time, so would - // be more efficient to just keep a list of expiration times rather - // than checking each font individually. - - bool have_expired = false; - bool expired_in_use = false; - - LLUUID current_effect = LLVoiceClient::instance().getVoiceEffectDefault(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter) - { - voiceFontEntry* voice_font = iter->second; - LLFrameTimer& expiry_timer = voice_font->mExpiryTimer; - - // Check for expired voice fonts - if (expiry_timer.getStarted() && expiry_timer.hasExpired()) - { - // Check whether it is the active voice font - if (voice_font->mID == current_effect) - { - // Reset to no voice effect. - setVoiceEffect(LLUUID::null); - expired_in_use = true; - } - - LL_DEBUGS("Voice") << "Voice Font " << voice_font->mName << " has expired." << LL_ENDL; - deleteVoiceFont(voice_font->mID); - have_expired = true; - } - } - - LLSD args; - args["URL"] = LLTrans::getString("voice_morphing_url"); - args["PREMIUM_URL"] = LLTrans::getString("premium_voice_morphing_url"); - - // Give a notification if any voice fonts have expired. - if (have_expired) - { - if (expired_in_use) - { - LLNotificationsUtil::add("VoiceEffectsExpiredInUse", args); - } - else - { - LLNotificationsUtil::add("VoiceEffectsExpired", args); - } - - // Refresh voice font lists in the UI. - notifyVoiceFontObservers(); - } -} - -void LLVivoxVoiceClient::deleteVoiceFont(const LLUUID& id) -{ - // Remove the entry from the voice font list. - voice_effect_list_t::iterator list_iter = mVoiceFontList.begin(); - while (list_iter != mVoiceFontList.end()) - { - if (list_iter->second == id) - { - LL_DEBUGS("VoiceFont") << "Removing " << id << " from the voice font list." << LL_ENDL; - list_iter = mVoiceFontList.erase(list_iter); - mVoiceFontListDirty = true; - } - else - { - ++list_iter; - } - } - - // Find the entry in the voice font map and erase its data. - voice_font_map_t::iterator map_iter = mVoiceFontMap.find(id); - if (map_iter != mVoiceFontMap.end()) - { - delete map_iter->second; - } - - // Remove the entry from the voice font map. - mVoiceFontMap.erase(map_iter); -} - -void LLVivoxVoiceClient::deleteAllVoiceFonts() -{ - mVoiceFontList.clear(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter) - { - delete iter->second; - } - mVoiceFontMap.clear(); -} - -void LLVivoxVoiceClient::deleteVoiceFontTemplates() -{ - mVoiceFontTemplateList.clear(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontTemplateMap.begin(); iter != mVoiceFontTemplateMap.end(); ++iter) - { - delete iter->second; - } - mVoiceFontTemplateMap.clear(); -} - -S32 LLVivoxVoiceClient::getVoiceFontIndex(const LLUUID& id) const -{ - S32 result = 0; - if (!id.isNull()) - { - voice_font_map_t::const_iterator it = mVoiceFontMap.find(id); - if (it != mVoiceFontMap.end()) - { - result = it->second->mFontIndex; - } - else - { - LL_WARNS("VoiceFont") << "Selected voice font " << id << " is not available." << LL_ENDL; - } - } - return result; -} - -S32 LLVivoxVoiceClient::getVoiceFontTemplateIndex(const LLUUID& id) const -{ - S32 result = 0; - if (!id.isNull()) - { - voice_font_map_t::const_iterator it = mVoiceFontTemplateMap.find(id); - if (it != mVoiceFontTemplateMap.end()) - { - result = it->second->mFontIndex; - } - else - { - LL_WARNS("VoiceFont") << "Selected voice font template " << id << " is not available." << LL_ENDL; - } - } - return result; -} - -void LLVivoxVoiceClient::accountGetSessionFontsSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("VoiceFont") << "Requesting voice font list." << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.GetSessionFonts.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::accountGetTemplateFontsSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("VoiceFont") << "Requesting voice font template list." << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.GetTemplateFonts.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionSetVoiceFontSendMessage(const sessionStatePtr_t &session) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("VoiceFont") << "Requesting voice font: " << session->mVoiceFontID << " (" << font_index << "), session handle: " << session->mHandle << LL_ENDL; - - std::ostringstream stream; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetVoiceFont.1\">" - << "<SessionHandle>" << session->mHandle << "</SessionHandle>" - << "<SessionFontID>" << font_index << "</SessionFontID>" - << "</Request>\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::accountGetSessionFontsResponse(int statusCode, const std::string &statusString) -{ - if (mIsWaitingForFonts) - { - // *TODO: We seem to get multiple events of this type. Should figure a way to advance only after - // receiving the last one. - LLSD result(LLSDMap("voice_fonts", LLSD::Boolean(true))); - - mVivoxPump.post(result); - } - notifyVoiceFontObservers(); - mVoiceFontsReceived = true; -} - -void LLVivoxVoiceClient::accountGetTemplateFontsResponse(int statusCode, const std::string &statusString) -{ - // Voice font list entries were updated via addVoiceFont() during parsing. - notifyVoiceFontObservers(); -} -void LLVivoxVoiceClient::addObserver(LLVoiceEffectObserver* observer) -{ - mVoiceFontObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceEffectObserver* observer) -{ - mVoiceFontObservers.erase(observer); -} - -// method checks the item in VoiceMorphing menu for appropriate current voice font -bool LLVivoxVoiceClient::onCheckVoiceEffect(const std::string& voice_effect_name) -{ - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (NULL != effect_interfacep) - { - const LLUUID& currect_voice_effect_id = effect_interfacep->getVoiceEffect(); - - if (currect_voice_effect_id.isNull()) - { - if (voice_effect_name == "NoVoiceMorphing") - { - return true; - } - } - else - { - const LLSD& voice_effect_props = effect_interfacep->getVoiceEffectProperties(currect_voice_effect_id); - if (voice_effect_props["name"].asString() == voice_effect_name) - { - return true; - } - } - } - - return false; -} - -// method changes voice font for selected VoiceMorphing menu item -void LLVivoxVoiceClient::onClickVoiceEffect(const std::string& voice_effect_name) -{ - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (NULL != effect_interfacep) - { - if (voice_effect_name == "NoVoiceMorphing") - { - effect_interfacep->setVoiceEffect(LLUUID()); - return; - } - const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList(); - if (!effect_list.empty()) - { - for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) - { - if (voice_effect_name == it->first) - { - effect_interfacep->setVoiceEffect(it->second); - return; - } - } - } - } -} - -// it updates VoiceMorphing menu items in accordance with purchased properties -void LLVivoxVoiceClient::updateVoiceMorphingMenu() -{ - if (mVoiceFontListDirty) - { - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interfacep) - { - const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList(); - if (!effect_list.empty()) - { - LLMenuGL * voice_morphing_menup = gMenuBarView->findChildMenuByName("VoiceMorphing", true); - - if (NULL != voice_morphing_menup) - { - S32 items = voice_morphing_menup->getItemCount(); - if (items > 0) - { - voice_morphing_menup->erase(1, items - 3, false); - - S32 pos = 1; - for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) - { - LLMenuItemCheckGL::Params p; - p.name = it->first; - p.label = it->first; - p.on_check.function(boost::bind(&LLVivoxVoiceClient::onCheckVoiceEffect, this, it->first)); - p.on_click.function(boost::bind(&LLVivoxVoiceClient::onClickVoiceEffect, this, it->first)); - LLMenuItemCheckGL * voice_effect_itemp = LLUICtrlFactory::create<LLMenuItemCheckGL>(p); - voice_morphing_menup->insert(pos++, voice_effect_itemp, false); - } - - voice_morphing_menup->needsArrange(); - } - } - } - } - } -} -void LLVivoxVoiceClient::notifyVoiceFontObservers() -{ - LL_DEBUGS("VoiceFont") << "Notifying voice effect observers. Lists changed: " << mVoiceFontListDirty << LL_ENDL; - - updateVoiceMorphingMenu(); - - for (voice_font_observer_set_t::iterator it = mVoiceFontObservers.begin(); - it != mVoiceFontObservers.end();) - { - LLVoiceEffectObserver* observer = *it; - observer->onVoiceEffectChanged(mVoiceFontListDirty); - // In case onVoiceEffectChanged() deleted an entry. - it = mVoiceFontObservers.upper_bound(observer); - } - mVoiceFontListDirty = false; - - // If new Voice Fonts have been added notify the user. - if (mVoiceFontsNew) - { - if (mVoiceFontsReceived) - { - LLNotificationsUtil::add("VoiceEffectsNew"); - } - mVoiceFontsNew = false; - } -} - -void LLVivoxVoiceClient::enablePreviewBuffer(bool enable) -{ - LLSD result; - mCaptureBufferMode = enable; - - if (enable) - result["recplay"] = "start"; - else - result["recplay"] = "quit"; - - mVivoxPump.post(result); - - if(mCaptureBufferMode && mIsInChannel) - { - LL_DEBUGS("Voice") << "no channel" << LL_ENDL; - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::recordPreviewBuffer() -{ - if (!mCaptureBufferMode) - { - LL_DEBUGS("Voice") << "Not in voice effect preview mode, cannot start recording." << LL_ENDL; - mCaptureBufferRecording = false; - return; - } - - mCaptureBufferRecording = true; - - LLSD result(LLSDMap("recplay", "record")); - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id) -{ - if (!mCaptureBufferMode) - { - LL_DEBUGS("Voice") << "Not in voice effect preview mode, no buffer to play." << LL_ENDL; - mCaptureBufferRecording = false; - return; - } - - if (!mCaptureBufferRecorded) - { - // Can't play until we have something recorded! - mCaptureBufferPlaying = false; - return; - } - - mPreviewVoiceFont = effect_id; - mCaptureBufferPlaying = true; - - LLSD result(LLSDMap("recplay", "playback")); - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::stopPreviewBuffer() -{ - mCaptureBufferRecording = false; - mCaptureBufferPlaying = false; - - LLSD result(LLSDMap("recplay", "quit")); - mVivoxPump.post(result); -} - -bool LLVivoxVoiceClient::isPreviewRecording() -{ - return (mCaptureBufferMode && mCaptureBufferRecording); -} - -bool LLVivoxVoiceClient::isPreviewPlaying() -{ - return (mCaptureBufferMode && mCaptureBufferPlaying); -} - -void LLVivoxVoiceClient::captureBufferRecordStartSendMessage() -{ if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Starting audio capture to buffer." << LL_ENDL; - - // Start capture - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.StartBufferCapture.1\">" - << "</Request>" - << "\n\n\n"; - - // Unmute the mic - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>false</Value>" - << "</Request>\n\n\n"; - - // Dirty the mute mic state so that it will get reset when we finishing previewing - mMuteMicDirty = true; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferRecordStopSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Stopping audio capture to buffer." << LL_ENDL; - - // Mute the mic. Mic mute state was dirtied at recording start, so will be reset when finished previewing. - stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">" - << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>" - << "<Value>true</Value>" - << "</Request>\n\n\n"; - - // Stop capture - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferPlayStartSendMessage(const LLUUID& voice_font_id) -{ - if(mAccountLoggedIn) - { - // Track how may play requests are sent, so we know how many stop events to - // expect before play actually stops. - ++mPlayRequestCount; - - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Starting audio buffer playback." << LL_ENDL; - - S32 font_index = getVoiceFontTemplateIndex(voice_font_id); - LL_DEBUGS("Voice") << "With voice font: " << voice_font_id << " (" << font_index << ")" << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.PlayAudioBuffer.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "<TemplateFontID>" << font_index << "</TemplateFontID>" - << "<FontDelta />" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferPlayStopSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Stopping audio buffer playback." << LL_ENDL; - - stream - << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">" - << "<AccountHandle>" << LLVivoxSecurity::getInstance()->accountHandle() << "</AccountHandle>" - << "</Request>" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -LLVivoxProtocolParser::LLVivoxProtocolParser() -{ - parser = XML_ParserCreate(NULL); - - reset(); -} - -void LLVivoxProtocolParser::reset() -{ - responseDepth = 0; - ignoringTags = false; - accumulateText = false; - energy = 0.f; - hasText = false; - hasAudio = false; - hasVideo = false; - terminated = false; - ignoreDepth = 0; - isChannel = false; - incoming = false; - enabled = false; - isEvent = false; - isLocallyMuted = false; - isModeratorMuted = false; - isSpeaking = false; - participantType = 0; - returnCode = -1; - state = 0; - statusCode = 0; - volume = 0; - textBuffer.clear(); - alias.clear(); - numberOfAliases = 0; - applicationString.clear(); -} - -//virtual -LLVivoxProtocolParser::~LLVivoxProtocolParser() -{ - if (parser) - XML_ParserFree(parser); -} - -static LLTrace::BlockTimerStatHandle FTM_VIVOX_PROCESS("Vivox Process"); - -// virtual -LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump) -{ - LL_RECORD_BLOCK_TIME(FTM_VIVOX_PROCESS); - LLBufferStream istr(channels, buffer.get()); - std::ostringstream ostr; - while (istr.good()) - { - char buf[1024]; - istr.read(buf, sizeof(buf)); - mInput.append(buf, istr.gcount()); - } - - // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser. - size_t start = 0; - size_t delim; - while((delim = mInput.find("\n\n\n", start)) != std::string::npos) - { - - // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser) - reset(); - - XML_ParserReset(parser, NULL); - XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag); - XML_SetCharacterDataHandler(parser, ExpatCharHandler); - XML_SetUserData(parser, this); - XML_Parse(parser, mInput.data() + start, static_cast<int>(delim - start), false); - - - LL_DEBUGS("VivoxProtocolParser") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; - start = delim + 3; - } - - if(start != 0) - mInput = mInput.substr(start); - - LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL; - - if(!LLVivoxVoiceClient::sConnected) - { - // If voice has been disabled, we just want to close the socket. This does so. - LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL; - return STATUS_STOP; - } - - return STATUS_OK; -} - -void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->StartTag(el, attr); - } -} - -// -------------------------------------------------------------------------------- - -void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->EndTag(el); - } -} - -// -------------------------------------------------------------------------------- - -void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->CharData(s, len); - } -} - -// -------------------------------------------------------------------------------- - - -void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) -{ - // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags - textBuffer.clear(); - // only accumulate text if we're not ignoring tags. - accumulateText = !ignoringTags; - - if (responseDepth == 0) - { - isEvent = !stricmp("Event", tag); - - if (!stricmp("Response", tag) || isEvent) - { - // Grab the attributes - while (*attr) - { - const char *key = *attr++; - const char *value = *attr++; - - if (!stricmp("requestId", key)) - { - requestId = value; - } - else if (!stricmp("action", key)) - { - actionString = value; - } - else if (!stricmp("type", key)) - { - eventTypeString = value; - } - } - } - LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - } - else - { - if (ignoringTags) - { - LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - } - else - { - LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - - // Ignore the InputXml stuff so we don't get confused - if (!stricmp("InputXml", tag)) - { - ignoringTags = true; - ignoreDepth = responseDepth; - accumulateText = false; - - LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; - } - else if (!stricmp("CaptureDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->clearCaptureDevices(); - } - else if (!stricmp("RenderDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->clearRenderDevices(); - } - else if (!stricmp("CaptureDevice", tag)) - { - deviceString.clear(); - } - else if (!stricmp("RenderDevice", tag)) - { - deviceString.clear(); - } - else if (!stricmp("SessionFont", tag)) - { - id = 0; - nameString.clear(); - descriptionString.clear(); - expirationDate = LLDate(); - hasExpired = false; - fontType = 0; - fontStatus = 0; - } - else if (!stricmp("TemplateFont", tag)) - { - id = 0; - nameString.clear(); - descriptionString.clear(); - expirationDate = LLDate(); - hasExpired = false; - fontType = 0; - fontStatus = 0; - } - else if (!stricmp("MediaCompletionType", tag)) - { - mediaCompletionType.clear(); - } - } - } - responseDepth++; -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::EndTag(const char *tag) -{ - const std::string& string = textBuffer; - - responseDepth--; - - if (ignoringTags) - { - if (ignoreDepth == responseDepth) - { - LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL; - ignoringTags = false; - } - else - { - LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - } - } - - if (!ignoringTags) - { - LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - - // Closing a tag. Finalize the text we've accumulated and reset - if (!stricmp("ReturnCode", tag)) - returnCode = strtol(string.c_str(), NULL, 10); - else if (!stricmp("SessionHandle", tag)) - sessionHandle = string; - else if (!stricmp("SessionGroupHandle", tag)) - sessionGroupHandle = string; - else if (!stricmp("StatusCode", tag)) - statusCode = strtol(string.c_str(), NULL, 10); - else if (!stricmp("StatusString", tag)) - statusString = string; - else if (!stricmp("ParticipantURI", tag)) - uriString = string; - else if (!stricmp("Volume", tag)) - volume = strtol(string.c_str(), NULL, 10); - else if (!stricmp("Energy", tag)) - energy = (F32)strtod(string.c_str(), NULL); - else if (!stricmp("IsModeratorMuted", tag)) - isModeratorMuted = !stricmp(string.c_str(), "true"); - else if (!stricmp("IsSpeaking", tag)) - isSpeaking = !stricmp(string.c_str(), "true"); - else if (!stricmp("Alias", tag)) - alias = string; - else if (!stricmp("NumberOfAliases", tag)) - numberOfAliases = strtol(string.c_str(), NULL, 10); - else if (!stricmp("Application", tag)) - applicationString = string; - else if (!stricmp("ConnectorHandle", tag)) - connectorHandle = string; - else if (!stricmp("VersionID", tag)) - versionID = string; - else if (!stricmp("Version", tag)) - mBuildID = string; - else if (!stricmp("AccountHandle", tag)) - accountHandle = string; - else if (!stricmp("State", tag)) - state = strtol(string.c_str(), NULL, 10); - else if (!stricmp("URI", tag)) - uriString = string; - else if (!stricmp("IsChannel", tag)) - isChannel = !stricmp(string.c_str(), "true"); - else if (!stricmp("Incoming", tag)) - incoming = !stricmp(string.c_str(), "true"); - else if (!stricmp("Enabled", tag)) - enabled = !stricmp(string.c_str(), "true"); - else if (!stricmp("Name", tag)) - nameString = string; - else if (!stricmp("AudioMedia", tag)) - audioMediaString = string; - else if (!stricmp("ChannelName", tag)) - nameString = string; - else if (!stricmp("DisplayName", tag)) - displayNameString = string; - else if (!stricmp("Device", tag)) - deviceString = string; - else if (!stricmp("AccountName", tag)) - nameString = string; - else if (!stricmp("ParticipantType", tag)) - participantType = strtol(string.c_str(), NULL, 10); - else if (!stricmp("IsLocallyMuted", tag)) - isLocallyMuted = !stricmp(string.c_str(), "true"); - else if (!stricmp("MicEnergy", tag)) - energy = (F32)strtod(string.c_str(), NULL); - else if (!stricmp("ChannelName", tag)) - nameString = string; - else if (!stricmp("ChannelURI", tag)) - uriString = string; - else if (!stricmp("BuddyURI", tag)) - uriString = string; - else if (!stricmp("Presence", tag)) - statusString = string; - else if (!stricmp("CaptureDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); - } - else if (!stricmp("RenderDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); - } - else if (!stricmp("CaptureDevice", tag)) - { - LLVivoxVoiceClient::getInstance()->addCaptureDevice(LLVoiceDevice(displayNameString, deviceString)); - } - else if (!stricmp("RenderDevice", tag)) - { - LLVivoxVoiceClient::getInstance()->addRenderDevice(LLVoiceDevice(displayNameString, deviceString)); - } - else if (!stricmp("BlockMask", tag)) - blockMask = string; - else if (!stricmp("PresenceOnly", tag)) - presenceOnly = string; - else if (!stricmp("AutoAcceptMask", tag)) - autoAcceptMask = string; - else if (!stricmp("AutoAddAsBuddy", tag)) - autoAddAsBuddy = string; - else if (!stricmp("MessageHeader", tag)) - messageHeader = string; - else if (!stricmp("MessageBody", tag)) - messageBody = string; - else if (!stricmp("NotificationType", tag)) - notificationType = string; - else if (!stricmp("HasText", tag)) - hasText = !stricmp(string.c_str(), "true"); - else if (!stricmp("HasAudio", tag)) - hasAudio = !stricmp(string.c_str(), "true"); - else if (!stricmp("HasVideo", tag)) - hasVideo = !stricmp(string.c_str(), "true"); - else if (!stricmp("Terminated", tag)) - terminated = !stricmp(string.c_str(), "true"); - else if (!stricmp("SubscriptionHandle", tag)) - subscriptionHandle = string; - else if (!stricmp("SubscriptionType", tag)) - subscriptionType = string; - else if (!stricmp("SessionFont", tag)) - { - LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, false); - } - else if (!stricmp("TemplateFont", tag)) - { - LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, true); - } - else if (!stricmp("ID", tag)) - { - id = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("Description", tag)) - { - descriptionString = string; - } - else if (!stricmp("ExpirationDate", tag)) - { - expirationDate = expiryTimeStampToLLDate(string); - } - else if (!stricmp("Expired", tag)) - { - hasExpired = !stricmp(string.c_str(), "1"); - } - else if (!stricmp("Type", tag)) - { - fontType = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("Status", tag)) - { - fontStatus = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("MediaCompletionType", tag)) - { - mediaCompletionType = string;; - } - - textBuffer.clear(); - accumulateText= false; - - if (responseDepth == 0) - { - // We finished all of the XML, process the data - processResponse(tag); - } - } -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::CharData(const char *buffer, int length) -{ - /* - This method is called for anything that isn't a tag, which can be text you - want that lies between tags, and a lot of stuff you don't want like file formatting - (tabs, spaces, CR/LF, etc). - - Only copy text if we are in accumulate mode... - */ - if (accumulateText) - textBuffer.append(buffer, length); -} - -// -------------------------------------------------------------------------------- - -LLDate LLVivoxProtocolParser::expiryTimeStampToLLDate(const std::string& vivox_ts) -{ - // *HACK: Vivox reports the time incorrectly. LLDate also only parses a - // subset of valid ISO 8601 dates (only handles Z, not offsets). - // So just use the date portion and fix the time here. - std::string time_stamp = vivox_ts.substr(0, 10); - time_stamp += VOICE_FONT_EXPIRY_TIME; - - LL_DEBUGS("VivoxProtocolParser") << "Vivox timestamp " << vivox_ts << " modified to: " << time_stamp << LL_ENDL; - - return LLDate(time_stamp); -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::processResponse(std::string tag) -{ - LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL; - - // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs. - // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned", - // so I believe this will give correct behavior. - - if(returnCode == 0) - statusCode = 0; - - if (isEvent) - { - const char *eventTypeCstr = eventTypeString.c_str(); - LL_DEBUGS("LowVoice") << eventTypeCstr << LL_ENDL; - - if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent")) - { - // These happen so often that logging them is pretty useless. - LL_DEBUGS("LowVoice") << "Updated Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << isModeratorMuted << ", " << isSpeaking << ", " << volume << ", " << energy << LL_ENDL; - LLVivoxVoiceClient::getInstance()->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy); - } - else if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent")) - { - LLVivoxVoiceClient::getInstance()->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state); - } - else if (!stricmp(eventTypeCstr, "SessionAddedEvent")) - { - /* - <Event type="SessionAddedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle> - <Uri>sip:confctl-1408789@bhr.vivox.com</Uri> - <IsChannel>true</IsChannel> - <Incoming>false</Incoming> - <ChannelName /> - </Event> - */ - LLVivoxVoiceClient::getInstance()->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString); - } - else if (!stricmp(eventTypeCstr, "SessionRemovedEvent")) - { - LLVivoxVoiceClient::getInstance()->sessionRemovedEvent(sessionHandle, sessionGroupHandle); - } - else if (!stricmp(eventTypeCstr, "SessionGroupUpdatedEvent")) - { - //nothng useful to process for this event, but we should not WARN that we have received it. - } - else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent")) - { - LLVivoxVoiceClient::getInstance()->sessionGroupAddedEvent(sessionGroupHandle); - } - else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent")) - { - /* - <Event type="MediaStreamUpdatedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle> - <StatusCode>200</StatusCode> - <StatusString>OK</StatusString> - <State>2</State> - <Incoming>false</Incoming> - </Event> - */ - LLVivoxVoiceClient::getInstance()->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming); - } - else if (!stricmp(eventTypeCstr, "MediaCompletionEvent")) - { - /* - <Event type="MediaCompletionEvent"> - <SessionGroupHandle /> - <MediaCompletionType>AuxBufferAudioCapture</MediaCompletionType> - </Event> - */ - LLVivoxVoiceClient::getInstance()->mediaCompletionEvent(sessionGroupHandle, mediaCompletionType); - } - else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent")) - { - /* - <Event type="ParticipantAddedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle> - <ParticipantUri>sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com</ParticipantUri> - <AccountName>xI5auBZ60SJWIk606-1JGRQ==</AccountName> - <DisplayName /> - <ParticipantType>0</ParticipantType> - </Event> - */ - LL_DEBUGS("LowVoice") << "Added Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << ", " << displayNameString << ", " << participantType << LL_ENDL; - LLVivoxVoiceClient::getInstance()->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType); - } - else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent")) - { - /* - <Event type="ParticipantRemovedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle> - <ParticipantUri>sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com</ParticipantUri> - <AccountName>xtx7YNV-3SGiG7rA1fo5Ndw==</AccountName> - </Event> - */ - LL_DEBUGS("LowVoice") << "Removed params:" << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << LL_ENDL; - - LLVivoxVoiceClient::getInstance()->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString); - } - else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent")) - { - // These are really spammy in tuning mode - LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy); - } - else if (!stricmp(eventTypeCstr, "MessageEvent")) - { - //TODO: This probably is not received any more, it was used to support SLim clients - LLVivoxVoiceClient::getInstance()->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString); - } - else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) - { - //TODO: This probably is not received any more, it was used to support SLim clients - LLVivoxVoiceClient::getInstance()->sessionNotificationEvent(sessionHandle, uriString, notificationType); - } - else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent")) - { - /* - <Event type="SessionUpdatedEvent"> - <SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle> - <SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle> - <Uri>sip:confctl-9@bhd.vivox.com</Uri> - <IsMuted>0</IsMuted> - <Volume>50</Volume> - <TransmitEnabled>1</TransmitEnabled> - <IsFocused>0</IsFocused> - <SpeakerPosition><Position><X>0</X><Y>0</Y><Z>0</Z></Position></SpeakerPosition> - <SessionFontID>0</SessionFontID> - </Event> - */ - // We don't need to process this, but we also shouldn't warn on it, since that confuses people. - } - else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent")) - { - // We don't need to process this, but we also shouldn't warn on it, since that confuses people. - } - else if (!stricmp(eventTypeCstr, "VoiceServiceConnectionStateChangedEvent")) - { - LLVivoxVoiceClient::getInstance()->voiceServiceConnectionStateChangedEvent(statusCode, statusString, mBuildID); - } - else if (!stricmp(eventTypeCstr, "AudioDeviceHotSwapEvent")) - { - /* - <Event type = "AudioDeviceHotSwapEvent"> - <EventType>RenderDeviceChanged< / EventType> - <RelevantDevice> - <Device>Speakers(Turtle Beach P11 Headset)< / Device> - <DisplayName>Speakers(Turtle Beach P11 Headset)< / DisplayName> - <Type>SpecificDevice< / Type> - < / RelevantDevice> - < / Event> - */ - // an audio device was removed or added, fetch and update the local list of audio devices. - LLVivoxVoiceClient::getInstance()->getCaptureDevicesSendMessage(); - LLVivoxVoiceClient::getInstance()->getRenderDevicesSendMessage(); - } - else - { - LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL; - } - } - else - { - const char *actionCstr = actionString.c_str(); - LL_DEBUGS("LowVoice") << actionCstr << LL_ENDL; - - if (!stricmp(actionCstr, "Session.Set3DPosition.1")) - { - // We don't need to process these - } - else if (!stricmp(actionCstr, "Connector.Create.1")) - { - LLVivoxVoiceClient::getInstance()->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID); - } - else if (!stricmp(actionCstr, "Account.Login.1")) - { - LLVivoxVoiceClient::getInstance()->loginResponse(statusCode, statusString, accountHandle, numberOfAliases); - } - else if (!stricmp(actionCstr, "Session.Create.1")) - { - LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle); - } - else if (!stricmp(actionCstr, "SessionGroup.AddSession.1")) - { - LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle); - } - else if (!stricmp(actionCstr, "Session.Connect.1")) - { - LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.Logout.1")) - { - LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1")) - { - LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.GetSessionFonts.1")) - { - LLVivoxVoiceClient::getInstance()->accountGetSessionFontsResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.GetTemplateFonts.1")) - { - LLVivoxVoiceClient::getInstance()->accountGetTemplateFontsResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Aux.SetVadProperties.1")) - { - // both values of statusCode (old and more recent) indicate valid requests - if (statusCode != 0 && statusCode != 200) - { - LL_WARNS("Voice") << "Aux.SetVadProperties.1 request failed: " - << "statusCode: " << statusCode - << " and " - << "statusString: " << statusString - << LL_ENDL; - } - } - /* - else if (!stricmp(actionCstr, "Account.ChannelGetList.1")) - { - LLVoiceClient::getInstance()->channelGetListResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Connector.AccountCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1")) - { - - } - else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelUpdate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelDelete.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1")) - { - - } - */ - } -} - -LLVivoxSecurity::LLVivoxSecurity() -{ - // This size is an arbitrary choice; Vivox does not care - // Use a multiple of three so that there is no '=' padding in the base64 (purely an esthetic choice) - #define VIVOX_TOKEN_BYTES 9 - U8 random_value[VIVOX_TOKEN_BYTES]; - - for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) - { - random_value[b] = ll_rand() & 0xff; - } - mConnectorHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); - - for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) - { - random_value[b] = ll_rand() & 0xff; - } - mAccountHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); -} - -LLVivoxSecurity::~LLVivoxSecurity() -{ -} - -bool LLVivoxVoiceP2PIncomingCall::answerInvite() { return LLVivoxVoiceClient::getInstance()->answerInvite(mCallInfo["session_handle"]); } - -void LLVivoxVoiceP2PIncomingCall::declineInvite() { LLVivoxVoiceClient::getInstance()->declineInvite(mCallInfo["session_handle"]); } diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h deleted file mode 100644 index ca1258e05e..0000000000 --- a/indra/newview/llvoicevivox.h +++ /dev/null @@ -1,1095 +0,0 @@ -/** - * @file llvoicevivox.h - * @brief Declaration of LLDiamondwareVoiceClient class which is the interface to the voice client process. - * - * $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_VOICE_VIVOX_H -#define LL_VOICE_VIVOX_H - -class LLVOAvatar; -class LLVivoxProtocolParser; - -#include "lliopipe.h" -#include "llpumpio.h" -#include "llchainio.h" -#include "lliosocket.h" -#include "v3math.h" -#include "llframetimer.h" -#include "llviewerregion.h" -#include "llcallingcard.h" // for LLFriendObserver -#include "lleventcoro.h" -#include "llcoros.h" -#include "llparcel.h" -#include "llmutelist.h" -#include <queue> - -#if 1 -# include "expat.h" -#else -# include "expat/expat.h" -#endif -#include "llvoiceclient.h" - -class LLAvatarName; -class LLVivoxVoiceClientMuteListObserver; - -extern const std::string VIVOX_VOICE_SERVER_TYPE; - -class LLVivoxVoiceP2PIncomingCall : public LLVoiceP2PIncomingCallInterface -{ - public: - LLVivoxVoiceP2PIncomingCall(const LLSD& call_info) : mCallInfo(call_info) {} - ~LLVivoxVoiceP2PIncomingCall() override {} - - bool answerInvite() override; - void declineInvite() override; - - protected: - LLSD mCallInfo; -}; - -class LLVivoxVoiceClient : public LLSingleton<LLVivoxVoiceClient>, - virtual public LLVoiceModuleInterface, - virtual public LLVoiceEffectInterface, - virtual public LLVoiceP2POutgoingCallInterface -{ - LLSINGLETON(LLVivoxVoiceClient); - LOG_CLASS(LLVivoxVoiceClient); - virtual ~LLVivoxVoiceClient(); - -public: - /// @name LLVoiceModuleInterface virtual implementations - /// @see LLVoiceModuleInterface - //@{ - void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) - void terminate() override; // Call this to clean up during shutdown - - const LLVoiceVersionInfo& getVersion() override; - - void updateSettings() override; // call after loading settings and whenever they change - - // Returns true if vivox has successfully logged in and is not in error state - bool isVoiceWorking() const override; - - void setHidden(bool hidden) override; // virtual - - ///////////////////// - /// @name Tuning - //@{ - void tuningStart() override; - void tuningStop() override; - bool inTuningMode() override; - - void tuningSetMicVolume(float volume) override; - void tuningSetSpeakerVolume(float volume) override; - float tuningGetEnergy(void) override; - - //@} - - ///////////////////// - /// @name Devices - //@{ - // This returns true when it's safe to bring up the "device settings" dialog in the prefs. - // i.e. when the daemon is running and connected, and the device lists are populated. - bool deviceSettingsAvailable() override; - bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel. - - // Requery the vivox daemon for the current list of input/output devices. - // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed - // (use this if you want to know when it's done). - // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. - void refreshDeviceLists(bool clearCurrentList = true) override; - - void setCaptureDevice(const std::string& name) override; - void setRenderDevice(const std::string& name) override; - - LLVoiceDeviceList& getCaptureDevices() override; - LLVoiceDeviceList& getRenderDevices() override; - //@} - - void getParticipantList(std::set<LLUUID> &participants) override; - bool isParticipant(const LLUUID& speaker_id) override; - - // Send a text message to the specified user, initiating the session if necessary. - // virtual bool sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;}; - - // Returns true if calling back the session URI after the session has closed is possible. - // Currently this will be false only for PSTN P2P calls. - // NOTE: this will return true if the session can't be found. - bool isSessionCallBackPossible(const LLUUID &session_id) override; - - // Returns true if the session can accepte text IM's. - // Currently this will be false only for PSTN P2P calls. - // NOTE: this will return true if the session can't be found. - bool isSessionTextIMPossible(const LLUUID &session_id) override; - - //////////////////////////// - /// @name Channel stuff - //@{ - // returns true iff the user is currently in a proximal (local spatial) channel. - // Note that gestures should only fire if this returns true. - bool inProximalChannel() override; - - void setNonSpatialChannel(const LLSD& channelInfo, - bool notify_on_first_join, - bool hangup_on_last_leave) override; - - bool setSpatialChannel(const LLSD& channelInfo) override; - - void leaveNonSpatialChannel() override; - - void processChannels(bool process) override; - - void leaveChannel(void); - - bool isCurrentChannel(const LLSD &channelInfo) override; - bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) override; - - //@} - - - ////////////////////////// - /// @name LLVoiceP2POutgoingCallInterface - //@{ - // start a voice channel with the specified user - void callUser(const LLUUID &uuid) override; - void hangup() override; - - //@} - - LLVoiceP2POutgoingCallInterface *getOutgoingCallInterface() override { return this; } - - LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voice_call_info) override; - - bool answerInvite(const std::string &sessionHandle); - void declineInvite(const std::string &sessionHandle); - - ///////////////////////// - /// @name Volume/gain - //@{ - void setVoiceVolume(F32 volume) override; - void setMicGain(F32 volume) override; - //@} - - ///////////////////////// - /// @name enable disable voice and features - //@{ - void setVoiceEnabled(bool enabled) override; - void setMuteMic(bool muted) override; // Set the mute state of the local mic. - //@} - - ////////////////////////// - /// @name nearby speaker accessors - //@{ - std::string getDisplayName(const LLUUID& id) override; - bool isParticipantAvatar(const LLUUID &id) override; - bool getIsSpeaking(const LLUUID& id) override; - bool getIsModeratorMuted(const LLUUID& id) override; - F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - F32 getUserVolume(const LLUUID& id) override; - void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal) - //@} - - // authorize the user - void userAuthorized(const std::string& user_id, - const LLUUID &agentID) override; - - ////////////////////////////// - /// @name Status notification - //@{ - void addObserver(LLVoiceClientStatusObserver* observer) override; - void removeObserver(LLVoiceClientStatusObserver* observer) override; - void addObserver(LLFriendObserver* observer) override; - void removeObserver(LLFriendObserver* observer) override; - void addObserver(LLVoiceClientParticipantObserver* observer) override; - void removeObserver(LLVoiceClientParticipantObserver* observer) override; - //@} - - std::string sipURIFromID(const LLUUID &id) const override; - LLSD getP2PChannelInfoTemplate(const LLUUID& id) const override; - //@} - - /// @name LLVoiceEffectInterface virtual implementations - /// @see LLVoiceEffectInterface - //@{ - - ////////////////////////// - /// @name Accessors - //@{ - bool setVoiceEffect(const LLUUID& id) override; - const LLUUID getVoiceEffect() override; - LLSD getVoiceEffectProperties(const LLUUID& id) override; - - void refreshVoiceEffectLists(bool clear_lists) override; - const voice_effect_list_t& getVoiceEffectList() const override; - const voice_effect_list_t& getVoiceEffectTemplateList() const override; - //@} - - ////////////////////////////// - /// @name Status notification - //@{ - void addObserver(LLVoiceEffectObserver* observer) override; - void removeObserver(LLVoiceEffectObserver* observer) override; - //@} - - ////////////////////////////// - /// @name Effect preview buffer - //@{ - void enablePreviewBuffer(bool enable) override; - void recordPreviewBuffer() override; - void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) override; - void stopPreviewBuffer() override; - - bool isPreviewRecording() override; - bool isPreviewPlaying() override; - //@} - - //@} - - bool onCheckVoiceEffect(const std::string& voice_effect_name); - void onClickVoiceEffect(const std::string& voice_effect_name); - -protected: - ////////////////////// - // Vivox Specific definitions - - friend class LLVivoxVoiceClientMuteListObserver; - friend class LLVivoxVoiceClientFriendsObserver; - - - enum streamState - { - streamStateUnknown = 0, - streamStateIdle = 1, - streamStateConnected = 2, - streamStateRinging = 3, - streamStateConnecting = 6, // same as Vivox session_media_connecting enum - streamStateDisconnecting = 7, //Same as Vivox session_media_disconnecting enum - }; - - struct participantState - { - public: - participantState(const std::string &uri); - - bool updateMuteState(); // true if mute state has changed - bool isAvatar(); - - std::string mURI; - LLUUID mAvatarID; - std::string mAccountName; - std::string mDisplayName; - LLFrameTimer mSpeakingTimeout; - F32 mLastSpokeTimestamp; - F32 mPower; - F32 mVolume; - std::string mGroupID; - int mUserVolume; - bool mPTT; - bool mIsSpeaking; - bool mIsModeratorMuted; - bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted) - bool mVolumeSet; // true if incoming volume messages should not change the volume - bool mVolumeDirty; // true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed) - bool mAvatarIDValid; - bool mIsSelf; - }; - typedef std::shared_ptr<participantState> participantStatePtr_t; - typedef std::weak_ptr<participantState> participantStateWptr_t; - - typedef std::map<const std::string, participantStatePtr_t> participantMap; - typedef std::map<const LLUUID, participantStatePtr_t> participantUUIDMap; - - struct sessionState - { - public: - typedef std::shared_ptr<sessionState> ptr_t; - typedef std::weak_ptr<sessionState> wptr_t; - - typedef std::function<void(const ptr_t &)> sessionFunc_t; - - static ptr_t createSession(); - ~sessionState(); - - LLSD getVoiceChannelInfo(); - - participantStatePtr_t addParticipant(const std::string &uri); - void removeParticipant(const participantStatePtr_t &participant); - void removeAllParticipants(); - - participantStatePtr_t findParticipant(const std::string &uri); - participantStatePtr_t findParticipantByID(const LLUUID& id); - - static ptr_t matchSessionByHandle(const std::string &handle); - static ptr_t matchCreatingSessionByURI(const std::string &uri); - static ptr_t matchSessionByURI(const std::string &uri); - static ptr_t matchSessionByParticipant(const LLUUID &participant_id); - - bool isCallBackPossible(); - bool isTextIMPossible(); - bool isSpatial() { return mIsSpatial; } - - static void for_each(sessionFunc_t func); - - std::string mHandle; - std::string mGroupHandle; - std::string mSIPURI; - std::string mAlias; - std::string mName; - std::string mAlternateSIPURI; - std::string mHash; // Channel password - std::string mErrorStatusString; - std::queue<std::string> mTextMsgQueue; - - LLUUID mIMSessionID; - LLUUID mCallerID; - int mErrorStatusCode; - int mMediaStreamState; - bool mCreateInProgress; // True if a Session.Create has been sent for this session and no response has been received yet. - bool mMediaConnectInProgress; // True if a Session.MediaConnect has been sent for this session and no response has been received yet. - bool mVoiceInvitePending; // True if a voice invite is pending for this session (usually waiting on a name lookup) - bool mTextInvitePending; // True if a text invite is pending for this session (usually waiting on a name lookup) - bool mSynthesizedCallerID; // True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup. - bool mIsChannel; // True for both group and spatial channels (false for p2p, PSTN) - bool mIsSpatial; // True for spatial channels - bool mIsP2P; - bool mIncoming; - bool mVoiceActive; - bool mReconnect; // Whether we should try to reconnect to this session if it's dropped - - // Set to true when the volume/mute state of someone in the participant list changes. - // The code will have to walk the list to find the changed participant(s). - bool mVolumeDirty; - bool mMuteDirty; - - bool mParticipantsChanged; - participantMap mParticipantsByURI; - participantUUIDMap mParticipantsByUUID; - - LLUUID mVoiceFontID; - - static void VerifySessions(); - - private: - sessionState(); - - static std::set<wptr_t, std::owner_less<wptr_t>> mSession; // canonical list of outstanding sessions. - std::set<wptr_t>::iterator mMyIterator; // used for delete - - static void for_eachPredicate(const wptr_t &a, sessionFunc_t func); - - static bool testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle); - static bool testByCreatingURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri); - static bool testBySIPOrAlterateURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri); - static bool testByCallerId(const LLVivoxVoiceClient::sessionState::wptr_t &a, LLUUID participantId); - - }; - typedef std::shared_ptr<sessionState> sessionStatePtr_t; - - typedef std::map<std::string, sessionStatePtr_t> sessionMap; - - /////////////////////////////////////////////////////// - // Private Member Functions - ////////////////////////////////////////////////////// - - - - ////////////////////////////// - /// @name TVC/Server management and communication - //@{ - // Call this if the connection to the daemon terminates unexpectedly. It will attempt to reset everything and relaunch. - void daemonDied(); - - // Call this if we're just giving up on voice (can't provision an account, etc.). It will clean up and go away. - void giveUp(); - - // write to the tvc - bool writeString(const std::string &str); - - void connectorCreate(); - void connectorShutdown(); - void closeSocket(void); - -// void requestVoiceAccountProvision(S32 retries = 3); - void setLoginInfo( - const std::string& account_name, - const std::string& password, - const std::string& voice_sip_uri_hostname, - const std::string& voice_account_server_uri); - void loginSendMessage(); - void logout(); - void logoutSendMessage(); - - - //@} - - //------------------------------------ - // tuning - - void tuningRenderStartSendMessage(const std::string& name, bool loop); - void tuningRenderStopSendMessage(); - - void tuningCaptureStartSendMessage(int duration); - void tuningCaptureStopSendMessage(); - - //---------------------------------- - // devices - void clearCaptureDevices(); - void addCaptureDevice(const LLVoiceDevice& device); - void clearRenderDevices(); - void setDevicesListUpdated(bool state); - void addRenderDevice(const LLVoiceDevice& device); - void buildSetAudioDevices(std::ostringstream &stream); - - void getCaptureDevicesSendMessage(); - void getRenderDevicesSendMessage(); - - // local audio updates, mic mute, speaker mute, mic volume and speaker volumes - void sendLocalAudioUpdates(); - - ///////////////////////////// - // Response/Event handlers - void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID); - void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases); - void sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); - void sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); - void sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString); - void logoutResponse(int statusCode, std::string &statusString); - void connectorShutdownResponse(int statusCode, std::string &statusString); - - void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); - void mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType); - void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming); - void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString); - void sessionGroupAddedEvent(std::string &sessionGroupHandle); - void sessionRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle); - void participantAddedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString, std::string &displayNameString, int participantType); - void participantRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString); - void participantUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy); - void voiceServiceConnectionStateChangedEvent(int statusCode, std::string &statusString, std::string &build_id); - void auxAudioPropertiesEvent(F32 energy); - void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString); - void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType); - - void muteListChanged(); - - ///////////////////////////// - // VAD changes - // disable auto-VAD and configure VAD parameters explicitly - void setupVADParams(unsigned int vad_auto, unsigned int vad_hangover, unsigned int vad_noise_floor, unsigned int vad_sensitivity); - void onVADSettingsChange(); - - ///////////////////////////// - // Sending updates of current state - void updatePosition(void); - void setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot); - void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot); - bool channelFromRegion(LLViewerRegion *region, std::string &name); - - void setEarLocation(S32 loc); - - - ///////////////////////////// - // Accessors for data related to nearby speakers - - // MBW -- XXX -- Not sure how to get this data out of the TVC - bool getUsingPTT(const LLUUID& id); - std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable) - - ///////////////////////////// - // Recording controls - void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200); - void recordingLoopSave(const std::string& filename); - void recordingStop(); - - // Playback controls - void filePlaybackStart(const std::string& filename); - void filePlaybackStop(); - void filePlaybackSetPaused(bool paused); - void filePlaybackSetMode(bool vox = false, float speed = 1.0f); - - participantStatePtr_t findParticipantByID(const LLUUID& id); - - -#if 0 - //////////////////////////////////////// - // voice sessions. - typedef std::set<sessionStatePtr_t> sessionSet; - - typedef sessionSet::iterator sessionIterator; - sessionIterator sessionsBegin(void); - sessionIterator sessionsEnd(void); -#endif - - sessionStatePtr_t findSession(const std::string &handle); - sessionStatePtr_t findSessionBeingCreatedByURI(const std::string &uri); - sessionStatePtr_t findSession(const LLUUID &participant_id); - - sessionStatePtr_t addSession(const std::string &uri, const std::string &handle = std::string()); - void clearSessionHandle(const sessionStatePtr_t &session); - void setSessionHandle(const sessionStatePtr_t &session, const std::string &handle); - void setSessionURI(const sessionStatePtr_t &session, const std::string &uri); - void deleteSession(const sessionStatePtr_t &session); - void deleteAllSessions(void); - - void verifySessionState(void); - - void joinedAudioSession(const sessionStatePtr_t &session); - void leftAudioSession(const sessionStatePtr_t &session); - - // This is called in several places where the session _may_ need to be deleted. - // It contains logic for whether to delete the session or keep it around. - void reapSession(const sessionStatePtr_t &session); - - // Returns true if the session seems to indicate we've moved to a region on a different voice server - bool sessionNeedsRelog(const sessionStatePtr_t &session); - - - ////////////////////////////////////// - // buddy list stuff, needed for SLIM later - struct buddyListEntry - { - buddyListEntry(const std::string &uri); - std::string mURI; - std::string mDisplayName; - LLUUID mUUID; - bool mOnlineSL; - bool mOnlineSLim; - bool mCanSeeMeOnline; - bool mHasBlockListEntry; - bool mHasAutoAcceptListEntry; - bool mNameResolved; - bool mInSLFriends; - bool mInVivoxBuddies; - }; - - typedef std::map<std::string, buddyListEntry*> buddyListMap; - - ///////////////////////////// - // session control messages - - void accountListBlockRulesSendMessage(); - void accountListAutoAcceptRulesSendMessage(); - - void sessionGroupCreateSendMessage(); - void sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); - void sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); - void sessionMediaConnectSendMessage(const sessionStatePtr_t &session); // just joins the audio session - void sessionTextConnectSendMessage(const sessionStatePtr_t &session); // just joins the text session - void sessionTerminateSendMessage(const sessionStatePtr_t &session); - void sessionGroupTerminateSendMessage(const sessionStatePtr_t &session); - void sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session); - // void sessionTextDisconnectSendMessage(sessionState *session); - - - - // Pokes the state machine to leave the audio session next time around. - void sessionTerminate(); - - // Pokes the state machine to shut down the connector and restart it. - void requestRelog(); - - // Does the actual work to get out of the audio session - void leaveAudioSession(); - - friend class LLVivoxVoiceClientCapResponder; - - - void lookupName(const LLUUID &id); - void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); - void avatarNameResolved(const LLUUID &id, const std::string &name); - static void predAvatarNameResolution(const LLVivoxVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name); - - boost::signals2::connection mAvatarNameCacheConnection; - - ///////////////////////////// - // Voice fonts - - void addVoiceFont(const S32 id, - const std::string &name, - const std::string &description, - const LLDate &expiration_date, - bool has_expired, - const S32 font_type, - const S32 font_status, - const bool template_font = false); - void accountGetSessionFontsResponse(int statusCode, const std::string &statusString); - void accountGetTemplateFontsResponse(int statusCode, const std::string &statusString); - -private: - - LLVoiceVersionInfo mVoiceVersion; - - // Coroutine support methods - //--- - void voiceControlCoro(); - void voiceControlStateMachine(S32 &coro_state); - - bool endAndDisconnectSession(); - - bool callbackEndDaemon(const LLSD& data); - bool startAndLaunchDaemon(); - bool provisionVoiceAccount(); - bool establishVoiceConnection(); - bool breakVoiceConnection(bool wait); - bool loginToVivox(); - void logoutOfVivox(bool wait); - bool retrieveVoiceFonts(); - - bool requestParcelVoiceInfo(); - - bool addAndJoinSession(const sessionStatePtr_t &nextSession); - bool terminateAudioSession(bool wait); - - bool waitForChannel(); - bool runSession(const sessionStatePtr_t &session); - - void recordingAndPlaybackMode(); - int voiceRecordBuffer(); - int voicePlaybackBuffer(); - - bool performMicTuning(); - //--- - /// Clean up objects created during a voice session. - void cleanUp(); - - bool mSessionTerminateRequested; - bool mRelogRequested; - // Number of times (in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine(). - // The larger it is the greater is possibility there is a problem with connection to voice server. - // Introduced while fixing EXT-4313. - int mSpatialJoiningNum; - - static void idle(void *user_data); - - LLHost mDaemonHost; - LLSocket::ptr_t mSocket; - - // We should kill the voice daemon in case of connection alert - bool mTerminateDaemon; - - friend class LLVivoxProtocolParser; - - std::string mAccountName; - std::string mAccountPassword; - std::string mAccountDisplayName; - - bool mTuningMode; - float mTuningEnergy; - std::string mTuningAudioFile; - int mTuningMicVolume; - bool mTuningMicVolumeDirty; - int mTuningSpeakerVolume; - bool mTuningSpeakerVolumeDirty; - bool mDevicesListUpdated; // set to true when the device list has been updated - // and false when the panelvoicedevicesettings has queried for an update status. - - std::string mSpatialSessionURI; - std::string mSpatialSessionCredentials; - - std::string mMainSessionGroupHandle; // handle of the "main" session group. - - std::string mChannelName; // Name of the channel to be looked up - sessionStatePtr_t mAudioSession; // Session state for the current audio session - - sessionStatePtr_t mNextAudioSession; // Session state for the audio session we're trying to join - - S32 mCurrentParcelLocalID; // Used to detect parcel boundary crossings - std::string mCurrentRegionName; // Used to detect parcel boundary crossings - - bool mConnectorEstablished; // set by "Create Connector" response - bool mAccountLoggedIn; // set by login message - int mNumberOfAliases; - U32 mCommandCookie; - - std::string mVoiceAccountServerURI; - std::string mVoiceSIPURIHostName; - - int mLoginRetryCount; - - sessionMap mSessionsByHandle; // Active sessions, indexed by session handle. Sessions which are being initiated may not be in this map. -#if 0 - sessionSet mSessions; // All sessions, not indexed. This is the canonical session list. -#endif - - bool mBuddyListMapPopulated; - bool mBlockRulesListReceived; - bool mAutoAcceptRulesListReceived; - buddyListMap mBuddyListMap; - - LLVoiceDeviceList mCaptureDevices; - LLVoiceDeviceList mRenderDevices; - - std::string mCaptureDevice; - std::string mRenderDevice; - bool mCaptureDeviceDirty; - bool mRenderDeviceDirty; - - bool mIsInitialized; - bool mShutdownComplete; - - bool checkParcelChanged(bool update = false); - bool switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); - void joinSession(const sessionStatePtr_t &session); - - std::string nameFromID(const LLUUID &id) const; - bool IDFromName(const std::string name, LLUUID &uuid); - std::string sipURIFromAvatar(LLVOAvatar *avatar); - std::string sipURIFromName(std::string &name); - - // Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not. - std::string nameFromsipURI(const std::string &uri); - - bool inSpatialChannel(void); - LLSD getAudioSessionChannelInfo(); - std::string getAudioSessionHandle(); - - void sendPositionAndVolumeUpdate(void); - - void sendCaptureAndRenderDevices(); - void buildSetCaptureDevice(std::ostringstream &stream); - void buildSetRenderDevice(std::ostringstream &stream); - - - void sendFriendsListUpdates(); - -#if 0 - // start a text IM session with the specified user - // This will be asynchronous, the session may be established at a future time. - sessionStatePtr_t startUserIMSession(const LLUUID& uuid); -#endif - - void enforceTether(void); - - bool mSpatialCoordsDirty; - - LLVector3d mCameraPosition; - LLVector3d mCameraRequestedPosition; - LLVector3 mCameraVelocity; - LLMatrix3 mCameraRot; - - LLVector3d mAvatarPosition; - LLVector3 mAvatarVelocity; - LLQuaternion mAvatarRot; - - bool mMuteMic; - bool mMuteMicDirty; - bool mHidden; //Set to true during teleport to hide the agent's position. - - // Set to true when the friends list is known to have changed. - bool mFriendsListDirty; - - enum - { - earLocCamera = 0, // ear at camera - earLocAvatar, // ear at avatar - earLocMixed // ear at avatar location/camera direction - }; - - S32 mEarLocation; - - bool mSpeakerVolumeDirty; - bool mSpeakerMuteDirty; - int mSpeakerVolume; - - int mMicVolume; - bool mMicVolumeDirty; - - bool mVoiceEnabled; - bool mProcessChannels; - bool mWriteInProgress; - std::string mWriteString; - size_t mWriteOffset; - - typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t; - observer_set_t mParticipantObservers; - - void notifyParticipantObservers(); - - typedef std::set<LLVoiceClientStatusObserver*> status_observer_set_t; - status_observer_set_t mStatusObservers; - - void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); - - typedef std::set<LLFriendObserver*> friend_observer_set_t; - friend_observer_set_t mFriendObservers; - void notifyFriendObservers(); - - // Voice Fonts - - void expireVoiceFonts(); - void deleteVoiceFont(const LLUUID& id); - void deleteAllVoiceFonts(); - void deleteVoiceFontTemplates(); - - S32 getVoiceFontIndex(const LLUUID& id) const; - S32 getVoiceFontTemplateIndex(const LLUUID& id) const; - - void accountGetSessionFontsSendMessage(); - void accountGetTemplateFontsSendMessage(); - void sessionSetVoiceFontSendMessage(const sessionStatePtr_t &session); - - void updateVoiceMorphingMenu(); - void notifyVoiceFontObservers(); - - typedef enum e_voice_font_type - { - VOICE_FONT_TYPE_NONE = 0, - VOICE_FONT_TYPE_ROOT = 1, - VOICE_FONT_TYPE_USER = 2, - VOICE_FONT_TYPE_UNKNOWN - } EVoiceFontType; - - typedef enum e_voice_font_status - { - VOICE_FONT_STATUS_NONE = 0, - VOICE_FONT_STATUS_FREE = 1, - VOICE_FONT_STATUS_NOT_FREE = 2, - VOICE_FONT_STATUS_UNKNOWN - } EVoiceFontStatus; - - struct voiceFontEntry - { - voiceFontEntry(LLUUID& id); - ~voiceFontEntry(); - - LLUUID mID; - S32 mFontIndex; - std::string mName; - LLDate mExpirationDate; - S32 mFontType; - S32 mFontStatus; - bool mIsNew; - - LLFrameTimer mExpiryTimer; - }; - - bool mVoiceFontsReceived; - bool mVoiceFontsNew; - bool mVoiceFontListDirty; - voice_effect_list_t mVoiceFontList; - voice_effect_list_t mVoiceFontTemplateList; - - typedef std::map<const LLUUID, voiceFontEntry*> voice_font_map_t; - voice_font_map_t mVoiceFontMap; - voice_font_map_t mVoiceFontTemplateMap; - - typedef std::set<LLVoiceEffectObserver*> voice_font_observer_set_t; - voice_font_observer_set_t mVoiceFontObservers; - - LLFrameTimer mVoiceFontExpiryTimer; - - - // Audio capture buffer - - void captureBufferRecordStartSendMessage(); - void captureBufferRecordStopSendMessage(); - void captureBufferPlayStartSendMessage(const LLUUID& voice_font_id = LLUUID::null); - void captureBufferPlayStopSendMessage(); - - bool mCaptureBufferMode; // Disconnected from voice channels while using the capture buffer. - bool mCaptureBufferRecording; // A voice sample is being captured. - bool mCaptureBufferRecorded; // A voice sample is captured in the buffer ready to play. - bool mCaptureBufferPlaying; // A voice sample is being played. - - LLTimer mCaptureTimer; - LLUUID mPreviewVoiceFont; - LLUUID mPreviewVoiceFontLast; - S32 mPlayRequestCount; - bool mIsInTuningMode; - bool mIsInChannel; - bool mIsJoiningSession; - bool mIsWaitingForFonts; - bool mIsLoggingIn; - bool mIsLoggedIn; - bool mIsProcessingChannels; - bool mIsCoroutineActive; - - // This variables can last longer than vivox in coroutines so we need them as static - static bool sShuttingDown; - static bool sConnected; - static LLPumpIO* sPump; - - LLEventMailDrop mVivoxPump; -}; - - -/** - * @class LLVivoxProtocolParser - * @brief This class helps construct new LLIOPipe specializations - * @see LLIOPipe - * - * THOROUGH_DESCRIPTION - */ -class LLVivoxProtocolParser : public LLIOPipe -{ - LOG_CLASS(LLVivoxProtocolParser); -public: - LLVivoxProtocolParser(); - virtual ~LLVivoxProtocolParser(); - -protected: - /* @name LLIOPipe virtual implementations - */ - //@{ - /** - * @brief Process the data in buffer - */ - virtual EStatus process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump); - //@} - - std::string mInput; - - // Expat control members - XML_Parser parser; - int responseDepth; - bool ignoringTags; - bool isEvent; - int ignoreDepth; - - // Members for processing responses. The values are transient and only valid within a call to processResponse(). - int returnCode; - int statusCode; - std::string statusString; - std::string requestId; - std::string actionString; - std::string connectorHandle; - std::string versionID; - std::string mBuildID; - std::string accountHandle; - std::string sessionHandle; - std::string sessionGroupHandle; - std::string alias; - std::string applicationString; - - // Members for processing events. The values are transient and only valid within a call to processResponse(). - std::string eventTypeString; - int state; - std::string uriString; - bool isChannel; - bool incoming; - bool enabled; - std::string nameString; - std::string audioMediaString; - std::string deviceString; - std::string displayNameString; - int participantType; - bool isLocallyMuted; - bool isModeratorMuted; - bool isSpeaking; - int volume; - F32 energy; - std::string messageHeader; - std::string messageBody; - std::string notificationType; - bool hasText; - bool hasAudio; - bool hasVideo; - bool terminated; - std::string blockMask; - std::string presenceOnly; - std::string autoAcceptMask; - std::string autoAddAsBuddy; - int numberOfAliases; - std::string subscriptionHandle; - std::string subscriptionType; - S32 id; - std::string descriptionString; - LLDate expirationDate; - bool hasExpired; - S32 fontType; - S32 fontStatus; - std::string mediaCompletionType; - - // Members for processing text between tags - std::string textBuffer; - bool accumulateText; - - void reset(); - - void processResponse(std::string tag); - - static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr); - static void XMLCALL ExpatEndTag(void *data, const char *el); - static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len); - - void StartTag(const char *tag, const char **attr); - void EndTag(const char *tag); - void CharData(const char *buffer, int length); - LLDate expiryTimeStampToLLDate(const std::string& vivox_ts); - -}; - -class LLVivoxSecurity : public LLSingleton<LLVivoxSecurity> -{ - LLSINGLETON(LLVivoxSecurity); - virtual ~LLVivoxSecurity(); - - public: - std::string connectorHandle() { return mConnectorHandle; }; - std::string accountHandle() { return mAccountHandle; }; - - private: - std::string mConnectorHandle; - std::string mAccountHandle; -}; - -class LLVoiceVivoxStats : public LLSingleton<LLVoiceVivoxStats> -{ - LLSINGLETON(LLVoiceVivoxStats); - LOG_CLASS(LLVoiceVivoxStats); - virtual ~LLVoiceVivoxStats(); - - private: - F64SecondsImplicit mStartTime; - - U32 mConnectCycles; - - F64 mConnectTime; - U32 mConnectAttempts; - - F64 mProvisionTime; - U32 mProvisionAttempts; - - F64 mEstablishTime; - U32 mEstablishAttempts; - - public: - - void reset(); - void connectionAttemptStart(); - void connectionAttemptEnd(bool success); - void provisionAttemptStart(); - void provisionAttemptEnd(bool success); - void establishAttemptStart(); - void establishAttemptEnd(bool success); - LLSD read(); -}; - -#endif //LL_VIVOX_VOICE_CLIENT_H - diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index c6967a8743..33cdf66272 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -347,6 +347,46 @@ const LLVoiceVersionInfo& LLWebRTCVoiceClient::getVersion() return mVoiceVersion; } +// -------------------------------------------------- + +void LLWebRTCVoiceClient::updateVersion() +{ + sessionStatePtr_t session = mNextSession.get() ? mNextSession : mSession; + + if (session) + { + // A WebRTC session can be connected to multiple servers at once. To more easily disambiguate which server version is being printed, show the connection type. In most cases, this shouldn't matter and the Janus version should be the same for all connections. Janus versions are also logged for each connection. + mVoiceVersion.serverVersion = session->getVersion(); + if (dynamic_cast<adhocSessionState*>(session.get())) + { + if (session->mHangupOnLastLeave) + { + mVoiceVersion.mBuildVersion = "p2p"; + } + else + { + mVoiceVersion.mBuildVersion = "ad-hoc"; + } + } + else if (session->isEstate()) + { + mVoiceVersion.mBuildVersion = "estate"; + } + else if (session->isSpatial()) + { + mVoiceVersion.mBuildVersion = "parcel"; + } + else + { + mVoiceVersion.mBuildVersion = mVoiceVersion.serverVersion; + } + } + else + { + mVoiceVersion.serverVersion = mVoiceVersion.mBuildVersion = ""; + } +} + //--------------------------------------------------- void LLWebRTCVoiceClient::updateSettings() @@ -1638,6 +1678,10 @@ void LLWebRTCVoiceClient::setVoiceVolume(F32 volume) void LLWebRTCVoiceClient::predSetSpeakerVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 volume) { + if (session->mShuttingDown) + { + return; + } session->setSpeakerVolume(volume); } @@ -1667,6 +1711,14 @@ void LLWebRTCVoiceClient::setVoiceEnabled(bool enabled) mVoiceEnabled = enabled; LLVoiceClientStatusObserver::EStatusType status; + // Gate the audio devices on voice being enabled: the capture mic and + // playout speaker only run while voice is on, and the mic isn't held + // open when voice is off. + if (mWebRTCDeviceInterface) + { + mWebRTCDeviceInterface->setVoiceEnabled(enabled); + } + if (enabled) { LL_DEBUGS("Voice") << "enabling" << LL_ENDL; @@ -1866,6 +1918,10 @@ LLWebRTCVoiceClient::sessionState::sessionState() : void LLWebRTCVoiceClient::predUpdateOwnVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 audio_level) { + if (session->mShuttingDown) + { + return; + } participantStatePtr_t participant = session->findParticipantByID(gAgentID); if (participant) { @@ -1894,9 +1950,16 @@ void LLWebRTCVoiceClient::sessionState::sendData(const std::string &data) void LLWebRTCVoiceClient::sessionState::setMuteMic(bool muted) { mMuted = muted; + if (mShuttingDown) + { + return; + } for (auto &connection : mWebRTCConnections) { - connection->setMuteMic(muted); + if (!connection->isShuttingDown()) + { + connection->setMuteMic(muted); + } } } @@ -1905,7 +1968,10 @@ void LLWebRTCVoiceClient::sessionState::setSpeakerVolume(F32 volume) mSpeakerVolume = volume; for (auto &connection : mWebRTCConnections) { - connection->setSpeakerVolume(volume); + if (!connection->isShuttingDown()) + { + connection->setSpeakerVolume(volume); + } } } @@ -1917,7 +1983,10 @@ void LLWebRTCVoiceClient::sessionState::setUserVolume(const LLUUID &id, F32 volu } for (auto &connection : mWebRTCConnections) { - connection->setUserVolume(id, volume); + if (!connection->isShuttingDown()) + { + connection->setUserVolume(id, volume); + } } } @@ -1929,7 +1998,10 @@ void LLWebRTCVoiceClient::sessionState::setUserMute(const LLUUID &id, bool mute) } for (auto &connection : mWebRTCConnections) { - connection->setUserMute(id, mute); + if (!connection->isShuttingDown()) + { + connection->setUserMute(id, mute); + } } } /*static*/ @@ -2058,6 +2130,22 @@ void LLWebRTCVoiceClient::sessionState::revive() mShuttingDown = false; } +const std::string LLWebRTCVoiceClient::sessionState::getVersion() const +{ + // Prefer the version of a primary connection which has already received a version string over the data channel. If that does not make sense, fall back to any non-empty version string we can find. + bool primary = true; + do + { + for (auto& connection : mWebRTCConnections) { + if (connection->isPrimary() == primary && connection->getVersion().length()) { + return connection->getVersion(); + } + } + primary = !primary; + } while (!primary); + return ""; +} + //========================================================================= // the following are methods to support the coroutine implementation of the // voice connection and processing. They should only be called in the context @@ -2254,6 +2342,11 @@ void LLWebRTCVoiceClient::deleteSession(const sessionStatePtr_t &session) { mNextSession.reset(); } + + if (!sShuttingDown) + { + updateVersion(); + } } @@ -2292,12 +2385,6 @@ void LLWebRTCVoiceClient::avatarNameResolved(const LLUUID &id, const std::string sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name)); } -// Leftover from vivox PTSN -std::string LLWebRTCVoiceClient::sipURIFromID(const LLUUID& id) const -{ - return id.asString(); -} - LLSD LLWebRTCVoiceClient::getP2PChannelInfoTemplate(const LLUUID& id) const { return LLSD(); @@ -2629,6 +2716,10 @@ void LLVoiceWebRTCConnection::sendData(const std::string &data) } } +const std::string& LLVoiceWebRTCConnection::getVersion() { + return mServerVersion; +} + // Tell the simulator that we're shutting down a voice connection. // The simulator will pass this on to the Secondlife WebRTC server. void LLVoiceWebRTCConnection::breakVoiceConnectionCoro(connectionPtr_t connection) @@ -3060,6 +3151,7 @@ bool LLVoiceWebRTCConnection::connectionStateMachine() // An object where each key is an agent id. (in the future, we may allow // integer indices into an agentid list, populated on join commands. For size. // Each key will point to a json object with keys identifying what's updated. +// 'V' - voice server version (string) // 'p' - audio source power (level/volume) (int8 as int) // 'j' - object of join data (currently only a boolean 'p' marking a primary participant) // 'l' - boolean, always true if exists. @@ -3127,6 +3219,16 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b boost::json::object participant_obj = participant_elem.value().as_object(); + if (participant_obj.contains("V") && participant_obj["V"].is_string() && agent_id == gAgentID) + { + // sendJoin was called on the connection. The voice server has responded with the new version string. Set it here. + mServerVersion = participant_obj["V"].as_string().c_str(); + LLWebRTCVoiceClient::getInstance()->updateVersion(); + LL_DEBUGS("Voice") << "Received version string \"" << participant_obj["V"].as_string().c_str() + << "\" for connection: primary=" << mPrimary << ", spatial=" << isSpatial() + << ", region=" << mRegionID << ", mChannelID=" << mChannelID << LL_ENDL; + } + LLWebRTCVoiceClient::participantStatePtr_t participant = LLWebRTCVoiceClient::getInstance()->findParticipantByID(mChannelID, agent_id); bool joined = false; @@ -3359,9 +3461,10 @@ void LLVoiceWebRTCConnection::OnStatsDelivered(const llwebrtc::LLWebRTCStatsMap& { if (attributes.contains("packetsLost")) { - U32 out_packets_lost = 0; - LLStringUtil::convertToU32(attributes.at("packetsLost"), out_packets_lost); - sample(LLStatViewer::WEBRTC_PACKETS_OUT_LOST, out_packets_lost); + // packetsLost may be negative, clamp to zero for unsigned Viewer stats + S32 out_packets_lost = 0; + LLStringUtil::convertToS32(attributes.at("packetsLost"), out_packets_lost); + sample(LLStatViewer::WEBRTC_PACKETS_OUT_LOST, static_cast<U32>(llmax(out_packets_lost, 0))); } if (attributes.contains("jitter")) { @@ -3375,9 +3478,10 @@ void LLVoiceWebRTCConnection::OnStatsDelivered(const llwebrtc::LLWebRTCStatsMap& { if (attributes.contains("packetsLost")) { - U32 in_packets_lost = 0; - LLStringUtil::convertToU32(attributes.at("packetsLost"), in_packets_lost); - sample(LLStatViewer::WEBRTC_PACKETS_IN_LOST, in_packets_lost); + // packetsLost may be negative, clamp to zero for unsigned Viewer stats + S32 in_packets_lost = 0; + LLStringUtil::convertToS32(attributes.at("packetsLost"), in_packets_lost); + sample(LLStatViewer::WEBRTC_PACKETS_IN_LOST, static_cast<U32>(llmax(in_packets_lost, 0))); } if (attributes.contains("packetsReceived")) { diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h index 033990f789..a3f3ebe8b0 100644 --- a/indra/newview/llvoicewebrtc.h +++ b/indra/newview/llvoicewebrtc.h @@ -80,13 +80,13 @@ public: static bool isShuttingDown() { return sShuttingDown; } const LLVoiceVersionInfo& getVersion() override; + void updateVersion(); void updateSettings() override; // call after loading settings and whenever they change // Returns true if WebRTC has successfully logged in and is not in error state bool isVoiceWorking() const override; - std::string sipURIFromID(const LLUUID &id) const override; LLSD getP2PChannelInfoTemplate(const LLUUID& id) const override; void setHidden(bool hidden) override; // virtual @@ -285,6 +285,7 @@ public: void shutdownAllConnections(); void revive(); + const std::string getVersion() const; static void processSessionStates(); @@ -613,6 +614,7 @@ class LLVoiceWebRTCConnection : void sendJoin(); void sendData(const std::string &data); + const std::string& getVersion(); void processIceUpdates(); @@ -627,6 +629,7 @@ class LLVoiceWebRTCConnection : bool connectionStateMachine(); virtual bool isSpatial() { return false; } + bool isPrimary() const { return mPrimary; } LLUUID getRegionID() { return mRegionID; } @@ -700,6 +703,7 @@ class LLVoiceWebRTCConnection : bool mPrimary; LLUUID mViewerSession; std::string mChannelID; + std::string mServerVersion; std::string mChannelSDP; std::string mRemoteChannelSDP; diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index d1feb7e875..16c262d1ad 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -1253,7 +1253,35 @@ void LLVOVolume::updateSculptTexture() void LLVOVolume::updateVisualComplexity() { - LLVOAvatar* avatar = getAvatarAncestor(); + LLVOAvatar* avatar = nullptr; + LLViewerObject* pobj = (LLViewerObject*)getParent(); + LLViewerObject* lobj = this; + while (pobj) + { + avatar = pobj->asAvatar(); + if (avatar) + { + break; + } + lobj = pobj; + pobj = (LLViewerObject*)pobj->getParent(); + } + + if (avatar) + { + // mark parent as dirty, complexity will be updated recursively. + avatar->markAttachmentComplexityDirty(lobj->getID()); + } + LLVOAvatar* rigged_avatar = getAvatar(); + if (rigged_avatar && (rigged_avatar != avatar)) + { + // This might be wrong. Control avatars update each run, + // due to lack of dirty mechanics, and this might be + // where we should implement and call + // markControlAvatarComplexityDirty() if !isAttachment(). + rigged_avatar->markAttachmentComplexityDirty(lobj->getID()); + } + /*LLVOAvatar* avatar = getAvatarAncestor(); if (avatar) { avatar->updateVisualComplexity(); @@ -1262,7 +1290,7 @@ void LLVOVolume::updateVisualComplexity() if(rigged_avatar && (rigged_avatar != avatar)) { rigged_avatar->updateVisualComplexity(); - } + }*/ } void LLVOVolume::notifyMeshLoaded() @@ -5982,7 +6010,7 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) bool should_render = true; if (gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) { - if (gltf_mat->mBaseColor.mV[3] == 0.0f) + if (gltf_mat->mBaseColor.mV[3] == 0.0f && !LLDrawPoolAlpha::sShowDebugAlpha) { should_render = false; } diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp index 47e1815bc2..d02694de7d 100644 --- a/indra/newview/llworld.cpp +++ b/indra/newview/llworld.cpp @@ -1217,6 +1217,7 @@ void process_disable_simulator(LLMessageSystem *mesgsys, void **user_data) void process_region_handshake(LLMessageSystem* msg, void** user_data) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; LLHost host = msg->getSender(); LLViewerRegion* regionp = LLWorld::getInstance()->getRegion(host); if (!regionp) diff --git a/indra/newview/llworldmap.cpp b/indra/newview/llworldmap.cpp index 1db36649fa..c7c8404eb9 100644 --- a/indra/newview/llworldmap.cpp +++ b/indra/newview/llworldmap.cpp @@ -603,25 +603,103 @@ void LLWorldMap::dropImagePriorities() // Load all regions in a given rectangle (in region grid coordinates, i.e. world / 256 meters) void LLWorldMap::updateRegions(S32 x0, S32 y0, S32 x1, S32 y1) { - // Convert those boundaries to the corresponding (MAP_BLOCK_SIZE x MAP_BLOCK_SIZE) block coordinates - x0 = x0 / MAP_BLOCK_SIZE; - x1 = x1 / MAP_BLOCK_SIZE; - y0 = y0 / MAP_BLOCK_SIZE; - y1 = y1 / MAP_BLOCK_SIZE; + constexpr S32 MAX_REQUEST_REGIONS = 64; // Server side enforced limit. + constexpr S32 REGIONS_PER_BLOCK = MAP_BLOCK_SIZE * MAP_BLOCK_SIZE; // 4 x 4 = 16 regions per block + constexpr S32 MAX_TOTAL_BLOCKS = MAX_REQUEST_REGIONS / REGIONS_PER_BLOCK; // 64 / 16 = 4 blocks total + constexpr S32 MAX_BLOCKS_PER_SIDE = MAX_TOTAL_BLOCKS; // Can have up to 4 blocks in one dimension (e.g., 4x1, 1x4, 2x2) - // Load the region info those blocks - for (S32 block_x = llmax(x0, 0); block_x <= llmin(x1, MAP_BLOCK_RES-1); ++block_x) + // Convert region coordinates to block coordinates + // We use fixed sized blocks for ease of storage and lookup, + // but the requests can be of variable size of up to + // MAX_REQUEST_REGIONS. + S32 block_x0 = x0 / MAP_BLOCK_SIZE; + S32 block_x1 = x1 / MAP_BLOCK_SIZE; + S32 block_y0 = y0 / MAP_BLOCK_SIZE; + S32 block_y1 = y1 / MAP_BLOCK_SIZE; + + // Clamp to valid range + block_x0 = llmax(block_x0, 0); + block_x1 = llmin(block_x1, MAP_BLOCK_RES - 1); + block_y0 = llmax(block_y0, 0); + block_y1 = llmin(block_y1, MAP_BLOCK_RES - 1); + + // Process blocks, grouping unloaded blocks into larger requests up to MAX_TOTAL_BLOCKS + for (S32 block_y = block_y0; block_y <= block_y1; ) { - for (S32 block_y = llmax(y0, 0); block_y <= llmin(y1, MAP_BLOCK_RES-1); ++block_y) + for (S32 block_x = block_x0; block_x <= block_x1; ) { S32 offset = block_x | (block_y * MAP_BLOCK_RES); if (!mMapBlockLoaded[offset]) { - //LL_INFOS("WorldMap") << "Loading Block (" << block_x << "," << block_y << ")" << LL_ENDL; - LLWorldMapMessage::getInstance()->sendMapBlockRequest(block_x * MAP_BLOCK_SIZE, block_y * MAP_BLOCK_SIZE, (block_x * MAP_BLOCK_SIZE) + MAP_BLOCK_SIZE - 1, (block_y * MAP_BLOCK_SIZE) + MAP_BLOCK_SIZE - 1); - mMapBlockLoaded[offset] = true; + // Find the maximum contiguous unloaded rectangle starting at this block + S32 request_width = 1; + S32 request_height = 1; + + // Expand width (check horizontal contiguous unloaded blocks) + while (request_width < MAX_BLOCKS_PER_SIDE && + (block_x + request_width) <= block_x1) + { + // request_height is 1, can add blocks one by one. + S32 check_offset = (block_x + request_width) | (block_y * MAP_BLOCK_RES); + if (mMapBlockLoaded[check_offset]) + { + break; + } + ++request_width; + } + + // Expand height (check vertical contiguous unloaded blocks) + while (request_height < MAX_BLOCKS_PER_SIDE && + (block_y + request_height) <= block_y1 && + (request_width * (request_height + 1) <= MAX_TOTAL_BLOCKS)) // Don't exceed 64 total regions + { + bool can_expand = true; + // Width can be >1, loop over blocks in the line + for (S32 x = 0; x < request_width; ++x) + { + S32 check_offset = (block_x + x) | ((block_y + request_height) * MAP_BLOCK_RES); + if (mMapBlockLoaded[check_offset]) + { + can_expand = false; + break; + } + } + if (!can_expand) break; + ++request_height; + } + + // Send request for the contiguous rectangle + S32 min_x = block_x * MAP_BLOCK_SIZE; + S32 min_y = block_y * MAP_BLOCK_SIZE; + S32 max_x = (block_x + request_width) * MAP_BLOCK_SIZE - 1; + S32 max_y = (block_y + request_height) * MAP_BLOCK_SIZE - 1; + + LL_DEBUGS("WorldMap") << "Loading Block rectangle (" << block_x << "," << block_y + << ") to (" << (block_x + request_width - 1) << "," << (block_y + request_height - 1) + << ") [" << (request_width * request_height * MAP_BLOCK_SIZE * MAP_BLOCK_SIZE) << " regions]" << LL_ENDL; + + LLWorldMapMessage::getInstance()->sendMapBlockRequest(min_x, min_y, max_x, max_y); + + // Mark all blocks in the requested rectangle as loaded + for (S32 y = 0; y < request_height; ++y) + { + for (S32 x = 0; x < request_width; ++x) + { + S32 mark_offset = (block_x + x) | ((block_y + y) * MAP_BLOCK_RES); + mMapBlockLoaded[mark_offset] = true; + } + } + + // Skip over the width of blocks we just requested + block_x += request_width; + } + else + { + // This block is already loaded, move to next + ++block_x; } } + ++block_y; } } diff --git a/indra/newview/llworldmapmessage.cpp b/indra/newview/llworldmapmessage.cpp index 3264f8ae8b..c039f9de3f 100644 --- a/indra/newview/llworldmapmessage.cpp +++ b/indra/newview/llworldmapmessage.cpp @@ -154,6 +154,7 @@ void LLWorldMapMessage::sendMapBlockRequest(U16 min_x, U16 min_y, U16 max_x, U16 // public static void LLWorldMapMessage::processMapBlockReply(LLMessageSystem* msg, void**) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; if (gNonInteractive) { return; @@ -248,6 +249,7 @@ void LLWorldMapMessage::processMapBlockReply(LLMessageSystem* msg, void**) // public static void LLWorldMapMessage::processMapItemReply(LLMessageSystem* msg, void**) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_INFOS("WorldMap") << LL_ENDL; U32 type; msg->getU32Fast(_PREHASH_RequestData, _PREHASH_ItemType, type); diff --git a/indra/newview/llworldmapview.cpp b/indra/newview/llworldmapview.cpp index 1be6a6cfff..8e7b86eb00 100755 --- a/indra/newview/llworldmapview.cpp +++ b/indra/newview/llworldmapview.cpp @@ -1737,8 +1737,11 @@ void LLWorldMapView::updateVisibleBlocks() const F32 half_height = F32(height) / 2.0f; // Compute center into sim grid coordinates - S32 world_center_x = S32((-mPanX / mMapScale) + (camera_global.mdV[0] / REGION_WIDTH_METERS)); - S32 world_center_y = S32((-mPanY / mMapScale) + (camera_global.mdV[1] / REGION_WIDTH_METERS)); + // mPanX and mPanY values can be obsolete as they are used for + // animation and there is no point loading regions we will see + // so briefly, they won't have time to load. Use target directly. + S32 world_center_x = S32((-mTargetPanX / mMapScale) + (camera_global.mdV[0] / REGION_WIDTH_METERS)); + S32 world_center_y = S32((-mTargetPanY / mMapScale) + (camera_global.mdV[1] / REGION_WIDTH_METERS)); // Compute the boundaries into sim grid coordinates S32 world_left = world_center_x - S32(half_width / mMapScale) - 1; diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 86f7a08233..f4c121c18b 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -623,7 +623,9 @@ void LLPipeline::init() { cntrl_ptr->getCommitSignal()->connect([](LLControlVariable* control, const LLSD& value, const LLSD& previous) { - LLFontVertexBuffer::enableBufferCollection(control->getValue().asBoolean()); + bool enable_buffers = control->getValue().asBoolean(); + LLFontVertexBuffer::enableBufferCollection(enable_buffers); + LLFontWidthBuffer::enableBufferCollection(enable_buffers); }); } } @@ -1205,7 +1207,9 @@ void LLPipeline::refreshCachedSettings() LLVOAvatar::updateImpostorRendering(LLVOAvatar::sMaxNonImpostors); } - LLFontVertexBuffer::enableBufferCollection(gSavedSettings.getBOOL("CollectFontVertexBuffers")); + bool enable_buffers = gSavedSettings.getBOOL("CollectFontVertexBuffers"); + LLFontVertexBuffer::enableBufferCollection(enable_buffers); + LLFontWidthBuffer::enableBufferCollection(enable_buffers); } void LLPipeline::releaseGLBuffers() diff --git a/indra/newview/skins/default/xui/en/floater_create_landmark.xml b/indra/newview/skins/default/xui/en/floater_create_landmark.xml index abe8344097..6d3da72962 100644 --- a/indra/newview/skins/default/xui/en/floater_create_landmark.xml +++ b/indra/newview/skins/default/xui/en/floater_create_landmark.xml @@ -87,7 +87,7 @@ name="notes_editor" spellcheck="true" text_readonly_color="white" - prevalidator="ascii_with_newline" + prevalidator="ascii" commit_on_focus_lost="true" top_pad="5" width="290" diff --git a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml index 3bb1a9f1e2..672db51ff6 100644 --- a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml +++ b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml @@ -141,14 +141,6 @@ tool_tip="Maximum resolution for 'level of detail' textures" width="90"> <combo_box.item - label="128" - name="128" - value="128"/> - <combo_box.item - label="256" - name="256" - value="256"/> - <combo_box.item label="512" name="512" value="512"/> diff --git a/indra/newview/skins/default/xui/en/floater_profile.xml b/indra/newview/skins/default/xui/en/floater_profile.xml index 32ab811a6e..3a33572785 100644 --- a/indra/newview/skins/default/xui/en/floater_profile.xml +++ b/indra/newview/skins/default/xui/en/floater_profile.xml @@ -31,7 +31,7 @@ follows="all" layout="topleft" halign="center" - tab_min_width="81" + tab_min_width="80" tab_height="30" tab_position="top" > diff --git a/indra/newview/skins/default/xui/en/floater_script.xml b/indra/newview/skins/default/xui/en/floater_script.xml index 900319620d..aca661a6d2 100644 --- a/indra/newview/skins/default/xui/en/floater_script.xml +++ b/indra/newview/skins/default/xui/en/floater_script.xml @@ -8,9 +8,9 @@ can_dock="true" can_minimize="true" save_rect="true" - visible="false" - width="350" + visible="false" + width="402" can_resize="false" - min_width="350" + min_width="402" min_height="200"> </floater> diff --git a/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml b/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml index 864bec5d9a..1e10a984da 100644 --- a/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml +++ b/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml @@ -34,6 +34,16 @@ function="CallLog.Check" parameter="sort_friends_on_top" /> </menu_item_check> + <menu_item_check + label="Show muted" + name="show_muted"> + <on_click + function="CallLog.Action" + parameter="show_blocked" /> + <on_check + function="CallLog.Check" + parameter="show_blocked" /> + </menu_item_check> <menu_item_separator /> <menu_item_call label="View Nearby chat history..." diff --git a/indra/newview/skins/default/xui/en/panel_block_list_sidetray.xml b/indra/newview/skins/default/xui/en/panel_block_list_sidetray.xml index 6bb891db63..d907a050b6 100644 --- a/indra/newview/skins/default/xui/en/panel_block_list_sidetray.xml +++ b/indra/newview/skins/default/xui/en/panel_block_list_sidetray.xml @@ -10,6 +10,18 @@ min_height="350" min_width="240" width="323"> + <string + name="no_blocked" + value="No blocked residents or objects." /> + <string + name="no_filtered_blocked" + value="No blocked residents or objects match your search." /> + <string + name="loading_blocked" + value="Loading blocked residents and objects..." /> + <string + name="failed_blocked" + value="Blocked list is currently unavailable." /> <panel follows="left|top|right" height="27" diff --git a/indra/newview/skins/default/xui/en/panel_navigation_bar.xml b/indra/newview/skins/default/xui/en/panel_navigation_bar.xml index 9cdbf68e72..09bc098437 100644 --- a/indra/newview/skins/default/xui/en/panel_navigation_bar.xml +++ b/indra/newview/skins/default/xui/en/panel_navigation_bar.xml @@ -196,8 +196,8 @@ text_color="LtGray" tool_tip="Drag Landmarks here for quick access to your favorite places in Second Life!" top="13" - valign="bottom" - width="290"> + valign="bottom" + width="310"> Places you save to your favorites bar will appear here. </label> <!-- More button actually is a text box. --> diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 418dd7ef45..b442024361 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -2336,6 +2336,7 @@ For AI Character: Get the closest navigable point to the point provided. <string name="InventorySingleFolderEmpty">Folder is empty.</string> <string name="InventorySingleFolderNoMatches">No matches.</string> <string name="InventoryNoTexture">You do not have a copy of this texture in your inventory</string> + <string name="InventoryNoMaterial">You do not have a copy of this material in your inventory</string> <string name="InventoryInboxNoItems">Your Marketplace purchases will appear here. You may then drag them into your inventory to use them.</string> <string name="MarketplaceURL">https://marketplace.[MARKETPLACE_DOMAIN_NAME]/</string> <string name="MarketplaceURL_CreateStore">http://community.secondlife.com/t5/English-Knowledge-Base/Selling-in-the-Marketplace/ta-p/700193#Section_.3</string> diff --git a/indra/newview/tests/llworldmap_test.cpp b/indra/newview/tests/llworldmap_test.cpp index 60172b3960..692dd5638c 100644 --- a/indra/newview/tests/llworldmap_test.cpp +++ b/indra/newview/tests/llworldmap_test.cpp @@ -54,10 +54,22 @@ LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTexture(const LLUUID&, LLGLint, LLGLenum, LLHost ) { return NULL; } // Stub related map calls + +// Records each sendMapBlockRequest call for inspection in tests +struct MapBlockRequest +{ + U16 min_x, min_y, max_x, max_y; +}; +static std::vector<MapBlockRequest> gMapBlockRequests; + LLWorldMapMessage::LLWorldMapMessage() { } LLWorldMapMessage::~LLWorldMapMessage() { } void LLWorldMapMessage::sendItemRequest(U32 type, U64 handle) { } -void LLWorldMapMessage::sendMapBlockRequest(U16 min_x, U16 min_y, U16 max_x, U16 max_y, bool return_nonexistent) { } +void LLWorldMapMessage::sendMapBlockRequest(U16 min_x, U16 min_y, U16 max_x, U16 max_y, bool return_nonexistent) +{ + MapBlockRequest req = { min_x, min_y, max_x, max_y }; + gMapBlockRequests.push_back(req); +} LLWorldMipmap::LLWorldMipmap() { } LLWorldMipmap::~LLWorldMipmap() { } @@ -518,4 +530,63 @@ namespace tut mWorld->cancelTracking(); ensure("LLWorldMap::cancelTracking() at end test failed", mWorld->isTracking() == false); } + // Test updateRegions() request grouping, size limits, and bounds + template<> template<> + void worldmap_object_t::test<4>() + { + // Test 27 : 1x1 block (4x4 = 16 regions) - single small request within bounds + mWorld->reset(); + gMapBlockRequests.clear(); + mWorld->updateRegions(0, 0, 3, 3); + ensure("updateRegions 1x1 block: expected 1 request", gMapBlockRequests.size() == 1); + { + const MapBlockRequest& req = gMapBlockRequests[0]; + ensure("updateRegions 1x1 block: min_x", req.min_x == 0); + ensure("updateRegions 1x1 block: min_y", req.min_y == 0); + ensure("updateRegions 1x1 block: max_x", req.max_x == 3); + ensure("updateRegions 1x1 block: max_y", req.max_y == 3); + S32 regions = (req.max_x - req.min_x + 1) * (req.max_y - req.min_y + 1); + ensure("updateRegions 1x1 block: must not exceed 64 regions", regions <= 64); + } + + // Test 28 : 2x2 blocks (8x8 = 64 regions) - single request at the 64-region limit + mWorld->reset(); + gMapBlockRequests.clear(); + mWorld->updateRegions(0, 0, 7, 7); + ensure("updateRegions 2x2 blocks: expected 1 request", gMapBlockRequests.size() == 1); + { + const MapBlockRequest& req = gMapBlockRequests[0]; + ensure("updateRegions 2x2 blocks: min_x", req.min_x == 0); + ensure("updateRegions 2x2 blocks: min_y", req.min_y == 0); + ensure("updateRegions 2x2 blocks: max_x", req.max_x == 7); + ensure("updateRegions 2x2 blocks: max_y", req.max_y == 7); + S32 regions = (req.max_x - req.min_x + 1) * (req.max_y - req.min_y + 1); + ensure("updateRegions 2x2 blocks: must not exceed 64 regions", regions <= 64); + } + + // Test 29 : 4x4 blocks (16x16 = 256 total regions) - must split into multiple requests + // each spanning a 4x1 strip (64 regions), none exceeding the server limit + mWorld->reset(); + gMapBlockRequests.clear(); + mWorld->updateRegions(0, 0, 15, 15); + ensure("updateRegions 4x4 blocks: expected 4 requests", gMapBlockRequests.size() == 4); + for (size_t i = 0; i < gMapBlockRequests.size(); ++i) + { + const MapBlockRequest& req = gMapBlockRequests[i]; + S32 regions = (req.max_x - req.min_x + 1) * (req.max_y - req.min_y + 1); + ensure("updateRegions 4x4 blocks: each request must not exceed 64 regions", regions <= 64); + // Requests must stay within the requested area (0..15) + ensure("updateRegions 4x4 blocks: max_x in bounds", req.max_x <= 15); + ensure("updateRegions 4x4 blocks: max_y in bounds", req.max_y <= 15); + } + // Each request covers one horizontal strip of 4 blocks (rows 0..3, 4..7, 8..11, 12..15) + ensure("updateRegions 4x4 blocks: request[0] min_y", gMapBlockRequests[0].min_y == 0); + ensure("updateRegions 4x4 blocks: request[0] max_y", gMapBlockRequests[0].max_y == 3); + ensure("updateRegions 4x4 blocks: request[1] min_y", gMapBlockRequests[1].min_y == 4); + ensure("updateRegions 4x4 blocks: request[1] max_y", gMapBlockRequests[1].max_y == 7); + ensure("updateRegions 4x4 blocks: request[2] min_y", gMapBlockRequests[2].min_y == 8); + ensure("updateRegions 4x4 blocks: request[2] max_y", gMapBlockRequests[2].max_y == 11); + ensure("updateRegions 4x4 blocks: request[3] min_y", gMapBlockRequests[3].min_y == 12); + ensure("updateRegions 4x4 blocks: request[3] max_y", gMapBlockRequests[3].max_y == 15); + } } diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 3a0df626f1..a8bbfe636f 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -578,14 +578,6 @@ class Windows_x86_64_Manifest(ViewerManifest): self.path_optional("vcruntime140_1.dll") self.path_optional("vcruntime140_threads.dll") - # SLVoice executable - with self.prefix(src=os.path.join(pkgdir, 'bin', 'release')): - self.path("SLVoice.exe") - - # Vivox libraries - self.path("vivoxsdk_x64.dll") - self.path("ortp_x64.dll") - # BugSplat if self.args.get('bugsplat'): self.path("BsSndRpt64.exe") @@ -774,7 +766,7 @@ class Windows_x86_64_Manifest(ViewerManifest): pack_version = '.'.join(self.args['version'][:3]) if len(self.args['version']) > 3 and self.args['version'][3]: pack_version += '-' + self.args['version'][3] - pack_title = self.app_name() # Display name with spaces + pack_title = pack_id #Wrapper exe, don't use spaces pack_dir = self.get_dst_prefix() main_exe = self.final_exe() installer_base = self.installer_base_name() @@ -1141,16 +1133,6 @@ class Darwin_x86_64_Manifest(ViewerManifest): # Need to get the llcommon dll from any of the build directories as well. libfile_parent = self.get_dst_prefix() dylibs=[] - # SLVoice executable - with self.prefix(src=os.path.join(pkgdir, 'bin', 'release')): - self.path("SLVoice") - - # Vivox libraries - for libfile in ( - 'libortp.dylib', - 'libvivoxsdk.dylib', - ): - self.path2basename(relpkgdir, libfile) # Discord social SDK if self.args['discord'] == 'ON': @@ -1954,16 +1936,34 @@ class Linux_x86_64_Manifest(LinuxManifest): self.path("libdirect-1.4.so.5*") self.path("libalut.so*") self.path("libopenal.so*") - self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname + # KLUDGE: As of 2012-04-11, the 'fontconfig' package installs + # libfontconfig.so.1.4.4, along with symlinks libfontconfig.so.1 + # and libfontconfig.so. Before we added support for library-file + # wildcards, though, this self.path() call specifically named + # libfontconfig.so.1.4.4 WITHOUT also copying the symlinks. When I + # (nat) changed the call to self.path("libfontconfig.so.*"), we + # ended up with the libfontconfig.so.1 symlink in the target + # directory as well. But guess what! At least on Ubuntu 10.04, + # certain viewer fonts look terrible with libfontconfig.so.1 + # present in the target directory. Removing that symlink suffices + # to improve them. I suspect that means we actually do better when + # the viewer fails to find our packaged libfontconfig.so*, falling + # back on the system one instead -- but diagnosing and fixing that + # is a bit out of scope for the present project. Meanwhile, this + # particular wildcard specification gets us exactly what the + # previous call did, without having to explicitly state the + # version number. + self.path("libfontconfig.so.*.*") - # Vivox runtimes - with self.prefix(src=relpkgdir, dst="bin"): - self.path("SLVoice") - with self.prefix(src=relpkgdir, dst="lib"): - self.path("libortp.so") - self.path("libsndfile.so.1") - #self.path("libvivoxoal.so.1") # no - we'll re-use the viewer's own OpenAL lib - self.path("libvivoxsdk.so") + # Include libfreetype.so. but have it work as libfontconfig does. + self.path("libfreetype.so.*.*") + + try: + self.path("libtcmalloc.so*") #formerly called google perf tools + pass + except: + print("tcmalloc files not found, skipping") + pass self.strip_binaries() ################################################################ diff --git a/indra/test/lldatapacker_tut.cpp b/indra/test/lldatapacker_tut.cpp index 2e975ee637..5aa5233806 100644 --- a/indra/test/lldatapacker_tut.cpp +++ b/indra/test/lldatapacker_tut.cpp @@ -127,7 +127,7 @@ namespace tut LLDataPackerBinaryBuffer lldp1(packbuf, cur_size); lldp1.unpackString(unpkstr , "linden_lab_str"); - lldp1.unpackBinaryData((U8*)unpkstrBinary, unpksizeBinary, "linden_lab_bd"); + lldp1.unpackBinaryData((U8*)unpkstrBinary, 256, unpksizeBinary, "linden_lab_bd"); lldp1.unpackBinaryDataFixed((U8*)unpkstrBinaryFixed, sizeBinaryFixed, "linden_lab_bdf"); lldp1.unpackU8(unpkvalU8,"linden_lab_u8"); lldp1.unpackU16(unpkvalU16,"linden_lab_u16"); @@ -286,7 +286,7 @@ namespace tut LLDataPackerAsciiBuffer lldp1(packbuf, cur_size); lldp1.unpackString(unpkstr , "linden_lab_str"); - lldp1.unpackBinaryData((U8*)unpkstrBinary, unpksizeBinary, "linden_lab_bd"); + lldp1.unpackBinaryData((U8*)unpkstrBinary, 256, unpksizeBinary, "linden_lab_bd"); lldp1.unpackBinaryDataFixed((U8*)unpkstrBinaryFixed, sizeBinaryFixed, "linden_lab_bdf"); lldp1.unpackU8(unpkvalU8,"linden_lab_u8"); lldp1.unpackU16(unpkvalU16,"linden_lab_u16"); @@ -431,7 +431,7 @@ namespace tut LLDataPackerAsciiFile lldp1(fp,2); lldp1.unpackString(unpkstr , "linden_lab_str"); - lldp1.unpackBinaryData((U8*)unpkstrBinary, unpksizeBinary, "linden_lab_bd"); + lldp1.unpackBinaryData((U8*)unpkstrBinary, 256, unpksizeBinary, "linden_lab_bd"); lldp1.unpackBinaryDataFixed((U8*)unpkstrBinaryFixed, sizeBinaryFixed, "linden_lab_bdf"); lldp1.unpackU8(unpkvalU8,"linden_lab_u8"); lldp1.unpackU16(unpkvalU16,"linden_lab_u16"); @@ -538,7 +538,7 @@ namespace tut LLDataPackerAsciiFile lldp1(istr,2); lldp1.unpackString(unpkstr , "linden_lab_str"); - lldp1.unpackBinaryData((U8*)unpkstrBinary, unpksizeBinary, "linden_lab_bd"); + lldp1.unpackBinaryData((U8*)unpkstrBinary, 256, unpksizeBinary, "linden_lab_bd"); lldp1.unpackBinaryDataFixed((U8*)unpkstrBinaryFixed, sizeBinaryFixed, "linden_lab_bdf"); lldp1.unpackU8(unpkvalU8,"linden_lab_u8"); lldp1.unpackU16(unpkvalU16,"linden_lab_u16"); |
