summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorAndrey Kleshchev <117672381+akleshchev@users.noreply.github.com>2026-01-09 22:14:10 +0200
committerAndrey Kleshchev <117672381+akleshchev@users.noreply.github.com>2026-01-09 22:16:49 +0200
commit9de30971d327a1c9bda9f8e4f8e01f1b9d49f5fe (patch)
treec52fcafd621736ac775383f3ead8abcc3d95317c /indra
parentc3b07f8f01ae2733b0fb1309af95a513878170c1 (diff)
parent31e909870b316b7b4f36396357f70e44c724e608 (diff)
Merge release/2026.01 into develop
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/CMakeLists.txt2
-rw-r--r--indra/llcommon/llthread.cpp6
-rw-r--r--indra/llcommon/llwatchdog.cpp (renamed from indra/newview/llwatchdog.cpp)36
-rw-r--r--indra/llcommon/llwatchdog.h (renamed from indra/newview/llwatchdog.h)25
-rw-r--r--indra/llcommon/workqueue.cpp7
-rw-r--r--indra/llcommon/workqueue.h6
-rw-r--r--indra/llimage/llimage.cpp16
-rw-r--r--indra/llmessage/llassetstorage.cpp3
-rw-r--r--indra/llmessage/llassetstorage.h1
-rw-r--r--indra/llmessage/llexperiencecache.cpp33
-rw-r--r--indra/llmessage/llexperiencecache.h4
-rw-r--r--indra/llui/llmenugl.cpp14
-rw-r--r--indra/llui/llmenugl.h2
-rw-r--r--indra/llwindow/llwindow.h2
-rw-r--r--indra/llwindow/llwindowwin32.cpp65
-rw-r--r--indra/llwindow/llwindowwin32.h141
-rw-r--r--indra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/VIEWER_VERSION.txt2
-rw-r--r--indra/newview/app_settings/settings.xml2
-rw-r--r--indra/newview/llappviewer.cpp42
-rw-r--r--indra/newview/lleventpoll.cpp58
-rw-r--r--indra/newview/llfavoritesbar.cpp36
-rw-r--r--indra/newview/lllandmarklist.cpp49
-rw-r--r--indra/newview/lllandmarklist.h1
-rw-r--r--indra/newview/llpanelface.cpp32
-rw-r--r--indra/newview/llpanelface.h2
-rw-r--r--indra/newview/llpanelplaceprofile.cpp4
-rw-r--r--indra/newview/llpanelplaceprofile.h2
-rw-r--r--indra/newview/llpanelsnapshotinventory.cpp6
-rw-r--r--indra/newview/llviewerassetstorage.cpp71
-rw-r--r--indra/newview/skins/default/xui/en/menu_favorites.xml6
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml11
-rwxr-xr-xindra/newview/viewer_manifest.py5
33 files changed, 526 insertions, 168 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 5adb0bf083..32226a62e6 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -102,6 +102,7 @@ set(llcommon_SOURCE_FILES
lluri.cpp
lluriparser.cpp
lluuid.cpp
+ llwatchdog.cpp
llworkerthread.cpp
hbxxh.cpp
u64.cpp
@@ -241,6 +242,7 @@ set(llcommon_HEADER_FILES
lluri.h
lluriparser.h
lluuid.h
+ llwatchdog.h
llwin32headers.h
llworkerthread.h
hbxxh.h
diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp
index 692941a892..8c12ee7f12 100644
--- a/indra/llcommon/llthread.cpp
+++ b/indra/llcommon/llthread.cpp
@@ -240,7 +240,11 @@ void LLThread::tryRun()
LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
main_queue->post(
// Bind the current exception, rethrow it in main loop.
- [exc = std::current_exception()]() { std::rethrow_exception(exc); });
+ [exc = std::current_exception(), name = mName]()
+ {
+ LL_INFOS("THREAD") << "Rethrowing exception from thread " << name << LL_ENDL;
+ std::rethrow_exception(exc);
+ });
}
#endif // else LL_WINDOWS
}
diff --git a/indra/newview/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp
index 072743505f..1bc1283d0b 100644
--- a/indra/newview/llwatchdog.cpp
+++ b/indra/llcommon/llwatchdog.cpp
@@ -24,12 +24,12 @@
* $/LicenseInfo$
*/
+// Precompiled header
+#include "linden_common.h"
-#include "llviewerprecompiledheaders.h"
#include "llwatchdog.h"
#include "llmutex.h"
#include "llthread.h"
-#include "llappviewer.h"
constexpr U32 WATCHDOG_SLEEP_TIME_USEC = 1000000U;
@@ -68,7 +68,9 @@ private:
};
// LLWatchdogEntry
-LLWatchdogEntry::LLWatchdogEntry()
+LLWatchdogEntry::LLWatchdogEntry(const std::string& thread_name)
+ : mThreadName(thread_name)
+ , mThreadID(LLThread::currentID())
{
}
@@ -90,11 +92,16 @@ void LLWatchdogEntry::stop()
LLWatchdog::getInstance()->remove(this);
}
}
+std::string LLWatchdogEntry::getThreadName() const
+{
+ return mThreadName + llformat(": %d", mThreadID);
+}
// LLWatchdogTimeout
const std::string UNINIT_STRING = "uninitialized";
-LLWatchdogTimeout::LLWatchdogTimeout() :
+LLWatchdogTimeout::LLWatchdogTimeout(const std::string& thread_name) :
+ LLWatchdogEntry(thread_name),
mTimeout(0.0f),
mPingState(UNINIT_STRING)
{
@@ -176,7 +183,7 @@ void LLWatchdog::remove(LLWatchdogEntry* e)
unlockThread();
}
-void LLWatchdog::init()
+void LLWatchdog::init(func_t set_error_state_callback)
{
if (!mSuspectsAccessMutex && !mTimer)
{
@@ -189,6 +196,7 @@ void LLWatchdog::init()
// start needs to use the mSuspectsAccessMutex
mTimer->start();
}
+ mCreateMarkerFnc = set_error_state_callback;
}
void LLWatchdog::cleanup()
@@ -242,17 +250,23 @@ void LLWatchdog::run()
{
mTimer->stop();
}
- if (LLAppViewer::instance()->logoutRequestSent())
+
+ // Sets error marker file
+ mCreateMarkerFnc();
+ // Todo1: Warn user?
+ // Todo2: We probably want to report even if 5 seconds passed, just not error 'yet'.
+ std::string last_state = (*result)->getLastState();
+ if (last_state.empty())
{
- LLAppViewer::instance()->createErrorMarker(LAST_EXEC_LOGOUT_FROZE);
+ LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName()
+ << " expired; assuming viewer is hung and crashing" << LL_ENDL;
}
else
{
- LLAppViewer::instance()->createErrorMarker(LAST_EXEC_FROZE);
+ LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName()
+ << " expired with state: " << last_state
+ << "; assuming viewer is hung and crashing" << LL_ENDL;
}
- // Todo1: warn user?
- // Todo2: We probably want to report even if 5 seconds passed, just not error 'yet'.
- LL_ERRS() << "Watchdog timer expired; assuming viewer is hung and crashing" << LL_ENDL;
}
}
diff --git a/indra/newview/llwatchdog.h b/indra/llcommon/llwatchdog.h
index b7dd55577e..fded881bb8 100644
--- a/indra/newview/llwatchdog.h
+++ b/indra/llcommon/llwatchdog.h
@@ -30,13 +30,17 @@
#ifndef LL_TIMER_H
#include "lltimer.h"
#endif
+#include "llmutex.h"
+#include "llsingleton.h"
+
+#include <functional>
// LLWatchdogEntry is the interface used by the tasks that
// need to be watched.
class LLWatchdogEntry
{
public:
- LLWatchdogEntry();
+ LLWatchdogEntry(const std::string &thread_name);
virtual ~LLWatchdogEntry();
// isAlive is accessed by the watchdog thread.
@@ -46,12 +50,19 @@ public:
virtual void reset() = 0;
virtual void start();
virtual void stop();
+ virtual std::string getLastState() const { return std::string(); }
+ typedef std::thread::id id_t;
+ std::string getThreadName() const;
+
+private:
+ id_t mThreadID; // ID of the thread being watched
+ std::string mThreadName;
};
class LLWatchdogTimeout : public LLWatchdogEntry
{
public:
- LLWatchdogTimeout();
+ LLWatchdogTimeout(const std::string& thread_name);
virtual ~LLWatchdogTimeout();
bool isAlive() const override;
@@ -63,6 +74,7 @@ public:
void setTimeout(F32 d);
void ping(std::string_view state);
const std::string& getState() {return mPingState; }
+ std::string getLastState() const override { return mPingState; }
private:
LLTimer mTimer;
@@ -81,10 +93,12 @@ public:
void add(LLWatchdogEntry* e);
void remove(LLWatchdogEntry* e);
- void init();
+ typedef std::function<void()> func_t;
+ void init(func_t set_error_state_callback);
void run();
void cleanup();
+
private:
void lockThread();
void unlockThread();
@@ -94,6 +108,11 @@ private:
LLMutex* mSuspectsAccessMutex;
LLWatchdogTimerThread* mTimer;
U64 mLastClockCount;
+
+ // At the moment watchdog expects app to set markers in mCreateMarkerFnc,
+ // but technically can be used to set any error states or do some cleanup
+ // or show warnings.
+ func_t mCreateMarkerFnc;
};
#endif // LL_LLTHREADWATCHDOG_H
diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp
index 4db44ff2a6..67c23358ed 100644
--- a/indra/llcommon/workqueue.cpp
+++ b/indra/llcommon/workqueue.cpp
@@ -216,10 +216,15 @@ void LL::WorkQueueBase::callWork(const Work& work)
LL_WARNS("LLCoros") << "Capturing and rethrowing uncaught exception in WorkQueueBase "
<< getKey() << LL_ENDL;
+ std::string name = getKey();
LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
main_queue->post(
// Bind the current exception, rethrow it in main loop.
- [exc = std::current_exception()]() { std::rethrow_exception(exc); });
+ [exc = std::current_exception(), name]()
+ {
+ LL_INFOS("LLCoros") << "Rethrowing exception from WorkQueueBase::callWork " << name << LL_ENDL;
+ std::rethrow_exception(exc);
+ });
}
else
{
diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h
index 735ad38a26..573203a5b3 100644
--- a/indra/llcommon/workqueue.h
+++ b/indra/llcommon/workqueue.h
@@ -530,7 +530,11 @@ namespace LL
reply,
// Bind the current exception to transport back to the
// originating WorkQueue. Once there, rethrow it.
- [exc = std::current_exception()](){ std::rethrow_exception(exc); });
+ [exc = std::current_exception()]()
+ {
+ LL_INFOS("LLCoros") << "Rethrowing exception from WorkQueueBase::postTo" << LL_ENDL;
+ std::rethrow_exception(exc);
+ });
}
},
// if caller passed a TimePoint, pass it along to post()
diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp
index ca8a4199e8..35bc7065b1 100644
--- a/indra/llimage/llimage.cpp
+++ b/indra/llimage/llimage.cpp
@@ -709,8 +709,20 @@ U8* LLImageBase::allocateData(S32 size)
mData = (U8*)ll_aligned_malloc_16(size);
if (!mData)
{
- LL_WARNS() << "Failed to allocate image data size [" << size << "]" << LL_ENDL;
- mBadBufferAllocation = true;
+ constexpr S32 MAX_TOLERANCE = 1024 * 1024 * 4; // 4 MB
+ if (size > MAX_TOLERANCE)
+ {
+ // If a big image failed to allocate, tollerate it for now.
+ // It's insightfull when crash logs without obvious cause are being analyzed,
+ // so a crash in a random location that normally is a mystery can get proper handling.
+ LL_WARNS() << "Failed to allocate image data size [" << size << "]" << LL_ENDL;
+ }
+ else
+ {
+ // We are too far gone if we can't allocate a small buffer.
+ LLError::LLUserWarningMsg::showOutOfMemory();
+ LL_ERRS() << "Failed to allocate image data size [" << size << "]" << LL_ENDL;
+ }
}
}
diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp
index 10fd56a68e..4c3acb27f4 100644
--- a/indra/llmessage/llassetstorage.cpp
+++ b/indra/llmessage/llassetstorage.cpp
@@ -1316,6 +1316,9 @@ const char* LLAssetStorage::getErrorString(S32 status)
case LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE:
return "Asset request: asset not found in database";
+ case LL_ERR_NO_CAP:
+ return "Asset request: region or asset capability not available";
+
case LL_ERR_EOF:
return "End of file";
diff --git a/indra/llmessage/llassetstorage.h b/indra/llmessage/llassetstorage.h
index 6d6526757d..d5daa0cb8f 100644
--- a/indra/llmessage/llassetstorage.h
+++ b/indra/llmessage/llassetstorage.h
@@ -57,6 +57,7 @@ const int LL_ERR_ASSET_REQUEST_FAILED = -1;
const int LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE = -3;
const int LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE = -4;
const int LL_ERR_INSUFFICIENT_PERMISSIONS = -5;
+const int LL_ERR_NO_CAP = -6;
const int LL_ERR_PRICE_MISMATCH = -23018;
// *TODO: these typedefs are passed into the cache via a legacy C function pointer
diff --git a/indra/llmessage/llexperiencecache.cpp b/indra/llmessage/llexperiencecache.cpp
index 149741b9f9..e4c7deb1c5 100644
--- a/indra/llmessage/llexperiencecache.cpp
+++ b/indra/llmessage/llexperiencecache.cpp
@@ -112,9 +112,7 @@ void LLExperienceCache::initSingleton()
constexpr size_t CORO_QUEUE_SIZE = 2048;
LLCoprocedureManager::instance().initializePool("ExpCache", CORO_QUEUE_SIZE);
- LLCoros::instance().launch("LLExperienceCache::idleCoro",
- boost::bind(&LLExperienceCache::idleCoro, this));
-
+ LLCoros::instance().launch("LLExperienceCache::idleCoro", LLExperienceCache::idleCoro);
}
void LLExperienceCache::cleanup()
@@ -246,6 +244,7 @@ const LLExperienceCache::cache_t& LLExperienceCache::getCached()
return mCache;
}
+// static because used by coroutine and can outlive the instance
void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, std::string url, RequestQueue_t requests)
{
LLCore::HttpRequest::ptr_t httpRequest = std::make_shared<LLCore::HttpRequest>();
@@ -254,6 +253,13 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
+ if (sShutdown)
+ {
+ return;
+ }
+
+ LLExperienceCache* self = LLExperienceCache::getInstance();
+
LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
@@ -265,7 +271,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
// build dummy entries for the failed requests
for (RequestQueue_t::const_iterator it = requests.begin(); it != requests.end(); ++it)
{
- LLSD exp = get(*it);
+ LLSD exp = self->get(*it);
//leave the properties alone if we already have a cache entry for this xp
if (exp.isUndefined())
{
@@ -278,7 +284,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
exp["error"] = (LLSD::Integer)status.getType();
exp[QUOTA] = DEFAULT_QUOTA;
- processExperience(*it, exp);
+ self->processExperience(*it, exp);
}
return;
}
@@ -294,7 +300,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
LL_DEBUGS("ExperienceCache") << "Received result for " << public_key
<< " display '" << row[LLExperienceCache::NAME].asString() << "'" << LL_ENDL;
- processExperience(public_key, row);
+ self->processExperience(public_key, row);
}
LLSD error_ids = result["error_ids"];
@@ -310,7 +316,7 @@ void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdap
exp[MISSING] = true;
exp[QUOTA] = DEFAULT_QUOTA;
- processExperience(id, exp);
+ self->processExperience(id, exp);
LL_WARNS("ExperienceCache") << "LLExperienceResponder::result() error result for " << id << LL_ENDL;
}
@@ -361,7 +367,7 @@ void LLExperienceCache::requestExperiences()
if (mRequestQueue.empty() || (ostr.tellp() > EXP_URL_SEND_THRESHOLD))
{ // request is placed in the coprocedure pool for the ExpCache cache. Throttling is done by the pool itself.
LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "RequestExperiences",
- boost::bind(&LLExperienceCache::requestExperiencesCoro, this, _1, ostr.str(), requests) );
+ boost::bind(&LLExperienceCache::requestExperiencesCoro, _1, ostr.str(), requests) );
ostr.str(std::string());
ostr << urlBase << "?page_size=" << PAGE_SIZE1;
@@ -393,7 +399,7 @@ void LLExperienceCache::setCapabilityQuery(LLExperienceCache::CapabilityQuery_t
mCapability = queryfn;
}
-
+// static, because coro can outlive the instance
void LLExperienceCache::idleCoro()
{
const F32 SECS_BETWEEN_REQUESTS = 0.5f;
@@ -402,14 +408,15 @@ void LLExperienceCache::idleCoro()
LL_INFOS("ExperienceCache") << "Launching Experience cache idle coro." << LL_ENDL;
do
{
- if (mEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
+ LLExperienceCache* self = LLExperienceCache::getInstance();
+ if (self->mEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
{
- eraseExpired();
+ self->eraseExpired();
}
- if (!mRequestQueue.empty())
+ if (!self->mRequestQueue.empty())
{
- requestExperiences();
+ self->requestExperiences();
}
llcoro::suspendUntilTimeout(SECS_BETWEEN_REQUESTS);
diff --git a/indra/llmessage/llexperiencecache.h b/indra/llmessage/llexperiencecache.h
index 4b344347d5..9ecdb9efca 100644
--- a/indra/llmessage/llexperiencecache.h
+++ b/indra/llmessage/llexperiencecache.h
@@ -144,9 +144,9 @@ private:
std::string mCacheFileName;
static bool sShutdown; // control for coroutines, they exist out of LLExperienceCache's scope, so they need a static control
- void idleCoro();
+ static void idleCoro();
void eraseExpired();
- void requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, std::string, RequestQueue_t);
+ static void requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, std::string, RequestQueue_t);
void requestExperiences();
void fetchAssociatedExperienceCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, LLUUID, LLUUID, std::string, ExperienceGetFn_t);
diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp
index 2ca2454040..6ba31c251e 100644
--- a/indra/llui/llmenugl.cpp
+++ b/indra/llui/llmenugl.cpp
@@ -4404,3 +4404,17 @@ bool LLContextMenu::addChild(LLView* view, S32 tab_group)
return addContextChild(view, tab_group);
}
+void LLContextMenu::deleteAllChildren()
+{
+ mHoverItem = nullptr;
+ LLMenuGL::deleteAllChildren();
+}
+
+void LLContextMenu::removeChild(LLView* ctrl)
+{
+ if (ctrl == mHoverItem)
+ {
+ mHoverItem = nullptr;
+ }
+ LLMenuGL::removeChild(ctrl);
+}
diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h
index eacf2c59d4..bca0a731fc 100644
--- a/indra/llui/llmenugl.h
+++ b/indra/llui/llmenugl.h
@@ -730,6 +730,8 @@ public:
virtual bool handleRightMouseUp ( S32 x, S32 y, MASK mask );
virtual bool addChild (LLView* view, S32 tab_group = 0);
+ /*virtual*/ void deleteAllChildren();
+ /*virtual*/ void removeChild(LLView* ctrl);
LLHandle<LLContextMenu> getHandle() { return getDerivedHandle<LLContextMenu>(); }
diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h
index 7a5404e615..185940e32d 100644
--- a/indra/llwindow/llwindow.h
+++ b/indra/llwindow/llwindow.h
@@ -205,6 +205,8 @@ public:
};
virtual S32 getRefreshRate() { return mRefreshRate; }
+
+ virtual void initWatchdog() {} // windows runs window as a thread and it needs a watchdog
protected:
LLWindow(LLWindowCallbacks* callbacks, bool fullscreen, U32 flags);
virtual ~LLWindow();
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index f826a60ddd..9d05d7e5a4 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -49,6 +49,7 @@
#include "llthreadsafequeue.h"
#include "stringize.h"
#include "llframetimer.h"
+#include "llwatchdog.h"
// System includes
#include <commdlg.h>
@@ -364,7 +365,8 @@ static LLMonitorInfo sMonitorInfo;
// the containing class a friend.
struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
{
- static const int MAX_QUEUE_SIZE = 2048;
+ static constexpr int MAX_QUEUE_SIZE = 2048;
+ static constexpr F32 WINDOW_TIMEOUT_SEC = 90.f;
LLThreadSafeQueue<MSG> mMessageQueue;
@@ -426,6 +428,50 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
PostMessage(windowHandle, WM_POST_FUNCTION_, wparam, LPARAM(ptr));
}
+ // Call from main thread.
+ void initTimeout()
+ {
+ // post into thread's queue to avoid threading issues
+ post([this]()
+ {
+ if (!mWindowTimeout)
+ {
+ mWindowTimeout = std::make_unique<LLWatchdogTimeout>("mainloop");
+ // supposed to be executed within run(),
+ // so no point checking if thread is alive
+ resumeTimeout("TimeoutInit");
+ }
+ });
+ }
+private:
+ // These timeout related functions are strictly for the thread.
+ void resumeTimeout(std::string_view state)
+ {
+ if (mWindowTimeout)
+ {
+ mWindowTimeout->setTimeout(WINDOW_TIMEOUT_SEC);
+ mWindowTimeout->start(state);
+ }
+ }
+
+ void pauseTimeout()
+ {
+ if (mWindowTimeout)
+ {
+ mWindowTimeout->stop();
+ }
+ }
+
+ void pingTimeout(std::string_view state)
+ {
+ if (mWindowTimeout)
+ {
+ mWindowTimeout->setTimeout(WINDOW_TIMEOUT_SEC);
+ mWindowTimeout->ping(state);
+ }
+ }
+
+public:
using FuncType = std::function<void()>;
// call GetMessage() and pull enqueue messages for later processing
HWND mWindowHandleThrd = NULL;
@@ -436,6 +482,8 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
bool mGLReady = false;
bool mGotGLBuffer = false;
LLAtomicBool mDeleteOnExit = false;
+private:
+ std::unique_ptr<LLWatchdogTimeout> mWindowTimeout;
};
@@ -4595,6 +4643,11 @@ bool LLWindowWin32::getInputDevices(U32 device_type_filter,
return false;
}
+void LLWindowWin32::initWatchdog()
+{
+ mWindowThread->initTimeout();
+}
+
F32 LLWindowWin32::getSystemUISize()
{
F32 scale_value = 1.f;
@@ -4732,6 +4785,8 @@ void LLWindowWin32::LLWindowWin32Thread::checkDXMem()
return;
}
+ pauseTimeout();
+
IDXGIFactory4* p_factory = nullptr;
HRESULT res = CreateDXGIFactory1(__uuidof(IDXGIFactory4), (void**)&p_factory);
@@ -4835,6 +4890,8 @@ void LLWindowWin32::LLWindowWin32Thread::checkDXMem()
}
mGotGLBuffer = true;
+
+ resumeTimeout("checkDXMem");
}
void LLWindowWin32::LLWindowWin32Thread::run()
@@ -4850,6 +4907,9 @@ void LLWindowWin32::LLWindowWin32Thread::run()
timeBeginPeriod(llclamp((U32) 1, tc.wPeriodMin, tc.wPeriodMax));
}
+ // Normally won't exist yet, but in case of re-init, make sure it's cleaned up
+ resumeTimeout("WindowThread");
+
while (! getQueue().done())
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
@@ -4859,6 +4919,7 @@ void LLWindowWin32::LLWindowWin32Thread::run()
if (mWindowHandleThrd != 0)
{
+ pingTimeout("messages");
MSG msg;
BOOL status;
if (mhDCThrd == 0)
@@ -4886,6 +4947,7 @@ void LLWindowWin32::LLWindowWin32Thread::run()
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - Function Queue");
+ pingTimeout("queue");
logger.onChange("runPending()");
//process any pending functions
getQueue().runPending();
@@ -4900,6 +4962,7 @@ void LLWindowWin32::LLWindowWin32Thread::run()
#endif
}
+ pauseTimeout();
destroyWindow();
if (mDeleteOnExit)
diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h
index 0fc93ad0b1..8159092794 100644
--- a/indra/llwindow/llwindowwin32.h
+++ b/indra/llwindow/llwindowwin32.h
@@ -45,83 +45,82 @@ typedef void (*LLW32MsgCallback)(const MSG &msg);
class LLWindowWin32 : public LLWindow
{
public:
- /*virtual*/ void show();
- /*virtual*/ void hide();
- /*virtual*/ void close();
- /*virtual*/ bool getVisible();
- /*virtual*/ bool getMinimized();
- /*virtual*/ bool getMaximized();
- /*virtual*/ bool maximize();
- /*virtual*/ void minimize();
- /*virtual*/ void restore();
- /*virtual*/ bool getFullscreen();
- /*virtual*/ bool getPosition(LLCoordScreen *position);
- /*virtual*/ bool getSize(LLCoordScreen *size);
- /*virtual*/ bool getSize(LLCoordWindow *size);
- /*virtual*/ bool setPosition(LLCoordScreen position);
- /*virtual*/ bool setSizeImpl(LLCoordScreen size);
- /*virtual*/ bool setSizeImpl(LLCoordWindow size);
- /*virtual*/ bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL);
- /*virtual*/ void setTitle(const std::string title);
+ void show() override;
+ void hide() override;
+ void close() override;
+ bool getVisible() override;
+ bool getMinimized() override;
+ bool getMaximized() override;
+ bool maximize() override;
+ void minimize() override;
+ void restore() override;
+ bool getFullscreen();
+ bool getPosition(LLCoordScreen *position) override;
+ bool getSize(LLCoordScreen *size) override;
+ bool getSize(LLCoordWindow *size) override;
+ bool setPosition(LLCoordScreen position) override;
+ bool setSizeImpl(LLCoordScreen size) override;
+ bool setSizeImpl(LLCoordWindow size) override;
+ bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp = NULL) override;
+ void setTitle(const std::string title) override;
void* createSharedContext() override;
void makeContextCurrent(void* context) override;
void destroySharedContext(void* context) override;
- /*virtual*/ void toggleVSync(bool enable_vsync);
- /*virtual*/ bool setCursorPosition(LLCoordWindow position);
- /*virtual*/ bool getCursorPosition(LLCoordWindow *position);
- /*virtual*/ bool getCursorDelta(LLCoordCommon* delta);
- /*virtual*/ bool isWrapMouse() const override { return !mAbsoluteCursorPosition; };
- /*virtual*/ void showCursor();
- /*virtual*/ void hideCursor();
- /*virtual*/ void showCursorFromMouseMove();
- /*virtual*/ void hideCursorUntilMouseMove();
- /*virtual*/ bool isCursorHidden();
- /*virtual*/ void updateCursor();
- /*virtual*/ ECursorType getCursor() const;
- /*virtual*/ void captureMouse();
- /*virtual*/ void releaseMouse();
- /*virtual*/ void setMouseClipping( bool b );
- /*virtual*/ bool isClipboardTextAvailable();
- /*virtual*/ bool pasteTextFromClipboard(LLWString &dst);
- /*virtual*/ bool copyTextToClipboard(const LLWString &src);
- /*virtual*/ void flashIcon(F32 seconds);
- /*virtual*/ F32 getGamma();
- /*virtual*/ bool setGamma(const F32 gamma); // Set the gamma
- /*virtual*/ void setFSAASamples(const U32 fsaa_samples);
- /*virtual*/ U32 getFSAASamples();
- /*virtual*/ bool restoreGamma(); // Restore original gamma table (before updating gamma)
- /*virtual*/ ESwapMethod getSwapMethod() { return mSwapMethod; }
- /*virtual*/ void gatherInput();
- /*virtual*/ void delayInputProcessing();
- /*virtual*/ void swapBuffers();
- /*virtual*/ void restoreGLContext() {};
+ void toggleVSync(bool enable_vsync) override;
+ bool setCursorPosition(LLCoordWindow position) override;
+ bool getCursorPosition(LLCoordWindow *position) override;
+ bool getCursorDelta(LLCoordCommon* delta) override;
+ bool isWrapMouse() const override { return !mAbsoluteCursorPosition; };
+ void showCursor() override;
+ void hideCursor() override;
+ void showCursorFromMouseMove() override;
+ void hideCursorUntilMouseMove() override;
+ bool isCursorHidden() override;
+ void updateCursor() override;
+ ECursorType getCursor() const override;
+ void captureMouse() override;
+ void releaseMouse() override;
+ void setMouseClipping( bool b ) override;
+ bool isClipboardTextAvailable() override;
+ bool pasteTextFromClipboard(LLWString &dst) override;
+ bool copyTextToClipboard(const LLWString &src) override;
+ void flashIcon(F32 seconds) override;
+ F32 getGamma() override;
+ bool setGamma(const F32 gamma) override; // Set the gamma
+ void setFSAASamples(const U32 fsaa_samples) override;
+ U32 getFSAASamples() override;
+ bool restoreGamma() override; // Restore original gamma table (before updating gamma)
+ ESwapMethod getSwapMethod() override { return mSwapMethod; }
+ void gatherInput() override;
+ void delayInputProcessing() override;
+ void swapBuffers() override;
// handy coordinate space conversion routines
- /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordWindow *to);
- /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordScreen *to);
- /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordGL *to);
- /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordWindow *to);
- /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordGL *to);
- /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordScreen *to);
+ bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override;
+ bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override;
+ bool convertCoords(LLCoordWindow from, LLCoordGL *to) override;
+ bool convertCoords(LLCoordGL from, LLCoordWindow *to) override;
+ bool convertCoords(LLCoordScreen from, LLCoordGL *to) override;
+ bool convertCoords(LLCoordGL from, LLCoordScreen *to) override;
- /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions);
- /*virtual*/ F32 getNativeAspectRatio();
- /*virtual*/ F32 getPixelAspectRatio();
- /*virtual*/ void setNativeAspectRatio(F32 ratio) { mOverrideAspectRatio = ratio; }
+ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions) override;
+ F32 getNativeAspectRatio() override;
+ F32 getPixelAspectRatio() override;
+ void setNativeAspectRatio(F32 ratio) override { mOverrideAspectRatio = ratio; }
- /*virtual*/ bool dialogColorPicker(F32 *r, F32 *g, F32 *b );
+ bool dialogColorPicker(F32 *r, F32 *g, F32 *b ) override;
- /*virtual*/ void *getPlatformWindow();
- /*virtual*/ void bringToFront();
- /*virtual*/ void focusClient();
+ void *getPlatformWindow() override;
+ void bringToFront() override;
+ void focusClient() override;
- /*virtual*/ void allowLanguageTextInput(LLPreeditor *preeditor, bool b);
- /*virtual*/ void setLanguageTextInput( const LLCoordGL & pos );
- /*virtual*/ void updateLanguageTextInputArea();
- /*virtual*/ void interruptLanguageTextInput();
- /*virtual*/ void spawnWebBrowser(const std::string& escaped_url, bool async);
+ void allowLanguageTextInput(LLPreeditor *preeditor, bool b) override;
+ void setLanguageTextInput( const LLCoordGL & pos ) override;
+ void updateLanguageTextInputArea() override;
+ void interruptLanguageTextInput() override;
+ void spawnWebBrowser(const std::string& escaped_url, bool async) override;
- /*virtual*/ F32 getSystemUISize();
+ F32 getSystemUISize() override;
LLWindowCallbacks::DragNDropResult completeDragNDropRequest( const LLCoordGL gl_coord, const MASK mask, LLWindowCallbacks::DragNDropAction action, const std::string url );
@@ -129,14 +128,16 @@ public:
static std::vector<std::string> getDynamicFallbackFontList();
static void setDPIAwareness();
- /*virtual*/ void* getDirectInput8();
- /*virtual*/ bool getInputDevices(U32 device_type_filter,
+ void* getDirectInput8() override;
+ bool getInputDevices(U32 device_type_filter,
std::function<bool(std::string&, LLSD&, void*)> osx_callback,
void* win_callback,
- void* userdata);
+ void* userdata) override;
U32 getRawWParam() { return mRawWParam; }
+ void initWatchdog() override;
+
protected:
LLWindowWin32(LLWindowCallbacks* callbacks,
const std::string& title, const std::string& name, int x, int y, int width, int height, U32 flags,
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 0c5f3f3fd9..0949a3b59f 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -736,7 +736,6 @@ set(viewer_SOURCE_FILES
llvovolume.cpp
llvowater.cpp
llvowlsky.cpp
- llwatchdog.cpp
llwearableitemslist.cpp
llwearablelist.cpp
llweb.cpp
@@ -1414,7 +1413,6 @@ set(viewer_HEADER_FILES
llvovolume.h
llvowater.h
llvowlsky.h
- llwatchdog.h
llwearableitemslist.h
llwearablelist.h
llweb.h
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index 4703009f54..2aaedf9944 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-2026.01.0
+26.1.0
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 5cbd007d7b..5a1a1187a0 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -9069,7 +9069,7 @@
<key>Comment</key>
<string>Show reflection probes in the transparency debug view</string>
<key>Persist</key>
- <integer>1</integer>
+ <integer>0</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index bf00a7fed4..8aabdc5669 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -1239,7 +1239,7 @@ bool LLAppViewer::init()
/*----------------------------------------------------------------------*/
// nat 2016-06-29 moved the following here from the former mainLoop().
- mMainloopTimeout = new LLWatchdogTimeout();
+ mMainloopTimeout = new LLWatchdogTimeout("mainloop");
// Create IO Pump to use for HTTP Requests.
gServicePump = new LLPumpIO(gAPRPoolp);
@@ -1429,12 +1429,14 @@ bool LLAppViewer::doFrame()
{
LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df mainloop");
+ pingMainloopTimeout("df mainloop");
// canonical per-frame event
mainloop.post(newFrame);
}
{
LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df suspend");
+ pingMainloopTimeout("df suspend");
// give listeners a chance to run
llcoro::suspend();
// if one of our coroutines threw an uncaught exception, rethrow it now
@@ -1470,6 +1472,7 @@ bool LLAppViewer::doFrame()
{
{
LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df pauseMainloopTimeout");
+ pingMainloopTimeout("df idle"); // So that it will be aware of last state.
pauseMainloopTimeout(); // *TODO: Remove. Messages shouldn't be stalling for 20+ seconds!
}
@@ -1481,7 +1484,7 @@ bool LLAppViewer::doFrame()
{
LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df resumeMainloopTimeout");
- resumeMainloopTimeout();
+ resumeMainloopTimeout("df idle");
}
}
@@ -1496,7 +1499,7 @@ bool LLAppViewer::doFrame()
}
disconnectViewer();
- resumeMainloopTimeout();
+ resumeMainloopTimeout("df snapshot n disconnect");
}
// Render scene.
@@ -2301,7 +2304,22 @@ void errorHandler(const std::string& title_string, const std::string& message_st
}
if (!message_string.empty())
{
- OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK);
+ if (on_main_thread())
+ {
+ // Prevent watchdog from killing us while dialog is up.
+ // Can't do pauseMainloopTimeout, since this may be called
+ // from threads and we are not going to need watchdog now.
+ LLAppViewer::instance()->pauseMainloopTimeout();
+
+ // todo: might want to have non-crashing timeout for OOM cases
+ // and needs a way to pause main loop.
+ OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK);
+ LLAppViewer::instance()->resumeMainloopTimeout();
+ }
+ else
+ {
+ OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK);
+ }
}
}
@@ -3167,7 +3185,19 @@ bool LLAppViewer::initWindow()
if (use_watchdog)
{
- LLWatchdog::getInstance()->init();
+ LLWatchdog::getInstance()->init([]()
+ {
+ LLAppViewer* app = LLAppViewer::instance();
+ if (app->logoutRequestSent())
+ {
+ app->createErrorMarker(LAST_EXEC_LOGOUT_FROZE);
+ }
+ else
+ {
+ app->createErrorMarker(LAST_EXEC_FROZE);
+ }
+ });
+ gViewerWindow->getWindow()->initWatchdog();
}
LLNotificationsUI::LLNotificationManager::getInstance();
@@ -5840,7 +5870,7 @@ void LLAppViewer::initMainloopTimeout(std::string_view state)
{
if (!mMainloopTimeout)
{
- mMainloopTimeout = new LLWatchdogTimeout();
+ mMainloopTimeout = new LLWatchdogTimeout("mainloop");
resumeMainloopTimeout(state);
}
}
diff --git a/indra/newview/lleventpoll.cpp b/indra/newview/lleventpoll.cpp
index de3752d879..f1b46f0533 100644
--- a/indra/newview/lleventpoll.cpp
+++ b/indra/newview/lleventpoll.cpp
@@ -56,6 +56,7 @@ namespace Details
private:
void eventPollCoro(std::string url);
+ void handleMessage(const std::string& msg_name, const LLSD& body);
void handleMessage(const LLSD &content);
bool mDone;
@@ -95,21 +96,23 @@ namespace Details
mSenderIp = sender.getIPandPort();
}
+ void LLEventPollImpl::handleMessage(const std::string &msg_name, const LLSD &body)
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_APP;
+ LLSD message;
+ message["sender"] = mSenderIp;
+ message["body"] = body;
+
+ LLMessageSystem::dispatch(msg_name, message);
+ }
+
void LLEventPollImpl::handleMessage(const LLSD& content)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_APP;
std::string msg_name = content["message"].asString();
LLSD message;
- try
- {
- message["sender"] = mSenderIp;
- message["body"] = content["body"];
- }
- catch (std::bad_alloc&)
- {
- LLError::LLUserWarningMsg::showOutOfMemory();
- LL_ERRS("LLCoros") << "Bad memory allocation on message: " << msg_name << LL_ENDL;
- }
+ message["sender"] = mSenderIp;
+ message["body"] = content["body"];
LLMessageSystem::dispatch(msg_name, message);
}
@@ -194,7 +197,7 @@ namespace Details
break;
}
- LLSD httpResults = result["http_result"];
+ LLSD &httpResults = result["http_result"];
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
if (!status)
@@ -299,7 +302,7 @@ namespace Details
}
acknowledge = result["id"];
- LLSD events = result["events"];
+ LLSD &events = result["events"];
if (acknowledge.isUndefined())
{
@@ -310,20 +313,37 @@ namespace Details
LL_DEBUGS("LLEventPollImpl") << " <" << counter << "> " << events.size() << "events (id " << acknowledge << ")" << LL_ENDL;
- LLSD::array_const_iterator i = events.beginArray();
- LLSD::array_const_iterator end = events.endArray();
+ LLSD::array_iterator i = events.beginArray();
+ LLSD::array_iterator end = events.endArray();
for (; i != end; ++i)
{
if (i->has("message"))
{
if (main_queue)
- { // shuttle to a sensible spot in the main thread instead
+ {
+ // Shuttle copy to a sensible spot in the main thread instead
// of wherever this coroutine happens to be executing
- const LLSD& msg = *i;
- main_queue->post([this, msg]()
+
+ LL::WorkQueue::Work work;
+ {
+ // LLSD is too smart for it's own good and may act like a smart
+ // pointer for the content of (*i), so instead of passing (*i)
+ // pass a prepared name and move ownership of "body",
+ // as we are not going to need "body" anywhere else.
+ std::string msg_name = (*i)["message"].asString();
+
+ // WARNING: This is a shallow copy!
+ // If something still retains the data (like in httpAdapter?) this might still
+ // result in a crash, if it does appear to be the case, make a deep copy or
+ // convert data to string and pass that string.
+ const LLSD body = (*i)["body"];
+ (*i)["body"].clear();
+ work = [this, msg_name, body]()
{
- handleMessage(msg);
- });
+ handleMessage(msg_name, body);
+ };
+ }
+ main_queue->post(work);
}
else
{
diff --git a/indra/newview/llfavoritesbar.cpp b/indra/newview/llfavoritesbar.cpp
index 377710c170..98b3ca820b 100644
--- a/indra/newview/llfavoritesbar.cpp
+++ b/indra/newview/llfavoritesbar.cpp
@@ -1395,6 +1395,19 @@ bool LLFavoritesBarCtrl::enableSelected(const LLSD& userdata)
{
return !LLAgentPicksInfo::getInstance()->isPickLimitReached();
}
+ else if (param == "copy_slurl"
+ || param == "show_on_map")
+ {
+ LLViewerInventoryItem* item = gInventory.getItem(mSelectedItemID);
+ if (nullptr == item)
+ return false; // shouldn't happen as it is selected from existing items
+
+ const LLUUID& asset_id = item->getAssetUUID();
+
+ // Favorites are supposed to be loaded first, it should be here already
+ LLLandmark* landmark = gLandmarkList.getAsset(asset_id, NULL /*callback*/);
+ return nullptr != landmark;
+ }
return false;
}
@@ -1425,10 +1438,17 @@ void LLFavoritesBarCtrl::doToSelected(const LLSD& userdata)
LLVector3d posGlobal;
LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal);
+ // inventory item and asset exist, otherwise
+ // enableSelected wouldn't have let it get here,
+ // only need to check location validity
if (!posGlobal.isExactlyZero())
{
LLLandmarkActions::getSLURLfromPosGlobal(posGlobal, copy_slurl_to_clipboard_cb);
}
+ else
+ {
+ LLNotificationsUtil::add("LandmarkLocationUnknown");
+ }
}
else if (action == "show_on_map")
{
@@ -1437,10 +1457,20 @@ void LLFavoritesBarCtrl::doToSelected(const LLSD& userdata)
LLVector3d posGlobal;
LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal);
- if (!posGlobal.isExactlyZero() && worldmap_instance)
+ if (worldmap_instance)
{
- worldmap_instance->trackLocation(posGlobal);
- LLFloaterReg::showInstance("world_map", "center");
+ // inventory item and asset exist, otherwise
+ // enableSelected wouldn't have let it get here,
+ // only need to check location validity
+ if (!posGlobal.isExactlyZero())
+ {
+ worldmap_instance->trackLocation(posGlobal);
+ LLFloaterReg::showInstance("world_map", "center");
+ }
+ else
+ {
+ LLNotificationsUtil::add("LandmarkLocationUnknown");
+ }
}
}
else if (action == "create_pick")
diff --git a/indra/newview/lllandmarklist.cpp b/indra/newview/lllandmarklist.cpp
index 3fa0ab99f3..b25a42a938 100644
--- a/indra/newview/lllandmarklist.cpp
+++ b/indra/newview/lllandmarklist.cpp
@@ -147,29 +147,66 @@ void LLLandmarkList::processGetAssetReply(
else
{
// failed to parse, shouldn't happen
+ LL_WARNS("Landmarks") << "Failed to parse landmark " << uuid << LL_ENDL;
gLandmarkList.eraseCallbacks(uuid);
}
}
else
{
// got a good status, but no file, shouldn't happen
+ LL_WARNS("Landmarks") << "Empty buffer for landmark " << uuid << LL_ENDL;
gLandmarkList.eraseCallbacks(uuid);
}
+
+ // We got this asset, remove it from retry and bad lists.
+ gLandmarkList.mRetryList.erase(uuid);
+ gLandmarkList.mBadList.erase(uuid);
}
else
{
- // SJB: No use case for a notification here. Use LL_DEBUGS() instead
- if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status )
+ if (LL_ERR_NO_CAP == status)
+ {
+ // A problem with asset cap, always allow retrying.
+ // Todo: should this reschedule?
+ gLandmarkList.mRequestedList.erase(uuid);
+ gLandmarkList.eraseCallbacks(uuid);
+ // If there was a previous request, it likely failed due to an obsolete cap
+ // so clear the retry marker to allow multiple retries.
+ gLandmarkList.mRetryList.erase(uuid);
+ return;
+ }
+ if (gLandmarkList.mBadList.find(uuid) != gLandmarkList.mBadList.end())
+ {
+ // Already on the 'bad' list, ignore
+ gLandmarkList.mRequestedList.erase(uuid);
+ gLandmarkList.eraseCallbacks(uuid);
+ return;
+ }
+ if (LL_ERR_ASSET_REQUEST_FAILED == status
+ && gLandmarkList.mRetryList.find(uuid) == gLandmarkList.mRetryList.end())
+ {
+ // There is a number of reasons why an asset request can fail,
+ // like a cap being obsolete due to user teleporting.
+ // Let viewer rerequest at least once more.
+ // Todo: should this reshchedule?
+ gLandmarkList.mRetryList.emplace(uuid);
+ gLandmarkList.mRequestedList.erase(uuid);
+ gLandmarkList.eraseCallbacks(uuid);
+ return;
+ }
+
+ if (LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status)
{
- LL_WARNS("Landmarks") << "Missing Landmark" << LL_ENDL;
- //LLNotificationsUtil::add("LandmarkMissing");
+ LL_WARNS("Landmarks") << "Missing Landmark " << uuid << LL_ENDL;
}
else
{
- LL_WARNS("Landmarks") << "Unable to load Landmark" << LL_ENDL;
- //LLNotificationsUtil::add("UnableToLoadLandmark");
+ LL_WARNS("Landmarks") << "Unable to load Landmark " << uuid
+ << ". asset status: " << status
+ << ". Extended status: " << (S64)ext_status << LL_ENDL;
}
+ gLandmarkList.mRetryList.erase(uuid);
gLandmarkList.mBadList.insert(uuid);
gLandmarkList.mRequestedList.erase(uuid); //mBadList effectively blocks any load, so no point keeping id in requests
gLandmarkList.eraseCallbacks(uuid);
diff --git a/indra/newview/lllandmarklist.h b/indra/newview/lllandmarklist.h
index fb8b5a1960..76b5b97211 100644
--- a/indra/newview/lllandmarklist.h
+++ b/indra/newview/lllandmarklist.h
@@ -72,6 +72,7 @@ protected:
typedef std::set<LLUUID> landmark_uuid_list_t;
landmark_uuid_list_t mBadList;
+ landmark_uuid_list_t mRetryList;
typedef std::map<LLUUID,F32> landmark_requested_list_t;
landmark_requested_list_t mRequestedList;
diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp
index de8ab95dee..bcb51b22ca 100644
--- a/indra/newview/llpanelface.cpp
+++ b/indra/newview/llpanelface.cpp
@@ -1201,7 +1201,7 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
// See if that's been overridden by a material setting for same...
//
- LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode, mIsAlpha);
+ LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode);
// it is invalid to have any alpha mode other than blend if transparency is greater than zero ...
// Want masking? Want emissive? Tough! You get BLEND!
@@ -1211,6 +1211,12 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
alpha_mode = mIsAlpha ? alpha_mode : LLMaterial::DIFFUSE_ALPHA_MODE_NONE;
mComboAlphaMode->getSelectionInterface()->selectNthItem(alpha_mode);
+ mComboAlphaMode->setTentative(!identical_alpha_mode);
+ if (!identical_alpha_mode)
+ {
+ std::string multiple = LLTrans::getString("multiple_textures");
+ mComboAlphaMode->setLabel(multiple);
+ }
updateAlphaControls();
mExcludeWater &= (LLMaterial::DIFFUSE_ALPHA_MODE_BLEND == alpha_mode);
@@ -5484,32 +5490,40 @@ void LLPanelFace::LLSelectedTEMaterial::getMaxNormalRepeats(F32& repeats, bool&
identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &max_norm_repeats_func, repeats);
}
-void LLPanelFace::LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical, bool diffuse_texture_has_alpha)
+void LLPanelFace::LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical)
{
struct LLSelectedTEGetDiffuseAlphaMode : public LLSelectedTEGetFunctor<U8>
{
- LLSelectedTEGetDiffuseAlphaMode() : _isAlpha(false) {}
- LLSelectedTEGetDiffuseAlphaMode(bool diffuse_texture_has_alpha) : _isAlpha(diffuse_texture_has_alpha) {}
+ LLSelectedTEGetDiffuseAlphaMode() {}
virtual ~LLSelectedTEGetDiffuseAlphaMode() {}
U8 get(LLViewerObject* object, S32 face)
{
- U8 diffuse_mode = _isAlpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE;
-
LLTextureEntry* tep = object->getTE(face);
if (tep)
{
LLMaterial* mat = tep->getMaterialParams().get();
if (mat)
{
- diffuse_mode = mat->getDiffuseAlphaMode();
+ return mat->getDiffuseAlphaMode();
+ }
+ }
+
+ bool has_alpha = false;
+ LLViewerTexture* image = object->getTEImage(face);
+ if (image)
+ {
+ LLGLenum format = image->getPrimaryFormat();
+ if (format == GL_RGBA || format == GL_ALPHA)
+ {
+ has_alpha = true;
}
}
+ U8 diffuse_mode = has_alpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE;
return diffuse_mode;
}
- bool _isAlpha; // whether or not the diffuse texture selected contains alpha information
- } get_diff_mode(diffuse_texture_has_alpha);
+ } get_diff_mode;
identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &get_diff_mode, diffuse_alpha_mode);
}
diff --git a/indra/newview/llpanelface.h b/indra/newview/llpanelface.h
index 63fee6bab8..82790ac54b 100644
--- a/indra/newview/llpanelface.h
+++ b/indra/newview/llpanelface.h
@@ -652,7 +652,7 @@ public:
static void getCurrent(LLMaterialPtr& material_ptr, bool& identical_material);
static void getMaxSpecularRepeats(F32& repeats, bool& identical);
static void getMaxNormalRepeats(F32& repeats, bool& identical);
- static void getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical, bool diffuse_texture_has_alpha);
+ static void getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical);
static void selectionNormalScaleAutofit(LLPanelFace* panel_face, F32 repeats_per_meter);
static void selectionSpecularScaleAutofit(LLPanelFace* panel_face, F32 repeats_per_meter);
diff --git a/indra/newview/llpanelplaceprofile.cpp b/indra/newview/llpanelplaceprofile.cpp
index 87f05f2028..c380b6860f 100644
--- a/indra/newview/llpanelplaceprofile.cpp
+++ b/indra/newview/llpanelplaceprofile.cpp
@@ -517,7 +517,7 @@ void LLPanelPlaceProfile::displaySelectedParcelInfo(LLParcel* parcel,
std::string parcel_owner =
LLSLURL("agent", parcel->getOwnerID(), "inspect").getSLURLString();
mParcelOwner->setText(parcel_owner);
- LLAvatarNameCache::get(region->getOwner(), boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mRegionOwnerText));
+ mAvatarNameCacheConnection = LLAvatarNameCache::get(region->getOwner(), boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mRegionOwnerText));
mRegionGroupText->setText( getString("none_text"));
}
@@ -548,7 +548,7 @@ void LLPanelPlaceProfile::displaySelectedParcelInfo(LLParcel* parcel,
const LLUUID& auth_buyer_id = parcel->getAuthorizedBuyerID();
if(auth_buyer_id.notNull())
{
- LLAvatarNameCache::get(auth_buyer_id, boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mSaleToText));
+ mAvatarNameCacheConnection = LLAvatarNameCache::get(auth_buyer_id, boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mSaleToText));
// Show sales info to a specific person or a group he belongs to.
if (auth_buyer_id != gAgent.getID() && !gAgent.isInGroup(auth_buyer_id))
diff --git a/indra/newview/llpanelplaceprofile.h b/indra/newview/llpanelplaceprofile.h
index f562be0f5d..0c161198f8 100644
--- a/indra/newview/llpanelplaceprofile.h
+++ b/indra/newview/llpanelplaceprofile.h
@@ -118,6 +118,8 @@ private:
LLTextEditor* mResaleText;
LLTextBox* mSaleToText;
LLAccordionCtrl* mAccordionCtrl;
+
+ boost::signals2::scoped_connection mAvatarNameCacheConnection;
};
#endif // LL_LLPANELPLACEPROFILE_H
diff --git a/indra/newview/llpanelsnapshotinventory.cpp b/indra/newview/llpanelsnapshotinventory.cpp
index b81b891685..7396f079b0 100644
--- a/indra/newview/llpanelsnapshotinventory.cpp
+++ b/indra/newview/llpanelsnapshotinventory.cpp
@@ -110,7 +110,11 @@ void LLPanelSnapshotInventory::updateControls(const LLSD& info)
void LLPanelSnapshotInventory::onResolutionCommit(LLUICtrl* ctrl)
{
- bool current_window_selected = (getChild<LLComboBox>(getImageSizeComboName())->getCurrentIndex() == 3);
+ LLComboBox* combo = getChild<LLComboBox>(getImageSizeComboName());
+ // Current window likely won't ever change position from being the penultimate item
+ // Custom window is last item
+ S32 curent_window_index = combo->getItemCount() - 2;
+ bool current_window_selected = (combo->getCurrentIndex() == curent_window_index);
getChild<LLSpinCtrl>(getWidthSpinnerName())->setVisible(!current_window_selected);
getChild<LLSpinCtrl>(getHeightSpinnerName())->setVisible(!current_window_selected);
}
diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp
index 141f370ecb..fd462fb225 100644
--- a/indra/newview/llviewerassetstorage.cpp
+++ b/indra/newview/llviewerassetstorage.cpp
@@ -42,6 +42,7 @@
#include "llcoproceduremanager.h"
#include "lleventcoro.h"
#include "llsdutil.h"
+#include "llstartup.h"
#include "llworld.h"
///----------------------------------------------------------------------------
@@ -460,28 +461,78 @@ void LLViewerAssetStorage::assetRequestCoro(
if (!gAgent.getRegion())
{
- LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: no region set" << LL_ENDL;
- result_code = LL_ERR_ASSET_REQUEST_FAILED;
- ext_status = LLExtStat::NONE;
- removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0);
- return;
+ if (STATE_WORLD_INIT <= LLStartUp::getStartupState())
+ {
+ // Viewer isn't ready, wait for region to become available
+ LL_INFOS_ONCE("ViewerAsset") << "Waiting for agent region to be set" << LL_ENDL;
+
+ LLEventStream region_init("waitForRegion", true);
+ std::string pump_name = region_init.getName();
+
+ boost::signals2::connection region_conn =
+ gAgent.addRegionChangedCallback([pump_name]()
+ {
+ LLEventPumps::instance().obtain(pump_name).post(LLSD());
+ });
+ F32Seconds timeout_seconds(LL_ASSET_STORAGE_TIMEOUT);
+ llcoro::suspendUntilEventOnWithTimeout(region_init, timeout_seconds, LLSDMap("timeout", LLSD::Boolean(true)));
+ gAgent.removeRegionChangedCallback(region_conn);
+ region_conn.disconnect();
+
+ if (LLApp::isExiting() || !gAssetStorage)
+ {
+ return;
+ }
+
+ // recheck region whether suspend ended on timeout or not
+ if (!gAgent.getRegion())
+ {
+ LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: timeout reached while waiting for region" << LL_ENDL;
+ result_code = LL_ERR_NO_CAP;
+ ext_status = LLExtStat::NONE;
+ removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0);
+ return;
+ }
+ }
+ else
+ {
+ LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: no region set" << LL_ENDL;
+ result_code = LL_ERR_NO_CAP;
+ ext_status = LLExtStat::NONE;
+ removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0);
+ return;
+ }
}
- else if (!gAgent.getRegion()->capabilitiesReceived())
+
+ if (!gAgent.getRegion()->capabilitiesReceived())
{
LL_WARNS_ONCE("ViewerAsset") << "Waiting for capabilities" << LL_ENDL;
LLEventStream capsRecv("waitForCaps", true);
- gAgent.getRegion()->setCapabilitiesReceivedCallback(
- boost::bind(&LLViewerAssetStorage::capsRecvForRegion, this, _1, capsRecv.getName()));
+ boost::signals2::connection caps_conn =
+ gAgent.getRegion()->setCapabilitiesReceivedCallback(
+ boost::bind(&LLViewerAssetStorage::capsRecvForRegion, this, _1, capsRecv.getName()));
- llcoro::suspendUntilEventOn(capsRecv);
+ F32Seconds timeout_seconds(LL_ASSET_STORAGE_TIMEOUT); // from minutes to seconds, by default 5 minutes
+ LLSD result = llcoro::suspendUntilEventOnWithTimeout(capsRecv, timeout_seconds, LLSDMap("timeout", LLSD::Boolean(true)));
+ caps_conn.disconnect();
if (LLApp::isExiting() || !gAssetStorage)
{
return;
}
+ if (result.has("timeout"))
+ {
+ // Caps failed to arrive in 5 minutes
+ LL_WARNS_ONCE("ViewerAsset") << "Asset " << uuid << " request fails : capabilities took too long to arrive" << LL_ENDL;
+ result_code = LL_ERR_NO_CAP;
+ ext_status = LLExtStat::NONE;
+ removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0);
+ return;
+ }
+
LL_WARNS_ONCE("ViewerAsset") << "capsRecv got event" << LL_ENDL;
LL_WARNS_ONCE("ViewerAsset") << "region " << gAgent.getRegion() << " mViewerAssetUrl " << mViewerAssetUrl << LL_ENDL;
}
@@ -492,7 +543,7 @@ void LLViewerAssetStorage::assetRequestCoro(
if (mViewerAssetUrl.empty())
{
LL_WARNS_ONCE("ViewerAsset") << "asset request fails: caps received but no viewer asset cap found" << LL_ENDL;
- result_code = LL_ERR_ASSET_REQUEST_FAILED;
+ result_code = LL_ERR_NO_CAP;
ext_status = LLExtStat::NONE;
removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status, 0);
return;
diff --git a/indra/newview/skins/default/xui/en/menu_favorites.xml b/indra/newview/skins/default/xui/en/menu_favorites.xml
index 6345394b46..f82f705fb7 100644
--- a/indra/newview/skins/default/xui/en/menu_favorites.xml
+++ b/indra/newview/skins/default/xui/en/menu_favorites.xml
@@ -35,6 +35,9 @@
<menu_item_call.on_click
function="Favorites.DoToSelected"
parameter="show_on_map" />
+ <menu_item_call.on_enable
+ function="Favorites.EnableSelected"
+ parameter="show_on_map" />
</menu_item_call>
<menu_item_call
label="Copy SLurl"
@@ -43,6 +46,9 @@
<menu_item_call.on_click
function="Favorites.DoToSelected"
parameter="copy_slurl" />
+ <menu_item_call.on_enable
+ function="Favorites.EnableSelected"
+ parameter="copy_slurl" />
</menu_item_call>
<menu_item_call
label="Create Pick"
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index d278bac075..e8da8a6322 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -2466,6 +2466,17 @@ You already have a landmark for this location.
</notification>
<notification
+ icon="alert.tga"
+ name="LandmarkLocationUnknown"
+ type="alert">
+Viewer wasn't able to get region's location. Region might be temporarily unavailable, was removed or landmark failed to load.
+ <usetemplate
+ name="okbutton"
+ yestext="OK"/>
+ <tag>fail</tag>
+ </notification>
+
+ <notification
icon="alertmodal.tga"
name="CannotCreateLandmarkNotOwner"
type="alertmodal">
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 94d234686a..109f00c9ae 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -259,9 +259,10 @@ class ViewerManifest(LLManifest):
global CHANNEL_VENDOR_BASE
channel_type=self.channel_type()
if channel_type == 'release':
- return CHANNEL_VENDOR_BASE
+ app_suffix='Viewer'
else:
- return CHANNEL_VENDOR_BASE + ' ' + self.channel_variant()
+ app_suffix=self.channel_variant()
+ return CHANNEL_VENDOR_BASE + ' ' + app_suffix
def exec_name(self):
return "SecondLifeViewer"