summaryrefslogtreecommitdiff
path: root/indra/newview
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview')
-rw-r--r--indra/newview/app_settings/settings.xml33
-rw-r--r--indra/newview/llappviewer.cpp4
-rw-r--r--indra/newview/llfirstuse.cpp58
-rw-r--r--indra/newview/llfirstuse.h9
-rw-r--r--indra/newview/llfloatermap.cpp3
-rw-r--r--indra/newview/llfolderview.cpp63
-rw-r--r--indra/newview/llfolderview.h3
-rw-r--r--indra/newview/llinspectavatar.cpp88
-rw-r--r--indra/newview/llinventoryfilter.cpp18
-rw-r--r--indra/newview/llinventorypanel.cpp73
-rw-r--r--indra/newview/llinventorypanel.h3
-rw-r--r--indra/newview/llmemoryview.cpp53
-rw-r--r--indra/newview/llmemoryview.h1
-rw-r--r--indra/newview/llnetmap.cpp149
-rw-r--r--indra/newview/llnetmap.h18
-rw-r--r--indra/newview/llpanelavatar.cpp62
-rw-r--r--indra/newview/llpanelavatar.h596
-rw-r--r--indra/newview/llpanelpeople.cpp13
-rw-r--r--indra/newview/llpanelpeople.h1
-rw-r--r--indra/newview/llsidetray.cpp11
-rw-r--r--indra/newview/lltexturefetch.cpp6022
-rw-r--r--indra/newview/llviewermedia.cpp21
-rw-r--r--indra/newview/llviewermessage.cpp8
-rw-r--r--indra/newview/llviewerwindow.cpp8
-rw-r--r--indra/newview/llwearableitemslist.cpp3
-rw-r--r--indra/newview/skins/default/textures/textures.xml1
-rw-r--r--indra/newview/skins/default/xui/da/panel_people.xml18
-rw-r--r--indra/newview/skins/default/xui/en/floater_map.xml6
-rw-r--r--indra/newview/skins/default/xui/en/floater_web_content.xml16
-rw-r--r--indra/newview/skins/default/xui/en/menu_inspect_avatar_gear.xml20
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml10
-rw-r--r--indra/newview/skins/default/xui/en/panel_nearby_chat_bar.xml4
-rw-r--r--indra/newview/skins/default/xui/en/panel_people.xml26
-rw-r--r--indra/newview/skins/default/xui/en/widgets/talk_button.xml10
34 files changed, 3945 insertions, 3487 deletions
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index ced46c7294..6630d8f400 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -697,28 +697,6 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>BrowserUseDefaultCAFile</key>
- <map>
- <key>Comment</key>
- <string>Tell the built-in web browser to use the CA.pem file shipped with the client.</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>1</integer>
- </map>
- <key>BrowserCAFilePath</key>
- <map>
- <key>Comment</key>
- <string>Tell the built-in web browser the path to an alternative CA.pem file (only used if BrowserUseDefaultCAFile is false).</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>String</string>
- <key>Value</key>
- <string></string>
- </map>
<key>BlockAvatarAppearanceMessages</key>
<map>
<key>Comment</key>
@@ -1852,6 +1830,17 @@
<key>Value</key>
<integer>0</integer>
</map>
+ <key>DebugShowMemory</key>
+ <map>
+ <key>Comment</key>
+ <string>Show Total Allocated Memory</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
<key>DebugShowRenderInfo</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 6a9dfaf21b..69333ff4a3 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -1296,7 +1296,7 @@ bool LLAppViewer::mainLoop()
resumeMainloopTimeout();
pingMainloopTimeout("Main:End");
- }
+ }
}
catch(std::bad_alloc)
{
@@ -1779,6 +1779,8 @@ bool LLAppViewer::cleanup()
ll_close_fail_log();
+ MEM_TRACK_RELEASE
+
llinfos << "Goodbye!" << llendflush;
// return 0;
diff --git a/indra/newview/llfirstuse.cpp b/indra/newview/llfirstuse.cpp
index 4c17199895..4d252dc662 100644
--- a/indra/newview/llfirstuse.cpp
+++ b/indra/newview/llfirstuse.cpp
@@ -41,35 +41,36 @@
// static
-std::set<std::string> LLFirstUse::sConfigVariables;
+//std::set<std::string> LLFirstUse::sConfigVariables;
+std::set<std::string> LLFirstUse::sConfigVariablesEnabled;
// static
-void LLFirstUse::addConfigVariable(const std::string& var)
-{
- sConfigVariables.insert(var);
-}
+//void LLFirstUse::addConfigVariable(const std::string& var)
+//{
+// sConfigVariables.insert(var);
+//}
// static
-void LLFirstUse::disableFirstUse()
-{
- // Set all first-use warnings to disabled
- for (std::set<std::string>::iterator iter = sConfigVariables.begin();
- iter != sConfigVariables.end(); ++iter)
- {
- gWarningSettings.setBOOL(*iter, FALSE);
- }
-}
+//void LLFirstUse::disableFirstUse()
+//{
+// // Set all first-use warnings to disabled
+// for (std::set<std::string>::iterator iter = sConfigVariables.begin();
+// iter != sConfigVariables.end(); ++iter)
+// {
+// gWarningSettings.setBOOL(*iter, FALSE);
+// }
+//}
// static
-void LLFirstUse::resetFirstUse()
-{
- // Set all first-use warnings to disabled
- for (std::set<std::string>::iterator iter = sConfigVariables.begin();
- iter != sConfigVariables.end(); ++iter)
- {
- gWarningSettings.setBOOL(*iter, TRUE);
- }
-}
+//void LLFirstUse::resetFirstUse()
+//{
+// // Set all first-use warnings to disabled
+// for (std::set<std::string>::iterator iter = sConfigVariables.begin();
+// iter != sConfigVariables.end(); ++iter)
+// {
+// gWarningSettings.setBOOL(*iter, TRUE);
+// }
+//}
// static
void LLFirstUse::otherAvatarChatFirst(bool enable)
@@ -151,13 +152,21 @@ void LLFirstUse::firstUseNotification(const std::string& control_var, bool enabl
if (enable)
{
+ if(sConfigVariablesEnabled.find(control_var) != sConfigVariablesEnabled.end())
+ {
+ return ; //already added
+ }
+
if (gSavedSettings.getBOOL("EnableUIHints"))
{
LL_DEBUGS("LLFirstUse") << "Trigger first use notification " << notification_name << LL_ENDL;
// if notification doesn't already exist and this notification hasn't been disabled...
if (gWarningSettings.getBOOL(control_var))
- { // create new notification
+ {
+ sConfigVariablesEnabled.insert(control_var) ;
+
+ // create new notification
LLNotifications::instance().add(LLNotification::Params().name(notification_name).substitutions(args).payload(payload.with("control_var", control_var)));
}
}
@@ -169,7 +178,6 @@ void LLFirstUse::firstUseNotification(const std::string& control_var, bool enabl
// redundantly clear settings var here, in case there are no notifications to cancel
gWarningSettings.setBOOL(control_var, FALSE);
}
-
}
// static
diff --git a/indra/newview/llfirstuse.h b/indra/newview/llfirstuse.h
index 81659988e6..489f58626a 100644
--- a/indra/newview/llfirstuse.h
+++ b/indra/newview/llfirstuse.h
@@ -78,11 +78,11 @@ class LLFirstUse
public:
// Add a config variable to be reset on resetFirstUse()
- static void addConfigVariable(const std::string& var);
+ //static void addConfigVariable(const std::string& var);
// Sets all controls back to show the dialogs.
- static void disableFirstUse();
- static void resetFirstUse();
+ //static void disableFirstUse();
+ //static void resetFirstUse();
static void otherAvatarChatFirst(bool enable = true);
static void sit(bool enable = true);
@@ -98,7 +98,8 @@ public:
protected:
static void firstUseNotification(const std::string& control_var, bool enable, const std::string& notification_name, LLSD args = LLSD(), LLSD payload = LLSD());
- static std::set<std::string> sConfigVariables;
+ //static std::set<std::string> sConfigVariables;
+ static std::set<std::string> sConfigVariablesEnabled;
static void init();
static bool processNotification(const LLSD& notify);
diff --git a/indra/newview/llfloatermap.cpp b/indra/newview/llfloatermap.cpp
index 1b94d8cbcd..80920c80d6 100644
--- a/indra/newview/llfloatermap.cpp
+++ b/indra/newview/llfloatermap.cpp
@@ -83,7 +83,8 @@ LLFloaterMap::~LLFloaterMap()
BOOL LLFloaterMap::postBuild()
{
mMap = getChild<LLNetMap>("Net Map");
- mMap->setToolTipMsg(getString("ToolTipMsg"));
+ mMap->setToolTipMsg(gSavedSettings.getBOOL("DoubleClickTeleport") ?
+ getString("AltToolTipMsg") : getString("ToolTipMsg"));
sendChildToBack(mMap);
mTextBoxNorth = getChild<LLTextBox> ("floater_map_north");
diff --git a/indra/newview/llfolderview.cpp b/indra/newview/llfolderview.cpp
index 62ba746a02..b3b1ce5743 100644
--- a/indra/newview/llfolderview.cpp
+++ b/indra/newview/llfolderview.cpp
@@ -1854,31 +1854,9 @@ BOOL LLFolderView::handleRightMouseDown( S32 x, S32 y, MASK mask )
{
if (mCallbackRegistrar)
mCallbackRegistrar->pushScope();
- //menu->empty();
- const LLView::child_list_t *list = menu->getChildList();
- LLView::child_list_t::const_iterator menu_itor;
- for (menu_itor = list->begin(); menu_itor != list->end(); ++menu_itor)
- {
- (*menu_itor)->setVisible(FALSE);
- (*menu_itor)->pushVisible(TRUE);
- (*menu_itor)->setEnabled(TRUE);
- }
-
- // Successively filter out invalid options
-
- U32 flags = FIRST_SELECTED_ITEM;
- for (selected_items_t::iterator item_itor = mSelectedItems.begin();
- item_itor != mSelectedItems.end();
- ++item_itor)
- {
- LLFolderViewItem* selected_item = (*item_itor);
- selected_item->buildContextMenu(*menu, flags);
- flags = 0x0;
- }
+ updateMenuOptions(menu);
- addNoOptions(menu);
-
menu->updateParent(LLMenuGL::sMenuContainer);
LLMenuGL::showPopup(this, menu, x, y);
if (mCallbackRegistrar)
@@ -2365,6 +2343,45 @@ void LLFolderView::updateRenamerPosition()
}
}
+// Update visibility and availability (i.e. enabled/disabled) of context menu items.
+void LLFolderView::updateMenuOptions(LLMenuGL* menu)
+{
+ const LLView::child_list_t *list = menu->getChildList();
+
+ LLView::child_list_t::const_iterator menu_itor;
+ for (menu_itor = list->begin(); menu_itor != list->end(); ++menu_itor)
+ {
+ (*menu_itor)->setVisible(FALSE);
+ (*menu_itor)->pushVisible(TRUE);
+ (*menu_itor)->setEnabled(TRUE);
+ }
+
+ // Successively filter out invalid options
+
+ U32 flags = FIRST_SELECTED_ITEM;
+ for (selected_items_t::iterator item_itor = mSelectedItems.begin();
+ item_itor != mSelectedItems.end();
+ ++item_itor)
+ {
+ LLFolderViewItem* selected_item = (*item_itor);
+ selected_item->buildContextMenu(*menu, flags);
+ flags = 0x0;
+ }
+
+ addNoOptions(menu);
+}
+
+// Refresh the context menu (that is already shown).
+void LLFolderView::updateMenu()
+{
+ LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get();
+ if (menu && menu->getVisible())
+ {
+ updateMenuOptions(menu);
+ menu->needsArrange(); // update menu height if needed
+ }
+}
+
bool LLFolderView::selectFirstItem()
{
for (folders_t::iterator iter = mFolders.begin();
diff --git a/indra/newview/llfolderview.h b/indra/newview/llfolderview.h
index afaac86b04..210ba9eb3c 100644
--- a/indra/newview/llfolderview.h
+++ b/indra/newview/llfolderview.h
@@ -269,7 +269,10 @@ public:
virtual S32 notify(const LLSD& info) ;
bool useLabelSuffix() { return mUseLabelSuffix; }
+ void updateMenu();
+
private:
+ void updateMenuOptions(LLMenuGL* menu);
void updateRenamerPosition();
protected:
diff --git a/indra/newview/llinspectavatar.cpp b/indra/newview/llinspectavatar.cpp
index 91ede6d221..17d0b0ffbb 100644
--- a/indra/newview/llinspectavatar.cpp
+++ b/indra/newview/llinspectavatar.cpp
@@ -47,6 +47,7 @@
#include "llvoiceclient.h"
#include "llviewerobjectlist.h"
#include "lltransientfloatermgr.h"
+#include "llnotificationsutil.h"
// Linden libraries
#include "llfloater.h"
@@ -126,16 +127,20 @@ private:
void onClickReport();
void onClickFreeze();
void onClickEject();
+ void onClickKick();
+ void onClickCSR();
void onClickZoomIn();
void onClickFindOnMap();
bool onVisibleFindOnMap();
- bool onVisibleFreezeEject();
+ bool onVisibleEject();
+ bool onVisibleFreeze();
bool onVisibleZoomIn();
void onClickMuteVolume();
void onVolumeChange(const LLSD& data);
bool enableMute();
bool enableUnmute();
bool enableTeleportOffer();
+ bool godModeEnabled();
// Is used to determine if "Add friend" option should be enabled in gear menu
bool isNotFriend();
@@ -214,20 +219,21 @@ LLInspectAvatar::LLInspectAvatar(const LLSD& sd)
mCommitCallbackRegistrar.add("InspectAvatar.Pay", boost::bind(&LLInspectAvatar::onClickPay, this));
mCommitCallbackRegistrar.add("InspectAvatar.Share", boost::bind(&LLInspectAvatar::onClickShare, this));
mCommitCallbackRegistrar.add("InspectAvatar.ToggleMute", boost::bind(&LLInspectAvatar::onToggleMute, this));
- mCommitCallbackRegistrar.add("InspectAvatar.Freeze",
- boost::bind(&LLInspectAvatar::onClickFreeze, this));
- mCommitCallbackRegistrar.add("InspectAvatar.Eject",
- boost::bind(&LLInspectAvatar::onClickEject, this));
+ mCommitCallbackRegistrar.add("InspectAvatar.Freeze", boost::bind(&LLInspectAvatar::onClickFreeze, this));
+ mCommitCallbackRegistrar.add("InspectAvatar.Eject", boost::bind(&LLInspectAvatar::onClickEject, this));
+ mCommitCallbackRegistrar.add("InspectAvatar.Kick", boost::bind(&LLInspectAvatar::onClickKick, this));
+ mCommitCallbackRegistrar.add("InspectAvatar.CSR", boost::bind(&LLInspectAvatar::onClickCSR, this));
mCommitCallbackRegistrar.add("InspectAvatar.Report", boost::bind(&LLInspectAvatar::onClickReport, this));
mCommitCallbackRegistrar.add("InspectAvatar.FindOnMap", boost::bind(&LLInspectAvatar::onClickFindOnMap, this));
mCommitCallbackRegistrar.add("InspectAvatar.ZoomIn", boost::bind(&LLInspectAvatar::onClickZoomIn, this));
mCommitCallbackRegistrar.add("InspectAvatar.DisableVoice", boost::bind(&LLInspectAvatar::toggleSelectedVoice, this, false));
mCommitCallbackRegistrar.add("InspectAvatar.EnableVoice", boost::bind(&LLInspectAvatar::toggleSelectedVoice, this, true));
+
+ mEnableCallbackRegistrar.add("InspectAvatar.EnableGod", boost::bind(&LLInspectAvatar::godModeEnabled, this));
mEnableCallbackRegistrar.add("InspectAvatar.VisibleFindOnMap", boost::bind(&LLInspectAvatar::onVisibleFindOnMap, this));
- mEnableCallbackRegistrar.add("InspectAvatar.VisibleFreezeEject",
- boost::bind(&LLInspectAvatar::onVisibleFreezeEject, this));
- mEnableCallbackRegistrar.add("InspectAvatar.VisibleZoomIn",
- boost::bind(&LLInspectAvatar::onVisibleZoomIn, this));
+ mEnableCallbackRegistrar.add("InspectAvatar.VisibleEject", boost::bind(&LLInspectAvatar::onVisibleEject, this));
+ mEnableCallbackRegistrar.add("InspectAvatar.VisibleFreeze", boost::bind(&LLInspectAvatar::onVisibleFreeze, this));
+ mEnableCallbackRegistrar.add("InspectAvatar.VisibleZoomIn", boost::bind(&LLInspectAvatar::onVisibleZoomIn, this));
mEnableCallbackRegistrar.add("InspectAvatar.Gear.Enable", boost::bind(&LLInspectAvatar::isNotFriend, this));
mEnableCallbackRegistrar.add("InspectAvatar.Gear.EnableCall", boost::bind(&LLAvatarActions::canCall));
mEnableCallbackRegistrar.add("InspectAvatar.Gear.EnableTeleportOffer", boost::bind(&LLInspectAvatar::enableTeleportOffer, this));
@@ -656,11 +662,18 @@ bool LLInspectAvatar::onVisibleFindOnMap()
return gAgent.isGodlike() || is_agent_mappable(mAvatarID);
}
-bool LLInspectAvatar::onVisibleFreezeEject()
+bool LLInspectAvatar::onVisibleEject()
{
return enable_freeze_eject( LLSD(mAvatarID) );
}
+bool LLInspectAvatar::onVisibleFreeze()
+{
+ // either user is a god and can do long distance freeze
+ // or check for target proximity and permissions
+ return gAgent.isGodlike() || enable_freeze_eject(LLSD(mAvatarID));
+}
+
bool LLInspectAvatar::onVisibleZoomIn()
{
return gObjectList.findObject(mAvatarID);
@@ -704,7 +717,7 @@ void LLInspectAvatar::onClickShare()
void LLInspectAvatar::onToggleMute()
{
- LLMute mute(mAvatarID, mAvatarName.getLegacyName(), LLMute::AGENT);
+ LLMute mute(mAvatarID, mAvatarName.mDisplayName, LLMute::AGENT);
if (LLMuteList::getInstance()->isMuted(mute.mID, mute.mName))
{
@@ -725,9 +738,41 @@ void LLInspectAvatar::onClickReport()
closeFloater();
}
+bool godlike_freeze(const LLSD& notification, const LLSD& response)
+{
+ LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID();
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+
+ switch (option)
+ {
+ case 0:
+ LLAvatarActions::freeze(avatar_id);
+ break;
+ case 1:
+ LLAvatarActions::unfreeze(avatar_id);
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
void LLInspectAvatar::onClickFreeze()
{
- handle_avatar_freeze( LLSD(mAvatarID) );
+ if (gAgent.isGodlike())
+ {
+ // use godlike freeze-at-a-distance, with confirmation
+ LLNotificationsUtil::add("FreezeAvatar",
+ LLSD(),
+ LLSD().with("avatar_id", mAvatarID),
+ godlike_freeze);
+ }
+ else
+ {
+ // use default "local" version of freezing that requires avatar to be in range
+ handle_avatar_freeze( LLSD(mAvatarID) );
+ }
closeFloater();
}
@@ -737,6 +782,20 @@ void LLInspectAvatar::onClickEject()
closeFloater();
}
+void LLInspectAvatar::onClickKick()
+{
+ LLAvatarActions::kick(mAvatarID);
+ closeFloater();
+}
+
+void LLInspectAvatar::onClickCSR()
+{
+ std::string name;
+ gCacheName->getFullName(mAvatarID, name);
+ LLAvatarActions::csr(mAvatarID, name);
+ closeFloater();
+}
+
void LLInspectAvatar::onClickZoomIn()
{
handle_zoom_to_object(mAvatarID);
@@ -785,6 +844,11 @@ bool LLInspectAvatar::enableTeleportOffer()
return LLAvatarActions::canOfferTeleport(mAvatarID);
}
+bool LLInspectAvatar::godModeEnabled()
+{
+ return gAgent.isGodlike();
+}
+
//////////////////////////////////////////////////////////////////////////////
// LLInspectAvatarUtil
//////////////////////////////////////////////////////////////////////////////
diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp
index ef4774a06d..e22363c2f6 100644
--- a/indra/newview/llinventoryfilter.cpp
+++ b/indra/newview/llinventoryfilter.cpp
@@ -393,18 +393,22 @@ void LLInventoryFilter::setFilterUUID(const LLUUID& object_id)
void LLInventoryFilter::setFilterSubString(const std::string& string)
{
- if (mFilterSubString != string)
+ std::string filter_sub_string_new = string;
+ mFilterSubStringOrig = string;
+ LLStringUtil::trimHead(filter_sub_string_new);
+ LLStringUtil::toUpper(filter_sub_string_new);
+
+ if (mFilterSubString != filter_sub_string_new)
{
// hitting BACKSPACE, for example
- const BOOL less_restrictive = mFilterSubString.size() >= string.size() && !mFilterSubString.substr(0, string.size()).compare(string);
+ const BOOL less_restrictive = mFilterSubString.size() >= filter_sub_string_new.size()
+ && !mFilterSubString.substr(0, filter_sub_string_new.size()).compare(filter_sub_string_new);
// appending new characters
- const BOOL more_restrictive = mFilterSubString.size() < string.size() && !string.substr(0, mFilterSubString.size()).compare(mFilterSubString);
+ const BOOL more_restrictive = mFilterSubString.size() < filter_sub_string_new.size()
+ && !filter_sub_string_new.substr(0, mFilterSubString.size()).compare(mFilterSubString);
- mFilterSubStringOrig = string;
- LLStringUtil::trimHead(mFilterSubStringOrig);
- mFilterSubString = mFilterSubStringOrig;
- LLStringUtil::toUpper(mFilterSubString);
+ mFilterSubString = filter_sub_string_new;
if (less_restrictive)
{
setModified(FILTER_LESS_RESTRICTIVE);
diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp
index 5a9d1524f3..1dcb91ad4d 100644
--- a/indra/newview/llinventorypanel.cpp
+++ b/indra/newview/llinventorypanel.cpp
@@ -60,6 +60,7 @@ static const LLInventoryFVBridgeBuilder INVENTORY_BRIDGE_BUILDER;
//
// Bridge to support knowing when the inventory has changed.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
class LLInventoryPanelObserver : public LLInventoryObserver
{
public:
@@ -73,9 +74,57 @@ protected:
LLInventoryPanel* mIP;
};
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Class LLInvPanelComplObserver
+//
+// Calls specified callback when all specified items become complete.
+//
+// Usage:
+// observer = new LLInvPanelComplObserver(boost::bind(onComplete));
+// inventory->addObserver(observer);
+// observer->reset(); // (optional)
+// observer->watchItem(incomplete_item1_id);
+// observer->watchItem(incomplete_item2_id);
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class LLInvPanelComplObserver : public LLInventoryCompletionObserver
+{
+public:
+ typedef boost::function<void()> callback_t;
+
+ LLInvPanelComplObserver(callback_t cb)
+ : mCallback(cb)
+ {
+ }
+
+ void reset();
+
+private:
+ /*virtual*/ void done();
+
+ /// Called when all the items are complete.
+ callback_t mCallback;
+};
+
+void LLInvPanelComplObserver::reset()
+{
+ mIncomplete.clear();
+ mComplete.clear();
+}
+
+void LLInvPanelComplObserver::done()
+{
+ mCallback();
+}
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Class LLInventoryPanel
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
LLInventoryPanel::LLInventoryPanel(const LLInventoryPanel::Params& p) :
LLPanel(p),
mInventoryObserver(NULL),
+ mCompletionObserver(NULL),
mFolderRoot(NULL),
mScroller(NULL),
mSortOrderSetting(p.sort_order_setting),
@@ -152,6 +201,9 @@ void LLInventoryPanel::initFromParams(const LLInventoryPanel::Params& params)
mInventoryObserver = new LLInventoryPanelObserver(this);
mInventory->addObserver(mInventoryObserver);
+ mCompletionObserver = new LLInvPanelComplObserver(boost::bind(&LLInventoryPanel::onItemsCompletion, this));
+ mInventory->addObserver(mCompletionObserver);
+
// Build view of inventory if we need default full hierarchy and inventory ready,
// otherwise wait for idle callback.
if (mBuildDefaultHierarchy && mInventory->isInventoryUsable() && !mViewsInitialized)
@@ -189,7 +241,10 @@ LLInventoryPanel::~LLInventoryPanel()
// LLView destructor will take care of the sub-views.
mInventory->removeObserver(mInventoryObserver);
+ mInventory->removeObserver(mCompletionObserver);
delete mInventoryObserver;
+ delete mCompletionObserver;
+
mScroller = NULL;
}
@@ -654,6 +709,11 @@ void LLInventoryPanel::openStartFolderOrMyInventory()
}
}
+void LLInventoryPanel::onItemsCompletion()
+{
+ if (mFolderRoot) mFolderRoot->updateMenu();
+}
+
void LLInventoryPanel::openSelected()
{
LLFolderViewItem* folder_item = mFolderRoot->getCurSelectedItem();
@@ -757,6 +817,19 @@ void LLInventoryPanel::clearSelection()
void LLInventoryPanel::onSelectionChange(const std::deque<LLFolderViewItem*>& items, BOOL user_action)
{
+ // Schedule updating the folder view context menu when all selected items become complete (STORM-373).
+ mCompletionObserver->reset();
+ for (std::deque<LLFolderViewItem*>::const_iterator it = items.begin(); it != items.end(); ++it)
+ {
+ LLUUID id = (*it)->getListener()->getUUID();
+ LLViewerInventoryItem* inv_item = mInventory->getItem(id);
+
+ if (inv_item && !inv_item->isFinished())
+ {
+ mCompletionObserver->watchItem(id);
+ }
+ }
+
LLFolderView* fv = getRootFolder();
if (fv->needsAutoRename()) // auto-selecting a new user-created asset and preparing to rename
{
diff --git a/indra/newview/llinventorypanel.h b/indra/newview/llinventorypanel.h
index 6545fc0d5e..9da9f7d8ba 100644
--- a/indra/newview/llinventorypanel.h
+++ b/indra/newview/llinventorypanel.h
@@ -52,6 +52,7 @@ class LLIconCtrl;
class LLSaveFolderState;
class LLFilterEditor;
class LLTabContainer;
+class LLInvPanelComplObserver;
class LLInventoryPanel : public LLPanel
{
@@ -167,9 +168,11 @@ public:
protected:
void openStartFolderOrMyInventory(); // open the first level of inventory
+ void onItemsCompletion(); // called when selected items are complete
LLInventoryModel* mInventory;
LLInventoryObserver* mInventoryObserver;
+ LLInvPanelComplObserver* mCompletionObserver;
BOOL mAllowMultiSelect;
BOOL mShowItemLinkOverlays; // Shows link graphic over inventory item icons
diff --git a/indra/newview/llmemoryview.cpp b/indra/newview/llmemoryview.cpp
index 9a244e2562..7e9c3c84a7 100644
--- a/indra/newview/llmemoryview.cpp
+++ b/indra/newview/llmemoryview.cpp
@@ -37,9 +37,11 @@
#include <sstream>
#include <boost/algorithm/string/split.hpp>
+#include "llmemory.h"
LLMemoryView::LLMemoryView(const LLMemoryView::Params& p)
: LLView(p),
+ mPaused(FALSE),
//mDelay(120),
mAlloc(NULL)
{
@@ -59,6 +61,7 @@ BOOL LLMemoryView::handleMouseDown(S32 x, S32 y, MASK mask)
}
else
{
+ mPaused = !mPaused;
}
return TRUE;
}
@@ -148,13 +151,14 @@ void LLMemoryView::draw()
// cut off lines on bottom
U32 max_lines = U32((height - 2 * line_height) / line_height);
- std::vector<LLWString>::const_iterator end = mLines.end();
+ y_pos = height - MARGIN_AMT - line_height;
+ y_off = 0.f;
+
+#if !MEM_TRACK_MEM
+ std::vector<LLWString>::const_iterator end = mLines.end();
if(mLines.size() > max_lines) {
end = mLines.begin() + max_lines;
}
-
- y_pos = height - MARGIN_AMT - line_height;
- y_off = 0.f;
for (std::vector<LLWString>::const_iterator i = mLines.begin(); i != end; ++i)
{
font->render(*i, 0, MARGIN_AMT, y_pos - y_off,
@@ -169,6 +173,47 @@ void LLMemoryView::draw()
y_off += line_height;
}
+#else
+ LLMemTracker::getInstance()->preDraw(mPaused) ;
+
+ {
+ F32 x_pos = MARGIN_AMT ;
+ U32 lines = 0 ;
+ const char* str = LLMemTracker::getInstance()->getNextLine() ;
+ while(str != NULL)
+ {
+ lines++ ;
+ font->renderUTF8(str, 0, x_pos, y_pos - y_off,
+ LLColor4::white,
+ LLFontGL::LEFT,
+ LLFontGL::BASELINE,
+ LLFontGL::NORMAL,
+ LLFontGL::DROP_SHADOW,
+ S32_MAX,
+ target_width,
+ NULL, FALSE);
+
+ str = LLMemTracker::getInstance()->getNextLine() ;
+ y_off += line_height;
+
+ if(lines >= max_lines)
+ {
+ lines = 0 ;
+ x_pos += 512.f ;
+ if(x_pos + 512.f > target_width)
+ {
+ break ;
+ }
+
+ y_pos = height - MARGIN_AMT - line_height;
+ y_off = 0.f;
+ }
+ }
+ }
+
+ LLMemTracker::getInstance()->postDraw() ;
+#endif
+
#if MEM_TRACK_TYPE
S32 left, top, right, bottom;
diff --git a/indra/newview/llmemoryview.h b/indra/newview/llmemoryview.h
index 24ea058279..9bdc59ab10 100644
--- a/indra/newview/llmemoryview.h
+++ b/indra/newview/llmemoryview.h
@@ -55,6 +55,7 @@ public:
private:
std::vector<LLWString> mLines;
LLAllocator* mAlloc;
+ BOOL mPaused ;
};
diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp
index 1a8ec4991d..93039d935d 100644
--- a/indra/newview/llnetmap.cpp
+++ b/indra/newview/llnetmap.cpp
@@ -47,6 +47,7 @@
#include "llagentcamera.h"
#include "llappviewer.h" // for gDisconnected
#include "llcallingcard.h" // LLAvatarTracker
+#include "llfloaterworldmap.h"
#include "lltracker.h"
#include "llsurface.h"
#include "llviewercamera.h"
@@ -91,7 +92,8 @@ LLNetMap::LLNetMap (const Params & p)
mObjectImagep(),
mClosestAgentToCursor(),
mClosestAgentAtLastRightClick(),
- mToolTipMsg()
+ mToolTipMsg(),
+ mPopupMenu(NULL)
{
mDotRadius = llmax(DOT_SCALE * mPixelsPerMeter, MIN_DOT_RADIUS);
setScale(gSavedSettings.getF32("MiniMapScale"));
@@ -102,6 +104,21 @@ LLNetMap::~LLNetMap()
gSavedSettings.setF32("MiniMapScale", mScale);
}
+BOOL LLNetMap::postBuild()
+{
+ LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+
+ registrar.add("Minimap.Zoom", boost::bind(&LLNetMap::handleZoom, this, _2));
+ registrar.add("Minimap.Tracker", boost::bind(&LLNetMap::handleStopTracking, this, _2));
+
+ mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_mini_map.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
+ if (mPopupMenu && !LLTracker::isTracking(0))
+ {
+ mPopupMenu->setItemEnabled ("Stop Tracking", false);
+ }
+ return TRUE;
+}
+
void LLNetMap::setScale( F32 scale )
{
scale = llclamp(scale, MAP_SCALE_MIN, MAP_SCALE_MAX);
@@ -354,16 +371,49 @@ void LLNetMap::draw()
pos_map = globalPosToView(pos_global);
+ LLUUID uuid(NULL);
BOOL show_as_friend = FALSE;
if( i < regionp->mMapAvatarIDs.count())
{
- show_as_friend = (LLAvatarTracker::instance().getBuddyInfo(regionp->mMapAvatarIDs.get(i)) != NULL);
+ uuid = regionp->mMapAvatarIDs.get(i);
+ show_as_friend = (LLAvatarTracker::instance().getBuddyInfo(uuid) != NULL);
}
+
+ LLColor4 color = show_as_friend ? map_avatar_friend_color : map_avatar_color;
LLWorldMapView::drawAvatar(
pos_map.mV[VX], pos_map.mV[VY],
- show_as_friend ? map_avatar_friend_color : map_avatar_color,
+ color,
pos_map.mV[VZ], mDotRadius);
+ if(uuid.notNull())
+ {
+ bool selected = false;
+ uuid_vec_t::iterator sel_iter = gmSelected.begin();
+ for (; sel_iter != gmSelected.end(); sel_iter++)
+ {
+ if(*sel_iter == uuid)
+ {
+ selected = true;
+ break;
+ }
+ }
+ if(selected)
+ {
+ if( (pos_map.mV[VX] < 0) ||
+ (pos_map.mV[VY] < 0) ||
+ (pos_map.mV[VX] >= getRect().getWidth()) ||
+ (pos_map.mV[VY] >= getRect().getHeight()) )
+ {
+ S32 x = llround( pos_map.mV[VX] );
+ S32 y = llround( pos_map.mV[VY] );
+ LLWorldMapView::drawTrackingCircle( getRect(), x, y, color, 1, 10);
+ } else
+ {
+ LLWorldMapView::drawTrackingDot(pos_map.mV[VX],pos_map.mV[VY],color,0.f);
+ }
+ }
+ }
+
F32 dist_to_cursor = dist_vec(LLVector2(pos_map.mV[VX], pos_map.mV[VY]),
LLVector2(local_mouse_x,local_mouse_y));
if(dist_to_cursor < min_pick_dist && dist_to_cursor < closest_dist)
@@ -460,6 +510,13 @@ void LLNetMap::draw()
gGL.popUIMatrix();
LLUICtrl::draw();
+
+ if (LLTracker::isTracking(0))
+ {
+ mPopupMenu->setItemEnabled ("Stop Tracking", true);
+ }
+
+
}
void LLNetMap::reshape(S32 width, S32 height, BOOL called_from_parent)
@@ -600,7 +657,6 @@ BOOL LLNetMap::handleToolTip( S32 x, S32 y, MASK mask )
args["[REGION]"] = region_name;
std::string msg = mToolTipMsg;
LLStringUtil::format(msg, args);
-
LLToolTipMgr::instance().show(LLToolTip::Params()
.message(msg)
.sticky_rect(sticky_rect));
@@ -793,6 +849,9 @@ BOOL LLNetMap::handleMouseDown( S32 x, S32 y, MASK mask )
BOOL LLNetMap::handleMouseUp( S32 x, S32 y, MASK mask )
{
+ if(abs(mMouseDown.mX-x)<3 && abs(mMouseDown.mY-y)<3)
+ handleClick(x,y,mask);
+
if (hasMouseCapture())
{
if (mPanning)
@@ -821,6 +880,53 @@ BOOL LLNetMap::handleMouseUp( S32 x, S32 y, MASK mask )
return FALSE;
}
+BOOL LLNetMap::handleRightMouseDown(S32 x, S32 y, MASK mask)
+{
+ if (mPopupMenu)
+ {
+ mPopupMenu->buildDrawLabels();
+ mPopupMenu->updateParent(LLMenuGL::sMenuContainer);
+ LLMenuGL::showPopup(this, mPopupMenu, x, y);
+ }
+ return TRUE;
+}
+
+BOOL LLNetMap::handleClick(S32 x, S32 y, MASK mask)
+{
+ // TODO: allow clicking an avatar on minimap to select avatar in the nearby avatar list
+ // if(mClosestAgentToCursor.notNull())
+ // mNearbyList->selectUser(mClosestAgentToCursor);
+ // Needs a registered observer i guess to accomplish this without using
+ // globals to tell the mNearbyList in llpeoplepanel to select the user
+ return TRUE;
+}
+
+BOOL LLNetMap::handleDoubleClick(S32 x, S32 y, MASK mask)
+{
+ LLVector3d pos_global = viewPosToGlobal(x, y);
+
+ // If we're not tracking a beacon already, double-click will set one
+ if (!LLTracker::isTracking(NULL))
+ {
+ LLFloaterWorldMap* world_map = LLFloaterWorldMap::getInstance();
+ if (world_map)
+ {
+ world_map->trackLocation(pos_global);
+ }
+ }
+
+ if (gSavedSettings.getBOOL("DoubleClickTeleport"))
+ {
+ // If DoubleClickTeleport is on, double clicking the minimap will teleport there
+ gAgent.teleportViaLocationLookAt(pos_global);
+ }
+ else
+ {
+ LLFloaterReg::showInstance("world_map");
+ }
+ return TRUE;
+}
+
// static
bool LLNetMap::outsideSlop( S32 x, S32 y, S32 start_x, S32 start_y, S32 slop )
{
@@ -871,3 +977,38 @@ BOOL LLNetMap::handleHover( S32 x, S32 y, MASK mask )
return TRUE;
}
+
+void LLNetMap::handleZoom(const LLSD& userdata)
+{
+ std::string level = userdata.asString();
+
+ F32 scale = 0.0f;
+ if (level == std::string("default"))
+ {
+ LLControlVariable *pvar = gSavedSettings.getControl("MiniMapScale");
+ if(pvar)
+ {
+ pvar->resetToDefault();
+ scale = gSavedSettings.getF32("MiniMapScale");
+ }
+ }
+ else if (level == std::string("close"))
+ scale = LLNetMap::MAP_SCALE_MAX;
+ else if (level == std::string("medium"))
+ scale = LLNetMap::MAP_SCALE_MID;
+ else if (level == std::string("far"))
+ scale = LLNetMap::MAP_SCALE_MIN;
+ if (scale != 0.0f)
+ {
+ setScale(scale);
+ }
+}
+
+void LLNetMap::handleStopTracking (const LLSD& userdata)
+{
+ if (mPopupMenu)
+ {
+ mPopupMenu->setItemEnabled ("Stop Tracking", false);
+ LLTracker::stopTracking ((void*)LLTracker::isTracking(NULL));
+ }
+}
diff --git a/indra/newview/llnetmap.h b/indra/newview/llnetmap.h
index e053b1c177..20fcee0814 100644
--- a/indra/newview/llnetmap.h
+++ b/indra/newview/llnetmap.h
@@ -39,6 +39,7 @@ class LLCoordGL;
class LLImageRaw;
class LLViewerTexture;
class LLFloaterMap;
+class LLMenuGL;
class LLNetMap : public LLUICtrl
{
@@ -72,7 +73,12 @@ public:
/*virtual*/ BOOL handleHover( S32 x, S32 y, MASK mask );
/*virtual*/ BOOL handleToolTip( S32 x, S32 y, MASK mask);
/*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
-
+
+ /*virtual*/ BOOL postBuild();
+ /*virtual*/ BOOL handleRightMouseDown( S32 x, S32 y, MASK mask );
+ /*virtual*/ BOOL handleClick(S32 x, S32 y, MASK mask);
+ /*virtual*/ BOOL handleDoubleClick( S32 x, S32 y, MASK mask );
+
void setScale( F32 scale );
void setToolTipMsg(const std::string& msg) { mToolTipMsg = msg; }
void renderScaledPointGlobal( const LLVector3d& pos, const LLColor4U &color, F32 radius );
@@ -120,6 +126,16 @@ private:
LLUUID mClosestAgentAtLastRightClick;
std::string mToolTipMsg;
+
+public:
+ void setSelected(uuid_vec_t uuids) { gmSelected=uuids; };
+
+private:
+ void handleZoom(const LLSD& userdata);
+ void handleStopTracking (const LLSD& userdata);
+
+ LLMenuGL* mPopupMenu;
+ uuid_vec_t gmSelected;
};
diff --git a/indra/newview/llpanelavatar.cpp b/indra/newview/llpanelavatar.cpp
index 94b2340c93..53529b9f8b 100644
--- a/indra/newview/llpanelavatar.cpp
+++ b/indra/newview/llpanelavatar.cpp
@@ -625,10 +625,10 @@ void LLPanelAvatarProfile::processGroupProperties(const LLAvatarGroups* avatar_g
getChild<LLUICtrl>("sl_groups")->setValue(groups);
}
-void LLPanelAvatarProfile::got_full_name_callback( const LLUUID& id, const std::string& full_name, bool is_group )
-{
- LLStringUtil::format_map_t args;
-
+void LLPanelAvatarProfile::got_full_name_callback( const LLUUID& id, const std::string& full_name, bool is_group )
+{
+ LLStringUtil::format_map_t args;
+
std::string name;
if (LLAvatarNameCache::useDisplayNames())
{
@@ -637,21 +637,21 @@ void LLPanelAvatarProfile::got_full_name_callback( const LLUUID& id, const std::
else
{
name = full_name;
- }
-
- args["[NAME]"] = name;
-
- std::string linden_name = getString("name_text_args", args);
- getChild<LLUICtrl>("name_descr_text")->setValue(linden_name);
-}
+ }
+
+ args["[NAME]"] = name;
+
+ std::string linden_name = getString("name_text_args", args);
+ getChild<LLUICtrl>("name_descr_text")->setValue(linden_name);
+}
void LLPanelAvatarProfile::onNameCache(const LLUUID& agent_id, const LLAvatarName& av_name)
{
- LLStringUtil::format_map_t args;
- args["[DISPLAY_NAME]"] = av_name.mDisplayName;
-
- std::string display_name = getString("display_name_text_args", args);
- getChild<LLUICtrl>("display_name_descr_text")->setValue(display_name);
+ LLStringUtil::format_map_t args;
+ args["[DISPLAY_NAME]"] = av_name.mDisplayName;
+
+ std::string display_name = getString("display_name_text_args", args);
+ getChild<LLUICtrl>("display_name_descr_text")->setValue(display_name);
}
void LLPanelAvatarProfile::fillCommonData(const LLAvatarData* avatar_data)
@@ -667,22 +667,22 @@ void LLPanelAvatarProfile::fillCommonData(const LLAvatarData* avatar_data)
}
// ask (asynchronously) for the avatar name
- std::string full_name;
- if (gCacheName->getFullName(avatar_data->agent_id, full_name))
- {
- // name in cache, call callback directly
- got_full_name_callback( avatar_data->agent_id, full_name, false );
- }
- else
- {
- // not in cache, lookup name
- gCacheName->get(avatar_data->agent_id, false, boost::bind( &LLPanelAvatarProfile::got_full_name_callback, this, _1, _2, _3 ));
- }
-
- // get display name
+ std::string full_name;
+ if (gCacheName->getFullName(avatar_data->agent_id, full_name))
+ {
+ // name in cache, call callback directly
+ got_full_name_callback( avatar_data->agent_id, full_name, false );
+ }
+ else
+ {
+ // not in cache, lookup name
+ gCacheName->get(avatar_data->agent_id, false, boost::bind( &LLPanelAvatarProfile::got_full_name_callback, this, _1, _2, _3 ));
+ }
+
+ // get display name
LLAvatarNameCache::get(avatar_data->avatar_id,
- boost::bind(&LLPanelAvatarProfile::onNameCache, this, _1, _2));
-
+ boost::bind(&LLPanelAvatarProfile::onNameCache, this, _1, _2));
+
args["[AGE]"] = LLDateUtil::ageFromDate( avatar_data->born_on, LLDate::now());
std::string register_date = getString("RegisterDateFormat", args);
getChild<LLUICtrl>("register_date")->setValue(register_date );
diff --git a/indra/newview/llpanelavatar.h b/indra/newview/llpanelavatar.h
index 070fe4579a..5f36d1026f 100644
--- a/indra/newview/llpanelavatar.h
+++ b/indra/newview/llpanelavatar.h
@@ -1,298 +1,298 @@
-/**
- * @file llpanelavatar.h
- * @brief LLPanelAvatar and related class definitions
- *
- * $LicenseInfo:firstyear=2004&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_LLPANELAVATAR_H
-#define LL_LLPANELAVATAR_H
-
-#include "llpanel.h"
-#include "llavatarpropertiesprocessor.h"
-#include "llcallingcard.h"
-#include "llvoiceclient.h"
-#include "llavatarnamecache.h"
-
-class LLComboBox;
-class LLLineEditor;
-
-enum EOnlineStatus
-{
- ONLINE_STATUS_NO = 0,
- ONLINE_STATUS_YES = 1
-};
-
-/**
-* Base class for any Profile View or My Profile Panel.
-*/
-class LLPanelProfileTab
- : public LLPanel
- , public LLAvatarPropertiesObserver
-{
-public:
-
- /**
- * Sets avatar ID, sets panel as observer of avatar related info replies from server.
- */
- virtual void setAvatarId(const LLUUID& id);
-
- /**
- * Returns avatar ID.
- */
- virtual const LLUUID& getAvatarId() { return mAvatarId; }
-
- /**
- * Sends update data request to server.
- */
- virtual void updateData() = 0;
-
- /**
- * Clears panel data if viewing avatar info for first time and sends update data request.
- */
- virtual void onOpen(const LLSD& key);
-
- /**
- * Profile tabs should close any opened panels here.
- *
- * Called from LLPanelProfile::onOpen() before opening new profile.
- * See LLPanelPicks::onClosePanel for example. LLPanelPicks closes picture info panel
- * before new profile is displayed, otherwise new profile will
- * be hidden behind picture info panel.
- */
- virtual void onClosePanel() {}
-
- /**
- * Resets controls visibility, state, etc.
- */
- virtual void resetControls(){};
-
- /**
- * Clears all data received from server.
- */
- virtual void resetData(){};
-
- /*virtual*/ ~LLPanelProfileTab();
-
-protected:
-
- LLPanelProfileTab();
-
- /**
- * Scrolls panel to top when viewing avatar info for first time.
- */
- void scrollToTop();
-
- virtual void onMapButtonClick();
-
- virtual void updateButtons();
-
-private:
-
- LLUUID mAvatarId;
-};
-
-/**
-* Panel for displaying Avatar's first and second life related info.
-*/
-class LLPanelAvatarProfile
- : public LLPanelProfileTab
- , public LLFriendObserver
- , public LLVoiceClientStatusObserver
-{
-public:
- LLPanelAvatarProfile();
- /*virtual*/ ~LLPanelAvatarProfile();
-
- /*virtual*/ void onOpen(const LLSD& key);
-
- /**
- * LLFriendObserver trigger
- */
- virtual void changed(U32 mask);
-
- // Implements LLVoiceClientStatusObserver::onChange() to enable the call
- // button when voice is available
- /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal);
-
- /*virtual*/ void setAvatarId(const LLUUID& id);
-
- /**
- * Processes data received from server.
- */
- /*virtual*/ void processProperties(void* data, EAvatarProcessorType type);
-
- /*virtual*/ BOOL postBuild();
-
- /*virtual*/ void updateData();
-
- /*virtual*/ void resetControls();
-
- /*virtual*/ void resetData();
-
-protected:
-
- /**
- * Process profile related data received from server.
- */
- virtual void processProfileProperties(const LLAvatarData* avatar_data);
-
- /**
- * Processes group related data received from server.
- */
- virtual void processGroupProperties(const LLAvatarGroups* avatar_groups);
-
- /**
- * Fills common for Avatar profile and My Profile fields.
- */
- virtual void fillCommonData(const LLAvatarData* avatar_data);
-
- /**
- * Fills partner data.
- */
- virtual void fillPartnerData(const LLAvatarData* avatar_data);
-
- /**
- * Fills account status.
- */
- virtual void fillAccountStatus(const LLAvatarData* avatar_data);
-
- /**
- * Opens "Pay Resident" dialog.
- */
- void pay();
-
- /**
- * opens inventory and IM for sharing items
- */
- void share();
-
- /**
- * Add/remove resident to/from your block list.
- */
- void toggleBlock();
-
- void kick();
- void freeze();
- void unfreeze();
- void csr();
-
- bool enableShowOnMap();
- bool enableBlock();
- bool enableUnblock();
- bool enableGod();
-
- void onSeeProfileBtnClick();
- void onAddFriendButtonClick();
- void onIMButtonClick();
- void onCallButtonClick();
- void onTeleportButtonClick();
- void onShareButtonClick();
-
-private:
- void got_full_name_callback( const LLUUID& id, const std::string& full_name, bool is_group );
- void onNameCache(const LLUUID& agent_id, const LLAvatarName& av_name);
-
- typedef std::map< std::string,LLUUID> group_map_t;
- group_map_t mGroups;
-};
-
-/**
- * Panel for displaying own first and second life related info.
- */
-class LLPanelMyProfile
- : public LLPanelAvatarProfile
-{
-public:
- LLPanelMyProfile();
-
- /*virtual*/ BOOL postBuild();
-
-protected:
-
- /*virtual*/ void onOpen(const LLSD& key);
-
- /*virtual*/ void processProfileProperties(const LLAvatarData* avatar_data);
-
- /*virtual*/ void resetControls();
-
-protected:
- void onStatusMessageChanged();
-};
-
-/**
- * Panel for displaying Avatar's notes and modifying friend's rights.
- */
-class LLPanelAvatarNotes
- : public LLPanelProfileTab
- , public LLFriendObserver
- , public LLVoiceClientStatusObserver
-{
-public:
- LLPanelAvatarNotes();
- /*virtual*/ ~LLPanelAvatarNotes();
-
- virtual void setAvatarId(const LLUUID& id);
-
- /**
- * LLFriendObserver trigger
- */
- virtual void changed(U32 mask);
-
- // Implements LLVoiceClientStatusObserver::onChange() to enable the call
- // button when voice is available
- /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal);
-
- /*virtual*/ void onOpen(const LLSD& key);
-
- /*virtual*/ BOOL postBuild();
-
- /*virtual*/ void processProperties(void* data, EAvatarProcessorType type);
-
- /*virtual*/ void updateData();
-
-protected:
-
- /*virtual*/ void resetControls();
-
- /*virtual*/ void resetData();
-
- /**
- * Fills rights data for friends.
- */
- void fillRightsData();
-
- void rightsConfirmationCallback(const LLSD& notification,
- const LLSD& response, S32 rights);
- void confirmModifyRights(bool grant, S32 rights);
- void onCommitRights();
- void onCommitNotes();
-
- void onAddFriendButtonClick();
- void onIMButtonClick();
- void onCallButtonClick();
- void onTeleportButtonClick();
- void onShareButtonClick();
- void enableCheckboxes(bool enable);
-};
-
-#endif // LL_LLPANELAVATAR_H
+/**
+ * @file llpanelavatar.h
+ * @brief LLPanelAvatar and related class definitions
+ *
+ * $LicenseInfo:firstyear=2004&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_LLPANELAVATAR_H
+#define LL_LLPANELAVATAR_H
+
+#include "llpanel.h"
+#include "llavatarpropertiesprocessor.h"
+#include "llcallingcard.h"
+#include "llvoiceclient.h"
+#include "llavatarnamecache.h"
+
+class LLComboBox;
+class LLLineEditor;
+
+enum EOnlineStatus
+{
+ ONLINE_STATUS_NO = 0,
+ ONLINE_STATUS_YES = 1
+};
+
+/**
+* Base class for any Profile View or My Profile Panel.
+*/
+class LLPanelProfileTab
+ : public LLPanel
+ , public LLAvatarPropertiesObserver
+{
+public:
+
+ /**
+ * Sets avatar ID, sets panel as observer of avatar related info replies from server.
+ */
+ virtual void setAvatarId(const LLUUID& id);
+
+ /**
+ * Returns avatar ID.
+ */
+ virtual const LLUUID& getAvatarId() { return mAvatarId; }
+
+ /**
+ * Sends update data request to server.
+ */
+ virtual void updateData() = 0;
+
+ /**
+ * Clears panel data if viewing avatar info for first time and sends update data request.
+ */
+ virtual void onOpen(const LLSD& key);
+
+ /**
+ * Profile tabs should close any opened panels here.
+ *
+ * Called from LLPanelProfile::onOpen() before opening new profile.
+ * See LLPanelPicks::onClosePanel for example. LLPanelPicks closes picture info panel
+ * before new profile is displayed, otherwise new profile will
+ * be hidden behind picture info panel.
+ */
+ virtual void onClosePanel() {}
+
+ /**
+ * Resets controls visibility, state, etc.
+ */
+ virtual void resetControls(){};
+
+ /**
+ * Clears all data received from server.
+ */
+ virtual void resetData(){};
+
+ /*virtual*/ ~LLPanelProfileTab();
+
+protected:
+
+ LLPanelProfileTab();
+
+ /**
+ * Scrolls panel to top when viewing avatar info for first time.
+ */
+ void scrollToTop();
+
+ virtual void onMapButtonClick();
+
+ virtual void updateButtons();
+
+private:
+
+ LLUUID mAvatarId;
+};
+
+/**
+* Panel for displaying Avatar's first and second life related info.
+*/
+class LLPanelAvatarProfile
+ : public LLPanelProfileTab
+ , public LLFriendObserver
+ , public LLVoiceClientStatusObserver
+{
+public:
+ LLPanelAvatarProfile();
+ /*virtual*/ ~LLPanelAvatarProfile();
+
+ /*virtual*/ void onOpen(const LLSD& key);
+
+ /**
+ * LLFriendObserver trigger
+ */
+ virtual void changed(U32 mask);
+
+ // Implements LLVoiceClientStatusObserver::onChange() to enable the call
+ // button when voice is available
+ /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal);
+
+ /*virtual*/ void setAvatarId(const LLUUID& id);
+
+ /**
+ * Processes data received from server.
+ */
+ /*virtual*/ void processProperties(void* data, EAvatarProcessorType type);
+
+ /*virtual*/ BOOL postBuild();
+
+ /*virtual*/ void updateData();
+
+ /*virtual*/ void resetControls();
+
+ /*virtual*/ void resetData();
+
+protected:
+
+ /**
+ * Process profile related data received from server.
+ */
+ virtual void processProfileProperties(const LLAvatarData* avatar_data);
+
+ /**
+ * Processes group related data received from server.
+ */
+ virtual void processGroupProperties(const LLAvatarGroups* avatar_groups);
+
+ /**
+ * Fills common for Avatar profile and My Profile fields.
+ */
+ virtual void fillCommonData(const LLAvatarData* avatar_data);
+
+ /**
+ * Fills partner data.
+ */
+ virtual void fillPartnerData(const LLAvatarData* avatar_data);
+
+ /**
+ * Fills account status.
+ */
+ virtual void fillAccountStatus(const LLAvatarData* avatar_data);
+
+ /**
+ * Opens "Pay Resident" dialog.
+ */
+ void pay();
+
+ /**
+ * opens inventory and IM for sharing items
+ */
+ void share();
+
+ /**
+ * Add/remove resident to/from your block list.
+ */
+ void toggleBlock();
+
+ void kick();
+ void freeze();
+ void unfreeze();
+ void csr();
+
+ bool enableShowOnMap();
+ bool enableBlock();
+ bool enableUnblock();
+ bool enableGod();
+
+ void onSeeProfileBtnClick();
+ void onAddFriendButtonClick();
+ void onIMButtonClick();
+ void onCallButtonClick();
+ void onTeleportButtonClick();
+ void onShareButtonClick();
+
+private:
+ void got_full_name_callback( const LLUUID& id, const std::string& full_name, bool is_group );
+ void onNameCache(const LLUUID& agent_id, const LLAvatarName& av_name);
+
+ typedef std::map< std::string,LLUUID> group_map_t;
+ group_map_t mGroups;
+};
+
+/**
+ * Panel for displaying own first and second life related info.
+ */
+class LLPanelMyProfile
+ : public LLPanelAvatarProfile
+{
+public:
+ LLPanelMyProfile();
+
+ /*virtual*/ BOOL postBuild();
+
+protected:
+
+ /*virtual*/ void onOpen(const LLSD& key);
+
+ /*virtual*/ void processProfileProperties(const LLAvatarData* avatar_data);
+
+ /*virtual*/ void resetControls();
+
+protected:
+ void onStatusMessageChanged();
+};
+
+/**
+ * Panel for displaying Avatar's notes and modifying friend's rights.
+ */
+class LLPanelAvatarNotes
+ : public LLPanelProfileTab
+ , public LLFriendObserver
+ , public LLVoiceClientStatusObserver
+{
+public:
+ LLPanelAvatarNotes();
+ /*virtual*/ ~LLPanelAvatarNotes();
+
+ virtual void setAvatarId(const LLUUID& id);
+
+ /**
+ * LLFriendObserver trigger
+ */
+ virtual void changed(U32 mask);
+
+ // Implements LLVoiceClientStatusObserver::onChange() to enable the call
+ // button when voice is available
+ /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal);
+
+ /*virtual*/ void onOpen(const LLSD& key);
+
+ /*virtual*/ BOOL postBuild();
+
+ /*virtual*/ void processProperties(void* data, EAvatarProcessorType type);
+
+ /*virtual*/ void updateData();
+
+protected:
+
+ /*virtual*/ void resetControls();
+
+ /*virtual*/ void resetData();
+
+ /**
+ * Fills rights data for friends.
+ */
+ void fillRightsData();
+
+ void rightsConfirmationCallback(const LLSD& notification,
+ const LLSD& response, S32 rights);
+ void confirmModifyRights(bool grant, S32 rights);
+ void onCommitRights();
+ void onCommitNotes();
+
+ void onAddFriendButtonClick();
+ void onIMButtonClick();
+ void onCallButtonClick();
+ void onTeleportButtonClick();
+ void onShareButtonClick();
+ void enableCheckboxes(bool enable);
+};
+
+#endif // LL_LLPANELAVATAR_H
diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp
index 54198d6aa4..b07a46a222 100644
--- a/indra/newview/llpanelpeople.cpp
+++ b/indra/newview/llpanelpeople.cpp
@@ -54,6 +54,7 @@
#include "llgroupactions.h"
#include "llgrouplist.h"
#include "llinventoryobserver.h"
+#include "llnetmap.h"
#include "llpanelpeoplemenus.h"
#include "llsidetray.h"
#include "llsidetraypanelcontainer.h"
@@ -494,7 +495,8 @@ LLPanelPeople::LLPanelPeople()
mNearbyGearButton(NULL),
mFriendsGearButton(NULL),
mGroupsGearButton(NULL),
- mRecentGearButton(NULL)
+ mRecentGearButton(NULL),
+ mMiniMap(NULL)
{
mFriendListUpdater = new LLFriendListUpdater(boost::bind(&LLPanelPeople::updateFriendList, this));
mNearbyListUpdater = new LLNearbyListUpdater(boost::bind(&LLPanelPeople::updateNearbyList, this));
@@ -567,6 +569,9 @@ BOOL LLPanelPeople::postBuild()
mNearbyList->setNoItemsMsg(getString("no_one_near"));
mNearbyList->setNoFilteredItemsMsg(getString("no_one_filtered_near"));
mNearbyList->setShowIcons("NearbyListShowIcons");
+ mMiniMap = (LLNetMap*)getChildView("Net Map",true);
+ mMiniMap->setToolTipMsg(gSavedSettings.getBOOL("DoubleClickTeleport") ?
+ getString("AltMiniMapToolTipMsg") : getString("MiniMapToolTipMsg"));
mRecentList = getChild<LLPanel>(RECENT_TAB_NAME)->getChild<LLAvatarList>("avatar_list");
mRecentList->setNoItemsCommentText(getString("no_recent_people"));
@@ -1088,6 +1093,12 @@ void LLPanelPeople::onAvatarListDoubleClicked(LLUICtrl* ctrl)
void LLPanelPeople::onAvatarListCommitted(LLAvatarList* list)
{
+ if (getActiveTabName() == NEARBY_TAB_NAME)
+ {
+ uuid_vec_t selected_uuids;
+ getCurrentItemIDs(selected_uuids);
+ mMiniMap->setSelected(selected_uuids);
+ } else
// Make sure only one of the friends lists (online/all) has selection.
if (getActiveTabName() == FRIENDS_TAB_NAME)
{
diff --git a/indra/newview/llpanelpeople.h b/indra/newview/llpanelpeople.h
index b496bb3779..46c58cd139 100644
--- a/indra/newview/llpanelpeople.h
+++ b/indra/newview/llpanelpeople.h
@@ -142,6 +142,7 @@ private:
LLAvatarList* mNearbyList;
LLAvatarList* mRecentList;
LLGroupList* mGroupList;
+ LLNetMap* mMiniMap;
LLHandle<LLView> mGroupPlusMenuHandle;
LLHandle<LLView> mNearbyViewSortMenuHandle;
diff --git a/indra/newview/llsidetray.cpp b/indra/newview/llsidetray.cpp
index 19d1bdee86..eb537c7d7b 100644
--- a/indra/newview/llsidetray.cpp
+++ b/indra/newview/llsidetray.cpp
@@ -141,6 +141,8 @@ public:
void toggleTabDocked();
+ BOOL handleScrollWheel(S32 x, S32 y, S32 clicks);
+
LLPanel *getPanel();
private:
std::string mTabTitle;
@@ -269,6 +271,15 @@ void LLSideTrayTab::toggleTabDocked()
LLFloaterReg::toggleInstance("side_bar_tab", tab_name);
}
+BOOL LLSideTrayTab::handleScrollWheel(S32 x, S32 y, S32 clicks)
+{
+ // Let children handle the event
+ LLUICtrl::handleScrollWheel(x, y, clicks);
+
+ // and then eat it to prevent in-world scrolling (STORM-351).
+ return TRUE;
+}
+
void LLSideTrayTab::dock(LLFloater* floater_tab)
{
LLSideTray* side_tray = getSideTray();
diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp
index 5cdf1706e6..18c3a3b87d 100644
--- a/indra/newview/lltexturefetch.cpp
+++ b/indra/newview/lltexturefetch.cpp
@@ -1,3011 +1,3011 @@
-/**
- * @file lltexturefetch.cpp
- * @brief Object which fetches textures from the cache and/or network
- *
- * $LicenseInfo:firstyear=2000&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "llviewerprecompiledheaders.h"
-
-#include <iostream>
-#include <map>
-
-#include "llstl.h"
-
-#include "lltexturefetch.h"
-
-#include "llcurl.h"
-#include "lldir.h"
-#include "llhttpclient.h"
-#include "llhttpstatuscodes.h"
-#include "llimage.h"
-#include "llimagej2c.h"
-#include "llimageworker.h"
-#include "llworkerthread.h"
-#include "message.h"
-
-#include "llagent.h"
-#include "lltexturecache.h"
-#include "llviewercontrol.h"
-#include "llviewertexturelist.h"
-#include "llviewertexture.h"
-#include "llviewerregion.h"
-#include "llviewerstats.h"
-#include "llviewerassetstats.h"
-#include "llworld.h"
-
-//////////////////////////////////////////////////////////////////////////////
-class LLTextureFetchWorker : public LLWorkerClass
-{
- friend class LLTextureFetch;
- friend class HTTPGetResponder;
-
-private:
- class CacheReadResponder : public LLTextureCache::ReadResponder
- {
- public:
- CacheReadResponder(LLTextureFetch* fetcher, const LLUUID& id, LLImageFormatted* image)
- : mFetcher(fetcher), mID(id)
- {
- setImage(image);
- }
- virtual void completed(bool success)
- {
- LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
- if (worker)
- {
- worker->callbackCacheRead(success, mFormattedImage, mImageSize, mImageLocal);
- }
- }
- private:
- LLTextureFetch* mFetcher;
- LLUUID mID;
- };
-
- class CacheWriteResponder : public LLTextureCache::WriteResponder
- {
- public:
- CacheWriteResponder(LLTextureFetch* fetcher, const LLUUID& id)
- : mFetcher(fetcher), mID(id)
- {
- }
- virtual void completed(bool success)
- {
- LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
- if (worker)
- {
- worker->callbackCacheWrite(success);
- }
- }
- private:
- LLTextureFetch* mFetcher;
- LLUUID mID;
- };
-
- class DecodeResponder : public LLImageDecodeThread::Responder
- {
- public:
- DecodeResponder(LLTextureFetch* fetcher, const LLUUID& id, LLTextureFetchWorker* worker)
- : mFetcher(fetcher), mID(id), mWorker(worker)
- {
- }
- virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux)
- {
- LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
- if (worker)
- {
- worker->callbackDecoded(success, raw, aux);
- }
- }
- private:
- LLTextureFetch* mFetcher;
- LLUUID mID;
- LLTextureFetchWorker* mWorker; // debug only (may get deleted from under us, use mFetcher/mID)
- };
-
- struct Compare
- {
- // lhs < rhs
- bool operator()(const LLTextureFetchWorker* lhs, const LLTextureFetchWorker* rhs) const
- {
- // greater priority is "less"
- const F32 lpriority = lhs->mImagePriority;
- const F32 rpriority = rhs->mImagePriority;
- if (lpriority > rpriority) // higher priority
- return true;
- else if (lpriority < rpriority)
- return false;
- else
- return lhs < rhs;
- }
- };
-
-public:
- /*virtual*/ bool doWork(S32 param); // Called from LLWorkerThread::processRequest()
- /*virtual*/ void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD)
- /*virtual*/ bool deleteOK(); // called from update() (WORK THREAD)
-
- ~LLTextureFetchWorker();
- // void relese() { --mActiveCount; }
-
- S32 callbackHttpGet(const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer,
- bool partial, bool success);
- void callbackCacheRead(bool success, LLImageFormatted* image,
- S32 imagesize, BOOL islocal);
- void callbackCacheWrite(bool success);
- void callbackDecoded(bool success, LLImageRaw* raw, LLImageRaw* aux);
-
- void setGetStatus(U32 status, const std::string& reason)
- {
- LLMutexLock lock(&mWorkMutex);
-
- mGetStatus = status;
- mGetReason = reason;
- }
-
- void setCanUseHTTP(bool can_use_http) { mCanUseHTTP = can_use_http; }
- bool getCanUseHTTP() const { return mCanUseHTTP; }
-
- LLTextureFetch & getFetcher() { return *mFetcher; }
-
-protected:
- LLTextureFetchWorker(LLTextureFetch* fetcher, const std::string& url, const LLUUID& id, const LLHost& host,
- F32 priority, S32 discard, S32 size);
-
-private:
- /*virtual*/ void startWork(S32 param); // called from addWork() (MAIN THREAD)
- /*virtual*/ void endWork(S32 param, bool aborted); // called from doWork() (MAIN THREAD)
-
- void resetFormattedData();
-
- void setImagePriority(F32 priority);
- void setDesiredDiscard(S32 discard, S32 size);
- bool insertPacket(S32 index, U8* data, S32 size);
- void clearPackets();
- void setupPacketData();
- U32 calcWorkPriority();
- void removeFromCache();
- bool processSimulatorPackets();
- bool writeToCacheComplete();
-
- void lockWorkMutex() { mWorkMutex.lock(); }
- void unlockWorkMutex() { mWorkMutex.unlock(); }
-
-private:
- enum e_state // mState
- {
- // NOTE: Affects LLTextureBar::draw in lltextureview.cpp (debug hack)
- INVALID = 0,
- INIT,
- LOAD_FROM_TEXTURE_CACHE,
- CACHE_POST,
- LOAD_FROM_NETWORK,
- LOAD_FROM_SIMULATOR,
- SEND_HTTP_REQ,
- WAIT_HTTP_REQ,
- DECODE_IMAGE,
- DECODE_IMAGE_UPDATE,
- WRITE_TO_CACHE,
- WAIT_ON_WRITE,
- DONE
- };
- enum e_request_state // mSentRequest
- {
- UNSENT = 0,
- QUEUED = 1,
- SENT_SIM = 2
- };
- enum e_write_to_cache_state //mWriteToCacheState
- {
- NOT_WRITE = 0,
- CAN_WRITE = 1,
- SHOULD_WRITE = 2
- };
- static const char* sStateDescs[];
- e_state mState;
- e_write_to_cache_state mWriteToCacheState;
- LLTextureFetch* mFetcher;
- LLPointer<LLImageFormatted> mFormattedImage;
- LLPointer<LLImageRaw> mRawImage;
- LLPointer<LLImageRaw> mAuxImage;
- LLUUID mID;
- LLHost mHost;
- std::string mUrl;
- U8 mType;
- F32 mImagePriority;
- U32 mWorkPriority;
- F32 mRequestedPriority;
- S32 mDesiredDiscard;
- S32 mSimRequestedDiscard;
- S32 mRequestedDiscard;
- S32 mLoadedDiscard;
- S32 mDecodedDiscard;
- LLFrameTimer mRequestedTimer;
- LLFrameTimer mFetchTimer;
- LLTextureCache::handle_t mCacheReadHandle;
- LLTextureCache::handle_t mCacheWriteHandle;
- U8* mBuffer;
- S32 mBufferSize;
- S32 mRequestedSize;
- S32 mDesiredSize;
- S32 mFileSize;
- S32 mCachedSize;
- e_request_state mSentRequest;
- handle_t mDecodeHandle;
- BOOL mLoaded;
- BOOL mDecoded;
- BOOL mWritten;
- BOOL mNeedsAux;
- BOOL mHaveAllData;
- BOOL mInLocalCache;
- bool mCanUseHTTP ;
- bool mCanUseNET ; //can get from asset server.
- S32 mHTTPFailCount;
- S32 mRetryAttempt;
- S32 mActiveCount;
- U32 mGetStatus;
- std::string mGetReason;
-
- // Work Data
- LLMutex mWorkMutex;
- struct PacketData
- {
- PacketData(U8* data, S32 size) { mData = data; mSize = size; }
- ~PacketData() { clearData(); }
- void clearData() { delete[] mData; mData = NULL; }
- U8* mData;
- U32 mSize;
- };
- std::vector<PacketData*> mPackets;
- S32 mFirstPacket;
- S32 mLastPacket;
- U16 mTotalPackets;
- U8 mImageCodec;
-
- LLViewerAssetStats::duration_t mMetricsStartTime;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-class HTTPGetResponder : public LLCurl::Responder
-{
- LOG_CLASS(HTTPGetResponder);
-public:
- HTTPGetResponder(LLTextureFetch* fetcher, const LLUUID& id, U64 startTime, S32 requestedSize, U32 offset, bool redir)
- : mFetcher(fetcher), mID(id), mStartTime(startTime), mRequestedSize(requestedSize), mOffset(offset), mFollowRedir(redir)
- {
- }
- ~HTTPGetResponder()
- {
- }
-
- virtual void completedRaw(U32 status, const std::string& reason,
- const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer)
- {
- static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog");
- static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator");
- static LLCachedControl<bool> log_texture_traffic(gSavedSettings,"LogTextureNetworkTraffic") ;
-
- if (log_to_viewer_log || log_to_sim)
- {
- mFetcher->mTextureInfo.setRequestStartTime(mID, mStartTime);
- U64 timeNow = LLTimer::getTotalTime();
- mFetcher->mTextureInfo.setRequestType(mID, LLTextureInfoDetails::REQUEST_TYPE_HTTP);
- mFetcher->mTextureInfo.setRequestSize(mID, mRequestedSize);
- mFetcher->mTextureInfo.setRequestOffset(mID, mOffset);
- mFetcher->mTextureInfo.setRequestCompleteTimeAndLog(mID, timeNow);
- }
-
- lldebugs << "HTTP COMPLETE: " << mID << llendl;
- LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
- if (worker)
- {
- bool success = false;
- bool partial = false;
- if (HTTP_OK <= status && status < HTTP_MULTIPLE_CHOICES)
- {
- success = true;
- if (HTTP_PARTIAL_CONTENT == status) // partial information
- {
- partial = true;
- }
- }
-
- if (!success)
- {
- worker->setGetStatus(status, reason);
-// llwarns << "CURL GET FAILED, status:" << status << " reason:" << reason << llendl;
- }
-
- S32 data_size = worker->callbackHttpGet(channels, buffer, partial, success);
-
- if(log_texture_traffic && data_size > 0)
- {
- LLViewerTexture* tex = LLViewerTextureManager::findTexture(mID) ;
- if(tex)
- {
- gTotalTextureBytesPerBoostLevel[tex->getBoostLevel()] += data_size ;
- }
- }
-
- mFetcher->removeFromHTTPQueue(mID, data_size);
-
- if (worker->mMetricsStartTime)
- {
- LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE,
- true,
- LLImageBase::TYPE_AVATAR_BAKE == worker->mType,
- LLViewerAssetStatsFF::get_timestamp() - worker->mMetricsStartTime);
- worker->mMetricsStartTime = 0;
- }
- LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE,
- true,
- LLImageBase::TYPE_AVATAR_BAKE == worker->mType);
- }
- else
- {
- mFetcher->removeFromHTTPQueue(mID);
- llwarns << "Worker not found: " << mID << llendl;
- }
- }
-
- virtual bool followRedir()
- {
- return mFollowRedir;
- }
-
-private:
- LLTextureFetch* mFetcher;
- LLUUID mID;
- U64 mStartTime;
- S32 mRequestedSize;
- U32 mOffset;
- bool mFollowRedir;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-// Cross-thread messaging for asset metrics.
-
-/**
- * @brief Base class for cross-thread requests made of the fetcher
- *
- * I believe the intent of the LLQueuedThread class was to
- * have these operations derived from LLQueuedThread::QueuedRequest
- * but the texture fetcher has elected to manage the queue
- * in its own manner. So these are free-standing objects which are
- * managed in simple FIFO order on the mCommands queue of the
- * LLTextureFetch object.
- *
- * What each represents is a simple command sent from an
- * outside thread into the TextureFetch thread to be processed
- * in order and in a timely fashion (though not an absolute
- * higher priority than other operations of the thread).
- * Each operation derives a new class from the base customizing
- * members, constructors and the doWork() method to effect
- * the command.
- *
- * The flow is one-directional. There are two global instances
- * of the LLViewerAssetStats collector, one for the main program's
- * thread pointed to by gViewerAssetStatsMain and one for the
- * TextureFetch thread pointed to by gViewerAssetStatsThread1.
- * Common operations has each thread recording metrics events
- * into the respective collector unconcerned with locking and
- * the state of any other thread. But when the agent moves into
- * a different region or the metrics timer expires and a report
- * needs to be sent back to the grid, messaging across threads
- * is required to distribute data and perform global actions.
- * In pseudo-UML, it looks like:
- *
- * Main Thread1
- * . .
- * . .
- * +-----+ .
- * | AM | .
- * +--+--+ .
- * +-------+ | .
- * | Main | +--+--+ .
- * | | | SRE |---. .
- * | Stats | +-----+ \ .
- * | | | \ (uuid) +-----+
- * | Coll. | +--+--+ `-------->| SR |
- * +-------+ | MSC | +--+--+
- * | ^ +-----+ |
- * | | (uuid) / . +-----+ (uuid)
- * | `--------' . | MSC |---------.
- * | . +-----+ |
- * | +-----+ . v
- * | | TE | . +-------+
- * | +--+--+ . | Thd1 |
- * | | . | |
- * | +-----+ . | Stats |
- * `--------->| RSC | . | |
- * +--+--+ . | Coll. |
- * | . +-------+
- * +--+--+ . |
- * | SME |---. . |
- * +-----+ \ . |
- * . \ (clone) +-----+ |
- * . `-------->| SM | |
- * . +--+--+ |
- * . | |
- * . +-----+ |
- * . | RSC |<--------'
- * . +-----+
- * . |
- * . +-----+
- * . | CP |--> HTTP POST
- * . +-----+
- * . .
- * . .
- *
- *
- * Key:
- *
- * SRE - Set Region Enqueued. Enqueue a 'Set Region' command in
- * the other thread providing the new UUID of the region.
- * TFReqSetRegion carries the data.
- * SR - Set Region. New region UUID is sent to the thread-local
- * collector.
- * SME - Send Metrics Enqueued. Enqueue a 'Send Metrics' command
- * including an ownership transfer of a cloned LLViewerAssetStats.
- * TFReqSendMetrics carries the data.
- * SM - Send Metrics. Global metrics reporting operation. Takes
- * the cloned stats from the command, merges it with the
- * thread's local stats, converts to LLSD and sends it on
- * to the grid.
- * AM - Agent Moved. Agent has completed some sort of move to a
- * new region.
- * TE - Timer Expired. Metrics timer has expired (on the order
- * of 10 minutes).
- * CP - CURL Post
- * MSC - Modify Stats Collector. State change in the thread-local
- * collector. Typically a region change which affects the
- * global pointers used to find the 'current stats'.
- * RSC - Read Stats Collector. Extract collector data cloning it
- * (i.e. deep copy) when necessary.
- *
- */
-class LLTextureFetch::TFRequest // : public LLQueuedThread::QueuedRequest
-{
-public:
- // Default ctors and assignment operator are correct.
-
- virtual ~TFRequest()
- {}
-
- // Patterned after QueuedRequest's method but expected behavior
- // is different. Always expected to complete on the first call
- // and work dispatcher will assume the same and delete the
- // request after invocation.
- virtual bool doWork(LLTextureFetch * fetcher) = 0;
-};
-
-namespace
-{
-
-/**
- * @brief Implements a 'Set Region' cross-thread command.
- *
- * When an agent moves to a new region, subsequent metrics need
- * to be binned into a new or existing stats collection in 1:1
- * relationship with the region. We communicate this region
- * change across the threads involved in the communication with
- * this message.
- *
- * Corresponds to LLTextureFetch::commandSetRegion()
- */
-class TFReqSetRegion : public LLTextureFetch::TFRequest
-{
-public:
- TFReqSetRegion(U64 region_handle)
- : LLTextureFetch::TFRequest(),
- mRegionHandle(region_handle)
- {}
- TFReqSetRegion & operator=(const TFReqSetRegion &); // Not defined
-
- virtual ~TFReqSetRegion()
- {}
-
- virtual bool doWork(LLTextureFetch * fetcher);
-
-public:
- const U64 mRegionHandle;
-};
-
-
-/**
- * @brief Implements a 'Send Metrics' cross-thread command.
- *
- * This is the big operation. The main thread gathers metrics
- * for a period of minutes into LLViewerAssetStats and other
- * objects then makes a snapshot of the data by cloning the
- * collector. This command transfers the clone, along with a few
- * additional arguments (UUIDs), handing ownership to the
- * TextureFetch thread. It then merges its own data into the
- * cloned copy, converts to LLSD and kicks off an HTTP POST of
- * the resulting data to the currently active metrics collector.
- *
- * Corresponds to LLTextureFetch::commandSendMetrics()
- */
-class TFReqSendMetrics : public LLTextureFetch::TFRequest
-{
-public:
- /**
- * Construct the 'Send Metrics' command to have the TextureFetch
- * thread add and log metrics data.
- *
- * @param caps_url URL of a "ViewerMetrics" Caps target
- * to receive the data. Does not have to
- * be associated with a particular region.
- *
- * @param session_id UUID of the agent's session.
- *
- * @param agent_id UUID of the agent. (Being pure here...)
- *
- * @param main_stats Pointer to a clone of the main thread's
- * LLViewerAssetStats data. Thread1 takes
- * ownership of the copy and disposes of it
- * when done.
- */
- TFReqSendMetrics(const std::string & caps_url,
- const LLUUID & session_id,
- const LLUUID & agent_id,
- LLViewerAssetStats * main_stats)
- : LLTextureFetch::TFRequest(),
- mCapsURL(caps_url),
- mSessionID(session_id),
- mAgentID(agent_id),
- mMainStats(main_stats)
- {}
- TFReqSendMetrics & operator=(const TFReqSendMetrics &); // Not defined
-
- virtual ~TFReqSendMetrics();
-
- virtual bool doWork(LLTextureFetch * fetcher);
-
-public:
- const std::string mCapsURL;
- const LLUUID mSessionID;
- const LLUUID mAgentID;
- LLViewerAssetStats * mMainStats;
-};
-
-/*
- * Examines the merged viewer metrics report and if found to be too long,
- * will attempt to truncate it in some reasonable fashion.
- *
- * @param max_regions Limit of regions allowed in report.
- *
- * @param metrics Full, merged viewer metrics report.
- *
- * @returns If data was truncated, returns true.
- */
-bool truncate_viewer_metrics(int max_regions, LLSD & metrics);
-
-} // end of anonymous namespace
-
-
-//////////////////////////////////////////////////////////////////////////////
-
-//static
-const char* LLTextureFetchWorker::sStateDescs[] = {
- "INVALID",
- "INIT",
- "LOAD_FROM_TEXTURE_CACHE",
- "CACHE_POST",
- "LOAD_FROM_NETWORK",
- "LOAD_FROM_SIMULATOR",
- "SEND_HTTP_REQ",
- "WAIT_HTTP_REQ",
- "DECODE_IMAGE",
- "DECODE_IMAGE_UPDATE",
- "WRITE_TO_CACHE",
- "WAIT_ON_WRITE",
- "DONE",
-};
-
-// static
-volatile bool LLTextureFetch::svMetricsDataBreak(true); // Start with a data break
-
-// called from MAIN THREAD
-
-LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher,
- const std::string& url, // Optional URL
- const LLUUID& id, // Image UUID
- const LLHost& host, // Simulator host
- F32 priority, // Priority
- S32 discard, // Desired discard
- S32 size) // Desired size
- : LLWorkerClass(fetcher, "TextureFetch"),
- mState(INIT),
- mWriteToCacheState(NOT_WRITE),
- mFetcher(fetcher),
- mID(id),
- mHost(host),
- mUrl(url),
- mImagePriority(priority),
- mWorkPriority(0),
- mRequestedPriority(0.f),
- mDesiredDiscard(-1),
- mSimRequestedDiscard(-1),
- mRequestedDiscard(-1),
- mLoadedDiscard(-1),
- mDecodedDiscard(-1),
- mCacheReadHandle(LLTextureCache::nullHandle()),
- mCacheWriteHandle(LLTextureCache::nullHandle()),
- mBuffer(NULL),
- mBufferSize(0),
- mRequestedSize(0),
- mDesiredSize(TEXTURE_CACHE_ENTRY_SIZE),
- mFileSize(0),
- mCachedSize(0),
- mLoaded(FALSE),
- mSentRequest(UNSENT),
- mDecodeHandle(0),
- mDecoded(FALSE),
- mWritten(FALSE),
- mNeedsAux(FALSE),
- mHaveAllData(FALSE),
- mInLocalCache(FALSE),
- mCanUseHTTP(true),
- mHTTPFailCount(0),
- mRetryAttempt(0),
- mActiveCount(0),
- mGetStatus(0),
- mWorkMutex(NULL),
- mFirstPacket(0),
- mLastPacket(-1),
- mTotalPackets(0),
- mImageCodec(IMG_CODEC_INVALID),
- mMetricsStartTime(0)
-{
- mCanUseNET = mUrl.empty() ;
-
- calcWorkPriority();
- mType = host.isOk() ? LLImageBase::TYPE_AVATAR_BAKE : LLImageBase::TYPE_NORMAL;
-// llinfos << "Create: " << mID << " mHost:" << host << " Discard=" << discard << llendl;
- if (!mFetcher->mDebugPause)
- {
- U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
- addWork(0, work_priority );
- }
- setDesiredDiscard(discard, size);
-}
-
-LLTextureFetchWorker::~LLTextureFetchWorker()
-{
-// llinfos << "Destroy: " << mID
-// << " Decoded=" << mDecodedDiscard
-// << " Requested=" << mRequestedDiscard
-// << " Desired=" << mDesiredDiscard << llendl;
- llassert_always(!haveWork());
- lockWorkMutex();
- if (mCacheReadHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache)
- {
- mFetcher->mTextureCache->readComplete(mCacheReadHandle, true);
- }
- if (mCacheWriteHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache)
- {
- mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true);
- }
- mFormattedImage = NULL;
- clearPackets();
- unlockWorkMutex();
- mFetcher->removeFromHTTPQueue(mID);
-}
-
-void LLTextureFetchWorker::clearPackets()
-{
- for_each(mPackets.begin(), mPackets.end(), DeletePointer());
- mPackets.clear();
- mTotalPackets = 0;
- mLastPacket = -1;
- mFirstPacket = 0;
-}
-
-void LLTextureFetchWorker::setupPacketData()
-{
- S32 data_size = 0;
- if (mFormattedImage.notNull())
- {
- data_size = mFormattedImage->getDataSize();
- }
- if (data_size > 0)
- {
- // Only used for simulator requests
- mFirstPacket = (data_size - FIRST_PACKET_SIZE) / MAX_IMG_PACKET_SIZE + 1;
- if (FIRST_PACKET_SIZE + (mFirstPacket-1) * MAX_IMG_PACKET_SIZE != data_size)
- {
- llwarns << "Bad CACHED TEXTURE size: " << data_size << " removing." << llendl;
- removeFromCache();
- resetFormattedData();
- clearPackets();
- }
- else if (mFileSize > 0)
- {
- mLastPacket = mFirstPacket-1;
- mTotalPackets = (mFileSize - FIRST_PACKET_SIZE + MAX_IMG_PACKET_SIZE-1) / MAX_IMG_PACKET_SIZE + 1;
- }
- else
- {
- // This file was cached using HTTP so we have to refetch the first packet
- resetFormattedData();
- clearPackets();
- }
- }
-}
-
-U32 LLTextureFetchWorker::calcWorkPriority()
-{
- //llassert_always(mImagePriority >= 0 && mImagePriority <= LLViewerFetchedTexture::maxDecodePriority());
- static const F32 PRIORITY_SCALE = (F32)LLWorkerThread::PRIORITY_LOWBITS / LLViewerFetchedTexture::maxDecodePriority();
-
- mWorkPriority = llmin((U32)LLWorkerThread::PRIORITY_LOWBITS, (U32)(mImagePriority * PRIORITY_SCALE));
- return mWorkPriority;
-}
-
-// mWorkMutex is locked
-void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size)
-{
- bool prioritize = false;
- if (mDesiredDiscard != discard)
- {
- if (!haveWork())
- {
- calcWorkPriority();
- if (!mFetcher->mDebugPause)
- {
- U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
- addWork(0, work_priority);
- }
- }
- else if (mDesiredDiscard < discard)
- {
- prioritize = true;
- }
- mDesiredDiscard = discard;
- mDesiredSize = size;
- }
- else if (size > mDesiredSize)
- {
- mDesiredSize = size;
- prioritize = true;
- }
- mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE);
- if ((prioritize && mState == INIT) || mState == DONE)
- {
- mState = INIT;
- U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
- setPriority(work_priority);
- }
-}
-
-void LLTextureFetchWorker::setImagePriority(F32 priority)
-{
-// llassert_always(priority >= 0 && priority <= LLViewerTexture::maxDecodePriority());
- F32 delta = fabs(priority - mImagePriority);
- if (delta > (mImagePriority * .05f) || mState == DONE)
- {
- mImagePriority = priority;
- calcWorkPriority();
- U32 work_priority = mWorkPriority | (getPriority() & LLWorkerThread::PRIORITY_HIGHBITS);
- setPriority(work_priority);
- }
-}
-
-void LLTextureFetchWorker::resetFormattedData()
-{
- delete[] mBuffer;
- mBuffer = NULL;
- mBufferSize = 0;
- if (mFormattedImage.notNull())
- {
- mFormattedImage->deleteData();
- }
- mHaveAllData = FALSE;
-}
-
-// Called from MAIN thread
-void LLTextureFetchWorker::startWork(S32 param)
-{
- llassert(mFormattedImage.isNull());
-}
-
-#include "llviewertexturelist.h" // debug
-
-// Called from LLWorkerThread::processRequest()
-bool LLTextureFetchWorker::doWork(S32 param)
-{
- LLMutexLock lock(&mWorkMutex);
-
- if ((mFetcher->isQuitting() || getFlags(LLWorkerClass::WCF_DELETE_REQUESTED)))
- {
- if (mState < DECODE_IMAGE)
- {
- return true; // abort
- }
- }
-
- if(mImagePriority < F_ALMOST_ZERO)
- {
- if (mState == INIT || mState == LOAD_FROM_NETWORK || mState == LOAD_FROM_SIMULATOR)
- {
- return true; // abort
- }
- }
- if(mState > CACHE_POST && !mCanUseNET && !mCanUseHTTP)
- {
- //nowhere to get data, abort.
- return true ;
- }
-
- if (mFetcher->mDebugPause)
- {
- return false; // debug: don't do any work
- }
- if (mID == mFetcher->mDebugID)
- {
- mFetcher->mDebugCount++; // for setting breakpoints
- }
-
- if (mState != DONE)
- {
- mFetchTimer.reset();
- }
-
- if (mState == INIT)
- {
- mRawImage = NULL ;
- mRequestedDiscard = -1;
- mLoadedDiscard = -1;
- mDecodedDiscard = -1;
- mRequestedSize = 0;
- mFileSize = 0;
- mCachedSize = 0;
- mLoaded = FALSE;
- mSentRequest = UNSENT;
- mDecoded = FALSE;
- mWritten = FALSE;
- delete[] mBuffer;
- mBuffer = NULL;
- mBufferSize = 0;
- mHaveAllData = FALSE;
- clearPackets(); // TODO: Shouldn't be necessary
- mCacheReadHandle = LLTextureCache::nullHandle();
- mCacheWriteHandle = LLTextureCache::nullHandle();
- mState = LOAD_FROM_TEXTURE_CACHE;
- mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE); // min desired size is TEXTURE_CACHE_ENTRY_SIZE
- LL_DEBUGS("Texture") << mID << ": Priority: " << llformat("%8.0f",mImagePriority)
- << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL;
- // fall through
- }
-
- if (mState == LOAD_FROM_TEXTURE_CACHE)
- {
- if (mCacheReadHandle == LLTextureCache::nullHandle())
- {
- U32 cache_priority = mWorkPriority;
- S32 offset = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
- S32 size = mDesiredSize - offset;
- if (size <= 0)
- {
- mState = CACHE_POST;
- return false;
- }
- mFileSize = 0;
- mLoaded = FALSE;
-
- if (mUrl.compare(0, 7, "file://") == 0)
- {
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
-
- // read file from local disk
- std::string filename = mUrl.substr(7, std::string::npos);
- CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage);
- mCacheReadHandle = mFetcher->mTextureCache->readFromCache(filename, mID, cache_priority,
- offset, size, responder);
- }
- else if (mUrl.empty())
- {
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
-
- CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage);
- mCacheReadHandle = mFetcher->mTextureCache->readFromCache(mID, cache_priority,
- offset, size, responder);
- }
- else if(mCanUseHTTP)
- {
- if (!(mUrl.compare(0, 7, "http://") == 0))
- {
- // *TODO:?remove this warning
- llwarns << "Unknown URL Type: " << mUrl << llendl;
- }
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- mState = SEND_HTTP_REQ;
- }
- else
- {
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- mState = LOAD_FROM_NETWORK;
- }
- }
-
- if (mLoaded)
- {
- // Make sure request is complete. *TODO: make this auto-complete
- if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, false))
- {
- mCacheReadHandle = LLTextureCache::nullHandle();
- mState = CACHE_POST;
- // fall through
- }
- else
- {
- return false;
- }
- }
- else
- {
- return false;
- }
- }
-
- if (mState == CACHE_POST)
- {
- mCachedSize = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
- // Successfully loaded
- if ((mCachedSize >= mDesiredSize) || mHaveAllData)
- {
- // we have enough data, decode it
- llassert_always(mFormattedImage->getDataSize() > 0);
- mLoadedDiscard = mDesiredDiscard;
- mState = DECODE_IMAGE;
- mWriteToCacheState = NOT_WRITE ;
- LL_DEBUGS("Texture") << mID << ": Cached. Bytes: " << mFormattedImage->getDataSize()
- << " Size: " << llformat("%dx%d",mFormattedImage->getWidth(),mFormattedImage->getHeight())
- << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL;
- // fall through
- }
- else
- {
- if (mUrl.compare(0, 7, "file://") == 0)
- {
- // failed to load local file, we're done.
- return true;
- }
- // need more data
- else
- {
- LL_DEBUGS("Texture") << mID << ": Not in Cache" << LL_ENDL;
- mState = LOAD_FROM_NETWORK;
- }
- // fall through
- }
- }
-
- if (mState == LOAD_FROM_NETWORK)
- {
- static LLCachedControl<bool> use_http(gSavedSettings,"ImagePipelineUseHTTP");
-
-// if (mHost != LLHost::invalid) get_url = false;
- if ( use_http && mCanUseHTTP && mUrl.empty())//get http url.
- {
- LLViewerRegion* region = NULL;
- if (mHost == LLHost::invalid)
- region = gAgent.getRegion();
- else
- region = LLWorld::getInstance()->getRegion(mHost);
-
- if (region)
- {
- std::string http_url = region->getHttpUrl() ;
- if (!http_url.empty())
- {
- mUrl = http_url + "/?texture_id=" + mID.asString().c_str();
- mWriteToCacheState = CAN_WRITE ; //because this texture has a fixed texture id.
- }
- else
- {
- mCanUseHTTP = false ;
- }
- }
- else
- {
- // This will happen if not logged in or if a region deoes not have HTTP Texture enabled
- //llwarns << "Region not found for host: " << mHost << llendl;
- mCanUseHTTP = false;
- }
- }
- if (mCanUseHTTP && !mUrl.empty())
- {
- mState = LLTextureFetchWorker::SEND_HTTP_REQ;
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- if(mWriteToCacheState != NOT_WRITE)
- {
- mWriteToCacheState = CAN_WRITE ;
- }
- // don't return, fall through to next state
- }
- else if (mSentRequest == UNSENT && mCanUseNET)
- {
- // Add this to the network queue and sit here.
- // LLTextureFetch::update() will send off a request which will change our state
- mWriteToCacheState = CAN_WRITE ;
- mRequestedSize = mDesiredSize;
- mRequestedDiscard = mDesiredDiscard;
- mSentRequest = QUEUED;
- mFetcher->addToNetworkQueue(this);
- if (! mMetricsStartTime)
- {
- mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
- }
- LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE,
- false,
- LLImageBase::TYPE_AVATAR_BAKE == mType);
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
-
- return false;
- }
- else
- {
- // Shouldn't need to do anything here
- //llassert_always(mFetcher->mNetworkQueue.find(mID) != mFetcher->mNetworkQueue.end());
- // Make certain this is in the network queue
- //mFetcher->addToNetworkQueue(this);
- //if (! mMetricsStartTime)
- //{
- // mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
- //}
- //LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, false,
- // LLImageBase::TYPE_AVATAR_BAKE == mType);
- //setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- return false;
- }
- }
-
- if (mState == LOAD_FROM_SIMULATOR)
- {
- if (mFormattedImage.isNull())
- {
- mFormattedImage = new LLImageJ2C;
- }
- if (processSimulatorPackets())
- {
- LL_DEBUGS("Texture") << mID << ": Loaded from Sim. Bytes: " << mFormattedImage->getDataSize() << LL_ENDL;
- mFetcher->removeFromNetworkQueue(this, false);
- if (mFormattedImage.isNull() || !mFormattedImage->getDataSize())
- {
- // processSimulatorPackets() failed
-// llwarns << "processSimulatorPackets() failed to load buffer" << llendl;
- return true; // failed
- }
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- mState = DECODE_IMAGE;
- mWriteToCacheState = SHOULD_WRITE;
-
- if (mMetricsStartTime)
- {
- LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE,
- false,
- LLImageBase::TYPE_AVATAR_BAKE == mType,
- LLViewerAssetStatsFF::get_timestamp() - mMetricsStartTime);
- mMetricsStartTime = 0;
- }
- LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE,
- false,
- LLImageBase::TYPE_AVATAR_BAKE == mType);
- }
- else
- {
- mFetcher->addToNetworkQueue(this); // failsafe
- if (! mMetricsStartTime)
- {
- mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
- }
- LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE,
- false,
- LLImageBase::TYPE_AVATAR_BAKE == mType);
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- }
- return false;
- }
-
- if (mState == SEND_HTTP_REQ)
- {
- if(mCanUseHTTP)
- {
- //NOTE:
- //control the number of the http requests issued for:
- //1, not openning too many file descriptors at the same time;
- //2, control the traffic of http so udp gets bandwidth.
- //
- static const S32 MAX_NUM_OF_HTTP_REQUESTS_IN_QUEUE = 8 ;
- if(mFetcher->getNumHTTPRequests() > MAX_NUM_OF_HTTP_REQUESTS_IN_QUEUE)
- {
- return false ; //wait.
- }
-
- mFetcher->removeFromNetworkQueue(this, false);
-
- S32 cur_size = 0;
- if (mFormattedImage.notNull())
- {
- cur_size = mFormattedImage->getDataSize(); // amount of data we already have
- if (mFormattedImage->getDiscardLevel() == 0)
- {
- if(cur_size > 0)
- {
- // We already have all the data, just decode it
- mLoadedDiscard = mFormattedImage->getDiscardLevel();
- mState = DECODE_IMAGE;
- return false;
- }
- else
- {
- return true ; //abort.
- }
- }
- }
- mRequestedSize = mDesiredSize;
- mRequestedDiscard = mDesiredDiscard;
- mRequestedSize -= cur_size;
- S32 offset = cur_size;
- mBufferSize = cur_size; // This will get modified by callbackHttpGet()
-
- bool res = false;
- if (!mUrl.empty())
- {
- mLoaded = FALSE;
- mGetStatus = 0;
- mGetReason.clear();
- LL_DEBUGS("Texture") << "HTTP GET: " << mID << " Offset: " << offset
- << " Bytes: " << mRequestedSize
- << " Bandwidth(kbps): " << mFetcher->getTextureBandwidth() << "/" << mFetcher->mMaxBandwidth
- << LL_ENDL;
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- mState = WAIT_HTTP_REQ;
-
- mFetcher->addToHTTPQueue(mID);
- if (! mMetricsStartTime)
- {
- mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
- }
- LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE,
- true,
- LLImageBase::TYPE_AVATAR_BAKE == mType);
-
- // Will call callbackHttpGet when curl request completes
- std::vector<std::string> headers;
- headers.push_back("Accept: image/x-j2c");
- res = mFetcher->mCurlGetRequest->getByteRange(mUrl, headers, offset, mRequestedSize,
- new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, offset, true));
- }
- if (!res)
- {
- llwarns << "HTTP GET request failed for " << mID << llendl;
- resetFormattedData();
- ++mHTTPFailCount;
- return true; // failed
- }
- // fall through
- }
- else //can not use http fetch.
- {
- return true ; //abort
- }
- }
-
- if (mState == WAIT_HTTP_REQ)
- {
- if (mLoaded)
- {
- S32 cur_size = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
- if (mRequestedSize < 0)
- {
- S32 max_attempts;
- if (mGetStatus == HTTP_NOT_FOUND)
- {
- mHTTPFailCount = max_attempts = 1; // Don't retry
- llwarns << "Texture missing from server (404): " << mUrl << llendl;
-
- //roll back to try UDP
- if(mCanUseNET)
- {
- mState = INIT ;
- mCanUseHTTP = false ;
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- return false ;
- }
- }
- else if (mGetStatus == HTTP_SERVICE_UNAVAILABLE)
- {
- // *TODO: Should probably introduce a timer here to delay future HTTP requsts
- // for a short time (~1s) to ease server load? Ideally the server would queue
- // requests instead of returning 503... we already limit the number pending.
- ++mHTTPFailCount;
- max_attempts = mHTTPFailCount+1; // Keep retrying
- LL_INFOS_ONCE("Texture") << "Texture server busy (503): " << mUrl << LL_ENDL;
- }
- else
- {
- const S32 HTTP_MAX_RETRY_COUNT = 3;
- max_attempts = HTTP_MAX_RETRY_COUNT + 1;
- ++mHTTPFailCount;
- llinfos << "HTTP GET failed for: " << mUrl
- << " Status: " << mGetStatus << " Reason: '" << mGetReason << "'"
- << " Attempt:" << mHTTPFailCount+1 << "/" << max_attempts << llendl;
- }
-
- if (mHTTPFailCount >= max_attempts)
- {
- if (cur_size > 0)
- {
- // Use available data
- mLoadedDiscard = mFormattedImage->getDiscardLevel();
- mState = DECODE_IMAGE;
- return false;
- }
- else
- {
- resetFormattedData();
- mState = DONE;
- return true; // failed
- }
- }
- else
- {
- mState = SEND_HTTP_REQ;
- return false; // retry
- }
- }
-
- llassert_always(mBufferSize == cur_size + mRequestedSize);
- if(!mBufferSize)//no data received.
- {
- delete[] mBuffer;
- mBuffer = NULL;
-
- //abort.
- mState = DONE;
- return true;
- }
-
- if (mFormattedImage.isNull())
- {
- // For now, create formatted image based on extension
- std::string extension = gDirUtilp->getExtension(mUrl);
- mFormattedImage = LLImageFormatted::createFromType(LLImageBase::getCodecFromExtension(extension));
- if (mFormattedImage.isNull())
- {
- mFormattedImage = new LLImageJ2C; // default
- }
- }
-
- if (mHaveAllData && mRequestedDiscard == 0) //the image file is fully loaded.
- {
- mFileSize = mBufferSize;
- }
- else //the file size is unknown.
- {
- mFileSize = mBufferSize + 1 ; //flag the file is not fully loaded.
- }
-
- U8* buffer = new U8[mBufferSize];
- if (cur_size > 0)
- {
- memcpy(buffer, mFormattedImage->getData(), cur_size);
- }
- memcpy(buffer + cur_size, mBuffer, mRequestedSize); // append
- // NOTE: setData releases current data and owns new data (buffer)
- mFormattedImage->setData(buffer, mBufferSize);
- // delete temp data
- delete[] mBuffer; // Note: not 'buffer' (assigned in setData())
- mBuffer = NULL;
- mBufferSize = 0;
- mLoadedDiscard = mRequestedDiscard;
- mState = DECODE_IMAGE;
- if(mWriteToCacheState != NOT_WRITE)
- {
- mWriteToCacheState = SHOULD_WRITE ;
- }
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- return false;
- }
- else
- {
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- return false;
- }
- }
-
- if (mState == DECODE_IMAGE)
- {
- static LLCachedControl<bool> textures_decode_disabled(gSavedSettings,"TextureDecodeDisabled");
- if(textures_decode_disabled)
- {
- // for debug use, don't decode
- mState = DONE;
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- return true;
- }
-
- if (mDesiredDiscard < 0)
- {
- // We aborted, don't decode
- mState = DONE;
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- return true;
- }
-
- if (mFormattedImage->getDataSize() <= 0)
- {
- //llerrs << "Decode entered with invalid mFormattedImage. ID = " << mID << llendl;
-
- //abort, don't decode
- mState = DONE;
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- return true;
- }
- if (mLoadedDiscard < 0)
- {
- //llerrs << "Decode entered with invalid mLoadedDiscard. ID = " << mID << llendl;
-
- //abort, don't decode
- mState = DONE;
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- return true;
- }
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
- mRawImage = NULL;
- mAuxImage = NULL;
- llassert_always(mFormattedImage.notNull());
- S32 discard = mHaveAllData ? 0 : mLoadedDiscard;
- U32 image_priority = LLWorkerThread::PRIORITY_NORMAL | mWorkPriority;
- mDecoded = FALSE;
- mState = DECODE_IMAGE_UPDATE;
- LL_DEBUGS("Texture") << mID << ": Decoding. Bytes: " << mFormattedImage->getDataSize() << " Discard: " << discard
- << " All Data: " << mHaveAllData << LL_ENDL;
- mDecodeHandle = mFetcher->mImageDecodeThread->decodeImage(mFormattedImage, image_priority, discard, mNeedsAux,
- new DecodeResponder(mFetcher, mID, this));
- // fall though
- }
-
- if (mState == DECODE_IMAGE_UPDATE)
- {
- if (mDecoded)
- {
- if (mDecodedDiscard < 0)
- {
- LL_DEBUGS("Texture") << mID << ": Failed to Decode." << LL_ENDL;
- if (mCachedSize > 0 && !mInLocalCache && mRetryAttempt == 0)
- {
- // Cache file should be deleted, try again
-// llwarns << mID << ": Decode of cached file failed (removed), retrying" << llendl;
- llassert_always(mDecodeHandle == 0);
- mFormattedImage = NULL;
- ++mRetryAttempt;
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- mState = INIT;
- return false;
- }
- else
- {
-// llwarns << "UNABLE TO LOAD TEXTURE: " << mID << " RETRIES: " << mRetryAttempt << llendl;
- mState = DONE; // failed
- }
- }
- else
- {
- llassert_always(mRawImage.notNull());
- LL_DEBUGS("Texture") << mID << ": Decoded. Discard: " << mDecodedDiscard
- << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL;
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- mState = WRITE_TO_CACHE;
- }
- // fall through
- }
- else
- {
- return false;
- }
- }
-
- if (mState == WRITE_TO_CACHE)
- {
- if (mWriteToCacheState != SHOULD_WRITE || mFormattedImage.isNull())
- {
- // If we're in a local cache or we didn't actually receive any new data,
- // or we failed to load anything, skip
- mState = DONE;
- return false;
- }
- S32 datasize = mFormattedImage->getDataSize();
- if(mFileSize < datasize)//This could happen when http fetching and sim fetching mixed.
- {
- if(mHaveAllData)
- {
- mFileSize = datasize ;
- }
- else
- {
- mFileSize = datasize + 1 ; //flag not fully loaded.
- }
- }
- llassert_always(datasize);
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
- U32 cache_priority = mWorkPriority;
- mWritten = FALSE;
- mState = WAIT_ON_WRITE;
- CacheWriteResponder* responder = new CacheWriteResponder(mFetcher, mID);
- mCacheWriteHandle = mFetcher->mTextureCache->writeToCache(mID, cache_priority,
- mFormattedImage->getData(), datasize,
- mFileSize, responder);
- // fall through
- }
-
- if (mState == WAIT_ON_WRITE)
- {
- if (writeToCacheComplete())
- {
- mState = DONE;
- // fall through
- }
- else
- {
- if (mDesiredDiscard < mDecodedDiscard)
- {
- // We're waiting for this write to complete before we can receive more data
- // (we can't touch mFormattedImage until the write completes)
- // Prioritize the write
- mFetcher->mTextureCache->prioritizeWrite(mCacheWriteHandle);
- }
- return false;
- }
- }
-
- if (mState == DONE)
- {
- if (mDecodedDiscard >= 0 && mDesiredDiscard < mDecodedDiscard)
- {
- // More data was requested, return to INIT
- mState = INIT;
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
- return false;
- }
- else
- {
- setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
- return true;
- }
- }
-
- return false;
-}
-
-// Called from MAIN thread
-void LLTextureFetchWorker::endWork(S32 param, bool aborted)
-{
- if (mDecodeHandle != 0)
- {
- mFetcher->mImageDecodeThread->abortRequest(mDecodeHandle, false);
- mDecodeHandle = 0;
- }
- mFormattedImage = NULL;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-// virtual
-void LLTextureFetchWorker::finishWork(S32 param, bool completed)
-{
- // The following are required in case the work was aborted
- if (mCacheReadHandle != LLTextureCache::nullHandle())
- {
- mFetcher->mTextureCache->readComplete(mCacheReadHandle, true);
- mCacheReadHandle = LLTextureCache::nullHandle();
- }
- if (mCacheWriteHandle != LLTextureCache::nullHandle())
- {
- mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true);
- mCacheWriteHandle = LLTextureCache::nullHandle();
- }
-}
-
-// virtual
-bool LLTextureFetchWorker::deleteOK()
-{
- bool delete_ok = true;
- // Allow any pending reads or writes to complete
- if (mCacheReadHandle != LLTextureCache::nullHandle())
- {
- if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, true))
- {
- mCacheReadHandle = LLTextureCache::nullHandle();
- }
- else
- {
- delete_ok = false;
- }
- }
- if (mCacheWriteHandle != LLTextureCache::nullHandle())
- {
- if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle))
- {
- mCacheWriteHandle = LLTextureCache::nullHandle();
- }
- else
- {
- delete_ok = false;
- }
- }
-
- if ((haveWork() &&
- // not ok to delete from these states
- ((mState >= WRITE_TO_CACHE && mState <= WAIT_ON_WRITE))))
- {
- delete_ok = false;
- }
-
- return delete_ok;
-}
-
-void LLTextureFetchWorker::removeFromCache()
-{
- if (!mInLocalCache)
- {
- mFetcher->mTextureCache->removeFromCache(mID);
- }
-}
-
-
-//////////////////////////////////////////////////////////////////////////////
-
-bool LLTextureFetchWorker::processSimulatorPackets()
-{
- if (mFormattedImage.isNull() || mRequestedSize < 0)
- {
- // not sure how we got here, but not a valid state, abort!
- llassert_always(mDecodeHandle == 0);
- mFormattedImage = NULL;
- return true;
- }
-
- if (mLastPacket >= mFirstPacket)
- {
- S32 buffer_size = mFormattedImage->getDataSize();
- for (S32 i = mFirstPacket; i<=mLastPacket; i++)
- {
- llassert_always(mPackets[i]);
- buffer_size += mPackets[i]->mSize;
- }
- bool have_all_data = mLastPacket >= mTotalPackets-1;
- if (mRequestedSize <= 0)
- {
- // We received a packed but haven't requested anything yet (edge case)
- // Return true (we're "done") since we didn't request anything
- return true;
- }
- if (buffer_size >= mRequestedSize || have_all_data)
- {
- /// We have enough (or all) data
- if (have_all_data)
- {
- mHaveAllData = TRUE;
- }
- S32 cur_size = mFormattedImage->getDataSize();
- if (buffer_size > cur_size)
- {
- /// We have new data
- U8* buffer = new U8[buffer_size];
- S32 offset = 0;
- if (cur_size > 0 && mFirstPacket > 0)
- {
- memcpy(buffer, mFormattedImage->getData(), cur_size);
- offset = cur_size;
- }
- for (S32 i=mFirstPacket; i<=mLastPacket; i++)
- {
- memcpy(buffer + offset, mPackets[i]->mData, mPackets[i]->mSize);
- offset += mPackets[i]->mSize;
- }
- // NOTE: setData releases current data
- mFormattedImage->setData(buffer, buffer_size);
- }
- mLoadedDiscard = mRequestedDiscard;
- return true;
- }
- }
- return false;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-S32 LLTextureFetchWorker::callbackHttpGet(const LLChannelDescriptors& channels,
- const LLIOPipe::buffer_ptr_t& buffer,
- bool partial, bool success)
-{
- S32 data_size = 0 ;
-
- LLMutexLock lock(&mWorkMutex);
-
- if (mState != WAIT_HTTP_REQ)
- {
- llwarns << "callbackHttpGet for unrequested fetch worker: " << mID
- << " req=" << mSentRequest << " state= " << mState << llendl;
- return data_size;
- }
- if (mLoaded)
- {
- llwarns << "Duplicate callback for " << mID.asString() << llendl;
- return data_size ; // ignore duplicate callback
- }
- if (success)
- {
- // get length of stream:
- data_size = buffer->countAfter(channels.in(), NULL);
-
- LL_DEBUGS("Texture") << "HTTP RECEIVED: " << mID.asString() << " Bytes: " << data_size << LL_ENDL;
- if (data_size > 0)
- {
- // *TODO: set the formatted image data here directly to avoid the copy
- mBuffer = new U8[data_size];
- buffer->readAfter(channels.in(), NULL, mBuffer, data_size);
- mBufferSize += data_size;
- if (data_size < mRequestedSize && mRequestedDiscard == 0)
- {
- mHaveAllData = TRUE;
- }
- else if (data_size > mRequestedSize)
- {
- // *TODO: This shouldn't be happening any more
- llwarns << "data_size = " << data_size << " > requested: " << mRequestedSize << llendl;
- mHaveAllData = TRUE;
- llassert_always(mDecodeHandle == 0);
- mFormattedImage = NULL; // discard any previous data we had
- mBufferSize = data_size;
- }
- }
- else
- {
- // We requested data but received none (and no error),
- // so presumably we have all of it
- mHaveAllData = TRUE;
- }
- mRequestedSize = data_size;
- }
- else
- {
- mRequestedSize = -1; // error
- }
- mLoaded = TRUE;
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
-
- return data_size ;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-void LLTextureFetchWorker::callbackCacheRead(bool success, LLImageFormatted* image,
- S32 imagesize, BOOL islocal)
-{
- LLMutexLock lock(&mWorkMutex);
- if (mState != LOAD_FROM_TEXTURE_CACHE)
- {
-// llwarns << "Read callback for " << mID << " with state = " << mState << llendl;
- return;
- }
- if (success)
- {
- llassert_always(imagesize >= 0);
- mFileSize = imagesize;
- mFormattedImage = image;
- mImageCodec = image->getCodec();
- mInLocalCache = islocal;
- if (mFileSize != 0 && mFormattedImage->getDataSize() >= mFileSize)
- {
- mHaveAllData = TRUE;
- }
- }
- mLoaded = TRUE;
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
-}
-
-void LLTextureFetchWorker::callbackCacheWrite(bool success)
-{
- LLMutexLock lock(&mWorkMutex);
- if (mState != WAIT_ON_WRITE)
- {
-// llwarns << "Write callback for " << mID << " with state = " << mState << llendl;
- return;
- }
- mWritten = TRUE;
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-void LLTextureFetchWorker::callbackDecoded(bool success, LLImageRaw* raw, LLImageRaw* aux)
-{
- LLMutexLock lock(&mWorkMutex);
- if (mDecodeHandle == 0)
- {
- return; // aborted, ignore
- }
- if (mState != DECODE_IMAGE_UPDATE)
- {
-// llwarns << "Decode callback for " << mID << " with state = " << mState << llendl;
- mDecodeHandle = 0;
- return;
- }
- llassert_always(mFormattedImage.notNull());
-
- mDecodeHandle = 0;
- if (success)
- {
- llassert_always(raw);
- mRawImage = raw;
- mAuxImage = aux;
- mDecodedDiscard = mFormattedImage->getDiscardLevel();
- LL_DEBUGS("Texture") << mID << ": Decode Finished. Discard: " << mDecodedDiscard
- << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL;
- }
- else
- {
- llwarns << "DECODE FAILED: " << mID << " Discard: " << (S32)mFormattedImage->getDiscardLevel() << llendl;
- removeFromCache();
- mDecodedDiscard = -1; // Redundant, here for clarity and paranoia
- }
- mDecoded = TRUE;
-// llinfos << mID << " : DECODE COMPLETE " << llendl;
- setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-bool LLTextureFetchWorker::writeToCacheComplete()
-{
- // Complete write to cache
- if (mCacheWriteHandle != LLTextureCache::nullHandle())
- {
- if (!mWritten)
- {
- return false;
- }
- if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle))
- {
- mCacheWriteHandle = LLTextureCache::nullHandle();
- }
- else
- {
- return false;
- }
- }
- return true;
-}
-
-
-//////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////
-// public
-
-LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded, bool qa_mode)
- : LLWorkerThread("TextureFetch", threaded),
- mDebugCount(0),
- mDebugPause(FALSE),
- mPacketCount(0),
- mBadPacketCount(0),
- mQueueMutex(getAPRPool()),
- mNetworkQueueMutex(getAPRPool()),
- mTextureCache(cache),
- mImageDecodeThread(imagedecodethread),
- mTextureBandwidth(0),
- mHTTPTextureBits(0),
- mTotalHTTPRequests(0),
- mCurlGetRequest(NULL),
- mQAMode(qa_mode)
-{
- mCurlPOSTRequestCount = 0;
- mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS");
- mTextureInfo.setUpLogging(gSavedSettings.getBOOL("LogTextureDownloadsToViewerLog"), gSavedSettings.getBOOL("LogTextureDownloadsToSimulator"), gSavedSettings.getU32("TextureLoggingThreshold"));
-}
-
-LLTextureFetch::~LLTextureFetch()
-{
- clearDeleteList() ;
-
- while (! mCommands.empty())
- {
- TFRequest * req(mCommands.front());
- mCommands.erase(mCommands.begin());
- delete req;
- }
-
- // ~LLQueuedThread() called here
-}
-
-bool LLTextureFetch::createRequest(const std::string& url, const LLUUID& id, const LLHost& host, F32 priority,
- S32 w, S32 h, S32 c, S32 desired_discard, bool needs_aux, bool can_use_http)
-{
- if (mDebugPause)
- {
- return false;
- }
-
- LLTextureFetchWorker* worker = getWorker(id) ;
- if (worker)
- {
- if (worker->mHost != host)
- {
- llwarns << "LLTextureFetch::createRequest " << id << " called with multiple hosts: "
- << host << " != " << worker->mHost << llendl;
- removeRequest(worker, true);
- worker = NULL;
- return false;
- }
- }
-
- S32 desired_size;
- std::string exten = gDirUtilp->getExtension(url);
- if (!url.empty() && (!exten.empty() && LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C))
- {
- // Only do partial requests for J2C at the moment
- desired_size = MAX_IMAGE_DATA_SIZE;
- desired_discard = 0;
- }
- else if (desired_discard == 0)
- {
- // if we want the entire image, and we know its size, then get it all
- // (calcDataSizeJ2C() below makes assumptions about how the image
- // was compressed - this code ensures that when we request the entire image,
- // we really do get it.)
- desired_size = MAX_IMAGE_DATA_SIZE;
- }
- else if (w*h*c > 0)
- {
- // If the requester knows the dimensions of the image,
- // this will calculate how much data we need without having to parse the header
-
- desired_size = LLImageJ2C::calcDataSizeJ2C(w, h, c, desired_discard);
- }
- else
- {
- desired_size = TEXTURE_CACHE_ENTRY_SIZE;
- desired_discard = MAX_DISCARD_LEVEL;
- }
-
-
- if (worker)
- {
- if (worker->wasAborted())
- {
- return false; // need to wait for previous aborted request to complete
- }
- worker->lockWorkMutex();
- worker->mActiveCount++;
- worker->mNeedsAux = needs_aux;
- worker->setImagePriority(priority);
- worker->setDesiredDiscard(desired_discard, desired_size);
- worker->setCanUseHTTP(can_use_http) ;
- if (!worker->haveWork())
- {
- worker->mState = LLTextureFetchWorker::INIT;
- worker->unlockWorkMutex();
-
- worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
- }
- else
- {
- worker->unlockWorkMutex();
- }
- }
- else
- {
- worker = new LLTextureFetchWorker(this, url, id, host, priority, desired_discard, desired_size);
- lockQueue() ;
- mRequestMap[id] = worker;
- unlockQueue() ;
-
- worker->lockWorkMutex();
- worker->mActiveCount++;
- worker->mNeedsAux = needs_aux;
- worker->setCanUseHTTP(can_use_http) ;
- worker->unlockWorkMutex();
- }
-
-// llinfos << "REQUESTED: " << id << " Discard: " << desired_discard << llendl;
- return true;
-}
-
-// protected
-void LLTextureFetch::addToNetworkQueue(LLTextureFetchWorker* worker)
-{
- lockQueue() ;
- bool in_request_map = (mRequestMap.find(worker->mID) != mRequestMap.end()) ;
- unlockQueue() ;
-
- LLMutexLock lock(&mNetworkQueueMutex);
- if (in_request_map)
- {
- // only add to the queue if in the request map
- // i.e. a delete has not been requested
- mNetworkQueue.insert(worker->mID);
- }
- for (cancel_queue_t::iterator iter1 = mCancelQueue.begin();
- iter1 != mCancelQueue.end(); ++iter1)
- {
- iter1->second.erase(worker->mID);
- }
-}
-
-void LLTextureFetch::removeFromNetworkQueue(LLTextureFetchWorker* worker, bool cancel)
-{
- LLMutexLock lock(&mNetworkQueueMutex);
- size_t erased = mNetworkQueue.erase(worker->mID);
- if (cancel && erased > 0)
- {
- mCancelQueue[worker->mHost].insert(worker->mID);
- }
-}
-
-// protected
-void LLTextureFetch::addToHTTPQueue(const LLUUID& id)
-{
- LLMutexLock lock(&mNetworkQueueMutex);
- mHTTPTextureQueue.insert(id);
- mTotalHTTPRequests++;
-}
-
-void LLTextureFetch::removeFromHTTPQueue(const LLUUID& id, S32 received_size)
-{
- LLMutexLock lock(&mNetworkQueueMutex);
- mHTTPTextureQueue.erase(id);
- mHTTPTextureBits += received_size * 8; // Approximate - does not include header bits
-}
-
-void LLTextureFetch::deleteRequest(const LLUUID& id, bool cancel)
-{
- lockQueue() ;
- LLTextureFetchWorker* worker = getWorkerAfterLock(id);
- if (worker)
- {
- size_t erased_1 = mRequestMap.erase(worker->mID);
- unlockQueue() ;
-
- llassert_always(erased_1 > 0) ;
-
- removeFromNetworkQueue(worker, cancel);
- llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ;
-
- worker->scheduleDelete();
- }
- else
- {
- unlockQueue() ;
- }
-}
-
-void LLTextureFetch::removeRequest(LLTextureFetchWorker* worker, bool cancel)
-{
- lockQueue() ;
- size_t erased_1 = mRequestMap.erase(worker->mID);
- unlockQueue() ;
-
- llassert_always(erased_1 > 0) ;
- removeFromNetworkQueue(worker, cancel);
- llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ;
-
- worker->scheduleDelete();
-}
-
-S32 LLTextureFetch::getNumRequests()
-{
- lockQueue() ;
- S32 size = (S32)mRequestMap.size();
- unlockQueue() ;
-
- return size ;
-}
-
-S32 LLTextureFetch::getNumHTTPRequests()
-{
- mNetworkQueueMutex.lock() ;
- S32 size = (S32)mHTTPTextureQueue.size();
- mNetworkQueueMutex.unlock() ;
-
- return size ;
-}
-
-U32 LLTextureFetch::getTotalNumHTTPRequests()
-{
- mNetworkQueueMutex.lock() ;
- U32 size = mTotalHTTPRequests ;
- mNetworkQueueMutex.unlock() ;
-
- return size ;
-}
-
-// call lockQueue() first!
-LLTextureFetchWorker* LLTextureFetch::getWorkerAfterLock(const LLUUID& id)
-{
- LLTextureFetchWorker* res = NULL;
- map_t::iterator iter = mRequestMap.find(id);
- if (iter != mRequestMap.end())
- {
- res = iter->second;
- }
- return res;
-}
-
-LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id)
-{
- LLMutexLock lock(&mQueueMutex) ;
-
- return getWorkerAfterLock(id) ;
-}
-
-
-bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level,
- LLPointer<LLImageRaw>& raw, LLPointer<LLImageRaw>& aux)
-{
- bool res = false;
- LLTextureFetchWorker* worker = getWorker(id);
- if (worker)
- {
- if (worker->wasAborted())
- {
- res = true;
- }
- else if (!worker->haveWork())
- {
- // Should only happen if we set mDebugPause...
- if (!mDebugPause)
- {
-// llwarns << "Adding work for inactive worker: " << id << llendl;
- worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
- }
- }
- else if (worker->checkWork())
- {
- worker->lockWorkMutex();
- discard_level = worker->mDecodedDiscard;
- raw = worker->mRawImage;
- aux = worker->mAuxImage;
- res = true;
- LL_DEBUGS("Texture") << id << ": Request Finished. State: " << worker->mState << " Discard: " << discard_level << LL_ENDL;
- worker->unlockWorkMutex();
- }
- else
- {
- worker->lockWorkMutex();
- if ((worker->mDecodedDiscard >= 0) &&
- (worker->mDecodedDiscard < discard_level || discard_level < 0) &&
- (worker->mState >= LLTextureFetchWorker::WAIT_ON_WRITE))
- {
- // Not finished, but data is ready
- discard_level = worker->mDecodedDiscard;
- raw = worker->mRawImage;
- aux = worker->mAuxImage;
- }
- worker->unlockWorkMutex();
- }
- }
- else
- {
- res = true;
- }
- return res;
-}
-
-bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority)
-{
- bool res = false;
- LLTextureFetchWorker* worker = getWorker(id);
- if (worker)
- {
- worker->lockWorkMutex();
- worker->setImagePriority(priority);
- worker->unlockWorkMutex();
- res = true;
- }
- return res;
-}
-
-// Replicates and expands upon the base class's
-// getPending() implementation. getPending() and
-// runCondition() replicate one another's logic to
-// an extent and are sometimes used for the same
-// function (deciding whether or not to sleep/pause
-// a thread). So the implementations need to stay
-// in step, at least until this can be refactored and
-// the redundancy eliminated.
-//
-// May be called from any thread
-
-//virtual
-S32 LLTextureFetch::getPending()
-{
- S32 res;
- lockData();
- {
- LLMutexLock lock(&mQueueMutex);
-
- res = mRequestQueue.size();
- res += mCurlPOSTRequestCount;
- res += mCommands.size();
- }
- unlockData();
- return res;
-}
-
-// virtual
-bool LLTextureFetch::runCondition()
-{
- // Caller is holding the lock on LLThread's condition variable.
-
- // LLQueuedThread, unlike its base class LLThread, makes this a
- // private method which is unfortunate. I want to use it directly
- // but I'm going to have to re-implement the logic here (or change
- // declarations, which I don't want to do right now).
- //
- // Changes here may need to be reflected in getPending().
-
- bool have_no_commands(false);
- {
- LLMutexLock lock(&mQueueMutex);
-
- have_no_commands = mCommands.empty();
- }
-
- bool have_no_curl_requests(0 == mCurlPOSTRequestCount);
-
- return ! (have_no_commands
- && have_no_curl_requests
- && (mRequestQueue.empty() && mIdleThread)); // From base class
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-// MAIN THREAD (unthreaded envs), WORKER THREAD (threaded envs)
-void LLTextureFetch::commonUpdate()
-{
- // Run a cross-thread command, if any.
- cmdDoWork();
-
- // Update Curl on same thread as mCurlGetRequest was constructed
- S32 processed = mCurlGetRequest->process();
- if (processed > 0)
- {
- lldebugs << "processed: " << processed << " messages." << llendl;
- }
-}
-
-
-// MAIN THREAD
-//virtual
-S32 LLTextureFetch::update(U32 max_time_ms)
-{
- static LLCachedControl<F32> band_width(gSavedSettings,"ThrottleBandwidthKBPS");
-
- {
- mNetworkQueueMutex.lock() ;
- mMaxBandwidth = band_width ;
-
- gTextureList.sTextureBits += mHTTPTextureBits ;
- mHTTPTextureBits = 0 ;
-
- mNetworkQueueMutex.unlock() ;
- }
-
- S32 res = LLWorkerThread::update(max_time_ms);
-
- if (!mDebugPause)
- {
- sendRequestListToSimulators();
- }
-
- if (!mThreaded)
- {
- commonUpdate();
- }
-
- return res;
-}
-
-//called in the MAIN thread after the TextureCacheThread shuts down.
-void LLTextureFetch::shutDownTextureCacheThread()
-{
- if(mTextureCache)
- {
- llassert_always(mTextureCache->isQuitting() || mTextureCache->isStopped()) ;
- mTextureCache = NULL ;
- }
-}
-
-//called in the MAIN thread after the ImageDecodeThread shuts down.
-void LLTextureFetch::shutDownImageDecodeThread()
-{
- if(mImageDecodeThread)
- {
- llassert_always(mImageDecodeThread->isQuitting() || mImageDecodeThread->isStopped()) ;
- mImageDecodeThread = NULL ;
- }
-}
-
-// WORKER THREAD
-void LLTextureFetch::startThread()
-{
- // Construct mCurlGetRequest from Worker Thread
- mCurlGetRequest = new LLCurlRequest();
-}
-
-// WORKER THREAD
-void LLTextureFetch::endThread()
-{
- // Destroy mCurlGetRequest from Worker Thread
- delete mCurlGetRequest;
- mCurlGetRequest = NULL;
-}
-
-// WORKER THREAD
-void LLTextureFetch::threadedUpdate()
-{
- llassert_always(mCurlGetRequest);
-
- // Limit update frequency
- const F32 PROCESS_TIME = 0.05f;
- static LLFrameTimer process_timer;
- if (process_timer.getElapsedTimeF32() < PROCESS_TIME)
- {
- return;
- }
- process_timer.reset();
-
- commonUpdate();
-
-#if 0
- const F32 INFO_TIME = 1.0f;
- static LLFrameTimer info_timer;
- if (info_timer.getElapsedTimeF32() >= INFO_TIME)
- {
- S32 q = mCurlGetRequest->getQueued();
- if (q > 0)
- {
- llinfos << "Queued gets: " << q << llendl;
- info_timer.reset();
- }
- }
-#endif
-
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-void LLTextureFetch::sendRequestListToSimulators()
-{
- // All requests
- const F32 REQUEST_DELTA_TIME = 0.10f; // 10 fps
-
- // Sim requests
- const S32 IMAGES_PER_REQUEST = 50;
- const F32 SIM_LAZY_FLUSH_TIMEOUT = 10.0f; // temp
- const F32 MIN_REQUEST_TIME = 1.0f;
- const F32 MIN_DELTA_PRIORITY = 1000.f;
-
- // Periodically, gather the list of textures that need data from the network
- // And send the requests out to the simulators
- static LLFrameTimer timer;
- if (timer.getElapsedTimeF32() < REQUEST_DELTA_TIME)
- {
- return;
- }
- timer.reset();
-
- // Send requests
- typedef std::set<LLTextureFetchWorker*,LLTextureFetchWorker::Compare> request_list_t;
- typedef std::map< LLHost, request_list_t > work_request_map_t;
- work_request_map_t requests;
- {
- LLMutexLock lock2(&mNetworkQueueMutex);
- for (queue_t::iterator iter = mNetworkQueue.begin(); iter != mNetworkQueue.end(); )
- {
- queue_t::iterator curiter = iter++;
- LLTextureFetchWorker* req = getWorker(*curiter);
- if (!req)
- {
- mNetworkQueue.erase(curiter);
- continue; // paranoia
- }
- if ((req->mState != LLTextureFetchWorker::LOAD_FROM_NETWORK) &&
- (req->mState != LLTextureFetchWorker::LOAD_FROM_SIMULATOR))
- {
- // We already received our URL, remove from the queue
- llwarns << "Worker: " << req->mID << " in mNetworkQueue but in wrong state: " << req->mState << llendl;
- mNetworkQueue.erase(curiter);
- continue;
- }
- if (req->mID == mDebugID)
- {
- mDebugCount++; // for setting breakpoints
- }
- if (req->mSentRequest == LLTextureFetchWorker::SENT_SIM &&
- req->mTotalPackets > 0 &&
- req->mLastPacket >= req->mTotalPackets-1)
- {
- // We have all the packets... make sure this is high priority
-// req->setPriority(LLWorkerThread::PRIORITY_HIGH | req->mWorkPriority);
- continue;
- }
- F32 elapsed = req->mRequestedTimer.getElapsedTimeF32();
- {
- F32 delta_priority = llabs(req->mRequestedPriority - req->mImagePriority);
- if ((req->mSimRequestedDiscard != req->mDesiredDiscard) ||
- (delta_priority > MIN_DELTA_PRIORITY && elapsed >= MIN_REQUEST_TIME) ||
- (elapsed >= SIM_LAZY_FLUSH_TIMEOUT))
- {
- requests[req->mHost].insert(req);
- }
- }
- }
- }
-
- for (work_request_map_t::iterator iter1 = requests.begin();
- iter1 != requests.end(); ++iter1)
- {
- LLHost host = iter1->first;
- // invalid host = use agent host
- if (host == LLHost::invalid)
- {
- host = gAgent.getRegionHost();
- }
-
- S32 sim_request_count = 0;
-
- for (request_list_t::iterator iter2 = iter1->second.begin();
- iter2 != iter1->second.end(); ++iter2)
- {
- LLTextureFetchWorker* req = *iter2;
- if (gMessageSystem)
- {
- if (req->mSentRequest != LLTextureFetchWorker::SENT_SIM)
- {
- // Initialize packet data based on data read from cache
- req->lockWorkMutex();
- req->setupPacketData();
- req->unlockWorkMutex();
- }
- if (0 == sim_request_count)
- {
- gMessageSystem->newMessageFast(_PREHASH_RequestImage);
- gMessageSystem->nextBlockFast(_PREHASH_AgentData);
- gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
- gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
- }
- S32 packet = req->mLastPacket + 1;
- gMessageSystem->nextBlockFast(_PREHASH_RequestImage);
- gMessageSystem->addUUIDFast(_PREHASH_Image, req->mID);
- gMessageSystem->addS8Fast(_PREHASH_DiscardLevel, (S8)req->mDesiredDiscard);
- gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, req->mImagePriority);
- gMessageSystem->addU32Fast(_PREHASH_Packet, packet);
- gMessageSystem->addU8Fast(_PREHASH_Type, req->mType);
-// llinfos << "IMAGE REQUEST: " << req->mID << " Discard: " << req->mDesiredDiscard
-// << " Packet: " << packet << " Priority: " << req->mImagePriority << llendl;
-
- static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog");
- static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator");
- if (log_to_viewer_log || log_to_sim)
- {
- mTextureInfo.setRequestStartTime(req->mID, LLTimer::getTotalTime());
- mTextureInfo.setRequestOffset(req->mID, 0);
- mTextureInfo.setRequestSize(req->mID, 0);
- mTextureInfo.setRequestType(req->mID, LLTextureInfoDetails::REQUEST_TYPE_UDP);
- }
-
- req->lockWorkMutex();
- req->mSentRequest = LLTextureFetchWorker::SENT_SIM;
- req->mSimRequestedDiscard = req->mDesiredDiscard;
- req->mRequestedPriority = req->mImagePriority;
- req->mRequestedTimer.reset();
- req->unlockWorkMutex();
- sim_request_count++;
- if (sim_request_count >= IMAGES_PER_REQUEST)
- {
-// llinfos << "REQUESTING " << sim_request_count << " IMAGES FROM HOST: " << host.getIPString() << llendl;
-
- gMessageSystem->sendSemiReliable(host, NULL, NULL);
- sim_request_count = 0;
- }
- }
- }
- if (gMessageSystem && sim_request_count > 0 && sim_request_count < IMAGES_PER_REQUEST)
- {
-// llinfos << "REQUESTING " << sim_request_count << " IMAGES FROM HOST: " << host.getIPString() << llendl;
- gMessageSystem->sendSemiReliable(host, NULL, NULL);
- sim_request_count = 0;
- }
- }
-
- // Send cancelations
- {
- LLMutexLock lock2(&mNetworkQueueMutex);
- if (gMessageSystem && !mCancelQueue.empty())
- {
- for (cancel_queue_t::iterator iter1 = mCancelQueue.begin();
- iter1 != mCancelQueue.end(); ++iter1)
- {
- LLHost host = iter1->first;
- if (host == LLHost::invalid)
- {
- host = gAgent.getRegionHost();
- }
- S32 request_count = 0;
- for (queue_t::iterator iter2 = iter1->second.begin();
- iter2 != iter1->second.end(); ++iter2)
- {
- if (0 == request_count)
- {
- gMessageSystem->newMessageFast(_PREHASH_RequestImage);
- gMessageSystem->nextBlockFast(_PREHASH_AgentData);
- gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
- gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
- }
- gMessageSystem->nextBlockFast(_PREHASH_RequestImage);
- gMessageSystem->addUUIDFast(_PREHASH_Image, *iter2);
- gMessageSystem->addS8Fast(_PREHASH_DiscardLevel, -1);
- gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, 0);
- gMessageSystem->addU32Fast(_PREHASH_Packet, 0);
- gMessageSystem->addU8Fast(_PREHASH_Type, 0);
-// llinfos << "CANCELING IMAGE REQUEST: " << (*iter2) << llendl;
-
- request_count++;
- if (request_count >= IMAGES_PER_REQUEST)
- {
- gMessageSystem->sendSemiReliable(host, NULL, NULL);
- request_count = 0;
- }
- }
- if (request_count > 0 && request_count < IMAGES_PER_REQUEST)
- {
- gMessageSystem->sendSemiReliable(host, NULL, NULL);
- }
- }
- mCancelQueue.clear();
- }
- }
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-bool LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size)
-{
- mRequestedTimer.reset();
- if (index >= mTotalPackets)
- {
-// llwarns << "Received Image Packet " << index << " > max: " << mTotalPackets << " for image: " << mID << llendl;
- return false;
- }
- if (index > 0 && index < mTotalPackets-1 && size != MAX_IMG_PACKET_SIZE)
- {
-// llwarns << "Received bad sized packet: " << index << ", " << size << " != " << MAX_IMG_PACKET_SIZE << " for image: " << mID << llendl;
- return false;
- }
-
- if (index >= (S32)mPackets.size())
- {
- mPackets.resize(index+1, (PacketData*)NULL); // initializes v to NULL pointers
- }
- else if (mPackets[index] != NULL)
- {
-// llwarns << "Received duplicate packet: " << index << " for image: " << mID << llendl;
- return false;
- }
-
- mPackets[index] = new PacketData(data, size);
- while (mLastPacket+1 < (S32)mPackets.size() && mPackets[mLastPacket+1] != NULL)
- {
- ++mLastPacket;
- }
- return true;
-}
-
-bool LLTextureFetch::receiveImageHeader(const LLHost& host, const LLUUID& id, U8 codec, U16 packets, U32 totalbytes,
- U16 data_size, U8* data)
-{
- LLTextureFetchWorker* worker = getWorker(id);
- bool res = true;
-
- ++mPacketCount;
-
- if (!worker)
- {
-// llwarns << "Received header for non active worker: " << id << llendl;
- res = false;
- }
- else if (worker->mState != LLTextureFetchWorker::LOAD_FROM_NETWORK ||
- worker->mSentRequest != LLTextureFetchWorker::SENT_SIM)
- {
-// llwarns << "receiveImageHeader for worker: " << id
-// << " in state: " << LLTextureFetchWorker::sStateDescs[worker->mState]
-// << " sent: " << worker->mSentRequest << llendl;
- res = false;
- }
- else if (worker->mLastPacket != -1)
- {
- // check to see if we've gotten this packet before
-// llwarns << "Received duplicate header for: " << id << llendl;
- res = false;
- }
- else if (!data_size)
- {
-// llwarns << "Img: " << id << ":" << " Empty Image Header" << llendl;
- res = false;
- }
- if (!res)
- {
- ++mBadPacketCount;
- mNetworkQueueMutex.lock() ;
- mCancelQueue[host].insert(id);
- mNetworkQueueMutex.unlock() ;
- return false;
- }
-
- worker->lockWorkMutex();
-
- // Copy header data into image object
- worker->mImageCodec = codec;
- worker->mTotalPackets = packets;
- worker->mFileSize = (S32)totalbytes;
- llassert_always(totalbytes > 0);
- llassert_always(data_size == FIRST_PACKET_SIZE || data_size == worker->mFileSize);
- res = worker->insertPacket(0, data, data_size);
- worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
- worker->mState = LLTextureFetchWorker::LOAD_FROM_SIMULATOR;
- worker->unlockWorkMutex();
- return res;
-}
-
-bool LLTextureFetch::receiveImagePacket(const LLHost& host, const LLUUID& id, U16 packet_num, U16 data_size, U8* data)
-{
- LLTextureFetchWorker* worker = getWorker(id);
- bool res = true;
-
- ++mPacketCount;
-
- if (!worker)
- {
-// llwarns << "Received packet " << packet_num << " for non active worker: " << id << llendl;
- res = false;
- }
- else if (worker->mLastPacket == -1)
- {
-// llwarns << "Received packet " << packet_num << " before header for: " << id << llendl;
- res = false;
- }
- else if (!data_size)
- {
-// llwarns << "Img: " << id << ":" << " Empty Image Header" << llendl;
- res = false;
- }
- if (!res)
- {
- ++mBadPacketCount;
- mNetworkQueueMutex.lock() ;
- mCancelQueue[host].insert(id);
- mNetworkQueueMutex.unlock() ;
- return false;
- }
-
- worker->lockWorkMutex();
-
- res = worker->insertPacket(packet_num, data, data_size);
-
- if ((worker->mState == LLTextureFetchWorker::LOAD_FROM_SIMULATOR) ||
- (worker->mState == LLTextureFetchWorker::LOAD_FROM_NETWORK))
- {
- worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
- worker->mState = LLTextureFetchWorker::LOAD_FROM_SIMULATOR;
- }
- else
- {
-// llwarns << "receiveImagePacket " << packet_num << "/" << worker->mLastPacket << " for worker: " << id
-// << " in state: " << LLTextureFetchWorker::sStateDescs[worker->mState] << llendl;
- removeFromNetworkQueue(worker, true); // failsafe
- }
-
- if(packet_num >= (worker->mTotalPackets - 1))
- {
- static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog");
- static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator");
-
- if (log_to_viewer_log || log_to_sim)
- {
- U64 timeNow = LLTimer::getTotalTime();
- mTextureInfo.setRequestSize(id, worker->mFileSize);
- mTextureInfo.setRequestCompleteTimeAndLog(id, timeNow);
- }
- }
- worker->unlockWorkMutex();
-
- return res;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-BOOL LLTextureFetch::isFromLocalCache(const LLUUID& id)
-{
- BOOL from_cache = FALSE ;
-
- LLTextureFetchWorker* worker = getWorker(id);
- if (worker)
- {
- worker->lockWorkMutex() ;
- from_cache = worker->mInLocalCache ;
- worker->unlockWorkMutex() ;
- }
-
- return from_cache ;
-}
-
-S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& requested_priority_p,
- U32& fetch_priority_p, F32& fetch_dtime_p, F32& request_dtime_p, bool& can_use_http)
-{
- S32 state = LLTextureFetchWorker::INVALID;
- F32 data_progress = 0.0f;
- F32 requested_priority = 0.0f;
- F32 fetch_dtime = 999999.f;
- F32 request_dtime = 999999.f;
- U32 fetch_priority = 0;
-
- LLTextureFetchWorker* worker = getWorker(id);
- if (worker && worker->haveWork())
- {
- worker->lockWorkMutex();
- state = worker->mState;
- fetch_dtime = worker->mFetchTimer.getElapsedTimeF32();
- request_dtime = worker->mRequestedTimer.getElapsedTimeF32();
- if (worker->mFileSize > 0)
- {
- if (state == LLTextureFetchWorker::LOAD_FROM_SIMULATOR)
- {
- S32 data_size = FIRST_PACKET_SIZE + (worker->mLastPacket-1) * MAX_IMG_PACKET_SIZE;
- data_size = llmax(data_size, 0);
- data_progress = (F32)data_size / (F32)worker->mFileSize;
- }
- else if (worker->mFormattedImage.notNull())
- {
- data_progress = (F32)worker->mFormattedImage->getDataSize() / (F32)worker->mFileSize;
- }
- }
- if (state >= LLTextureFetchWorker::LOAD_FROM_NETWORK && state <= LLTextureFetchWorker::WAIT_HTTP_REQ)
- {
- requested_priority = worker->mRequestedPriority;
- }
- else
- {
- requested_priority = worker->mImagePriority;
- }
- fetch_priority = worker->getPriority();
- can_use_http = worker->getCanUseHTTP() ;
- worker->unlockWorkMutex();
- }
- data_progress_p = data_progress;
- requested_priority_p = requested_priority;
- fetch_priority_p = fetch_priority;
- fetch_dtime_p = fetch_dtime;
- request_dtime_p = request_dtime;
- return state;
-}
-
-void LLTextureFetch::dump()
-{
- llinfos << "LLTextureFetch REQUESTS:" << llendl;
- for (request_queue_t::iterator iter = mRequestQueue.begin();
- iter != mRequestQueue.end(); ++iter)
- {
- LLQueuedThread::QueuedRequest* qreq = *iter;
- LLWorkerThread::WorkRequest* wreq = (LLWorkerThread::WorkRequest*)qreq;
- LLTextureFetchWorker* worker = (LLTextureFetchWorker*)wreq->getWorkerClass();
- llinfos << " ID: " << worker->mID
- << " PRI: " << llformat("0x%08x",wreq->getPriority())
- << " STATE: " << worker->sStateDescs[worker->mState]
- << llendl;
- }
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-// cross-thread command methods
-
-void LLTextureFetch::commandSetRegion(U64 region_handle)
-{
- TFReqSetRegion * req = new TFReqSetRegion(region_handle);
-
- cmdEnqueue(req);
-}
-
-void LLTextureFetch::commandSendMetrics(const std::string & caps_url,
- const LLUUID & session_id,
- const LLUUID & agent_id,
- LLViewerAssetStats * main_stats)
-{
- TFReqSendMetrics * req = new TFReqSendMetrics(caps_url, session_id, agent_id, main_stats);
-
- cmdEnqueue(req);
-}
-
-void LLTextureFetch::commandDataBreak()
-{
- // The pedantically correct way to implement this is to create a command
- // request object in the above fashion and enqueue it. However, this is
- // simple data of an advisorial not operational nature and this case
- // of shared-write access is tolerable.
-
- LLTextureFetch::svMetricsDataBreak = true;
-}
-
-void LLTextureFetch::cmdEnqueue(TFRequest * req)
-{
- lockQueue();
- mCommands.push_back(req);
- unlockQueue();
-
- unpause();
-}
-
-LLTextureFetch::TFRequest * LLTextureFetch::cmdDequeue()
-{
- TFRequest * ret = 0;
-
- lockQueue();
- if (! mCommands.empty())
- {
- ret = mCommands.front();
- mCommands.erase(mCommands.begin());
- }
- unlockQueue();
-
- return ret;
-}
-
-void LLTextureFetch::cmdDoWork()
-{
- if (mDebugPause)
- {
- return; // debug: don't do any work
- }
-
- TFRequest * req = cmdDequeue();
- if (req)
- {
- // One request per pass should really be enough for this.
- req->doWork(this);
- delete req;
- }
-}
-
-
-//////////////////////////////////////////////////////////////////////////////
-
-// Private (anonymous) class methods implementing the command scheme.
-
-namespace
-{
-
-/**
- * Implements the 'Set Region' command.
- *
- * Thread: Thread1 (TextureFetch)
- */
-bool
-TFReqSetRegion::doWork(LLTextureFetch *)
-{
- LLViewerAssetStatsFF::set_region_thread1(mRegionHandle);
-
- return true;
-}
-
-
-TFReqSendMetrics::~TFReqSendMetrics()
-{
- delete mMainStats;
- mMainStats = 0;
-}
-
-
-/**
- * Implements the 'Send Metrics' command. Takes over
- * ownership of the passed LLViewerAssetStats pointer.
- *
- * Thread: Thread1 (TextureFetch)
- */
-bool
-TFReqSendMetrics::doWork(LLTextureFetch * fetcher)
-{
- /*
- * HTTP POST responder. Doesn't do much but tries to
- * detect simple breaks in recording the metrics stream.
- *
- * The 'volatile' modifiers don't indicate signals,
- * mmap'd memory or threads, really. They indicate that
- * the referenced data is part of a pseudo-closure for
- * this responder rather than being required for correct
- * operation.
- *
- * We don't try very hard with the POST request. We give
- * it one shot and that's more-or-less it. With a proper
- * refactoring of the LLQueuedThread usage, these POSTs
- * could be put in a request object and made more reliable.
- */
- class lcl_responder : public LLCurl::Responder
- {
- public:
- lcl_responder(LLTextureFetch * fetcher,
- S32 expected_sequence,
- volatile const S32 & live_sequence,
- volatile bool & reporting_break,
- volatile bool & reporting_started)
- : LLCurl::Responder(),
- mFetcher(fetcher),
- mExpectedSequence(expected_sequence),
- mLiveSequence(live_sequence),
- mReportingBreak(reporting_break),
- mReportingStarted(reporting_started)
- {
- mFetcher->incrCurlPOSTCount();
- }
-
- ~lcl_responder()
- {
- mFetcher->decrCurlPOSTCount();
- }
-
- // virtual
- void error(U32 status_num, const std::string & reason)
- {
- if (mLiveSequence == mExpectedSequence)
- {
- mReportingBreak = true;
- }
- LL_WARNS("Texture") << "Break in metrics stream due to POST failure to metrics collection service. Reason: "
- << reason << LL_ENDL;
- }
-
- // virtual
- void result(const LLSD & content)
- {
- if (mLiveSequence == mExpectedSequence)
- {
- mReportingBreak = false;
- mReportingStarted = true;
- }
- }
-
- private:
- LLTextureFetch * mFetcher;
- S32 mExpectedSequence;
- volatile const S32 & mLiveSequence;
- volatile bool & mReportingBreak;
- volatile bool & mReportingStarted;
-
- }; // class lcl_responder
-
- if (! gViewerAssetStatsThread1)
- return true;
-
- static volatile bool reporting_started(false);
- static volatile S32 report_sequence(0);
-
- // We've taken over ownership of the stats copy at this
- // point. Get a working reference to it for merging here
- // but leave it in 'this'. Destructor will rid us of it.
- LLViewerAssetStats & main_stats = *mMainStats;
-
- // Merge existing stats into those from main, convert to LLSD
- main_stats.merge(*gViewerAssetStatsThread1);
- LLSD merged_llsd = main_stats.asLLSD(true);
-
- // Add some additional meta fields to the content
- merged_llsd["session_id"] = mSessionID;
- merged_llsd["agent_id"] = mAgentID;
- merged_llsd["message"] = "ViewerAssetMetrics"; // Identifies the type of metrics
- merged_llsd["sequence"] = report_sequence; // Sequence number
- merged_llsd["initial"] = ! reporting_started; // Initial data from viewer
- merged_llsd["break"] = LLTextureFetch::svMetricsDataBreak; // Break in data prior to this report
-
- // Update sequence number
- if (S32_MAX == ++report_sequence)
- report_sequence = 0;
-
- // Limit the size of the stats report if necessary.
- merged_llsd["truncated"] = truncate_viewer_metrics(10, merged_llsd);
-
- if (! mCapsURL.empty())
- {
- LLCurlRequest::headers_t headers;
- fetcher->getCurlRequest().post(mCapsURL,
- headers,
- merged_llsd,
- new lcl_responder(fetcher,
- report_sequence,
- report_sequence,
- LLTextureFetch::svMetricsDataBreak,
- reporting_started));
- }
- else
- {
- LLTextureFetch::svMetricsDataBreak = true;
- }
-
- // In QA mode, Metrics submode, log the result for ease of testing
- if (fetcher->isQAMode())
- {
- LL_INFOS("Textures") << merged_llsd << LL_ENDL;
- }
-
- gViewerAssetStatsThread1->reset();
-
- return true;
-}
-
-
-bool
-truncate_viewer_metrics(int max_regions, LLSD & metrics)
-{
- static const LLSD::String reg_tag("regions");
- static const LLSD::String duration_tag("duration");
-
- LLSD & reg_map(metrics[reg_tag]);
- if (reg_map.size() <= max_regions)
- {
- return false;
- }
-
- // Build map of region hashes ordered by duration
- typedef std::multimap<LLSD::Real, int> reg_ordered_list_t;
- reg_ordered_list_t regions_by_duration;
-
- int ind(0);
- LLSD::array_const_iterator it_end(reg_map.endArray());
- for (LLSD::array_const_iterator it(reg_map.beginArray()); it_end != it; ++it, ++ind)
- {
- LLSD::Real duration = (*it)[duration_tag].asReal();
- regions_by_duration.insert(reg_ordered_list_t::value_type(duration, ind));
- }
-
- // Build a replacement regions array with the longest-persistence regions
- LLSD new_region(LLSD::emptyArray());
- reg_ordered_list_t::const_reverse_iterator it2_end(regions_by_duration.rend());
- reg_ordered_list_t::const_reverse_iterator it2(regions_by_duration.rbegin());
- for (int i(0); i < max_regions && it2_end != it2; ++i, ++it2)
- {
- new_region.append(reg_map[it2->second]);
- }
- reg_map = new_region;
-
- return true;
-}
-
-} // end of anonymous namespace
-
-
-
+/**
+ * @file lltexturefetch.cpp
+ * @brief Object which fetches textures from the cache and/or network
+ *
+ * $LicenseInfo:firstyear=2000&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include <iostream>
+#include <map>
+
+#include "llstl.h"
+
+#include "lltexturefetch.h"
+
+#include "llcurl.h"
+#include "lldir.h"
+#include "llhttpclient.h"
+#include "llhttpstatuscodes.h"
+#include "llimage.h"
+#include "llimagej2c.h"
+#include "llimageworker.h"
+#include "llworkerthread.h"
+#include "message.h"
+
+#include "llagent.h"
+#include "lltexturecache.h"
+#include "llviewercontrol.h"
+#include "llviewertexturelist.h"
+#include "llviewertexture.h"
+#include "llviewerregion.h"
+#include "llviewerstats.h"
+#include "llviewerassetstats.h"
+#include "llworld.h"
+
+//////////////////////////////////////////////////////////////////////////////
+class LLTextureFetchWorker : public LLWorkerClass
+{
+ friend class LLTextureFetch;
+ friend class HTTPGetResponder;
+
+private:
+ class CacheReadResponder : public LLTextureCache::ReadResponder
+ {
+ public:
+ CacheReadResponder(LLTextureFetch* fetcher, const LLUUID& id, LLImageFormatted* image)
+ : mFetcher(fetcher), mID(id)
+ {
+ setImage(image);
+ }
+ virtual void completed(bool success)
+ {
+ LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
+ if (worker)
+ {
+ worker->callbackCacheRead(success, mFormattedImage, mImageSize, mImageLocal);
+ }
+ }
+ private:
+ LLTextureFetch* mFetcher;
+ LLUUID mID;
+ };
+
+ class CacheWriteResponder : public LLTextureCache::WriteResponder
+ {
+ public:
+ CacheWriteResponder(LLTextureFetch* fetcher, const LLUUID& id)
+ : mFetcher(fetcher), mID(id)
+ {
+ }
+ virtual void completed(bool success)
+ {
+ LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
+ if (worker)
+ {
+ worker->callbackCacheWrite(success);
+ }
+ }
+ private:
+ LLTextureFetch* mFetcher;
+ LLUUID mID;
+ };
+
+ class DecodeResponder : public LLImageDecodeThread::Responder
+ {
+ public:
+ DecodeResponder(LLTextureFetch* fetcher, const LLUUID& id, LLTextureFetchWorker* worker)
+ : mFetcher(fetcher), mID(id), mWorker(worker)
+ {
+ }
+ virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux)
+ {
+ LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
+ if (worker)
+ {
+ worker->callbackDecoded(success, raw, aux);
+ }
+ }
+ private:
+ LLTextureFetch* mFetcher;
+ LLUUID mID;
+ LLTextureFetchWorker* mWorker; // debug only (may get deleted from under us, use mFetcher/mID)
+ };
+
+ struct Compare
+ {
+ // lhs < rhs
+ bool operator()(const LLTextureFetchWorker* lhs, const LLTextureFetchWorker* rhs) const
+ {
+ // greater priority is "less"
+ const F32 lpriority = lhs->mImagePriority;
+ const F32 rpriority = rhs->mImagePriority;
+ if (lpriority > rpriority) // higher priority
+ return true;
+ else if (lpriority < rpriority)
+ return false;
+ else
+ return lhs < rhs;
+ }
+ };
+
+public:
+ /*virtual*/ bool doWork(S32 param); // Called from LLWorkerThread::processRequest()
+ /*virtual*/ void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD)
+ /*virtual*/ bool deleteOK(); // called from update() (WORK THREAD)
+
+ ~LLTextureFetchWorker();
+ // void relese() { --mActiveCount; }
+
+ S32 callbackHttpGet(const LLChannelDescriptors& channels,
+ const LLIOPipe::buffer_ptr_t& buffer,
+ bool partial, bool success);
+ void callbackCacheRead(bool success, LLImageFormatted* image,
+ S32 imagesize, BOOL islocal);
+ void callbackCacheWrite(bool success);
+ void callbackDecoded(bool success, LLImageRaw* raw, LLImageRaw* aux);
+
+ void setGetStatus(U32 status, const std::string& reason)
+ {
+ LLMutexLock lock(&mWorkMutex);
+
+ mGetStatus = status;
+ mGetReason = reason;
+ }
+
+ void setCanUseHTTP(bool can_use_http) { mCanUseHTTP = can_use_http; }
+ bool getCanUseHTTP() const { return mCanUseHTTP; }
+
+ LLTextureFetch & getFetcher() { return *mFetcher; }
+
+protected:
+ LLTextureFetchWorker(LLTextureFetch* fetcher, const std::string& url, const LLUUID& id, const LLHost& host,
+ F32 priority, S32 discard, S32 size);
+
+private:
+ /*virtual*/ void startWork(S32 param); // called from addWork() (MAIN THREAD)
+ /*virtual*/ void endWork(S32 param, bool aborted); // called from doWork() (MAIN THREAD)
+
+ void resetFormattedData();
+
+ void setImagePriority(F32 priority);
+ void setDesiredDiscard(S32 discard, S32 size);
+ bool insertPacket(S32 index, U8* data, S32 size);
+ void clearPackets();
+ void setupPacketData();
+ U32 calcWorkPriority();
+ void removeFromCache();
+ bool processSimulatorPackets();
+ bool writeToCacheComplete();
+
+ void lockWorkMutex() { mWorkMutex.lock(); }
+ void unlockWorkMutex() { mWorkMutex.unlock(); }
+
+private:
+ enum e_state // mState
+ {
+ // NOTE: Affects LLTextureBar::draw in lltextureview.cpp (debug hack)
+ INVALID = 0,
+ INIT,
+ LOAD_FROM_TEXTURE_CACHE,
+ CACHE_POST,
+ LOAD_FROM_NETWORK,
+ LOAD_FROM_SIMULATOR,
+ SEND_HTTP_REQ,
+ WAIT_HTTP_REQ,
+ DECODE_IMAGE,
+ DECODE_IMAGE_UPDATE,
+ WRITE_TO_CACHE,
+ WAIT_ON_WRITE,
+ DONE
+ };
+ enum e_request_state // mSentRequest
+ {
+ UNSENT = 0,
+ QUEUED = 1,
+ SENT_SIM = 2
+ };
+ enum e_write_to_cache_state //mWriteToCacheState
+ {
+ NOT_WRITE = 0,
+ CAN_WRITE = 1,
+ SHOULD_WRITE = 2
+ };
+ static const char* sStateDescs[];
+ e_state mState;
+ e_write_to_cache_state mWriteToCacheState;
+ LLTextureFetch* mFetcher;
+ LLPointer<LLImageFormatted> mFormattedImage;
+ LLPointer<LLImageRaw> mRawImage;
+ LLPointer<LLImageRaw> mAuxImage;
+ LLUUID mID;
+ LLHost mHost;
+ std::string mUrl;
+ U8 mType;
+ F32 mImagePriority;
+ U32 mWorkPriority;
+ F32 mRequestedPriority;
+ S32 mDesiredDiscard;
+ S32 mSimRequestedDiscard;
+ S32 mRequestedDiscard;
+ S32 mLoadedDiscard;
+ S32 mDecodedDiscard;
+ LLFrameTimer mRequestedTimer;
+ LLFrameTimer mFetchTimer;
+ LLTextureCache::handle_t mCacheReadHandle;
+ LLTextureCache::handle_t mCacheWriteHandle;
+ U8* mBuffer;
+ S32 mBufferSize;
+ S32 mRequestedSize;
+ S32 mDesiredSize;
+ S32 mFileSize;
+ S32 mCachedSize;
+ e_request_state mSentRequest;
+ handle_t mDecodeHandle;
+ BOOL mLoaded;
+ BOOL mDecoded;
+ BOOL mWritten;
+ BOOL mNeedsAux;
+ BOOL mHaveAllData;
+ BOOL mInLocalCache;
+ bool mCanUseHTTP ;
+ bool mCanUseNET ; //can get from asset server.
+ S32 mHTTPFailCount;
+ S32 mRetryAttempt;
+ S32 mActiveCount;
+ U32 mGetStatus;
+ std::string mGetReason;
+
+ // Work Data
+ LLMutex mWorkMutex;
+ struct PacketData
+ {
+ PacketData(U8* data, S32 size) { mData = data; mSize = size; }
+ ~PacketData() { clearData(); }
+ void clearData() { delete[] mData; mData = NULL; }
+ U8* mData;
+ U32 mSize;
+ };
+ std::vector<PacketData*> mPackets;
+ S32 mFirstPacket;
+ S32 mLastPacket;
+ U16 mTotalPackets;
+ U8 mImageCodec;
+
+ LLViewerAssetStats::duration_t mMetricsStartTime;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class HTTPGetResponder : public LLCurl::Responder
+{
+ LOG_CLASS(HTTPGetResponder);
+public:
+ HTTPGetResponder(LLTextureFetch* fetcher, const LLUUID& id, U64 startTime, S32 requestedSize, U32 offset, bool redir)
+ : mFetcher(fetcher), mID(id), mStartTime(startTime), mRequestedSize(requestedSize), mOffset(offset), mFollowRedir(redir)
+ {
+ }
+ ~HTTPGetResponder()
+ {
+ }
+
+ virtual void completedRaw(U32 status, const std::string& reason,
+ const LLChannelDescriptors& channels,
+ const LLIOPipe::buffer_ptr_t& buffer)
+ {
+ static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog");
+ static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator");
+ static LLCachedControl<bool> log_texture_traffic(gSavedSettings,"LogTextureNetworkTraffic") ;
+
+ if (log_to_viewer_log || log_to_sim)
+ {
+ mFetcher->mTextureInfo.setRequestStartTime(mID, mStartTime);
+ U64 timeNow = LLTimer::getTotalTime();
+ mFetcher->mTextureInfo.setRequestType(mID, LLTextureInfoDetails::REQUEST_TYPE_HTTP);
+ mFetcher->mTextureInfo.setRequestSize(mID, mRequestedSize);
+ mFetcher->mTextureInfo.setRequestOffset(mID, mOffset);
+ mFetcher->mTextureInfo.setRequestCompleteTimeAndLog(mID, timeNow);
+ }
+
+ lldebugs << "HTTP COMPLETE: " << mID << llendl;
+ LLTextureFetchWorker* worker = mFetcher->getWorker(mID);
+ if (worker)
+ {
+ bool success = false;
+ bool partial = false;
+ if (HTTP_OK <= status && status < HTTP_MULTIPLE_CHOICES)
+ {
+ success = true;
+ if (HTTP_PARTIAL_CONTENT == status) // partial information
+ {
+ partial = true;
+ }
+ }
+
+ if (!success)
+ {
+ worker->setGetStatus(status, reason);
+// llwarns << "CURL GET FAILED, status:" << status << " reason:" << reason << llendl;
+ }
+
+ S32 data_size = worker->callbackHttpGet(channels, buffer, partial, success);
+
+ if(log_texture_traffic && data_size > 0)
+ {
+ LLViewerTexture* tex = LLViewerTextureManager::findTexture(mID) ;
+ if(tex)
+ {
+ gTotalTextureBytesPerBoostLevel[tex->getBoostLevel()] += data_size ;
+ }
+ }
+
+ mFetcher->removeFromHTTPQueue(mID, data_size);
+
+ if (worker->mMetricsStartTime)
+ {
+ LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE,
+ true,
+ LLImageBase::TYPE_AVATAR_BAKE == worker->mType,
+ LLViewerAssetStatsFF::get_timestamp() - worker->mMetricsStartTime);
+ worker->mMetricsStartTime = 0;
+ }
+ LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE,
+ true,
+ LLImageBase::TYPE_AVATAR_BAKE == worker->mType);
+ }
+ else
+ {
+ mFetcher->removeFromHTTPQueue(mID);
+ llwarns << "Worker not found: " << mID << llendl;
+ }
+ }
+
+ virtual bool followRedir()
+ {
+ return mFollowRedir;
+ }
+
+private:
+ LLTextureFetch* mFetcher;
+ LLUUID mID;
+ U64 mStartTime;
+ S32 mRequestedSize;
+ U32 mOffset;
+ bool mFollowRedir;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+// Cross-thread messaging for asset metrics.
+
+/**
+ * @brief Base class for cross-thread requests made of the fetcher
+ *
+ * I believe the intent of the LLQueuedThread class was to
+ * have these operations derived from LLQueuedThread::QueuedRequest
+ * but the texture fetcher has elected to manage the queue
+ * in its own manner. So these are free-standing objects which are
+ * managed in simple FIFO order on the mCommands queue of the
+ * LLTextureFetch object.
+ *
+ * What each represents is a simple command sent from an
+ * outside thread into the TextureFetch thread to be processed
+ * in order and in a timely fashion (though not an absolute
+ * higher priority than other operations of the thread).
+ * Each operation derives a new class from the base customizing
+ * members, constructors and the doWork() method to effect
+ * the command.
+ *
+ * The flow is one-directional. There are two global instances
+ * of the LLViewerAssetStats collector, one for the main program's
+ * thread pointed to by gViewerAssetStatsMain and one for the
+ * TextureFetch thread pointed to by gViewerAssetStatsThread1.
+ * Common operations has each thread recording metrics events
+ * into the respective collector unconcerned with locking and
+ * the state of any other thread. But when the agent moves into
+ * a different region or the metrics timer expires and a report
+ * needs to be sent back to the grid, messaging across threads
+ * is required to distribute data and perform global actions.
+ * In pseudo-UML, it looks like:
+ *
+ * Main Thread1
+ * . .
+ * . .
+ * +-----+ .
+ * | AM | .
+ * +--+--+ .
+ * +-------+ | .
+ * | Main | +--+--+ .
+ * | | | SRE |---. .
+ * | Stats | +-----+ \ .
+ * | | | \ (uuid) +-----+
+ * | Coll. | +--+--+ `-------->| SR |
+ * +-------+ | MSC | +--+--+
+ * | ^ +-----+ |
+ * | | (uuid) / . +-----+ (uuid)
+ * | `--------' . | MSC |---------.
+ * | . +-----+ |
+ * | +-----+ . v
+ * | | TE | . +-------+
+ * | +--+--+ . | Thd1 |
+ * | | . | |
+ * | +-----+ . | Stats |
+ * `--------->| RSC | . | |
+ * +--+--+ . | Coll. |
+ * | . +-------+
+ * +--+--+ . |
+ * | SME |---. . |
+ * +-----+ \ . |
+ * . \ (clone) +-----+ |
+ * . `-------->| SM | |
+ * . +--+--+ |
+ * . | |
+ * . +-----+ |
+ * . | RSC |<--------'
+ * . +-----+
+ * . |
+ * . +-----+
+ * . | CP |--> HTTP POST
+ * . +-----+
+ * . .
+ * . .
+ *
+ *
+ * Key:
+ *
+ * SRE - Set Region Enqueued. Enqueue a 'Set Region' command in
+ * the other thread providing the new UUID of the region.
+ * TFReqSetRegion carries the data.
+ * SR - Set Region. New region UUID is sent to the thread-local
+ * collector.
+ * SME - Send Metrics Enqueued. Enqueue a 'Send Metrics' command
+ * including an ownership transfer of a cloned LLViewerAssetStats.
+ * TFReqSendMetrics carries the data.
+ * SM - Send Metrics. Global metrics reporting operation. Takes
+ * the cloned stats from the command, merges it with the
+ * thread's local stats, converts to LLSD and sends it on
+ * to the grid.
+ * AM - Agent Moved. Agent has completed some sort of move to a
+ * new region.
+ * TE - Timer Expired. Metrics timer has expired (on the order
+ * of 10 minutes).
+ * CP - CURL Post
+ * MSC - Modify Stats Collector. State change in the thread-local
+ * collector. Typically a region change which affects the
+ * global pointers used to find the 'current stats'.
+ * RSC - Read Stats Collector. Extract collector data cloning it
+ * (i.e. deep copy) when necessary.
+ *
+ */
+class LLTextureFetch::TFRequest // : public LLQueuedThread::QueuedRequest
+{
+public:
+ // Default ctors and assignment operator are correct.
+
+ virtual ~TFRequest()
+ {}
+
+ // Patterned after QueuedRequest's method but expected behavior
+ // is different. Always expected to complete on the first call
+ // and work dispatcher will assume the same and delete the
+ // request after invocation.
+ virtual bool doWork(LLTextureFetch * fetcher) = 0;
+};
+
+namespace
+{
+
+/**
+ * @brief Implements a 'Set Region' cross-thread command.
+ *
+ * When an agent moves to a new region, subsequent metrics need
+ * to be binned into a new or existing stats collection in 1:1
+ * relationship with the region. We communicate this region
+ * change across the threads involved in the communication with
+ * this message.
+ *
+ * Corresponds to LLTextureFetch::commandSetRegion()
+ */
+class TFReqSetRegion : public LLTextureFetch::TFRequest
+{
+public:
+ TFReqSetRegion(U64 region_handle)
+ : LLTextureFetch::TFRequest(),
+ mRegionHandle(region_handle)
+ {}
+ TFReqSetRegion & operator=(const TFReqSetRegion &); // Not defined
+
+ virtual ~TFReqSetRegion()
+ {}
+
+ virtual bool doWork(LLTextureFetch * fetcher);
+
+public:
+ const U64 mRegionHandle;
+};
+
+
+/**
+ * @brief Implements a 'Send Metrics' cross-thread command.
+ *
+ * This is the big operation. The main thread gathers metrics
+ * for a period of minutes into LLViewerAssetStats and other
+ * objects then makes a snapshot of the data by cloning the
+ * collector. This command transfers the clone, along with a few
+ * additional arguments (UUIDs), handing ownership to the
+ * TextureFetch thread. It then merges its own data into the
+ * cloned copy, converts to LLSD and kicks off an HTTP POST of
+ * the resulting data to the currently active metrics collector.
+ *
+ * Corresponds to LLTextureFetch::commandSendMetrics()
+ */
+class TFReqSendMetrics : public LLTextureFetch::TFRequest
+{
+public:
+ /**
+ * Construct the 'Send Metrics' command to have the TextureFetch
+ * thread add and log metrics data.
+ *
+ * @param caps_url URL of a "ViewerMetrics" Caps target
+ * to receive the data. Does not have to
+ * be associated with a particular region.
+ *
+ * @param session_id UUID of the agent's session.
+ *
+ * @param agent_id UUID of the agent. (Being pure here...)
+ *
+ * @param main_stats Pointer to a clone of the main thread's
+ * LLViewerAssetStats data. Thread1 takes
+ * ownership of the copy and disposes of it
+ * when done.
+ */
+ TFReqSendMetrics(const std::string & caps_url,
+ const LLUUID & session_id,
+ const LLUUID & agent_id,
+ LLViewerAssetStats * main_stats)
+ : LLTextureFetch::TFRequest(),
+ mCapsURL(caps_url),
+ mSessionID(session_id),
+ mAgentID(agent_id),
+ mMainStats(main_stats)
+ {}
+ TFReqSendMetrics & operator=(const TFReqSendMetrics &); // Not defined
+
+ virtual ~TFReqSendMetrics();
+
+ virtual bool doWork(LLTextureFetch * fetcher);
+
+public:
+ const std::string mCapsURL;
+ const LLUUID mSessionID;
+ const LLUUID mAgentID;
+ LLViewerAssetStats * mMainStats;
+};
+
+/*
+ * Examines the merged viewer metrics report and if found to be too long,
+ * will attempt to truncate it in some reasonable fashion.
+ *
+ * @param max_regions Limit of regions allowed in report.
+ *
+ * @param metrics Full, merged viewer metrics report.
+ *
+ * @returns If data was truncated, returns true.
+ */
+bool truncate_viewer_metrics(int max_regions, LLSD & metrics);
+
+} // end of anonymous namespace
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+//static
+const char* LLTextureFetchWorker::sStateDescs[] = {
+ "INVALID",
+ "INIT",
+ "LOAD_FROM_TEXTURE_CACHE",
+ "CACHE_POST",
+ "LOAD_FROM_NETWORK",
+ "LOAD_FROM_SIMULATOR",
+ "SEND_HTTP_REQ",
+ "WAIT_HTTP_REQ",
+ "DECODE_IMAGE",
+ "DECODE_IMAGE_UPDATE",
+ "WRITE_TO_CACHE",
+ "WAIT_ON_WRITE",
+ "DONE",
+};
+
+// static
+volatile bool LLTextureFetch::svMetricsDataBreak(true); // Start with a data break
+
+// called from MAIN THREAD
+
+LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher,
+ const std::string& url, // Optional URL
+ const LLUUID& id, // Image UUID
+ const LLHost& host, // Simulator host
+ F32 priority, // Priority
+ S32 discard, // Desired discard
+ S32 size) // Desired size
+ : LLWorkerClass(fetcher, "TextureFetch"),
+ mState(INIT),
+ mWriteToCacheState(NOT_WRITE),
+ mFetcher(fetcher),
+ mID(id),
+ mHost(host),
+ mUrl(url),
+ mImagePriority(priority),
+ mWorkPriority(0),
+ mRequestedPriority(0.f),
+ mDesiredDiscard(-1),
+ mSimRequestedDiscard(-1),
+ mRequestedDiscard(-1),
+ mLoadedDiscard(-1),
+ mDecodedDiscard(-1),
+ mCacheReadHandle(LLTextureCache::nullHandle()),
+ mCacheWriteHandle(LLTextureCache::nullHandle()),
+ mBuffer(NULL),
+ mBufferSize(0),
+ mRequestedSize(0),
+ mDesiredSize(TEXTURE_CACHE_ENTRY_SIZE),
+ mFileSize(0),
+ mCachedSize(0),
+ mLoaded(FALSE),
+ mSentRequest(UNSENT),
+ mDecodeHandle(0),
+ mDecoded(FALSE),
+ mWritten(FALSE),
+ mNeedsAux(FALSE),
+ mHaveAllData(FALSE),
+ mInLocalCache(FALSE),
+ mCanUseHTTP(true),
+ mHTTPFailCount(0),
+ mRetryAttempt(0),
+ mActiveCount(0),
+ mGetStatus(0),
+ mWorkMutex(NULL),
+ mFirstPacket(0),
+ mLastPacket(-1),
+ mTotalPackets(0),
+ mImageCodec(IMG_CODEC_INVALID),
+ mMetricsStartTime(0)
+{
+ mCanUseNET = mUrl.empty() ;
+
+ calcWorkPriority();
+ mType = host.isOk() ? LLImageBase::TYPE_AVATAR_BAKE : LLImageBase::TYPE_NORMAL;
+// llinfos << "Create: " << mID << " mHost:" << host << " Discard=" << discard << llendl;
+ if (!mFetcher->mDebugPause)
+ {
+ U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
+ addWork(0, work_priority );
+ }
+ setDesiredDiscard(discard, size);
+}
+
+LLTextureFetchWorker::~LLTextureFetchWorker()
+{
+// llinfos << "Destroy: " << mID
+// << " Decoded=" << mDecodedDiscard
+// << " Requested=" << mRequestedDiscard
+// << " Desired=" << mDesiredDiscard << llendl;
+ llassert_always(!haveWork());
+ lockWorkMutex();
+ if (mCacheReadHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache)
+ {
+ mFetcher->mTextureCache->readComplete(mCacheReadHandle, true);
+ }
+ if (mCacheWriteHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache)
+ {
+ mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true);
+ }
+ mFormattedImage = NULL;
+ clearPackets();
+ unlockWorkMutex();
+ mFetcher->removeFromHTTPQueue(mID);
+}
+
+void LLTextureFetchWorker::clearPackets()
+{
+ for_each(mPackets.begin(), mPackets.end(), DeletePointer());
+ mPackets.clear();
+ mTotalPackets = 0;
+ mLastPacket = -1;
+ mFirstPacket = 0;
+}
+
+void LLTextureFetchWorker::setupPacketData()
+{
+ S32 data_size = 0;
+ if (mFormattedImage.notNull())
+ {
+ data_size = mFormattedImage->getDataSize();
+ }
+ if (data_size > 0)
+ {
+ // Only used for simulator requests
+ mFirstPacket = (data_size - FIRST_PACKET_SIZE) / MAX_IMG_PACKET_SIZE + 1;
+ if (FIRST_PACKET_SIZE + (mFirstPacket-1) * MAX_IMG_PACKET_SIZE != data_size)
+ {
+ llwarns << "Bad CACHED TEXTURE size: " << data_size << " removing." << llendl;
+ removeFromCache();
+ resetFormattedData();
+ clearPackets();
+ }
+ else if (mFileSize > 0)
+ {
+ mLastPacket = mFirstPacket-1;
+ mTotalPackets = (mFileSize - FIRST_PACKET_SIZE + MAX_IMG_PACKET_SIZE-1) / MAX_IMG_PACKET_SIZE + 1;
+ }
+ else
+ {
+ // This file was cached using HTTP so we have to refetch the first packet
+ resetFormattedData();
+ clearPackets();
+ }
+ }
+}
+
+U32 LLTextureFetchWorker::calcWorkPriority()
+{
+ //llassert_always(mImagePriority >= 0 && mImagePriority <= LLViewerFetchedTexture::maxDecodePriority());
+ static const F32 PRIORITY_SCALE = (F32)LLWorkerThread::PRIORITY_LOWBITS / LLViewerFetchedTexture::maxDecodePriority();
+
+ mWorkPriority = llmin((U32)LLWorkerThread::PRIORITY_LOWBITS, (U32)(mImagePriority * PRIORITY_SCALE));
+ return mWorkPriority;
+}
+
+// mWorkMutex is locked
+void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size)
+{
+ bool prioritize = false;
+ if (mDesiredDiscard != discard)
+ {
+ if (!haveWork())
+ {
+ calcWorkPriority();
+ if (!mFetcher->mDebugPause)
+ {
+ U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
+ addWork(0, work_priority);
+ }
+ }
+ else if (mDesiredDiscard < discard)
+ {
+ prioritize = true;
+ }
+ mDesiredDiscard = discard;
+ mDesiredSize = size;
+ }
+ else if (size > mDesiredSize)
+ {
+ mDesiredSize = size;
+ prioritize = true;
+ }
+ mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE);
+ if ((prioritize && mState == INIT) || mState == DONE)
+ {
+ mState = INIT;
+ U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH;
+ setPriority(work_priority);
+ }
+}
+
+void LLTextureFetchWorker::setImagePriority(F32 priority)
+{
+// llassert_always(priority >= 0 && priority <= LLViewerTexture::maxDecodePriority());
+ F32 delta = fabs(priority - mImagePriority);
+ if (delta > (mImagePriority * .05f) || mState == DONE)
+ {
+ mImagePriority = priority;
+ calcWorkPriority();
+ U32 work_priority = mWorkPriority | (getPriority() & LLWorkerThread::PRIORITY_HIGHBITS);
+ setPriority(work_priority);
+ }
+}
+
+void LLTextureFetchWorker::resetFormattedData()
+{
+ delete[] mBuffer;
+ mBuffer = NULL;
+ mBufferSize = 0;
+ if (mFormattedImage.notNull())
+ {
+ mFormattedImage->deleteData();
+ }
+ mHaveAllData = FALSE;
+}
+
+// Called from MAIN thread
+void LLTextureFetchWorker::startWork(S32 param)
+{
+ llassert(mFormattedImage.isNull());
+}
+
+#include "llviewertexturelist.h" // debug
+
+// Called from LLWorkerThread::processRequest()
+bool LLTextureFetchWorker::doWork(S32 param)
+{
+ LLMutexLock lock(&mWorkMutex);
+
+ if ((mFetcher->isQuitting() || getFlags(LLWorkerClass::WCF_DELETE_REQUESTED)))
+ {
+ if (mState < DECODE_IMAGE)
+ {
+ return true; // abort
+ }
+ }
+
+ if(mImagePriority < F_ALMOST_ZERO)
+ {
+ if (mState == INIT || mState == LOAD_FROM_NETWORK || mState == LOAD_FROM_SIMULATOR)
+ {
+ return true; // abort
+ }
+ }
+ if(mState > CACHE_POST && !mCanUseNET && !mCanUseHTTP)
+ {
+ //nowhere to get data, abort.
+ return true ;
+ }
+
+ if (mFetcher->mDebugPause)
+ {
+ return false; // debug: don't do any work
+ }
+ if (mID == mFetcher->mDebugID)
+ {
+ mFetcher->mDebugCount++; // for setting breakpoints
+ }
+
+ if (mState != DONE)
+ {
+ mFetchTimer.reset();
+ }
+
+ if (mState == INIT)
+ {
+ mRawImage = NULL ;
+ mRequestedDiscard = -1;
+ mLoadedDiscard = -1;
+ mDecodedDiscard = -1;
+ mRequestedSize = 0;
+ mFileSize = 0;
+ mCachedSize = 0;
+ mLoaded = FALSE;
+ mSentRequest = UNSENT;
+ mDecoded = FALSE;
+ mWritten = FALSE;
+ delete[] mBuffer;
+ mBuffer = NULL;
+ mBufferSize = 0;
+ mHaveAllData = FALSE;
+ clearPackets(); // TODO: Shouldn't be necessary
+ mCacheReadHandle = LLTextureCache::nullHandle();
+ mCacheWriteHandle = LLTextureCache::nullHandle();
+ mState = LOAD_FROM_TEXTURE_CACHE;
+ mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE); // min desired size is TEXTURE_CACHE_ENTRY_SIZE
+ LL_DEBUGS("Texture") << mID << ": Priority: " << llformat("%8.0f",mImagePriority)
+ << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL;
+ // fall through
+ }
+
+ if (mState == LOAD_FROM_TEXTURE_CACHE)
+ {
+ if (mCacheReadHandle == LLTextureCache::nullHandle())
+ {
+ U32 cache_priority = mWorkPriority;
+ S32 offset = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
+ S32 size = mDesiredSize - offset;
+ if (size <= 0)
+ {
+ mState = CACHE_POST;
+ return false;
+ }
+ mFileSize = 0;
+ mLoaded = FALSE;
+
+ if (mUrl.compare(0, 7, "file://") == 0)
+ {
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
+
+ // read file from local disk
+ std::string filename = mUrl.substr(7, std::string::npos);
+ CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage);
+ mCacheReadHandle = mFetcher->mTextureCache->readFromCache(filename, mID, cache_priority,
+ offset, size, responder);
+ }
+ else if (mUrl.empty())
+ {
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
+
+ CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage);
+ mCacheReadHandle = mFetcher->mTextureCache->readFromCache(mID, cache_priority,
+ offset, size, responder);
+ }
+ else if(mCanUseHTTP)
+ {
+ if (!(mUrl.compare(0, 7, "http://") == 0))
+ {
+ // *TODO:?remove this warning
+ llwarns << "Unknown URL Type: " << mUrl << llendl;
+ }
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ mState = SEND_HTTP_REQ;
+ }
+ else
+ {
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ mState = LOAD_FROM_NETWORK;
+ }
+ }
+
+ if (mLoaded)
+ {
+ // Make sure request is complete. *TODO: make this auto-complete
+ if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, false))
+ {
+ mCacheReadHandle = LLTextureCache::nullHandle();
+ mState = CACHE_POST;
+ // fall through
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ if (mState == CACHE_POST)
+ {
+ mCachedSize = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
+ // Successfully loaded
+ if ((mCachedSize >= mDesiredSize) || mHaveAllData)
+ {
+ // we have enough data, decode it
+ llassert_always(mFormattedImage->getDataSize() > 0);
+ mLoadedDiscard = mDesiredDiscard;
+ mState = DECODE_IMAGE;
+ mWriteToCacheState = NOT_WRITE ;
+ LL_DEBUGS("Texture") << mID << ": Cached. Bytes: " << mFormattedImage->getDataSize()
+ << " Size: " << llformat("%dx%d",mFormattedImage->getWidth(),mFormattedImage->getHeight())
+ << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL;
+ // fall through
+ }
+ else
+ {
+ if (mUrl.compare(0, 7, "file://") == 0)
+ {
+ // failed to load local file, we're done.
+ return true;
+ }
+ // need more data
+ else
+ {
+ LL_DEBUGS("Texture") << mID << ": Not in Cache" << LL_ENDL;
+ mState = LOAD_FROM_NETWORK;
+ }
+ // fall through
+ }
+ }
+
+ if (mState == LOAD_FROM_NETWORK)
+ {
+ static LLCachedControl<bool> use_http(gSavedSettings,"ImagePipelineUseHTTP");
+
+// if (mHost != LLHost::invalid) get_url = false;
+ if ( use_http && mCanUseHTTP && mUrl.empty())//get http url.
+ {
+ LLViewerRegion* region = NULL;
+ if (mHost == LLHost::invalid)
+ region = gAgent.getRegion();
+ else
+ region = LLWorld::getInstance()->getRegion(mHost);
+
+ if (region)
+ {
+ std::string http_url = region->getHttpUrl() ;
+ if (!http_url.empty())
+ {
+ mUrl = http_url + "/?texture_id=" + mID.asString().c_str();
+ mWriteToCacheState = CAN_WRITE ; //because this texture has a fixed texture id.
+ }
+ else
+ {
+ mCanUseHTTP = false ;
+ }
+ }
+ else
+ {
+ // This will happen if not logged in or if a region deoes not have HTTP Texture enabled
+ //llwarns << "Region not found for host: " << mHost << llendl;
+ mCanUseHTTP = false;
+ }
+ }
+ if (mCanUseHTTP && !mUrl.empty())
+ {
+ mState = LLTextureFetchWorker::SEND_HTTP_REQ;
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ if(mWriteToCacheState != NOT_WRITE)
+ {
+ mWriteToCacheState = CAN_WRITE ;
+ }
+ // don't return, fall through to next state
+ }
+ else if (mSentRequest == UNSENT && mCanUseNET)
+ {
+ // Add this to the network queue and sit here.
+ // LLTextureFetch::update() will send off a request which will change our state
+ mWriteToCacheState = CAN_WRITE ;
+ mRequestedSize = mDesiredSize;
+ mRequestedDiscard = mDesiredDiscard;
+ mSentRequest = QUEUED;
+ mFetcher->addToNetworkQueue(this);
+ if (! mMetricsStartTime)
+ {
+ mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
+ }
+ LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE,
+ false,
+ LLImageBase::TYPE_AVATAR_BAKE == mType);
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+
+ return false;
+ }
+ else
+ {
+ // Shouldn't need to do anything here
+ //llassert_always(mFetcher->mNetworkQueue.find(mID) != mFetcher->mNetworkQueue.end());
+ // Make certain this is in the network queue
+ //mFetcher->addToNetworkQueue(this);
+ //if (! mMetricsStartTime)
+ //{
+ // mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
+ //}
+ //LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, false,
+ // LLImageBase::TYPE_AVATAR_BAKE == mType);
+ //setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ return false;
+ }
+ }
+
+ if (mState == LOAD_FROM_SIMULATOR)
+ {
+ if (mFormattedImage.isNull())
+ {
+ mFormattedImage = new LLImageJ2C;
+ }
+ if (processSimulatorPackets())
+ {
+ LL_DEBUGS("Texture") << mID << ": Loaded from Sim. Bytes: " << mFormattedImage->getDataSize() << LL_ENDL;
+ mFetcher->removeFromNetworkQueue(this, false);
+ if (mFormattedImage.isNull() || !mFormattedImage->getDataSize())
+ {
+ // processSimulatorPackets() failed
+// llwarns << "processSimulatorPackets() failed to load buffer" << llendl;
+ return true; // failed
+ }
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ mState = DECODE_IMAGE;
+ mWriteToCacheState = SHOULD_WRITE;
+
+ if (mMetricsStartTime)
+ {
+ LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE,
+ false,
+ LLImageBase::TYPE_AVATAR_BAKE == mType,
+ LLViewerAssetStatsFF::get_timestamp() - mMetricsStartTime);
+ mMetricsStartTime = 0;
+ }
+ LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE,
+ false,
+ LLImageBase::TYPE_AVATAR_BAKE == mType);
+ }
+ else
+ {
+ mFetcher->addToNetworkQueue(this); // failsafe
+ if (! mMetricsStartTime)
+ {
+ mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
+ }
+ LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE,
+ false,
+ LLImageBase::TYPE_AVATAR_BAKE == mType);
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ }
+ return false;
+ }
+
+ if (mState == SEND_HTTP_REQ)
+ {
+ if(mCanUseHTTP)
+ {
+ //NOTE:
+ //control the number of the http requests issued for:
+ //1, not openning too many file descriptors at the same time;
+ //2, control the traffic of http so udp gets bandwidth.
+ //
+ static const S32 MAX_NUM_OF_HTTP_REQUESTS_IN_QUEUE = 8 ;
+ if(mFetcher->getNumHTTPRequests() > MAX_NUM_OF_HTTP_REQUESTS_IN_QUEUE)
+ {
+ return false ; //wait.
+ }
+
+ mFetcher->removeFromNetworkQueue(this, false);
+
+ S32 cur_size = 0;
+ if (mFormattedImage.notNull())
+ {
+ cur_size = mFormattedImage->getDataSize(); // amount of data we already have
+ if (mFormattedImage->getDiscardLevel() == 0)
+ {
+ if(cur_size > 0)
+ {
+ // We already have all the data, just decode it
+ mLoadedDiscard = mFormattedImage->getDiscardLevel();
+ mState = DECODE_IMAGE;
+ return false;
+ }
+ else
+ {
+ return true ; //abort.
+ }
+ }
+ }
+ mRequestedSize = mDesiredSize;
+ mRequestedDiscard = mDesiredDiscard;
+ mRequestedSize -= cur_size;
+ S32 offset = cur_size;
+ mBufferSize = cur_size; // This will get modified by callbackHttpGet()
+
+ bool res = false;
+ if (!mUrl.empty())
+ {
+ mLoaded = FALSE;
+ mGetStatus = 0;
+ mGetReason.clear();
+ LL_DEBUGS("Texture") << "HTTP GET: " << mID << " Offset: " << offset
+ << " Bytes: " << mRequestedSize
+ << " Bandwidth(kbps): " << mFetcher->getTextureBandwidth() << "/" << mFetcher->mMaxBandwidth
+ << LL_ENDL;
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ mState = WAIT_HTTP_REQ;
+
+ mFetcher->addToHTTPQueue(mID);
+ if (! mMetricsStartTime)
+ {
+ mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
+ }
+ LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE,
+ true,
+ LLImageBase::TYPE_AVATAR_BAKE == mType);
+
+ // Will call callbackHttpGet when curl request completes
+ std::vector<std::string> headers;
+ headers.push_back("Accept: image/x-j2c");
+ res = mFetcher->mCurlGetRequest->getByteRange(mUrl, headers, offset, mRequestedSize,
+ new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, offset, true));
+ }
+ if (!res)
+ {
+ llwarns << "HTTP GET request failed for " << mID << llendl;
+ resetFormattedData();
+ ++mHTTPFailCount;
+ return true; // failed
+ }
+ // fall through
+ }
+ else //can not use http fetch.
+ {
+ return true ; //abort
+ }
+ }
+
+ if (mState == WAIT_HTTP_REQ)
+ {
+ if (mLoaded)
+ {
+ S32 cur_size = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0;
+ if (mRequestedSize < 0)
+ {
+ S32 max_attempts;
+ if (mGetStatus == HTTP_NOT_FOUND)
+ {
+ mHTTPFailCount = max_attempts = 1; // Don't retry
+ llwarns << "Texture missing from server (404): " << mUrl << llendl;
+
+ //roll back to try UDP
+ if(mCanUseNET)
+ {
+ mState = INIT ;
+ mCanUseHTTP = false ;
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ return false ;
+ }
+ }
+ else if (mGetStatus == HTTP_SERVICE_UNAVAILABLE)
+ {
+ // *TODO: Should probably introduce a timer here to delay future HTTP requsts
+ // for a short time (~1s) to ease server load? Ideally the server would queue
+ // requests instead of returning 503... we already limit the number pending.
+ ++mHTTPFailCount;
+ max_attempts = mHTTPFailCount+1; // Keep retrying
+ LL_INFOS_ONCE("Texture") << "Texture server busy (503): " << mUrl << LL_ENDL;
+ }
+ else
+ {
+ const S32 HTTP_MAX_RETRY_COUNT = 3;
+ max_attempts = HTTP_MAX_RETRY_COUNT + 1;
+ ++mHTTPFailCount;
+ llinfos << "HTTP GET failed for: " << mUrl
+ << " Status: " << mGetStatus << " Reason: '" << mGetReason << "'"
+ << " Attempt:" << mHTTPFailCount+1 << "/" << max_attempts << llendl;
+ }
+
+ if (mHTTPFailCount >= max_attempts)
+ {
+ if (cur_size > 0)
+ {
+ // Use available data
+ mLoadedDiscard = mFormattedImage->getDiscardLevel();
+ mState = DECODE_IMAGE;
+ return false;
+ }
+ else
+ {
+ resetFormattedData();
+ mState = DONE;
+ return true; // failed
+ }
+ }
+ else
+ {
+ mState = SEND_HTTP_REQ;
+ return false; // retry
+ }
+ }
+
+ llassert_always(mBufferSize == cur_size + mRequestedSize);
+ if(!mBufferSize)//no data received.
+ {
+ delete[] mBuffer;
+ mBuffer = NULL;
+
+ //abort.
+ mState = DONE;
+ return true;
+ }
+
+ if (mFormattedImage.isNull())
+ {
+ // For now, create formatted image based on extension
+ std::string extension = gDirUtilp->getExtension(mUrl);
+ mFormattedImage = LLImageFormatted::createFromType(LLImageBase::getCodecFromExtension(extension));
+ if (mFormattedImage.isNull())
+ {
+ mFormattedImage = new LLImageJ2C; // default
+ }
+ }
+
+ if (mHaveAllData && mRequestedDiscard == 0) //the image file is fully loaded.
+ {
+ mFileSize = mBufferSize;
+ }
+ else //the file size is unknown.
+ {
+ mFileSize = mBufferSize + 1 ; //flag the file is not fully loaded.
+ }
+
+ U8* buffer = new U8[mBufferSize];
+ if (cur_size > 0)
+ {
+ memcpy(buffer, mFormattedImage->getData(), cur_size);
+ }
+ memcpy(buffer + cur_size, mBuffer, mRequestedSize); // append
+ // NOTE: setData releases current data and owns new data (buffer)
+ mFormattedImage->setData(buffer, mBufferSize);
+ // delete temp data
+ delete[] mBuffer; // Note: not 'buffer' (assigned in setData())
+ mBuffer = NULL;
+ mBufferSize = 0;
+ mLoadedDiscard = mRequestedDiscard;
+ mState = DECODE_IMAGE;
+ if(mWriteToCacheState != NOT_WRITE)
+ {
+ mWriteToCacheState = SHOULD_WRITE ;
+ }
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ return false;
+ }
+ else
+ {
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ return false;
+ }
+ }
+
+ if (mState == DECODE_IMAGE)
+ {
+ static LLCachedControl<bool> textures_decode_disabled(gSavedSettings,"TextureDecodeDisabled");
+ if(textures_decode_disabled)
+ {
+ // for debug use, don't decode
+ mState = DONE;
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ return true;
+ }
+
+ if (mDesiredDiscard < 0)
+ {
+ // We aborted, don't decode
+ mState = DONE;
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ return true;
+ }
+
+ if (mFormattedImage->getDataSize() <= 0)
+ {
+ //llerrs << "Decode entered with invalid mFormattedImage. ID = " << mID << llendl;
+
+ //abort, don't decode
+ mState = DONE;
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ return true;
+ }
+ if (mLoadedDiscard < 0)
+ {
+ //llerrs << "Decode entered with invalid mLoadedDiscard. ID = " << mID << llendl;
+
+ //abort, don't decode
+ mState = DONE;
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ return true;
+ }
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
+ mRawImage = NULL;
+ mAuxImage = NULL;
+ llassert_always(mFormattedImage.notNull());
+ S32 discard = mHaveAllData ? 0 : mLoadedDiscard;
+ U32 image_priority = LLWorkerThread::PRIORITY_NORMAL | mWorkPriority;
+ mDecoded = FALSE;
+ mState = DECODE_IMAGE_UPDATE;
+ LL_DEBUGS("Texture") << mID << ": Decoding. Bytes: " << mFormattedImage->getDataSize() << " Discard: " << discard
+ << " All Data: " << mHaveAllData << LL_ENDL;
+ mDecodeHandle = mFetcher->mImageDecodeThread->decodeImage(mFormattedImage, image_priority, discard, mNeedsAux,
+ new DecodeResponder(mFetcher, mID, this));
+ // fall though
+ }
+
+ if (mState == DECODE_IMAGE_UPDATE)
+ {
+ if (mDecoded)
+ {
+ if (mDecodedDiscard < 0)
+ {
+ LL_DEBUGS("Texture") << mID << ": Failed to Decode." << LL_ENDL;
+ if (mCachedSize > 0 && !mInLocalCache && mRetryAttempt == 0)
+ {
+ // Cache file should be deleted, try again
+// llwarns << mID << ": Decode of cached file failed (removed), retrying" << llendl;
+ llassert_always(mDecodeHandle == 0);
+ mFormattedImage = NULL;
+ ++mRetryAttempt;
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ mState = INIT;
+ return false;
+ }
+ else
+ {
+// llwarns << "UNABLE TO LOAD TEXTURE: " << mID << " RETRIES: " << mRetryAttempt << llendl;
+ mState = DONE; // failed
+ }
+ }
+ else
+ {
+ llassert_always(mRawImage.notNull());
+ LL_DEBUGS("Texture") << mID << ": Decoded. Discard: " << mDecodedDiscard
+ << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL;
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ mState = WRITE_TO_CACHE;
+ }
+ // fall through
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ if (mState == WRITE_TO_CACHE)
+ {
+ if (mWriteToCacheState != SHOULD_WRITE || mFormattedImage.isNull())
+ {
+ // If we're in a local cache or we didn't actually receive any new data,
+ // or we failed to load anything, skip
+ mState = DONE;
+ return false;
+ }
+ S32 datasize = mFormattedImage->getDataSize();
+ if(mFileSize < datasize)//This could happen when http fetching and sim fetching mixed.
+ {
+ if(mHaveAllData)
+ {
+ mFileSize = datasize ;
+ }
+ else
+ {
+ mFileSize = datasize + 1 ; //flag not fully loaded.
+ }
+ }
+ llassert_always(datasize);
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); // Set priority first since Responder may change it
+ U32 cache_priority = mWorkPriority;
+ mWritten = FALSE;
+ mState = WAIT_ON_WRITE;
+ CacheWriteResponder* responder = new CacheWriteResponder(mFetcher, mID);
+ mCacheWriteHandle = mFetcher->mTextureCache->writeToCache(mID, cache_priority,
+ mFormattedImage->getData(), datasize,
+ mFileSize, responder);
+ // fall through
+ }
+
+ if (mState == WAIT_ON_WRITE)
+ {
+ if (writeToCacheComplete())
+ {
+ mState = DONE;
+ // fall through
+ }
+ else
+ {
+ if (mDesiredDiscard < mDecodedDiscard)
+ {
+ // We're waiting for this write to complete before we can receive more data
+ // (we can't touch mFormattedImage until the write completes)
+ // Prioritize the write
+ mFetcher->mTextureCache->prioritizeWrite(mCacheWriteHandle);
+ }
+ return false;
+ }
+ }
+
+ if (mState == DONE)
+ {
+ if (mDecodedDiscard >= 0 && mDesiredDiscard < mDecodedDiscard)
+ {
+ // More data was requested, return to INIT
+ mState = INIT;
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+ return false;
+ }
+ else
+ {
+ setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Called from MAIN thread
+void LLTextureFetchWorker::endWork(S32 param, bool aborted)
+{
+ if (mDecodeHandle != 0)
+ {
+ mFetcher->mImageDecodeThread->abortRequest(mDecodeHandle, false);
+ mDecodeHandle = 0;
+ }
+ mFormattedImage = NULL;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// virtual
+void LLTextureFetchWorker::finishWork(S32 param, bool completed)
+{
+ // The following are required in case the work was aborted
+ if (mCacheReadHandle != LLTextureCache::nullHandle())
+ {
+ mFetcher->mTextureCache->readComplete(mCacheReadHandle, true);
+ mCacheReadHandle = LLTextureCache::nullHandle();
+ }
+ if (mCacheWriteHandle != LLTextureCache::nullHandle())
+ {
+ mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true);
+ mCacheWriteHandle = LLTextureCache::nullHandle();
+ }
+}
+
+// virtual
+bool LLTextureFetchWorker::deleteOK()
+{
+ bool delete_ok = true;
+ // Allow any pending reads or writes to complete
+ if (mCacheReadHandle != LLTextureCache::nullHandle())
+ {
+ if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, true))
+ {
+ mCacheReadHandle = LLTextureCache::nullHandle();
+ }
+ else
+ {
+ delete_ok = false;
+ }
+ }
+ if (mCacheWriteHandle != LLTextureCache::nullHandle())
+ {
+ if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle))
+ {
+ mCacheWriteHandle = LLTextureCache::nullHandle();
+ }
+ else
+ {
+ delete_ok = false;
+ }
+ }
+
+ if ((haveWork() &&
+ // not ok to delete from these states
+ ((mState >= WRITE_TO_CACHE && mState <= WAIT_ON_WRITE))))
+ {
+ delete_ok = false;
+ }
+
+ return delete_ok;
+}
+
+void LLTextureFetchWorker::removeFromCache()
+{
+ if (!mInLocalCache)
+ {
+ mFetcher->mTextureCache->removeFromCache(mID);
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool LLTextureFetchWorker::processSimulatorPackets()
+{
+ if (mFormattedImage.isNull() || mRequestedSize < 0)
+ {
+ // not sure how we got here, but not a valid state, abort!
+ llassert_always(mDecodeHandle == 0);
+ mFormattedImage = NULL;
+ return true;
+ }
+
+ if (mLastPacket >= mFirstPacket)
+ {
+ S32 buffer_size = mFormattedImage->getDataSize();
+ for (S32 i = mFirstPacket; i<=mLastPacket; i++)
+ {
+ llassert_always(mPackets[i]);
+ buffer_size += mPackets[i]->mSize;
+ }
+ bool have_all_data = mLastPacket >= mTotalPackets-1;
+ if (mRequestedSize <= 0)
+ {
+ // We received a packed but haven't requested anything yet (edge case)
+ // Return true (we're "done") since we didn't request anything
+ return true;
+ }
+ if (buffer_size >= mRequestedSize || have_all_data)
+ {
+ /// We have enough (or all) data
+ if (have_all_data)
+ {
+ mHaveAllData = TRUE;
+ }
+ S32 cur_size = mFormattedImage->getDataSize();
+ if (buffer_size > cur_size)
+ {
+ /// We have new data
+ U8* buffer = new U8[buffer_size];
+ S32 offset = 0;
+ if (cur_size > 0 && mFirstPacket > 0)
+ {
+ memcpy(buffer, mFormattedImage->getData(), cur_size);
+ offset = cur_size;
+ }
+ for (S32 i=mFirstPacket; i<=mLastPacket; i++)
+ {
+ memcpy(buffer + offset, mPackets[i]->mData, mPackets[i]->mSize);
+ offset += mPackets[i]->mSize;
+ }
+ // NOTE: setData releases current data
+ mFormattedImage->setData(buffer, buffer_size);
+ }
+ mLoadedDiscard = mRequestedDiscard;
+ return true;
+ }
+ }
+ return false;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+S32 LLTextureFetchWorker::callbackHttpGet(const LLChannelDescriptors& channels,
+ const LLIOPipe::buffer_ptr_t& buffer,
+ bool partial, bool success)
+{
+ S32 data_size = 0 ;
+
+ LLMutexLock lock(&mWorkMutex);
+
+ if (mState != WAIT_HTTP_REQ)
+ {
+ llwarns << "callbackHttpGet for unrequested fetch worker: " << mID
+ << " req=" << mSentRequest << " state= " << mState << llendl;
+ return data_size;
+ }
+ if (mLoaded)
+ {
+ llwarns << "Duplicate callback for " << mID.asString() << llendl;
+ return data_size ; // ignore duplicate callback
+ }
+ if (success)
+ {
+ // get length of stream:
+ data_size = buffer->countAfter(channels.in(), NULL);
+
+ LL_DEBUGS("Texture") << "HTTP RECEIVED: " << mID.asString() << " Bytes: " << data_size << LL_ENDL;
+ if (data_size > 0)
+ {
+ // *TODO: set the formatted image data here directly to avoid the copy
+ mBuffer = new U8[data_size];
+ buffer->readAfter(channels.in(), NULL, mBuffer, data_size);
+ mBufferSize += data_size;
+ if (data_size < mRequestedSize && mRequestedDiscard == 0)
+ {
+ mHaveAllData = TRUE;
+ }
+ else if (data_size > mRequestedSize)
+ {
+ // *TODO: This shouldn't be happening any more
+ llwarns << "data_size = " << data_size << " > requested: " << mRequestedSize << llendl;
+ mHaveAllData = TRUE;
+ llassert_always(mDecodeHandle == 0);
+ mFormattedImage = NULL; // discard any previous data we had
+ mBufferSize = data_size;
+ }
+ }
+ else
+ {
+ // We requested data but received none (and no error),
+ // so presumably we have all of it
+ mHaveAllData = TRUE;
+ }
+ mRequestedSize = data_size;
+ }
+ else
+ {
+ mRequestedSize = -1; // error
+ }
+ mLoaded = TRUE;
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+
+ return data_size ;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void LLTextureFetchWorker::callbackCacheRead(bool success, LLImageFormatted* image,
+ S32 imagesize, BOOL islocal)
+{
+ LLMutexLock lock(&mWorkMutex);
+ if (mState != LOAD_FROM_TEXTURE_CACHE)
+ {
+// llwarns << "Read callback for " << mID << " with state = " << mState << llendl;
+ return;
+ }
+ if (success)
+ {
+ llassert_always(imagesize >= 0);
+ mFileSize = imagesize;
+ mFormattedImage = image;
+ mImageCodec = image->getCodec();
+ mInLocalCache = islocal;
+ if (mFileSize != 0 && mFormattedImage->getDataSize() >= mFileSize)
+ {
+ mHaveAllData = TRUE;
+ }
+ }
+ mLoaded = TRUE;
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+}
+
+void LLTextureFetchWorker::callbackCacheWrite(bool success)
+{
+ LLMutexLock lock(&mWorkMutex);
+ if (mState != WAIT_ON_WRITE)
+ {
+// llwarns << "Write callback for " << mID << " with state = " << mState << llendl;
+ return;
+ }
+ mWritten = TRUE;
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void LLTextureFetchWorker::callbackDecoded(bool success, LLImageRaw* raw, LLImageRaw* aux)
+{
+ LLMutexLock lock(&mWorkMutex);
+ if (mDecodeHandle == 0)
+ {
+ return; // aborted, ignore
+ }
+ if (mState != DECODE_IMAGE_UPDATE)
+ {
+// llwarns << "Decode callback for " << mID << " with state = " << mState << llendl;
+ mDecodeHandle = 0;
+ return;
+ }
+ llassert_always(mFormattedImage.notNull());
+
+ mDecodeHandle = 0;
+ if (success)
+ {
+ llassert_always(raw);
+ mRawImage = raw;
+ mAuxImage = aux;
+ mDecodedDiscard = mFormattedImage->getDiscardLevel();
+ LL_DEBUGS("Texture") << mID << ": Decode Finished. Discard: " << mDecodedDiscard
+ << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL;
+ }
+ else
+ {
+ llwarns << "DECODE FAILED: " << mID << " Discard: " << (S32)mFormattedImage->getDiscardLevel() << llendl;
+ removeFromCache();
+ mDecodedDiscard = -1; // Redundant, here for clarity and paranoia
+ }
+ mDecoded = TRUE;
+// llinfos << mID << " : DECODE COMPLETE " << llendl;
+ setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool LLTextureFetchWorker::writeToCacheComplete()
+{
+ // Complete write to cache
+ if (mCacheWriteHandle != LLTextureCache::nullHandle())
+ {
+ if (!mWritten)
+ {
+ return false;
+ }
+ if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle))
+ {
+ mCacheWriteHandle = LLTextureCache::nullHandle();
+ }
+ else
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+// public
+
+LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded, bool qa_mode)
+ : LLWorkerThread("TextureFetch", threaded),
+ mDebugCount(0),
+ mDebugPause(FALSE),
+ mPacketCount(0),
+ mBadPacketCount(0),
+ mQueueMutex(getAPRPool()),
+ mNetworkQueueMutex(getAPRPool()),
+ mTextureCache(cache),
+ mImageDecodeThread(imagedecodethread),
+ mTextureBandwidth(0),
+ mHTTPTextureBits(0),
+ mTotalHTTPRequests(0),
+ mCurlGetRequest(NULL),
+ mQAMode(qa_mode)
+{
+ mCurlPOSTRequestCount = 0;
+ mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS");
+ mTextureInfo.setUpLogging(gSavedSettings.getBOOL("LogTextureDownloadsToViewerLog"), gSavedSettings.getBOOL("LogTextureDownloadsToSimulator"), gSavedSettings.getU32("TextureLoggingThreshold"));
+}
+
+LLTextureFetch::~LLTextureFetch()
+{
+ clearDeleteList() ;
+
+ while (! mCommands.empty())
+ {
+ TFRequest * req(mCommands.front());
+ mCommands.erase(mCommands.begin());
+ delete req;
+ }
+
+ // ~LLQueuedThread() called here
+}
+
+bool LLTextureFetch::createRequest(const std::string& url, const LLUUID& id, const LLHost& host, F32 priority,
+ S32 w, S32 h, S32 c, S32 desired_discard, bool needs_aux, bool can_use_http)
+{
+ if (mDebugPause)
+ {
+ return false;
+ }
+
+ LLTextureFetchWorker* worker = getWorker(id) ;
+ if (worker)
+ {
+ if (worker->mHost != host)
+ {
+ llwarns << "LLTextureFetch::createRequest " << id << " called with multiple hosts: "
+ << host << " != " << worker->mHost << llendl;
+ removeRequest(worker, true);
+ worker = NULL;
+ return false;
+ }
+ }
+
+ S32 desired_size;
+ std::string exten = gDirUtilp->getExtension(url);
+ if (!url.empty() && (!exten.empty() && LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C))
+ {
+ // Only do partial requests for J2C at the moment
+ desired_size = MAX_IMAGE_DATA_SIZE;
+ desired_discard = 0;
+ }
+ else if (desired_discard == 0)
+ {
+ // if we want the entire image, and we know its size, then get it all
+ // (calcDataSizeJ2C() below makes assumptions about how the image
+ // was compressed - this code ensures that when we request the entire image,
+ // we really do get it.)
+ desired_size = MAX_IMAGE_DATA_SIZE;
+ }
+ else if (w*h*c > 0)
+ {
+ // If the requester knows the dimensions of the image,
+ // this will calculate how much data we need without having to parse the header
+
+ desired_size = LLImageJ2C::calcDataSizeJ2C(w, h, c, desired_discard);
+ }
+ else
+ {
+ desired_size = TEXTURE_CACHE_ENTRY_SIZE;
+ desired_discard = MAX_DISCARD_LEVEL;
+ }
+
+
+ if (worker)
+ {
+ if (worker->wasAborted())
+ {
+ return false; // need to wait for previous aborted request to complete
+ }
+ worker->lockWorkMutex();
+ worker->mActiveCount++;
+ worker->mNeedsAux = needs_aux;
+ worker->setImagePriority(priority);
+ worker->setDesiredDiscard(desired_discard, desired_size);
+ worker->setCanUseHTTP(can_use_http) ;
+ if (!worker->haveWork())
+ {
+ worker->mState = LLTextureFetchWorker::INIT;
+ worker->unlockWorkMutex();
+
+ worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
+ }
+ else
+ {
+ worker->unlockWorkMutex();
+ }
+ }
+ else
+ {
+ worker = new LLTextureFetchWorker(this, url, id, host, priority, desired_discard, desired_size);
+ lockQueue() ;
+ mRequestMap[id] = worker;
+ unlockQueue() ;
+
+ worker->lockWorkMutex();
+ worker->mActiveCount++;
+ worker->mNeedsAux = needs_aux;
+ worker->setCanUseHTTP(can_use_http) ;
+ worker->unlockWorkMutex();
+ }
+
+// llinfos << "REQUESTED: " << id << " Discard: " << desired_discard << llendl;
+ return true;
+}
+
+// protected
+void LLTextureFetch::addToNetworkQueue(LLTextureFetchWorker* worker)
+{
+ lockQueue() ;
+ bool in_request_map = (mRequestMap.find(worker->mID) != mRequestMap.end()) ;
+ unlockQueue() ;
+
+ LLMutexLock lock(&mNetworkQueueMutex);
+ if (in_request_map)
+ {
+ // only add to the queue if in the request map
+ // i.e. a delete has not been requested
+ mNetworkQueue.insert(worker->mID);
+ }
+ for (cancel_queue_t::iterator iter1 = mCancelQueue.begin();
+ iter1 != mCancelQueue.end(); ++iter1)
+ {
+ iter1->second.erase(worker->mID);
+ }
+}
+
+void LLTextureFetch::removeFromNetworkQueue(LLTextureFetchWorker* worker, bool cancel)
+{
+ LLMutexLock lock(&mNetworkQueueMutex);
+ size_t erased = mNetworkQueue.erase(worker->mID);
+ if (cancel && erased > 0)
+ {
+ mCancelQueue[worker->mHost].insert(worker->mID);
+ }
+}
+
+// protected
+void LLTextureFetch::addToHTTPQueue(const LLUUID& id)
+{
+ LLMutexLock lock(&mNetworkQueueMutex);
+ mHTTPTextureQueue.insert(id);
+ mTotalHTTPRequests++;
+}
+
+void LLTextureFetch::removeFromHTTPQueue(const LLUUID& id, S32 received_size)
+{
+ LLMutexLock lock(&mNetworkQueueMutex);
+ mHTTPTextureQueue.erase(id);
+ mHTTPTextureBits += received_size * 8; // Approximate - does not include header bits
+}
+
+void LLTextureFetch::deleteRequest(const LLUUID& id, bool cancel)
+{
+ lockQueue() ;
+ LLTextureFetchWorker* worker = getWorkerAfterLock(id);
+ if (worker)
+ {
+ size_t erased_1 = mRequestMap.erase(worker->mID);
+ unlockQueue() ;
+
+ llassert_always(erased_1 > 0) ;
+
+ removeFromNetworkQueue(worker, cancel);
+ llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ;
+
+ worker->scheduleDelete();
+ }
+ else
+ {
+ unlockQueue() ;
+ }
+}
+
+void LLTextureFetch::removeRequest(LLTextureFetchWorker* worker, bool cancel)
+{
+ lockQueue() ;
+ size_t erased_1 = mRequestMap.erase(worker->mID);
+ unlockQueue() ;
+
+ llassert_always(erased_1 > 0) ;
+ removeFromNetworkQueue(worker, cancel);
+ llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ;
+
+ worker->scheduleDelete();
+}
+
+S32 LLTextureFetch::getNumRequests()
+{
+ lockQueue() ;
+ S32 size = (S32)mRequestMap.size();
+ unlockQueue() ;
+
+ return size ;
+}
+
+S32 LLTextureFetch::getNumHTTPRequests()
+{
+ mNetworkQueueMutex.lock() ;
+ S32 size = (S32)mHTTPTextureQueue.size();
+ mNetworkQueueMutex.unlock() ;
+
+ return size ;
+}
+
+U32 LLTextureFetch::getTotalNumHTTPRequests()
+{
+ mNetworkQueueMutex.lock() ;
+ U32 size = mTotalHTTPRequests ;
+ mNetworkQueueMutex.unlock() ;
+
+ return size ;
+}
+
+// call lockQueue() first!
+LLTextureFetchWorker* LLTextureFetch::getWorkerAfterLock(const LLUUID& id)
+{
+ LLTextureFetchWorker* res = NULL;
+ map_t::iterator iter = mRequestMap.find(id);
+ if (iter != mRequestMap.end())
+ {
+ res = iter->second;
+ }
+ return res;
+}
+
+LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id)
+{
+ LLMutexLock lock(&mQueueMutex) ;
+
+ return getWorkerAfterLock(id) ;
+}
+
+
+bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level,
+ LLPointer<LLImageRaw>& raw, LLPointer<LLImageRaw>& aux)
+{
+ bool res = false;
+ LLTextureFetchWorker* worker = getWorker(id);
+ if (worker)
+ {
+ if (worker->wasAborted())
+ {
+ res = true;
+ }
+ else if (!worker->haveWork())
+ {
+ // Should only happen if we set mDebugPause...
+ if (!mDebugPause)
+ {
+// llwarns << "Adding work for inactive worker: " << id << llendl;
+ worker->addWork(0, LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
+ }
+ }
+ else if (worker->checkWork())
+ {
+ worker->lockWorkMutex();
+ discard_level = worker->mDecodedDiscard;
+ raw = worker->mRawImage;
+ aux = worker->mAuxImage;
+ res = true;
+ LL_DEBUGS("Texture") << id << ": Request Finished. State: " << worker->mState << " Discard: " << discard_level << LL_ENDL;
+ worker->unlockWorkMutex();
+ }
+ else
+ {
+ worker->lockWorkMutex();
+ if ((worker->mDecodedDiscard >= 0) &&
+ (worker->mDecodedDiscard < discard_level || discard_level < 0) &&
+ (worker->mState >= LLTextureFetchWorker::WAIT_ON_WRITE))
+ {
+ // Not finished, but data is ready
+ discard_level = worker->mDecodedDiscard;
+ raw = worker->mRawImage;
+ aux = worker->mAuxImage;
+ }
+ worker->unlockWorkMutex();
+ }
+ }
+ else
+ {
+ res = true;
+ }
+ return res;
+}
+
+bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority)
+{
+ bool res = false;
+ LLTextureFetchWorker* worker = getWorker(id);
+ if (worker)
+ {
+ worker->lockWorkMutex();
+ worker->setImagePriority(priority);
+ worker->unlockWorkMutex();
+ res = true;
+ }
+ return res;
+}
+
+// Replicates and expands upon the base class's
+// getPending() implementation. getPending() and
+// runCondition() replicate one another's logic to
+// an extent and are sometimes used for the same
+// function (deciding whether or not to sleep/pause
+// a thread). So the implementations need to stay
+// in step, at least until this can be refactored and
+// the redundancy eliminated.
+//
+// May be called from any thread
+
+//virtual
+S32 LLTextureFetch::getPending()
+{
+ S32 res;
+ lockData();
+ {
+ LLMutexLock lock(&mQueueMutex);
+
+ res = mRequestQueue.size();
+ res += mCurlPOSTRequestCount;
+ res += mCommands.size();
+ }
+ unlockData();
+ return res;
+}
+
+// virtual
+bool LLTextureFetch::runCondition()
+{
+ // Caller is holding the lock on LLThread's condition variable.
+
+ // LLQueuedThread, unlike its base class LLThread, makes this a
+ // private method which is unfortunate. I want to use it directly
+ // but I'm going to have to re-implement the logic here (or change
+ // declarations, which I don't want to do right now).
+ //
+ // Changes here may need to be reflected in getPending().
+
+ bool have_no_commands(false);
+ {
+ LLMutexLock lock(&mQueueMutex);
+
+ have_no_commands = mCommands.empty();
+ }
+
+ bool have_no_curl_requests(0 == mCurlPOSTRequestCount);
+
+ return ! (have_no_commands
+ && have_no_curl_requests
+ && (mRequestQueue.empty() && mIdleThread)); // From base class
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// MAIN THREAD (unthreaded envs), WORKER THREAD (threaded envs)
+void LLTextureFetch::commonUpdate()
+{
+ // Run a cross-thread command, if any.
+ cmdDoWork();
+
+ // Update Curl on same thread as mCurlGetRequest was constructed
+ S32 processed = mCurlGetRequest->process();
+ if (processed > 0)
+ {
+ lldebugs << "processed: " << processed << " messages." << llendl;
+ }
+}
+
+
+// MAIN THREAD
+//virtual
+S32 LLTextureFetch::update(U32 max_time_ms)
+{
+ static LLCachedControl<F32> band_width(gSavedSettings,"ThrottleBandwidthKBPS");
+
+ {
+ mNetworkQueueMutex.lock() ;
+ mMaxBandwidth = band_width ;
+
+ gTextureList.sTextureBits += mHTTPTextureBits ;
+ mHTTPTextureBits = 0 ;
+
+ mNetworkQueueMutex.unlock() ;
+ }
+
+ S32 res = LLWorkerThread::update(max_time_ms);
+
+ if (!mDebugPause)
+ {
+ sendRequestListToSimulators();
+ }
+
+ if (!mThreaded)
+ {
+ commonUpdate();
+ }
+
+ return res;
+}
+
+//called in the MAIN thread after the TextureCacheThread shuts down.
+void LLTextureFetch::shutDownTextureCacheThread()
+{
+ if(mTextureCache)
+ {
+ llassert_always(mTextureCache->isQuitting() || mTextureCache->isStopped()) ;
+ mTextureCache = NULL ;
+ }
+}
+
+//called in the MAIN thread after the ImageDecodeThread shuts down.
+void LLTextureFetch::shutDownImageDecodeThread()
+{
+ if(mImageDecodeThread)
+ {
+ llassert_always(mImageDecodeThread->isQuitting() || mImageDecodeThread->isStopped()) ;
+ mImageDecodeThread = NULL ;
+ }
+}
+
+// WORKER THREAD
+void LLTextureFetch::startThread()
+{
+ // Construct mCurlGetRequest from Worker Thread
+ mCurlGetRequest = new LLCurlRequest();
+}
+
+// WORKER THREAD
+void LLTextureFetch::endThread()
+{
+ // Destroy mCurlGetRequest from Worker Thread
+ delete mCurlGetRequest;
+ mCurlGetRequest = NULL;
+}
+
+// WORKER THREAD
+void LLTextureFetch::threadedUpdate()
+{
+ llassert_always(mCurlGetRequest);
+
+ // Limit update frequency
+ const F32 PROCESS_TIME = 0.05f;
+ static LLFrameTimer process_timer;
+ if (process_timer.getElapsedTimeF32() < PROCESS_TIME)
+ {
+ return;
+ }
+ process_timer.reset();
+
+ commonUpdate();
+
+#if 0
+ const F32 INFO_TIME = 1.0f;
+ static LLFrameTimer info_timer;
+ if (info_timer.getElapsedTimeF32() >= INFO_TIME)
+ {
+ S32 q = mCurlGetRequest->getQueued();
+ if (q > 0)
+ {
+ llinfos << "Queued gets: " << q << llendl;
+ info_timer.reset();
+ }
+ }
+#endif
+
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void LLTextureFetch::sendRequestListToSimulators()
+{
+ // All requests
+ const F32 REQUEST_DELTA_TIME = 0.10f; // 10 fps
+
+ // Sim requests
+ const S32 IMAGES_PER_REQUEST = 50;
+ const F32 SIM_LAZY_FLUSH_TIMEOUT = 10.0f; // temp
+ const F32 MIN_REQUEST_TIME = 1.0f;
+ const F32 MIN_DELTA_PRIORITY = 1000.f;
+
+ // Periodically, gather the list of textures that need data from the network
+ // And send the requests out to the simulators
+ static LLFrameTimer timer;
+ if (timer.getElapsedTimeF32() < REQUEST_DELTA_TIME)
+ {
+ return;
+ }
+ timer.reset();
+
+ // Send requests
+ typedef std::set<LLTextureFetchWorker*,LLTextureFetchWorker::Compare> request_list_t;
+ typedef std::map< LLHost, request_list_t > work_request_map_t;
+ work_request_map_t requests;
+ {
+ LLMutexLock lock2(&mNetworkQueueMutex);
+ for (queue_t::iterator iter = mNetworkQueue.begin(); iter != mNetworkQueue.end(); )
+ {
+ queue_t::iterator curiter = iter++;
+ LLTextureFetchWorker* req = getWorker(*curiter);
+ if (!req)
+ {
+ mNetworkQueue.erase(curiter);
+ continue; // paranoia
+ }
+ if ((req->mState != LLTextureFetchWorker::LOAD_FROM_NETWORK) &&
+ (req->mState != LLTextureFetchWorker::LOAD_FROM_SIMULATOR))
+ {
+ // We already received our URL, remove from the queue
+ llwarns << "Worker: " << req->mID << " in mNetworkQueue but in wrong state: " << req->mState << llendl;
+ mNetworkQueue.erase(curiter);
+ continue;
+ }
+ if (req->mID == mDebugID)
+ {
+ mDebugCount++; // for setting breakpoints
+ }
+ if (req->mSentRequest == LLTextureFetchWorker::SENT_SIM &&
+ req->mTotalPackets > 0 &&
+ req->mLastPacket >= req->mTotalPackets-1)
+ {
+ // We have all the packets... make sure this is high priority
+// req->setPriority(LLWorkerThread::PRIORITY_HIGH | req->mWorkPriority);
+ continue;
+ }
+ F32 elapsed = req->mRequestedTimer.getElapsedTimeF32();
+ {
+ F32 delta_priority = llabs(req->mRequestedPriority - req->mImagePriority);
+ if ((req->mSimRequestedDiscard != req->mDesiredDiscard) ||
+ (delta_priority > MIN_DELTA_PRIORITY && elapsed >= MIN_REQUEST_TIME) ||
+ (elapsed >= SIM_LAZY_FLUSH_TIMEOUT))
+ {
+ requests[req->mHost].insert(req);
+ }
+ }
+ }
+ }
+
+ for (work_request_map_t::iterator iter1 = requests.begin();
+ iter1 != requests.end(); ++iter1)
+ {
+ LLHost host = iter1->first;
+ // invalid host = use agent host
+ if (host == LLHost::invalid)
+ {
+ host = gAgent.getRegionHost();
+ }
+
+ S32 sim_request_count = 0;
+
+ for (request_list_t::iterator iter2 = iter1->second.begin();
+ iter2 != iter1->second.end(); ++iter2)
+ {
+ LLTextureFetchWorker* req = *iter2;
+ if (gMessageSystem)
+ {
+ if (req->mSentRequest != LLTextureFetchWorker::SENT_SIM)
+ {
+ // Initialize packet data based on data read from cache
+ req->lockWorkMutex();
+ req->setupPacketData();
+ req->unlockWorkMutex();
+ }
+ if (0 == sim_request_count)
+ {
+ gMessageSystem->newMessageFast(_PREHASH_RequestImage);
+ gMessageSystem->nextBlockFast(_PREHASH_AgentData);
+ gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
+ gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
+ }
+ S32 packet = req->mLastPacket + 1;
+ gMessageSystem->nextBlockFast(_PREHASH_RequestImage);
+ gMessageSystem->addUUIDFast(_PREHASH_Image, req->mID);
+ gMessageSystem->addS8Fast(_PREHASH_DiscardLevel, (S8)req->mDesiredDiscard);
+ gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, req->mImagePriority);
+ gMessageSystem->addU32Fast(_PREHASH_Packet, packet);
+ gMessageSystem->addU8Fast(_PREHASH_Type, req->mType);
+// llinfos << "IMAGE REQUEST: " << req->mID << " Discard: " << req->mDesiredDiscard
+// << " Packet: " << packet << " Priority: " << req->mImagePriority << llendl;
+
+ static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog");
+ static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator");
+ if (log_to_viewer_log || log_to_sim)
+ {
+ mTextureInfo.setRequestStartTime(req->mID, LLTimer::getTotalTime());
+ mTextureInfo.setRequestOffset(req->mID, 0);
+ mTextureInfo.setRequestSize(req->mID, 0);
+ mTextureInfo.setRequestType(req->mID, LLTextureInfoDetails::REQUEST_TYPE_UDP);
+ }
+
+ req->lockWorkMutex();
+ req->mSentRequest = LLTextureFetchWorker::SENT_SIM;
+ req->mSimRequestedDiscard = req->mDesiredDiscard;
+ req->mRequestedPriority = req->mImagePriority;
+ req->mRequestedTimer.reset();
+ req->unlockWorkMutex();
+ sim_request_count++;
+ if (sim_request_count >= IMAGES_PER_REQUEST)
+ {
+// llinfos << "REQUESTING " << sim_request_count << " IMAGES FROM HOST: " << host.getIPString() << llendl;
+
+ gMessageSystem->sendSemiReliable(host, NULL, NULL);
+ sim_request_count = 0;
+ }
+ }
+ }
+ if (gMessageSystem && sim_request_count > 0 && sim_request_count < IMAGES_PER_REQUEST)
+ {
+// llinfos << "REQUESTING " << sim_request_count << " IMAGES FROM HOST: " << host.getIPString() << llendl;
+ gMessageSystem->sendSemiReliable(host, NULL, NULL);
+ sim_request_count = 0;
+ }
+ }
+
+ // Send cancelations
+ {
+ LLMutexLock lock2(&mNetworkQueueMutex);
+ if (gMessageSystem && !mCancelQueue.empty())
+ {
+ for (cancel_queue_t::iterator iter1 = mCancelQueue.begin();
+ iter1 != mCancelQueue.end(); ++iter1)
+ {
+ LLHost host = iter1->first;
+ if (host == LLHost::invalid)
+ {
+ host = gAgent.getRegionHost();
+ }
+ S32 request_count = 0;
+ for (queue_t::iterator iter2 = iter1->second.begin();
+ iter2 != iter1->second.end(); ++iter2)
+ {
+ if (0 == request_count)
+ {
+ gMessageSystem->newMessageFast(_PREHASH_RequestImage);
+ gMessageSystem->nextBlockFast(_PREHASH_AgentData);
+ gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
+ gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
+ }
+ gMessageSystem->nextBlockFast(_PREHASH_RequestImage);
+ gMessageSystem->addUUIDFast(_PREHASH_Image, *iter2);
+ gMessageSystem->addS8Fast(_PREHASH_DiscardLevel, -1);
+ gMessageSystem->addF32Fast(_PREHASH_DownloadPriority, 0);
+ gMessageSystem->addU32Fast(_PREHASH_Packet, 0);
+ gMessageSystem->addU8Fast(_PREHASH_Type, 0);
+// llinfos << "CANCELING IMAGE REQUEST: " << (*iter2) << llendl;
+
+ request_count++;
+ if (request_count >= IMAGES_PER_REQUEST)
+ {
+ gMessageSystem->sendSemiReliable(host, NULL, NULL);
+ request_count = 0;
+ }
+ }
+ if (request_count > 0 && request_count < IMAGES_PER_REQUEST)
+ {
+ gMessageSystem->sendSemiReliable(host, NULL, NULL);
+ }
+ }
+ mCancelQueue.clear();
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size)
+{
+ mRequestedTimer.reset();
+ if (index >= mTotalPackets)
+ {
+// llwarns << "Received Image Packet " << index << " > max: " << mTotalPackets << " for image: " << mID << llendl;
+ return false;
+ }
+ if (index > 0 && index < mTotalPackets-1 && size != MAX_IMG_PACKET_SIZE)
+ {
+// llwarns << "Received bad sized packet: " << index << ", " << size << " != " << MAX_IMG_PACKET_SIZE << " for image: " << mID << llendl;
+ return false;
+ }
+
+ if (index >= (S32)mPackets.size())
+ {
+ mPackets.resize(index+1, (PacketData*)NULL); // initializes v to NULL pointers
+ }
+ else if (mPackets[index] != NULL)
+ {
+// llwarns << "Received duplicate packet: " << index << " for image: " << mID << llendl;
+ return false;
+ }
+
+ mPackets[index] = new PacketData(data, size);
+ while (mLastPacket+1 < (S32)mPackets.size() && mPackets[mLastPacket+1] != NULL)
+ {
+ ++mLastPacket;
+ }
+ return true;
+}
+
+bool LLTextureFetch::receiveImageHeader(const LLHost& host, const LLUUID& id, U8 codec, U16 packets, U32 totalbytes,
+ U16 data_size, U8* data)
+{
+ LLTextureFetchWorker* worker = getWorker(id);
+ bool res = true;
+
+ ++mPacketCount;
+
+ if (!worker)
+ {
+// llwarns << "Received header for non active worker: " << id << llendl;
+ res = false;
+ }
+ else if (worker->mState != LLTextureFetchWorker::LOAD_FROM_NETWORK ||
+ worker->mSentRequest != LLTextureFetchWorker::SENT_SIM)
+ {
+// llwarns << "receiveImageHeader for worker: " << id
+// << " in state: " << LLTextureFetchWorker::sStateDescs[worker->mState]
+// << " sent: " << worker->mSentRequest << llendl;
+ res = false;
+ }
+ else if (worker->mLastPacket != -1)
+ {
+ // check to see if we've gotten this packet before
+// llwarns << "Received duplicate header for: " << id << llendl;
+ res = false;
+ }
+ else if (!data_size)
+ {
+// llwarns << "Img: " << id << ":" << " Empty Image Header" << llendl;
+ res = false;
+ }
+ if (!res)
+ {
+ ++mBadPacketCount;
+ mNetworkQueueMutex.lock() ;
+ mCancelQueue[host].insert(id);
+ mNetworkQueueMutex.unlock() ;
+ return false;
+ }
+
+ worker->lockWorkMutex();
+
+ // Copy header data into image object
+ worker->mImageCodec = codec;
+ worker->mTotalPackets = packets;
+ worker->mFileSize = (S32)totalbytes;
+ llassert_always(totalbytes > 0);
+ llassert_always(data_size == FIRST_PACKET_SIZE || data_size == worker->mFileSize);
+ res = worker->insertPacket(0, data, data_size);
+ worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
+ worker->mState = LLTextureFetchWorker::LOAD_FROM_SIMULATOR;
+ worker->unlockWorkMutex();
+ return res;
+}
+
+bool LLTextureFetch::receiveImagePacket(const LLHost& host, const LLUUID& id, U16 packet_num, U16 data_size, U8* data)
+{
+ LLTextureFetchWorker* worker = getWorker(id);
+ bool res = true;
+
+ ++mPacketCount;
+
+ if (!worker)
+ {
+// llwarns << "Received packet " << packet_num << " for non active worker: " << id << llendl;
+ res = false;
+ }
+ else if (worker->mLastPacket == -1)
+ {
+// llwarns << "Received packet " << packet_num << " before header for: " << id << llendl;
+ res = false;
+ }
+ else if (!data_size)
+ {
+// llwarns << "Img: " << id << ":" << " Empty Image Header" << llendl;
+ res = false;
+ }
+ if (!res)
+ {
+ ++mBadPacketCount;
+ mNetworkQueueMutex.lock() ;
+ mCancelQueue[host].insert(id);
+ mNetworkQueueMutex.unlock() ;
+ return false;
+ }
+
+ worker->lockWorkMutex();
+
+ res = worker->insertPacket(packet_num, data, data_size);
+
+ if ((worker->mState == LLTextureFetchWorker::LOAD_FROM_SIMULATOR) ||
+ (worker->mState == LLTextureFetchWorker::LOAD_FROM_NETWORK))
+ {
+ worker->setPriority(LLWorkerThread::PRIORITY_HIGH | worker->mWorkPriority);
+ worker->mState = LLTextureFetchWorker::LOAD_FROM_SIMULATOR;
+ }
+ else
+ {
+// llwarns << "receiveImagePacket " << packet_num << "/" << worker->mLastPacket << " for worker: " << id
+// << " in state: " << LLTextureFetchWorker::sStateDescs[worker->mState] << llendl;
+ removeFromNetworkQueue(worker, true); // failsafe
+ }
+
+ if(packet_num >= (worker->mTotalPackets - 1))
+ {
+ static LLCachedControl<bool> log_to_viewer_log(gSavedSettings,"LogTextureDownloadsToViewerLog");
+ static LLCachedControl<bool> log_to_sim(gSavedSettings,"LogTextureDownloadsToSimulator");
+
+ if (log_to_viewer_log || log_to_sim)
+ {
+ U64 timeNow = LLTimer::getTotalTime();
+ mTextureInfo.setRequestSize(id, worker->mFileSize);
+ mTextureInfo.setRequestCompleteTimeAndLog(id, timeNow);
+ }
+ }
+ worker->unlockWorkMutex();
+
+ return res;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+BOOL LLTextureFetch::isFromLocalCache(const LLUUID& id)
+{
+ BOOL from_cache = FALSE ;
+
+ LLTextureFetchWorker* worker = getWorker(id);
+ if (worker)
+ {
+ worker->lockWorkMutex() ;
+ from_cache = worker->mInLocalCache ;
+ worker->unlockWorkMutex() ;
+ }
+
+ return from_cache ;
+}
+
+S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& requested_priority_p,
+ U32& fetch_priority_p, F32& fetch_dtime_p, F32& request_dtime_p, bool& can_use_http)
+{
+ S32 state = LLTextureFetchWorker::INVALID;
+ F32 data_progress = 0.0f;
+ F32 requested_priority = 0.0f;
+ F32 fetch_dtime = 999999.f;
+ F32 request_dtime = 999999.f;
+ U32 fetch_priority = 0;
+
+ LLTextureFetchWorker* worker = getWorker(id);
+ if (worker && worker->haveWork())
+ {
+ worker->lockWorkMutex();
+ state = worker->mState;
+ fetch_dtime = worker->mFetchTimer.getElapsedTimeF32();
+ request_dtime = worker->mRequestedTimer.getElapsedTimeF32();
+ if (worker->mFileSize > 0)
+ {
+ if (state == LLTextureFetchWorker::LOAD_FROM_SIMULATOR)
+ {
+ S32 data_size = FIRST_PACKET_SIZE + (worker->mLastPacket-1) * MAX_IMG_PACKET_SIZE;
+ data_size = llmax(data_size, 0);
+ data_progress = (F32)data_size / (F32)worker->mFileSize;
+ }
+ else if (worker->mFormattedImage.notNull())
+ {
+ data_progress = (F32)worker->mFormattedImage->getDataSize() / (F32)worker->mFileSize;
+ }
+ }
+ if (state >= LLTextureFetchWorker::LOAD_FROM_NETWORK && state <= LLTextureFetchWorker::WAIT_HTTP_REQ)
+ {
+ requested_priority = worker->mRequestedPriority;
+ }
+ else
+ {
+ requested_priority = worker->mImagePriority;
+ }
+ fetch_priority = worker->getPriority();
+ can_use_http = worker->getCanUseHTTP() ;
+ worker->unlockWorkMutex();
+ }
+ data_progress_p = data_progress;
+ requested_priority_p = requested_priority;
+ fetch_priority_p = fetch_priority;
+ fetch_dtime_p = fetch_dtime;
+ request_dtime_p = request_dtime;
+ return state;
+}
+
+void LLTextureFetch::dump()
+{
+ llinfos << "LLTextureFetch REQUESTS:" << llendl;
+ for (request_queue_t::iterator iter = mRequestQueue.begin();
+ iter != mRequestQueue.end(); ++iter)
+ {
+ LLQueuedThread::QueuedRequest* qreq = *iter;
+ LLWorkerThread::WorkRequest* wreq = (LLWorkerThread::WorkRequest*)qreq;
+ LLTextureFetchWorker* worker = (LLTextureFetchWorker*)wreq->getWorkerClass();
+ llinfos << " ID: " << worker->mID
+ << " PRI: " << llformat("0x%08x",wreq->getPriority())
+ << " STATE: " << worker->sStateDescs[worker->mState]
+ << llendl;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// cross-thread command methods
+
+void LLTextureFetch::commandSetRegion(U64 region_handle)
+{
+ TFReqSetRegion * req = new TFReqSetRegion(region_handle);
+
+ cmdEnqueue(req);
+}
+
+void LLTextureFetch::commandSendMetrics(const std::string & caps_url,
+ const LLUUID & session_id,
+ const LLUUID & agent_id,
+ LLViewerAssetStats * main_stats)
+{
+ TFReqSendMetrics * req = new TFReqSendMetrics(caps_url, session_id, agent_id, main_stats);
+
+ cmdEnqueue(req);
+}
+
+void LLTextureFetch::commandDataBreak()
+{
+ // The pedantically correct way to implement this is to create a command
+ // request object in the above fashion and enqueue it. However, this is
+ // simple data of an advisorial not operational nature and this case
+ // of shared-write access is tolerable.
+
+ LLTextureFetch::svMetricsDataBreak = true;
+}
+
+void LLTextureFetch::cmdEnqueue(TFRequest * req)
+{
+ lockQueue();
+ mCommands.push_back(req);
+ unlockQueue();
+
+ unpause();
+}
+
+LLTextureFetch::TFRequest * LLTextureFetch::cmdDequeue()
+{
+ TFRequest * ret = 0;
+
+ lockQueue();
+ if (! mCommands.empty())
+ {
+ ret = mCommands.front();
+ mCommands.erase(mCommands.begin());
+ }
+ unlockQueue();
+
+ return ret;
+}
+
+void LLTextureFetch::cmdDoWork()
+{
+ if (mDebugPause)
+ {
+ return; // debug: don't do any work
+ }
+
+ TFRequest * req = cmdDequeue();
+ if (req)
+ {
+ // One request per pass should really be enough for this.
+ req->doWork(this);
+ delete req;
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+// Private (anonymous) class methods implementing the command scheme.
+
+namespace
+{
+
+/**
+ * Implements the 'Set Region' command.
+ *
+ * Thread: Thread1 (TextureFetch)
+ */
+bool
+TFReqSetRegion::doWork(LLTextureFetch *)
+{
+ LLViewerAssetStatsFF::set_region_thread1(mRegionHandle);
+
+ return true;
+}
+
+
+TFReqSendMetrics::~TFReqSendMetrics()
+{
+ delete mMainStats;
+ mMainStats = 0;
+}
+
+
+/**
+ * Implements the 'Send Metrics' command. Takes over
+ * ownership of the passed LLViewerAssetStats pointer.
+ *
+ * Thread: Thread1 (TextureFetch)
+ */
+bool
+TFReqSendMetrics::doWork(LLTextureFetch * fetcher)
+{
+ /*
+ * HTTP POST responder. Doesn't do much but tries to
+ * detect simple breaks in recording the metrics stream.
+ *
+ * The 'volatile' modifiers don't indicate signals,
+ * mmap'd memory or threads, really. They indicate that
+ * the referenced data is part of a pseudo-closure for
+ * this responder rather than being required for correct
+ * operation.
+ *
+ * We don't try very hard with the POST request. We give
+ * it one shot and that's more-or-less it. With a proper
+ * refactoring of the LLQueuedThread usage, these POSTs
+ * could be put in a request object and made more reliable.
+ */
+ class lcl_responder : public LLCurl::Responder
+ {
+ public:
+ lcl_responder(LLTextureFetch * fetcher,
+ S32 expected_sequence,
+ volatile const S32 & live_sequence,
+ volatile bool & reporting_break,
+ volatile bool & reporting_started)
+ : LLCurl::Responder(),
+ mFetcher(fetcher),
+ mExpectedSequence(expected_sequence),
+ mLiveSequence(live_sequence),
+ mReportingBreak(reporting_break),
+ mReportingStarted(reporting_started)
+ {
+ mFetcher->incrCurlPOSTCount();
+ }
+
+ ~lcl_responder()
+ {
+ mFetcher->decrCurlPOSTCount();
+ }
+
+ // virtual
+ void error(U32 status_num, const std::string & reason)
+ {
+ if (mLiveSequence == mExpectedSequence)
+ {
+ mReportingBreak = true;
+ }
+ LL_WARNS("Texture") << "Break in metrics stream due to POST failure to metrics collection service. Reason: "
+ << reason << LL_ENDL;
+ }
+
+ // virtual
+ void result(const LLSD & content)
+ {
+ if (mLiveSequence == mExpectedSequence)
+ {
+ mReportingBreak = false;
+ mReportingStarted = true;
+ }
+ }
+
+ private:
+ LLTextureFetch * mFetcher;
+ S32 mExpectedSequence;
+ volatile const S32 & mLiveSequence;
+ volatile bool & mReportingBreak;
+ volatile bool & mReportingStarted;
+
+ }; // class lcl_responder
+
+ if (! gViewerAssetStatsThread1)
+ return true;
+
+ static volatile bool reporting_started(false);
+ static volatile S32 report_sequence(0);
+
+ // We've taken over ownership of the stats copy at this
+ // point. Get a working reference to it for merging here
+ // but leave it in 'this'. Destructor will rid us of it.
+ LLViewerAssetStats & main_stats = *mMainStats;
+
+ // Merge existing stats into those from main, convert to LLSD
+ main_stats.merge(*gViewerAssetStatsThread1);
+ LLSD merged_llsd = main_stats.asLLSD(true);
+
+ // Add some additional meta fields to the content
+ merged_llsd["session_id"] = mSessionID;
+ merged_llsd["agent_id"] = mAgentID;
+ merged_llsd["message"] = "ViewerAssetMetrics"; // Identifies the type of metrics
+ merged_llsd["sequence"] = report_sequence; // Sequence number
+ merged_llsd["initial"] = ! reporting_started; // Initial data from viewer
+ merged_llsd["break"] = LLTextureFetch::svMetricsDataBreak; // Break in data prior to this report
+
+ // Update sequence number
+ if (S32_MAX == ++report_sequence)
+ report_sequence = 0;
+
+ // Limit the size of the stats report if necessary.
+ merged_llsd["truncated"] = truncate_viewer_metrics(10, merged_llsd);
+
+ if (! mCapsURL.empty())
+ {
+ LLCurlRequest::headers_t headers;
+ fetcher->getCurlRequest().post(mCapsURL,
+ headers,
+ merged_llsd,
+ new lcl_responder(fetcher,
+ report_sequence,
+ report_sequence,
+ LLTextureFetch::svMetricsDataBreak,
+ reporting_started));
+ }
+ else
+ {
+ LLTextureFetch::svMetricsDataBreak = true;
+ }
+
+ // In QA mode, Metrics submode, log the result for ease of testing
+ if (fetcher->isQAMode())
+ {
+ LL_INFOS("Textures") << merged_llsd << LL_ENDL;
+ }
+
+ gViewerAssetStatsThread1->reset();
+
+ return true;
+}
+
+
+bool
+truncate_viewer_metrics(int max_regions, LLSD & metrics)
+{
+ static const LLSD::String reg_tag("regions");
+ static const LLSD::String duration_tag("duration");
+
+ LLSD & reg_map(metrics[reg_tag]);
+ if (reg_map.size() <= max_regions)
+ {
+ return false;
+ }
+
+ // Build map of region hashes ordered by duration
+ typedef std::multimap<LLSD::Real, int> reg_ordered_list_t;
+ reg_ordered_list_t regions_by_duration;
+
+ int ind(0);
+ LLSD::array_const_iterator it_end(reg_map.endArray());
+ for (LLSD::array_const_iterator it(reg_map.beginArray()); it_end != it; ++it, ++ind)
+ {
+ LLSD::Real duration = (*it)[duration_tag].asReal();
+ regions_by_duration.insert(reg_ordered_list_t::value_type(duration, ind));
+ }
+
+ // Build a replacement regions array with the longest-persistence regions
+ LLSD new_region(LLSD::emptyArray());
+ reg_ordered_list_t::const_reverse_iterator it2_end(regions_by_duration.rend());
+ reg_ordered_list_t::const_reverse_iterator it2(regions_by_duration.rbegin());
+ for (int i(0); i < max_regions && it2_end != it2; ++i, ++it2)
+ {
+ new_region.append(reg_map[it2->second]);
+ }
+ reg_map = new_region;
+
+ return true;
+}
+
+} // end of anonymous namespace
+
+
+
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index ed18d67b62..f16d8814dd 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -1832,16 +1832,17 @@ bool LLViewerMediaImpl::initializePlugin(const std::string& media_type)
media_source->ignore_ssl_cert_errors(true);
}
- // start by assuming the default CA file will be used
- std::string ca_path = gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "lindenlab.pem" );
-
- // default turned off so pick up the user specified path
- if( ! gSavedSettings.getBOOL("BrowserUseDefaultCAFile"))
- {
- ca_path = gSavedSettings.getString("BrowserCAFilePath");
- }
- // set the path to the CA.pem file
- media_source->addCertificateFilePath( ca_path );
+ // NOTE: Removed as per STORM-927 - SSL handshake failed - setting local self-signed certs like this
+ // seems to screw things up big time. For now, devs will need to add these certs locally and Qt will pick them up.
+// // start by assuming the default CA file will be used
+// std::string ca_path = gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "lindenlab.pem" );
+// // default turned off so pick up the user specified path
+// if( ! gSavedSettings.getBOOL("BrowserUseDefaultCAFile"))
+// {
+// ca_path = gSavedSettings.getString("BrowserCAFilePath");
+// }
+// // set the path to the CA.pem file
+// media_source->addCertificateFilePath( ca_path );
media_source->proxy_setup(gSavedSettings.getBOOL("BrowserProxyEnabled"), gSavedSettings.getString("BrowserProxyAddress"), gSavedSettings.getS32("BrowserProxyPort"));
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 6fc85a3944..103989ee80 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -2721,6 +2721,14 @@ void process_improved_im(LLMessageSystem *msg, void **user_data)
LLSD args;
args["slurl"] = location;
args["type"] = LLNotificationsUI::NT_NEARBYCHAT;
+
+ // Look for IRC-style emotes here so object name formatting is correct
+ std::string prefix = message.substr(0, 4);
+ if (prefix == "/me " || prefix == "/me'")
+ {
+ chat.mChatStyle = CHAT_STYLE_IRC;
+ }
+
LLNotificationsUI::LLNotificationManager::instance().onChat(chat, args);
}
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 6b1fffe64d..274dbe2cc8 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -351,6 +351,14 @@ public:
addText(xpos, ypos, llformat("Time: %d:%02d:%02d", hours,mins,secs)); ypos += y_inc;
}
+#if LL_WINDOWS
+ if (gSavedSettings.getBOOL("DebugShowMemory"))
+ {
+ addText(xpos, ypos, llformat("Memory: %d (KB)", LLMemory::getWorkingSetSize() / 1024));
+ ypos += y_inc;
+ }
+#endif
+
if (gDisplayCameraPos)
{
std::string camera_view_text;
diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp
index a49dc1b59d..66a6ab5e94 100644
--- a/indra/newview/llwearableitemslist.cpp
+++ b/indra/newview/llwearableitemslist.cpp
@@ -287,6 +287,9 @@ BOOL LLPanelBodyPartsListItem::postBuild()
addWidgetToRightSide("btn_lock");
addWidgetToRightSide("btn_edit_panel");
+ setWidgetsVisible(false);
+ reshapeWidgets();
+
return TRUE;
}
diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml
index 2c00120177..7b3cc7bdfa 100644
--- a/indra/newview/skins/default/textures/textures.xml
+++ b/indra/newview/skins/default/textures/textures.xml
@@ -107,6 +107,7 @@ with the same filename but different name
<texture name="ComboButton_Selected" file_name="widgets/ComboButton_Selected.png" preload="true" scale.left="2" scale.top="19" scale.right="18" scale.bottom="2" />
<texture name="ComboButton_UpSelected" file_name="widgets/ComboButton_UpSelected.png" preload="true" scale.left="2" scale.top="19" scale.right="18" scale.bottom="2" />
<texture name="ComboButton_Up_On_Selected" file_name="widgets/ComboButton_Up_On_Selected.png" preload="true" scale.left="2" scale.top="19" scale.right="18" scale.bottom="2" />
+ <texture name="ComboButton_On" file_name="widgets/ComboButton_On.png" preload="true" scale.left="2" scale.top="19" scale.right="18" scale.bottom="2" />
<texture name="ComboButton_Off" file_name="widgets/ComboButton_Off.png" preload="true" scale.left="2" scale.top="19" scale.right="18" scale.bottom="2" />
<texture name="ComboButton_UpOff" file_name="widgets/ComboButton_UpOff.png" preload="true" scale.left="2" scale.top="19" scale.right="18" scale.bottom="2" />
<texture name="Container" file_name="containers/Container.png" preload="false" />
diff --git a/indra/newview/skins/default/xui/da/panel_people.xml b/indra/newview/skins/default/xui/da/panel_people.xml
index 599686d360..b85a33279a 100644
--- a/indra/newview/skins/default/xui/da/panel_people.xml
+++ b/indra/newview/skins/default/xui/da/panel_people.xml
@@ -1,23 +1,23 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!-- Side tray panel -->
<panel label="Personer" name="people_panel">
- <string name="no_recent_people" value="Ingen tidligere personer. Leder du efter nogen at være sammen med? Prøv [secondlife:///app/search/people Search] eller [secondlife:///app/worldmap World Map]."/>
- <string name="no_filtered_recent_people" value="Fandt du ikke det du søgte? Prøv [secondlife:///app/search/people/[SEARCH_TERM] Search]."/>
- <string name="no_one_near" value="Ingen i nærheden. Leder du efter nogen at være sammen med? Prøv [secondlife:///app/search/people Search] eller [secondlife:///app/worldmap World Map]."/>
- <string name="no_one_filtered_near" value="Fandt du ikke det du søgte? Prøv [secondlife:///app/search/people/[SEARCH_TERM] Search]."/>
+ <string name="no_recent_people" value="Ingen tidligere personer. Leder du efter nogen at være sammen med? Prøv [secondlife:///app/search/people Søg] eller [secondlife:///app/worldmap Verdenskort]."/>
+ <string name="no_filtered_recent_people" value="Fandt du ikke det du søgte? Prøv [secondlife:///app/search/people/[SEARCH_TERM] Søg]."/>
+ <string name="no_one_near" value="Ingen i nærheden. Leder du efter nogen at være sammen med? Prøv [secondlife:///app/search/people Søg] eller [secondlife:///app/worldmap Verdenskort]."/>
+ <string name="no_one_filtered_near" value="Fandt du ikke det du søgte? Prøv [secondlife:///app/search/people/[SEARCH_TERM] Søg]."/>
<string name="no_friends_online" value="Ingen venner online"/>
<string name="no_friends" value="Ingen venner"/>
<string name="no_friends_msg">
- Find venner via [secondlife:///app/search/people Search] eller højre-klik på en beboer og tilføj dem som venner.
-Leder du efter nogen at være sammen med? Prøv [secondlife:///app/worldmap World Map].
+ Find venner via [secondlife:///app/search/people Søg] eller højre-klik på en beboer og tilføj dem som venner.
+Leder du efter nogen at være sammen med? Prøv [secondlife:///app/worldmap Verdenskort].
</string>
<string name="no_filtered_friends_msg">
- Fandt du ikke det du søgte? Prøv [secondlife:///app/search/people/[SEARCH_TERM] Search].
+ Fandt du ikke det du søgte? Prøv [secondlife:///app/search/people/[SEARCH_TERM] Søg].
</string>
<string name="people_filter_label" value="Filtrér personer"/>
<string name="groups_filter_label" value="Filtrér grupper"/>
- <string name="no_filtered_groups_msg" value="Fandt du ikke det du søgte? Prøv [secondlife:///app/search/groups/[SEARCH_TERM] Search]."/>
- <string name="no_groups_msg" value="Leder du efter grupper at være med i? Prøv [secondlife:///app/search/groups Search]."/>
+ <string name="no_filtered_groups_msg" value="Fandt du ikke det du søgte? Prøv [secondlife:///app/search/groups/[SEARCH_TERM] Søg]."/>
+ <string name="no_groups_msg" value="Leder du efter grupper at være med i? Prøv [secondlife:///app/search/groups Søg]."/>
<filter_editor label="Filtrér" name="filter_input"/>
<tab_container name="tabs">
<panel label="TÆT PÅ" name="nearby_panel">
diff --git a/indra/newview/skins/default/xui/en/floater_map.xml b/indra/newview/skins/default/xui/en/floater_map.xml
index 6370ff9243..ae99fa8dd5 100644
--- a/indra/newview/skins/default/xui/en/floater_map.xml
+++ b/indra/newview/skins/default/xui/en/floater_map.xml
@@ -22,7 +22,11 @@
name="ToolTipMsg">
[REGION](Double-click to open Map, shift-drag to pan)
</floater.string>
- <floater.string name="mini_map_caption">
+ <floater.string
+ name="AltToolTipMsg">
+ [REGION](Double-click to teleport, shift-drag to pan)
+ </floater.string>
+ <floater.string name="mini_map_caption">
MINIMAP
</floater.string>
<net_map
diff --git a/indra/newview/skins/default/xui/en/floater_web_content.xml b/indra/newview/skins/default/xui/en/floater_web_content.xml
index 6ec063cd26..e04a72cbc0 100644
--- a/indra/newview/skins/default/xui/en/floater_web_content.xml
+++ b/indra/newview/skins/default/xui/en/floater_web_content.xml
@@ -12,7 +12,7 @@
auto_tile="true"
title=""
initial_mime_type="text/html"
- width="735">
+ width="780">
<layout_stack
bottom="775"
follows="left|right|top|bottom"
@@ -21,7 +21,7 @@
name="stack1"
orientation="vertical"
top="20"
- width="725">
+ width="770">
<layout_panel
auto_resize="false"
default_tab_group="1"
@@ -32,7 +32,7 @@
name="nav_controls"
top="400"
user_resize="false"
- width="725">
+ width="770">
<button
image_overlay="Arrow_Left_Off"
image_disabled="PushButton_Disabled"
@@ -115,7 +115,7 @@
combo_editor.select_on_focus="true"
tool_tip="Enter URL here"
top_delta="0"
- width="627">
+ width="672">
<combo_box.commit_callback
function="WebContent.EnterAddress" />
</combo_box>
@@ -125,7 +125,7 @@
follows="top|right"
image_name="Lock2"
layout="topleft"
- left_delta="575"
+ left_delta="620"
top_delta="2"
visible="false"
tool_tip="Secured Browsing"
@@ -142,7 +142,7 @@
height="22"
layout="topleft"
name="popexternal"
- right="725"
+ right="770"
top_delta="-2"
width="22">
<button.commit_callback
@@ -156,7 +156,7 @@
name="external_controls"
top_delta="0"
user_resize="false"
- width="540">
+ width="585">
<web_browser
bottom="-22"
follows="all"
@@ -175,7 +175,7 @@
parse_urls="false"
text_color="0.4 0.4 0.4 1"
top_pad="5"
- width="520"/>
+ width="495"/>
<progress_bar
color_bar="0.3 1.0 0.3 1"
follows="bottom|right"
diff --git a/indra/newview/skins/default/xui/en/menu_inspect_avatar_gear.xml b/indra/newview/skins/default/xui/en/menu_inspect_avatar_gear.xml
index 58d58a6ca9..76b188220d 100644
--- a/indra/newview/skins/default/xui/en/menu_inspect_avatar_gear.xml
+++ b/indra/newview/skins/default/xui/en/menu_inspect_avatar_gear.xml
@@ -78,7 +78,7 @@
<menu_item_call.on_click
function="InspectAvatar.Freeze"/>
<menu_item_call.on_visible
- function="InspectAvatar.VisibleFreezeEject"/>
+ function="InspectAvatar.VisibleFreeze"/>
</menu_item_call>
<menu_item_call
label="Eject"
@@ -86,7 +86,23 @@
<menu_item_call.on_click
function="InspectAvatar.Eject"/>
<menu_item_call.on_visible
- function="InspectAvatar.VisibleFreezeEject"/>
+ function="InspectAvatar.VisibleEject"/>
+ </menu_item_call>
+ <menu_item_call
+ label="Kick"
+ name="kick">
+ <menu_item_call.on_click
+ function="InspectAvatar.Kick"/>
+ <menu_item_call.on_visible
+ function="InspectAvatar.EnableGod"/>
+ </menu_item_call>
+ <menu_item_call
+ label="CSR"
+ name="csr">
+ <menu_item_call.on_click
+ function="InspectAvatar.CSR" />
+ <menu_item_call.on_visible
+ function="InspectAvatar.EnableGod" />
</menu_item_call>
<menu_item_call
label="Debug Textures"
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index c2735c85e4..08ae0c233e 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -1990,6 +1990,16 @@
function="ToggleControl"
parameter="DebugShowColor" />
</menu_item_check>
+ <menu_item_check
+ label="Show Memory"
+ name="Show Memory">
+ <menu_item_check.on_check
+ function="CheckControl"
+ parameter="DebugShowMemory" />
+ <menu_item_check.on_click
+ function="ToggleControl"
+ parameter="DebugShowMemory" />
+ </menu_item_check>
<menu_item_separator/>
diff --git a/indra/newview/skins/default/xui/en/panel_nearby_chat_bar.xml b/indra/newview/skins/default/xui/en/panel_nearby_chat_bar.xml
index 5871eb0654..21c627cdfb 100644
--- a/indra/newview/skins/default/xui/en/panel_nearby_chat_bar.xml
+++ b/indra/newview/skins/default/xui/en/panel_nearby_chat_bar.xml
@@ -45,9 +45,9 @@
left_pad="4"
image_disabled="ComboButton_UpOff"
image_unselected="ComboButton_UpOff"
- image_selected="ComboButton_Up_On_Selected"
+ image_selected="ComboButton_On"
image_pressed="ComboButton_UpSelected"
- image_pressed_selected="ComboButton_Up_On_Selected"
+ image_pressed_selected="ComboButton_Selected"
height="23"
name="show_nearby_chat"
tool_tip="Shows/hides nearby chat log">
diff --git a/indra/newview/skins/default/xui/en/panel_people.xml b/indra/newview/skins/default/xui/en/panel_people.xml
index 6a8bf87bc5..43431ea7c1 100644
--- a/indra/newview/skins/default/xui/en/panel_people.xml
+++ b/indra/newview/skins/default/xui/en/panel_people.xml
@@ -54,7 +54,13 @@ Looking for people to hang out with? Try the [secondlife:///app/worldmap World M
<string
name="no_groups_msg"
value="Looking for Groups to join? Try [secondlife:///app/search/groups Search]." />
- <filter_editor
+ <string
+ name="MiniMapToolTipMsg"
+ value="[REGION](Double-click to open Map, shift-drag to pan)"/>
+ <string
+ name="AltMiniMapToolTipMsg"
+ value="[REGION](Double-click to teleport, shift-drag to pan)"/>
+ <filter_editor
follows="left|top|right"
height="23"
layout="topleft"
@@ -93,16 +99,26 @@ Looking for people to hang out with? Try the [secondlife:///app/worldmap World M
name="nearby_panel"
top="0"
width="313">
- <avatar_list
+ <net_map
+ bg_color="NetMapBackgroundColor"
+ follows="top|left|right"
+ layout="topleft"
+ left="3"
+ mouse_opaque="false"
+ name="Net Map"
+ width="307"
+ height="140"
+ top="0"/>
+ <avatar_list
allow_select="true"
- follows="all"
- height="356"
+ follows="top|left|bottom|right"
+ height="216"
ignore_online_status="true"
layout="topleft"
left="3"
multi_select="true"
name="avatar_list"
- top="0"
+ top="145"
width="307" />
<panel
background_visible="true"
diff --git a/indra/newview/skins/default/xui/en/widgets/talk_button.xml b/indra/newview/skins/default/xui/en/widgets/talk_button.xml
index a7e271a1ff..d792e9f29c 100644
--- a/indra/newview/skins/default/xui/en/widgets/talk_button.xml
+++ b/indra/newview/skins/default/xui/en/widgets/talk_button.xml
@@ -23,11 +23,11 @@
bottom="0"
tab_stop="false"
is_toggle="true"
- image_selected="SegmentedBtn_Right_Selected_Press"
- image_unselected="SegmentedBtn_Right_Off"
- image_pressed="SegmentedBtn_Right_Press"
- image_pressed_selected="SegmentedBtn_Right_Selected_Press"
- image_overlay="Arrow_Small_Up"
+ image_disabled="ComboButton_UpOff"
+ image_unselected="ComboButton_UpOff"
+ image_selected="ComboButton_On"
+ image_pressed="ComboButton_UpSelected"
+ image_pressed_selected="ComboButton_Selected"
/>
<monitor
follows="right"