diff options
author | Oz Linden <oz@lindenlab.com> | 2014-02-25 13:31:01 -0500 |
---|---|---|
committer | Oz Linden <oz@lindenlab.com> | 2014-02-25 13:31:01 -0500 |
commit | 1d30fe955442855dd3102a006d6e4120d2f416b4 (patch) | |
tree | 1783c0818fb473e1bd8a5ed7a4622f45e30fc99d /indra/newview | |
parent | 9b42503379bc2e9698545543670306961f7db991 (diff) | |
parent | de8fea13627cc5978b8a6135802a52864a11c39a (diff) |
merge changes for 3.7.2-release
Diffstat (limited to 'indra/newview')
50 files changed, 3210 insertions, 1199 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 78c581b12e..5d24f05a1e 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -267,6 +267,7 @@ set(viewer_SOURCE_FILES llfloaterregiondebugconsole.cpp llfloaterregioninfo.cpp llfloaterreporter.cpp + llfloaterregionrestarting.cpp llfloaterscriptdebug.cpp llfloaterscriptlimits.cpp llfloatersearch.cpp @@ -856,6 +857,7 @@ set(viewer_HEADER_FILES llfloaterregiondebugconsole.h llfloaterregioninfo.h llfloaterreporter.h + llfloaterregionrestarting.h llfloaterscriptdebug.h llfloaterscriptlimits.h llfloatersearch.h diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index f5c2a4050b..c1e43e6d45 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -3.6.14 +3.7.3 diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml index ce878f156b..60c942094a 100755 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -3,8 +3,6 @@ <command name="aboutland" available_in_toybox="true" icon="Command_AboutLand_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_AboutLand_Label" tooltip_ref="Command_AboutLand_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -15,8 +13,6 @@ <command name="appearance" available_in_toybox="true" icon="Command_Appearance_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Appearance_Label" tooltip_ref="Command_Appearance_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -27,8 +23,6 @@ <command name="avatar" available_in_toybox="true" icon="Command_Avatar_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Avatar_Label" tooltip_ref="Command_Avatar_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -39,8 +33,6 @@ <command name="build" available_in_toybox="true" icon="Command_Build_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Build_Label" tooltip_ref="Command_Build_Tooltip" execute_function="Build.Toggle" @@ -54,8 +46,6 @@ available_in_toybox="true" is_flashing_allowed="true" icon="Command_Chat_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Chat_Label" tooltip_ref="Command_Conversations_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -66,8 +56,6 @@ <command name="compass" available_in_toybox="false" icon="Command_Compass_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Compass_Label" tooltip_ref="Command_Compass_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -78,8 +66,6 @@ <command name="destinations" available_in_toybox="true" icon="Command_Destinations_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Destinations_Label" tooltip_ref="Command_Destinations_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -90,8 +76,6 @@ <command name="gestures" available_in_toybox="true" icon="Command_Gestures_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Gestures_Label" tooltip_ref="Command_Gestures_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -102,8 +86,6 @@ <command name="howto" available_in_toybox="true" icon="Command_HowTo_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_HowTo_Label" tooltip_ref="Command_HowTo_Tooltip" execute_function="Help.ToggleHowTo" @@ -112,8 +94,6 @@ <command name="inventory" available_in_toybox="true" icon="Command_Inventory_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Inventory_Label" tooltip_ref="Command_Inventory_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -124,8 +104,6 @@ <command name="map" available_in_toybox="true" icon="Command_Map_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Map_Label" tooltip_ref="Command_Map_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -136,8 +114,6 @@ <command name="marketplace" available_in_toybox="false" icon="Command_Marketplace_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Marketplace_Label" tooltip_ref="Command_Marketplace_Tooltip" execute_function="Avatar.OpenMarketplace" @@ -145,8 +121,6 @@ <command name="minimap" available_in_toybox="true" icon="Command_MiniMap_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_MiniMap_Label" tooltip_ref="Command_MiniMap_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -157,8 +131,6 @@ <command name="move" available_in_toybox="true" icon="Command_Move_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Move_Label" tooltip_ref="Command_Move_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -169,8 +141,6 @@ <command name="outbox" available_in_toybox="false" icon="Command_Outbox_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Outbox_Label" tooltip_ref="Command_Outbox_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -181,8 +151,6 @@ <command name="people" available_in_toybox="true" icon="Command_People_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_People_Label" tooltip_ref="Command_People_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -193,8 +161,6 @@ <command name="picks" available_in_toybox="true" icon="Command_Picks_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Picks_Label" tooltip_ref="Command_Picks_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -205,8 +171,6 @@ <command name="places" available_in_toybox="true" icon="Command_Places_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Places_Label" tooltip_ref="Command_Places_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -217,8 +181,6 @@ <command name="preferences" available_in_toybox="true" icon="Command_Preferences_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Preferences_Label" tooltip_ref="Command_Preferences_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -229,8 +191,6 @@ <command name="profile" available_in_toybox="true" icon="Command_Profile_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Profile_Label" tooltip_ref="Command_Profile_Tooltip" execute_function="Avatar.ToggleMyProfile" @@ -239,8 +199,6 @@ <command name="search" available_in_toybox="true" icon="Command_Search_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Search_Label" tooltip_ref="Command_Search_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -251,8 +209,6 @@ <command name="snapshot" available_in_toybox="true" icon="Command_Snapshot_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Snapshot_Label" tooltip_ref="Command_Snapshot_Tooltip" execute_function="Floater.ToggleOrBringToFront" @@ -273,8 +229,6 @@ <command name="speak" available_in_toybox="true" icon="Command_Speak_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_Speak_Label" tooltip_ref="Command_Speak_Tooltip" execute_function="Agent.PressMicrophone" @@ -289,8 +243,6 @@ <command name="view" available_in_toybox="true" icon="Command_View_Icon" - hover_icon_unselected="Command_Highlighting_Icon" - hover_icon_selected="Command_Highlighting_Selected_Icon" label_ref="Command_View_Label" tooltip_ref="Command_View_Tooltip" execute_function="Floater.ToggleOrBringToFront" diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index d9093c2a6d..4c7b192ae5 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -10068,11 +10068,21 @@ <key>Value</key> <real>16</real> </map> - + <key>Mesh2MaxConcurrentRequests</key> + <map> + <key>Comment</key> + <string>Number of connections to use for loading meshes.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>U32</string> + <key>Value</key> + <integer>8</integer> + </map> <key>MeshMaxConcurrentRequests</key> <map> <key>Comment</key> - <string>Number of threads to use for loading meshes.</string> + <string>Number of connections to use for loading meshes (legacy system).</string> <key>Persist</key> <integer>1</integer> <key>Type</key> @@ -10080,6 +10090,28 @@ <key>Value</key> <integer>32</integer> </map> + <key>MeshUseHttpRetryAfter</key> + <map> + <key>Comment</key> + <string>If TRUE, use Retry-After response headers when rescheduling a mesh request that fails with an HTTP 503 status. Static.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <boolean>1</boolean> + </map> + <key>MeshUseGetMesh1</key> + <map> + <key>Comment</key> + <string>If TRUE, use the legacy GetMesh capability for mesh download requests. Semi-dynamic (read at region crossings).</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <boolean>0</boolean> + </map> <key>RunMultipleThreads</key> <map> <key>Comment</key> @@ -12632,6 +12664,17 @@ <key>Value</key> <string>00000000-0000-0000-0000-000000000000</string> </map> + <key>UISndRestart</key> + <map> + <key>Comment</key> + <string>Sound file for region restarting (uuid for sound asset)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string>b92a0f64-7709-8811-40c5-16afd624a45f</string> + </map> <key>UISndSnapshot</key> <map> <key>Comment</key> @@ -14871,17 +14914,6 @@ <key>Value</key> <integer>0</integer> </map> - <key>DisablePrecacheDelayAfterTeleporting</key> - <map> - <key>Comment</key> - <string>Disables the artificial delay in the viewer that precaches some incoming assets</string> - <key>Persist</key> - <integer>0</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> <key>FMODExProfilerEnable</key> <map> <key>Comment</key> diff --git a/indra/newview/app_settings/shaders/class1/avatar/objectSkinV.glsl b/indra/newview/app_settings/shaders/class1/avatar/objectSkinV.glsl index 39632d0cef..57129c3bd1 100755 --- a/indra/newview/app_settings/shaders/class1/avatar/objectSkinV.glsl +++ b/indra/newview/app_settings/shaders/class1/avatar/objectSkinV.glsl @@ -22,30 +22,46 @@ * $/LicenseInfo$ */ - - ATTRIBUTE vec4 weight4; -uniform mat4 matrixPalette[32]; +uniform mat3 matrixPalette[52]; +uniform vec3 translationPalette[52]; mat4 getObjectSkinnedTransform() { - int i; + int i; vec4 w = fract(weight4); vec4 index = floor(weight4); - index = min(index, vec4(31.0)); + index = min(index, vec4(51.0)); index = max(index, vec4( 0.0)); float scale = 1.0/(w.x+w.y+w.z+w.w); w *= scale; - mat4 mat = matrixPalette[int(index.x)]*w.x; - mat += matrixPalette[int(index.y)]*w.y; - mat += matrixPalette[int(index.z)]*w.z; - mat += matrixPalette[int(index.w)]*w.w; + int i1 = int(index.x); + int i2 = int(index.y); + int i3 = int(index.z); + int i4 = int(index.w); - return mat; + mat3 mat = matrixPalette[i1]*w.x; + mat += matrixPalette[i2]*w.y; + mat += matrixPalette[i3]*w.z; + mat += matrixPalette[i4]*w.w; + + vec3 trans = translationPalette[i1]*w.x; + trans += translationPalette[i2]*w.y; + trans += translationPalette[i3]*w.z; + trans += translationPalette[i4]*w.w; + + mat4 ret; + + ret[0] = vec4(mat[0], 0); + ret[1] = vec4(mat[1], 0); + ret[2] = vec4(mat[2], 0); + ret[3] = vec4(trans, 1.0); + + return ret; } diff --git a/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl b/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl index e5f7366b70..2b5f001873 100755 --- a/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl @@ -534,6 +534,7 @@ void main() #ifdef FOR_IMPOSTOR vec4 color; color.rgb = diff.rgb; + color.a = 1.0; #ifdef USE_VERTEX_COLOR float final_alpha = diff.a * vertex_color.a; diff --git a/indra/newview/character/avatar_lad.xml b/indra/newview/character/avatar_lad.xml index e5b385f4aa..5268498d56 100755 --- a/indra/newview/character/avatar_lad.xml +++ b/indra/newview/character/avatar_lad.xml @@ -3825,7 +3825,11 @@ <volume_morph name="BELLY" scale="0.075 0.04 0.03" - pos="0.07 0 -0.07"/> + pos="0.07 0 -0.02"/> + <volume_morph + name="PELVIS" + scale="0.075 0.04 0.03" + pos="0.07 0 -0.02"/> </param_morph> </param> @@ -3844,7 +3848,16 @@ camera_elevation=".1" camera_distance="1" camera_angle="15"> - <param_morph /> + <param_morph> + <volume_morph + name="LEFT_PEC" + scale="0.0273 0.0273 0.0273" + pos="0.038 0.024 -0.016"/> + <volume_morph + name="RIGHT_PEC" + scale="0.0273 0.0273 0.0273" + pos="0.038 -0.024 -0.016"/> + </param_morph> </param> <param @@ -3861,7 +3874,16 @@ value_max="1" camera_elevation="0" camera_distance=".28"> - <param_morph /> + <param_morph> + <volume_morph + name="LEFT_PEC" + scale="-0.05 0.0 0.0" + pos="-0.01 -0.01 -0.02"/> + <volume_morph + name="RIGHT_PEC" + scale="-0.05 0.0 0.0" + pos="-0.01 -0.01 -0.02"/> + </param_morph> </param> <param @@ -3878,7 +3900,16 @@ value_max="1" camera_elevation="0" camera_distance=".28"> - <param_morph /> + <param_morph> + <volume_morph + name="LEFT_PEC" + scale="-0.051 0.0 0.0" + pos="-0.02 -0.01 -0.03"/> + <volume_morph + name="RIGHT_PEC" + scale="-0.051 0.0 0.0" + pos="-0.02 -0.01 -0.03"/> + </param_morph> </param> <param @@ -3944,6 +3975,10 @@ scale="0.0 -0.01 0.0" pos="0.0 0.0 0"/> <volume_morph + name="UPPER_BACK" + scale="-0.01 -0.01 0.0" + pos="0.0 0.0 0"/> + <volume_morph name="CHEST" scale="-0.01 -0.01 0.0" pos="0.01 0.0 0"/> @@ -3994,6 +4029,10 @@ scale="-0.01 -0.01 0.0" pos="0.01 0.0 0"/> <volume_morph + name="UPPER_BACK" + scale="-0.01 -0.01 0.0" + pos="0.0 0.0 0"/> + <volume_morph name="CHEST" scale="-0.02 -0.02 0.0" pos="0.01 0.0 0"/> @@ -4042,6 +4081,32 @@ scale="0.02 0.03 0.03" pos="0 0 -0.03"/> <volume_morph + name="PELVIS" + scale="0.02 0.03 0.03" + pos="0 0 -0.03"/> + <volume_morph + name="UPPER_BACK" + scale="0.01 0.03 0.0" + pos="-0.03 0 0"/> + <volume_morph + name="LOWER_BACK" + scale="0.04 0.06 0.0" + pos="-0.06 0 0"/> + <volume_morph + name="LEFT_HANDLE" + pos="0.0 0.08 0.0"/> + <volume_morph + name="RIGHT_HANDLE" + pos="0.0 -0.08 0.0"/> + <volume_morph + name="LEFT_PEC" + scale="0.0367 0.0367 0.016" + pos="0.00 -0.005 -0.013"/> + <volume_morph + name="RIGHT_PEC" + scale="0.0367 0.0367 0.016" + pos="0.00 0.005 -0.013"/> + <volume_morph name="BELLY" scale="0.09 0.08 0.07" pos="0 0 -0.05"/> @@ -4093,7 +4158,16 @@ value_max="2" camera_elevation=".3" camera_distance=".8"> - <param_morph /> + <param_morph> + <volume_morph + name="LEFT_PEC" + scale="0.0 0.0 0.0" + pos="0.004 0.0 -0.01"/> + <volume_morph + name="RIGHT_PEC" + scale="0.0 0.0 0.0" + pos="0.004 0.0 -0.01"/> + </param_morph> </param> <param @@ -4143,6 +4217,15 @@ <volume_morph name="BELLY" scale="0.0 0.02 0.0"/> + <volume_morph + name="LOWER_BACK" + scale="0.0 0.02 0.0"/> + <volume_morph + name="LEFT_HANDLE" + pos="0.0 0.025 0.0"/> + <volume_morph + name="RIGHT_HANDLE" + pos="0.0 -0.025 0.0"/> </param_morph> </param> @@ -4162,7 +4245,16 @@ value_max="1.3" camera_elevation=".3" camera_distance=".8"> - <param_morph /> + <param_morph> + <volume_morph + name="LEFT_PEC" + scale="0.0 0.0 0.0" + pos="0.0 -0.026 0.0"/> + <volume_morph + name="RIGHT_PEC" + scale="0.0 0.0 0.0" + pos="0.0 0.026 0.0"/> + </param_morph> </param> <param @@ -4177,11 +4269,20 @@ label_min="Big Pectorals" label_max="Sunken Chest" value_default="0" - value_min="-.5" + value_min="-1.0" value_max="1.1" camera_elevation=".3" camera_distance="1.2"> - <param_morph /> + <param_morph> + <volume_morph + name="LEFT_PEC" + scale="0.0 0.0 0.0" + pos="-0.03 -0.024 -0.01"/> + <volume_morph + name="RIGHT_PEC" + scale="0.0 0.0 0.0" + pos="-0.03 0.024 -0.01"/> + </param_morph> </param> <!-- ############# # @@ -4206,6 +4307,14 @@ scale="0.03 0.03 0.0" pos="-0.03 0 0.02"/> <volume_morph + name="LEFT_PEC" + scale="0.0 0.0 0.0" + pos="0.008 -0.03 0.01"/> + <volume_morph + name="RIGHT_PEC" + scale="0.0 0.0 0.0" + pos="0.008 0.03 0.01"/> + <volume_morph name="L_CLAVICLE" scale="0.02 0.0 0.01" pos="-0.02 0 0"/> @@ -4376,7 +4485,16 @@ value_default="0" value_min="-3" value_max="3"> - <param_morph /> + <param_morph> + <volume_morph + name="LEFT_PEC" + scale="0.0 0.0 0.0" + pos="0.0 0.0 -0.01"/> + <volume_morph + name="RIGHT_PEC" + scale="0.0 0.0 0.0" + pos="0.0 0.0 -0.01"/> + </param_morph> </param> <param @@ -4389,7 +4507,16 @@ value_default="0" value_min="-1.25" value_max="1.25"> - <param_morph /> + <param_morph> + <volume_morph + name="LEFT_PEC" + scale="0.0 0.0 0.0" + pos="0.0 -0.026 0.0"/> + <volume_morph + name="RIGHT_PEC" + scale="0.0 0.0 0.0" + pos="0.0 0.026 -0.0"/> + </param_morph> </param> <param @@ -4402,7 +4529,12 @@ value_default="0" value_min="-1" value_max="1"> - <param_morph /> + <param_morph> + <volume_morph + name="BELLY" + scale="0.0 0.0 0.0" + pos="0.0 0.0 0.05"/> + </param_morph> </param> <param @@ -4415,7 +4547,16 @@ value_default="0" value_min="-2" value_max="2"> - <param_morph /> + <param_morph> + <volume_morph + name="LEFT_PEC" + scale="0.0 0.0 0.0" + pos="0.0 0.03 0.0"/> + <volume_morph + name="RIGHT_PEC" + scale="0.0 0.0 0.0" + pos="0.0 0.03 0.0"/> + </param_morph> </param> <!-- @@ -4518,6 +4659,10 @@ name="PELVIS" scale="-0.01 0.0 0.0" pos="0.01 0 0.0"/> + <volume_morph + name="BUTT" + scale="0.0 0.0886 0.0" + pos="0.03 0 0.0"/> </param_morph> </param> @@ -4949,7 +5094,11 @@ value_default="0" value_min="-1" value_max="1"> - <param_morph /> + <param_morph> + <volume_morph + name="BUTT" + pos="0.0 0.0 0.05"/> + </param_morph> </param> <param @@ -4962,7 +5111,11 @@ value_default="0" value_min="-1" value_max="1"> - <param_morph /> + <param_morph> + <volume_morph + name="BUTT" + pos="0.0 0.05 0.0"/> + </param_morph> </param> <!-- diff --git a/indra/newview/character/avatar_skeleton.xml b/indra/newview/character/avatar_skeleton.xml index 5e73804f2d..6b07bbc1d3 100755 --- a/indra/newview/character/avatar_skeleton.xml +++ b/indra/newview/character/avatar_skeleton.xml @@ -1,11 +1,18 @@ <?xml version="1.0" encoding="US-ASCII" standalone="yes"?> -<linden_skeleton version="1.0" num_bones="46" num_collision_volumes="19"> +<linden_skeleton version="1.0" num_bones="53" num_collision_volumes="26"> <bone name="mPelvis" pos="0.000 0.000 1.067" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" pivot="0.000000 0.000000 1.067015"> <collision_volume name="PELVIS" pos = "-0.01 0 -0.02" rot="0.000000 8.00000 0.000000" scale="0.12 0.16 0.17"/> + <collision_volume name="BUTT" pos = "-0.06 0 -0.1" rot="0.000000 0.00000 0.000000" scale="0.1 0.1 0.1"/> <bone name="mTorso" pos="0.000 0.000 0.084" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" pivot="0.000000 0.000000 0.084073"> <collision_volume name="BELLY" pos = "0.028 0 0.04" rot="0.000000 8.00000 0.000000" scale="0.09 0.13 0.15"/> + <collision_volume name="LOWER_BACK" pos = "0.0 0.0 0.023" rot="0.000000 0.00000 0.000000" scale="0.09 0.13 0.15"/> + <collision_volume name="LEFT_HANDLE" pos = "0.0 0.10 0.058" rot="0.000000 0.00000 0.000000" scale="0.05 0.05 0.05"/> + <collision_volume name="RIGHT_HANDLE" pos = "0.0 -0.10 0.058" rot="0.000000 0.00000 0.000000" scale="0.05 0.05 0.05"/> <bone name="mChest" pos="-0.015 0.000 0.205" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" pivot="-0.015368 0.000000 0.204877"> <collision_volume name="CHEST" pos = "0.028 0 0.07" rot="0.000000 -10.00000 0.000000" scale="0.11 0.15 0.2"/> + <collision_volume name="UPPER_BACK" pos = "0.0 0.0 0.017" rot="0.000000 0.00000 0.000000" scale="0.09 0.13 0.15"/> + <collision_volume name="LEFT_PEC" pos = "0.119 0.082 0.042" rot="0.000000 4.29000 0.000000" scale="0.05 0.05 0.05"/> + <collision_volume name="RIGHT_PEC" pos = "0.119 -0.082 0.042" rot="0.000000 4.29000 0.000000" scale="0.05 0.05 0.05"/> <bone name="mNeck" pos="-0.010 0.000 0.251" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" pivot="-0.009507 0.000000 0.251108"> <collision_volume name="NECK" pos = "0.0 0 0.02" rot="0.000000 0.000000 0.000000" scale="0.05 0.06 0.08"/> <bone name="mHead" pos="0.000 -0.000 0.076" rot="0.000000 0.000000 0.000000" scale="1.000 1.000 1.000" pivot="0.000000 -0.000000 0.075630"> diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index 0d7d41304d..70dcffefb2 100755 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, 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 @@ -28,18 +28,81 @@ #include "llappcorehttp.h" +#include "llappviewer.h" #include "llviewercontrol.h" +// Here is where we begin to get our connection usage under control. +// This establishes llcorehttp policy classes that, among other +// things, limit the maximum number of connections to outside +// services. Each of the entries below maps to a policy class and +// has a limit, sometimes configurable, of how many connections can +// be open at a time. + const F64 LLAppCoreHttp::MAX_THREAD_WAIT_TIME(10.0); +static const struct +{ + LLAppCoreHttp::EAppPolicy mPolicy; + U32 mDefault; + U32 mMin; + U32 mMax; + U32 mRate; + std::string mKey; + const char * mUsage; +} init_data[] = // Default and dynamic values for classes +{ + { + LLAppCoreHttp::AP_DEFAULT, 8, 8, 8, 0, + "", + "other" + }, + { + LLAppCoreHttp::AP_TEXTURE, 8, 1, 12, 0, + "TextureFetchConcurrency", + "texture fetch" + }, + { + LLAppCoreHttp::AP_MESH1, 32, 1, 128, 100, + "MeshMaxConcurrentRequests", + "mesh fetch" + }, + { + LLAppCoreHttp::AP_MESH2, 8, 1, 32, 100, + "Mesh2MaxConcurrentRequests", + "mesh2 fetch" + }, + { + LLAppCoreHttp::AP_LARGE_MESH, 2, 1, 8, 0, + "", + "large mesh fetch" + }, + { + LLAppCoreHttp::AP_UPLOADS, 2, 1, 8, 0, + "", + "asset upload" + }, + { + LLAppCoreHttp::AP_LONG_POLL, 32, 32, 32, 0, + "", + "long poll" + } +}; + +static void setting_changed(); + LLAppCoreHttp::LLAppCoreHttp() : mRequest(NULL), mStopHandle(LLCORE_HTTP_HANDLE_INVALID), mStopRequested(0.0), - mStopped(false), - mPolicyDefault(-1) -{} + mStopped(false) +{ + for (int i(0); i < LL_ARRAY_SIZE(mPolicies); ++i) + { + mPolicies[i] = LLCore::HttpRequest::DEFAULT_POLICY_ID; + mSettings[i] = 0U; + } +} LLAppCoreHttp::~LLAppCoreHttp() @@ -54,30 +117,28 @@ void LLAppCoreHttp::init() LLCore::HttpStatus status = LLCore::HttpRequest::createService(); if (! status) { - LL_ERRS("Init") << "Failed to initialize HTTP services. Reason: " - << status.toString() + LL_ERRS("Init") << "Failed to initialize HTTP services. Reason: " << status.toString() << LL_ENDL; } // Point to our certs or SSH/https: will fail on connect - status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_CA_FILE, - gDirUtilp->getCAFile()); + status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_CA_FILE, + LLCore::HttpRequest::GLOBAL_POLICY_ID, + gDirUtilp->getCAFile(), NULL); if (! status) { - LL_ERRS("Init") << "Failed to set CA File for HTTP services. Reason: " - << status.toString() + LL_ERRS("Init") << "Failed to set CA File for HTTP services. Reason: " << status.toString() << LL_ENDL; } - // Establish HTTP Proxy. "LLProxy" is a special string which directs - // the code to use LLProxy::applyProxySettings() to establish any - // HTTP or SOCKS proxy for http operations. - status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_LLPROXY, 1); + // Establish HTTP Proxy, if desired. + status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_LLPROXY, + LLCore::HttpRequest::GLOBAL_POLICY_ID, + 1, NULL); if (! status) { - LL_ERRS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: " - << status.toString() - << LL_ENDL; + LL_WARNS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: " << status.toString() + << LL_ENDL; } // Tracing levels for library & libcurl (note that 2 & 3 are beyond spammy): @@ -90,47 +151,74 @@ void LLAppCoreHttp::init() { long trace_level(0L); trace_level = long(gSavedSettings.getU32(http_trace)); - status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, trace_level); + status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_TRACE, + LLCore::HttpRequest::GLOBAL_POLICY_ID, + trace_level, NULL); } // Setup default policy and constrain if directed to - mPolicyDefault = LLCore::HttpRequest::DEFAULT_POLICY_ID; - static const std::string texture_concur("TextureFetchConcurrency"); - if (gSavedSettings.controlExists(texture_concur)) + mPolicies[AP_DEFAULT] = LLCore::HttpRequest::DEFAULT_POLICY_ID; + + // Setup additional policies based on table and some special rules + for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) { - U32 concur(llmin(gSavedSettings.getU32(texture_concur), U32(12))); + const EAppPolicy policy(init_data[i].mPolicy); - if (concur > 0) + if (AP_DEFAULT == policy) { - LLCore::HttpStatus status; - status = LLCore::HttpRequest::setPolicyClassOption(mPolicyDefault, - LLCore::HttpRequest::CP_CONNECTION_LIMIT, - concur); - if (! status) - { - LL_WARNS("Init") << "Unable to set texture fetch concurrency. Reason: " - << status.toString() - << LL_ENDL; - } - else - { - LL_INFOS("Init") << "Application settings overriding default texture fetch concurrency. New value: " - << concur - << LL_ENDL; - } + // Pre-created + continue; + } + + mPolicies[policy] = LLCore::HttpRequest::createPolicyClass(); + if (! mPolicies[policy]) + { + // Use default policy (but don't accidentally modify default) + LL_WARNS("Init") << "Failed to create HTTP policy class for " << init_data[i].mUsage + << ". Using default policy." + << LL_ENDL; + mPolicies[policy] = mPolicies[AP_DEFAULT]; + continue; } } + + // Need a request object to handle dynamic options before setting them + mRequest = new LLCore::HttpRequest; + + // Apply initial settings + refreshSettings(true); // Kick the thread status = LLCore::HttpRequest::startThread(); if (! status) { - LL_ERRS("Init") << "Failed to start HTTP servicing thread. Reason: " - << status.toString() + LL_ERRS("Init") << "Failed to start HTTP servicing thread. Reason: " << status.toString() << LL_ENDL; } - mRequest = new LLCore::HttpRequest; + // Register signals for settings and state changes + for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) + { + if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey)) + { + LLPointer<LLControlVariable> cntrl_ptr = gSavedSettings.getControl(init_data[i].mKey); + if (cntrl_ptr.isNull()) + { + LL_WARNS("Init") << "Unable to set signal on global setting '" << init_data[i].mKey + << "'" << LL_ENDL; + } + else + { + mSettingsSignal[i] = cntrl_ptr->getCommitSignal()->connect(boost::bind(&setting_changed)); + } + } + } +} + + +void setting_changed() +{ + LLAppViewer::instance()->getAppCoreHttp().refreshSettings(false); } @@ -173,6 +261,11 @@ void LLAppCoreHttp::cleanup() } } + for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) + { + mSettingsSignal[i].disconnect(); + } + delete mRequest; mRequest = NULL; @@ -185,6 +278,78 @@ void LLAppCoreHttp::cleanup() } } +void LLAppCoreHttp::refreshSettings(bool initial) +{ + LLCore::HttpStatus status; + + for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) + { + const EAppPolicy policy(init_data[i].mPolicy); + + // Set any desired throttle + if (initial && init_data[i].mRate) + { + // Init-time only, can use the static setters here + status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_THROTTLE_RATE, + mPolicies[policy], + init_data[i].mRate, + NULL); + if (! status) + { + LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage + << " throttle rate. Reason: " << status.toString() + << LL_ENDL; + } + } + + // Get target connection concurrency value + U32 setting(init_data[i].mDefault); + if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey)) + { + U32 new_setting(gSavedSettings.getU32(init_data[i].mKey)); + if (new_setting) + { + // Treat zero settings as an ask for default + setting = llclamp(new_setting, init_data[i].mMin, init_data[i].mMax); + } + } + + if (! initial && setting == mSettings[policy]) + { + // Unchanged, try next setting + continue; + } + + // Set it and report + // *TODO: These are intended to be per-host limits when we can + // support that in llcorehttp/libcurl. + LLCore::HttpHandle handle; + handle = mRequest->setPolicyOption(LLCore::HttpRequest::PO_CONNECTION_LIMIT, + mPolicies[policy], + setting, NULL); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + status = mRequest->getStatus(); + LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage + << " concurrency. Reason: " << status.toString() + << LL_ENDL; + } + else + { + LL_DEBUGS("Init") << "Changed " << init_data[i].mUsage + << " concurrency. New value: " << setting + << LL_ENDL; + mSettings[policy] = setting; + if (initial && setting != init_data[i].mDefault) + { + LL_INFOS("Init") << "Application settings overriding default " << init_data[i].mUsage + << " concurrency. New value: " << setting + << LL_ENDL; + } + } + } +} + void LLAppCoreHttp::onCompleted(LLCore::HttpHandle, LLCore::HttpResponse *) { diff --git a/indra/newview/llappcorehttp.h b/indra/newview/llappcorehttp.h index 241d73ad52..40e3042b84 100755 --- a/indra/newview/llappcorehttp.h +++ b/indra/newview/llappcorehttp.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, 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 @@ -41,6 +41,117 @@ class LLAppCoreHttp : public LLCore::HttpHandler { public: + typedef LLCore::HttpRequest::policy_t policy_t; + + enum EAppPolicy + { + /// Catchall policy class. Not used yet + /// but will have a generous concurrency + /// limit. Deep queueing possible by having + /// a chatty HTTP user. + /// + /// Destination: anywhere + /// Protocol: http: or https: + /// Transfer size: KB-MB + /// Long poll: no + /// Concurrency: high + /// Request rate: unknown + /// Pipelined: no + AP_DEFAULT, + + /// Texture fetching policy class. Used to + /// download textures via capability or SSA + /// baking service. Deep queueing of requests. + /// Do not share. + /// + /// Destination: simhost:12046 & bake-texture:80 + /// Protocol: http: + /// Transfer size: KB-MB + /// Long poll: no + /// Concurrency: high + /// Request rate: high + /// Pipelined: soon + AP_TEXTURE, + + /// Legacy mesh fetching policy class. Used to + /// download textures via 'GetMesh' capability. + /// To be deprecated. Do not share. + /// + /// Destination: simhost:12046 + /// Protocol: http: + /// Transfer size: KB-MB + /// Long poll: no + /// Concurrency: dangerously high + /// Request rate: high + /// Pipelined: no + AP_MESH1, + + /// New mesh fetching policy class. Used to + /// download textures via 'GetMesh2' capability. + /// Used when fetch request (typically one LOD) + /// is 'small', currently defined as 2MB. + /// Very deeply queued. Do not share. + /// + /// Destination: simhost:12046 + /// Protocol: http: + /// Transfer size: KB-MB + /// Long poll: no + /// Concurrency: high + /// Request rate: high + /// Pipelined: soon + AP_MESH2, + + /// Large mesh fetching policy class. Used to + /// download textures via 'GetMesh' or 'GetMesh2' + /// capability. Used when fetch request + /// is not small to avoid head-of-line problem + /// when large requests block a sequence of small, + /// fast requests. Can be shared with similar + /// traffic that can wait for longish stalls + /// (default timeout 600S). + /// + /// Destination: simhost:12046 + /// Protocol: http: + /// Transfer size: MB + /// Long poll: no + /// Concurrency: low + /// Request rate: low + /// Pipelined: soon + AP_LARGE_MESH, + + /// Asset upload policy class. Used to store + /// assets (mesh only at the moment) via + /// changeable URL. Responses may take some + /// time (default timeout 240S). + /// + /// Destination: simhost:12043 + /// Protocol: https: + /// Transfer size: KB-MB + /// Long poll: no + /// Concurrency: low + /// Request rate: low + /// Pipelined: no + AP_UPLOADS, + + /// Long-poll-type HTTP requests. Not + /// bound by a connection limit. Requests + /// will typically hang around for a long + /// time (~30S). Only shareable with other + /// long-poll requests. + /// + /// Destination: simhost:12043 + /// Protocol: https: + /// Transfer size: KB + /// Long poll: yes + /// Concurrency: unlimited but low in practice + /// Request rate: low + /// Pipelined: no + AP_LONG_POLL, + + AP_COUNT // Must be last + }; + +public: LLAppCoreHttp(); ~LLAppCoreHttp(); @@ -65,21 +176,27 @@ public: // Notification when the stop request is complete. virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); - // Retrieve the policy class for default operations. - int getPolicyDefault() const + // Retrieve a policy class identifier for desired + // application function. + policy_t getPolicy(EAppPolicy policy) const { - return mPolicyDefault; + return mPolicies[policy]; } + + // Apply initial or new settings from the environment. + void refreshSettings(bool initial); private: static const F64 MAX_THREAD_WAIT_TIME; private: - LLCore::HttpRequest * mRequest; + LLCore::HttpRequest * mRequest; // Request queue to issue shutdowns LLCore::HttpHandle mStopHandle; F64 mStopRequested; bool mStopped; - int mPolicyDefault; + policy_t mPolicies[AP_COUNT]; // Policy class id for each connection set + U32 mSettings[AP_COUNT]; + boost::signals2::connection mSettingsSignal[AP_COUNT]; // Signals to global settings that affect us }; diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp index f622d5a63a..24f467f954 100755 --- a/indra/newview/lldrawpoolavatar.cpp +++ b/indra/newview/lldrawpoolavatar.cpp @@ -55,6 +55,7 @@ static U32 sDataMask = LLDrawPoolAvatar::VERTEX_DATA_MASK; static U32 sBufferUsage = GL_STREAM_DRAW_ARB; static U32 sShaderLevel = 0; +#define JOINT_COUNT 52 LLGLSLShader* LLDrawPoolAvatar::sVertexProgram = NULL; BOOL LLDrawPoolAvatar::sSkipOpaque = FALSE; @@ -1582,10 +1583,11 @@ void LLDrawPoolAvatar::updateRiggedFaceVertexBuffer(LLVOAvatar* avatar, LLFace* LLVector4a* norm = has_normal ? (LLVector4a*) normal.get() : NULL; //build matrix palette - LLMatrix4a mp[64]; + LLMatrix4a mp[JOINT_COUNT]; LLMatrix4* mat = (LLMatrix4*) mp; - for (U32 j = 0; j < skin->mJointNames.size(); ++j) + U32 count = llmin((U32) skin->mJointNames.size(), (U32) JOINT_COUNT); + for (U32 j = 0; j < count; ++j) { LLJoint* joint = avatar->getJoint(skin->mJointNames[j]); if (joint) @@ -1642,6 +1644,7 @@ void LLDrawPoolAvatar::updateRiggedFaceVertexBuffer(LLVOAvatar* avatar, LLFace* LLVector4a& n = vol_face.mNormals[j]; bind_shape_matrix.rotate(n, t); final_mat.rotate(t, dst); + dst.normalize3fast(); norm[j] = dst; } } @@ -1708,9 +1711,9 @@ void LLDrawPoolAvatar::renderRigged(LLVOAvatar* avatar, U32 type, bool glow) { if (sShaderLevel > 0) { //upload matrix palette to shader - LLMatrix4 mat[32]; + LLMatrix4 mat[JOINT_COUNT]; - U32 count = llmin((U32) skin->mJointNames.size(), (U32) 32); + U32 count = llmin((U32) skin->mJointNames.size(), (U32) JOINT_COUNT); for (U32 i = 0; i < count; ++i) { @@ -1724,10 +1727,42 @@ void LLDrawPoolAvatar::renderRigged(LLVOAvatar* avatar, U32 type, bool glow) stop_glerror(); - LLDrawPoolAvatar::sVertexProgram->uniformMatrix4fv(LLViewerShaderMgr::AVATAR_MATRIX, + F32 mp[JOINT_COUNT*9]; + + F32 transp[JOINT_COUNT*3]; + + for (U32 i = 0; i < count; ++i) + { + F32* m = (F32*) mat[i].mMatrix; + + U32 idx = i*9; + + mp[idx+0] = m[0]; + mp[idx+1] = m[1]; + mp[idx+2] = m[2]; + + mp[idx+3] = m[4]; + mp[idx+4] = m[5]; + mp[idx+5] = m[6]; + + mp[idx+6] = m[8]; + mp[idx+7] = m[9]; + mp[idx+8] = m[10]; + + idx = i*3; + + transp[idx+0] = m[12]; + transp[idx+1] = m[13]; + transp[idx+2] = m[14]; + } + + LLDrawPoolAvatar::sVertexProgram->uniformMatrix3fv(LLViewerShaderMgr::AVATAR_MATRIX, count, FALSE, - (GLfloat*) mat[0].mMatrix); + (GLfloat*) mp); + + LLDrawPoolAvatar::sVertexProgram->uniform3fv(LLShaderMgr::AVATAR_TRANSLATION, count, transp); + stop_glerror(); } diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp index 14e1a486d3..84921849d0 100755 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -61,6 +61,9 @@ #include "llnotificationmanager.h" #include "llautoreplace.h" +const F32 ME_TYPING_TIMEOUT = 4.0f; +const F32 OTHER_TYPING_TIMEOUT = 9.0f; + floater_showed_signal_t LLFloaterIMSession::sIMFloaterShowedSignal; LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id) @@ -75,7 +78,10 @@ LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id) mTypingTimer(), mTypingTimeoutTimer(), mPositioned(false), - mSessionInitialized(false) + mSessionInitialized(false), + mMeTypingTimer(), + mOtherTypingTimer(), + mImInfo() { mIsNearbyChat = false; @@ -96,13 +102,31 @@ LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id) void LLFloaterIMSession::refresh() { if (mMeTyping) -{ + { + // Send an additional Start Typing packet every ME_TYPING_TIMEOUT seconds + if (mMeTypingTimer.getElapsedTimeF32() > ME_TYPING_TIMEOUT && false == mShouldSendTypingState) + { + LL_DEBUGS("TypingMsgs") << "Send additional Start Typing packet" << LL_ENDL; + LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, TRUE); + mMeTypingTimer.reset(); + } + // Time out if user hasn't typed for a while. if (mTypingTimeoutTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS) { - setTyping(false); + setTyping(false); + LL_DEBUGS("TypingMsgs") << "Send stop typing due to timeout" << LL_ENDL; } } + + // Clear <name is typing> message if no data received for OTHER_TYPING_TIMEOUT seconds + if (mOtherTyping && mOtherTypingTimer.getElapsedTimeF32() > OTHER_TYPING_TIMEOUT) + { + LL_DEBUGS("TypingMsgs") << "Received: is typing cleared due to timeout" << LL_ENDL; + removeTypingIndicator(mImInfo); + mOtherTyping = false; + } + } // virtual @@ -953,13 +977,21 @@ void LLFloaterIMSession::setTyping(bool typing) // much network traffic. Only send in person-to-person IMs. if ( mShouldSendTypingState && mDialog == IM_NOTHING_SPECIAL ) { - // Still typing, send 'start typing' notification or - // send 'stop typing' notification immediately - if (!mMeTyping || mTypingTimer.getElapsedTimeF32() > 1.f) + if ( mMeTyping ) { - LLIMModel::instance().sendTypingState(mSessionID, - mOtherParticipantUUID, mMeTyping); - mShouldSendTypingState = false; + if ( mTypingTimer.getElapsedTimeF32() > 1.f ) + { + // Still typing, send 'start typing' notification + LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, TRUE); + mShouldSendTypingState = false; + mMeTypingTimer.reset(); + } + } + else + { + // Send 'stop typing' notification immediately + LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, FALSE); + mShouldSendTypingState = false; } } @@ -975,10 +1007,12 @@ void LLFloaterIMSession::setTyping(bool typing) void LLFloaterIMSession::processIMTyping(const LLIMInfo* im_info, BOOL typing) { + LL_DEBUGS("TypingMsgs") << "typing=" << typing << LL_ENDL; if ( typing ) { // other user started typing addTypingIndicator(im_info); + mOtherTypingTimer.reset(); } else { @@ -1202,10 +1236,40 @@ BOOL LLFloaterIMSession::inviteToSession(const uuid_vec_t& ids) void LLFloaterIMSession::addTypingIndicator(const LLIMInfo* im_info) { +/* Operation of "<name> is typing" state machine: +Not Typing state: + + User types in P2P IM chat ... Send Start Typing, save Started time, + start Idle Timer (N seconds) go to Typing state + +Typing State: + + User enters a non-return character: if Now - Started > ME_TYPING_TIMEOUT, send + Start Typing, restart Idle Timer + User enters a return character: stop Idle Timer, send IM and Stop + Typing, go to Not Typing state + Idle Timer expires: send Stop Typing, go to Not Typing state + +The recipient has a complementary state machine in which a Start Typing +that is not followed by either an IM or another Start Typing within OTHER_TYPING_TIMEOUT +seconds switches the sender out of typing state. + +This has the nice quality of being self-healing for lost start/stop +messages while adding messages only for the (relatively rare) case of a +user who types a very long message (one that takes more than ME_TYPING_TIMEOUT seconds +to type). + +Note: OTHER_TYPING_TIMEOUT must be > ME_TYPING_TIMEOUT for proper operation of the state machine + +*/ + // We may have lost a "stop-typing" packet, don't add it twice if (im_info && !mOtherTyping) { mOtherTyping = true; + mOtherTypingTimer.reset(); + // Save im_info so that removeTypingIndicator can be properly called because a timeout has occurred + mImInfo = im_info; // Update speaker LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); diff --git a/indra/newview/llfloaterimsession.h b/indra/newview/llfloaterimsession.h index d6718843ca..2b9d06e744 100755 --- a/indra/newview/llfloaterimsession.h +++ b/indra/newview/llfloaterimsession.h @@ -187,6 +187,8 @@ private: LLFrameTimer mTypingTimer; LLFrameTimer mTypingTimeoutTimer; bool mSessionNameUpdatedForTyping; + LLFrameTimer mMeTypingTimer; + LLFrameTimer mOtherTypingTimer; bool mSessionInitialized; LLSD mQueuedMsgsForInit; @@ -196,6 +198,8 @@ private: // connection to voice channel state change signal boost::signals2::connection mVoiceChannelStateChangeConnection; + + const LLIMInfo* mImInfo; }; #endif // LL_FLOATERIMSESSION_H diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 6c8e81e563..b16ef6dd79 100755 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -1791,10 +1791,15 @@ void LLPanelLandObjects::onCommitClean(LLUICtrl *caller, void* user_data) LLParcel* parcel = lop->mParcel->getParcel(); if (parcel) { - lop->mOtherTime = atoi(lop->mCleanOtherObjectsTime->getText().c_str()); + S32 return_time = atoi(lop->mCleanOtherObjectsTime->getText().c_str()); + // Only send return time if it has changed + if (return_time != lop->mOtherTime) + { + lop->mOtherTime = return_time; - parcel->setCleanOtherTime(lop->mOtherTime); - send_other_clean_time_message(parcel->getLocalID(), lop->mOtherTime); + parcel->setCleanOtherTime(lop->mOtherTime); + send_other_clean_time_message(parcel->getLocalID(), lop->mOtherTime); + } } } diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 19cec55837..855836af7a 100755 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -535,9 +535,16 @@ BOOL LLFloaterModelPreview::postBuild() mUploadBtn = getChild<LLButton>("ok_btn"); mCalculateBtn = getChild<LLButton>("calculate_btn"); - mCalculateBtn->setClickedCallback(boost::bind(&LLFloaterModelPreview::onClickCalculateBtn, this)); + if (LLConvexDecomposition::getInstance() != NULL) + { + mCalculateBtn->setClickedCallback(boost::bind(&LLFloaterModelPreview::onClickCalculateBtn, this)); - toggleCalculateButton(true); + toggleCalculateButton(true); + } + else + { + mCalculateBtn->setEnabled(false); + } return TRUE; } diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index ed0209f90b..73c0963a1d 100755 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -91,6 +91,7 @@ #include "lltrans.h" #include "llagentui.h" #include "llmeshrepository.h" +#include "llfloaterregionrestarting.h" const S32 TERRAIN_TEXTURE_COUNT = 4; const S32 CORNER_COUNT = 4; diff --git a/indra/newview/llfloaterregionrestarting.cpp b/indra/newview/llfloaterregionrestarting.cpp new file mode 100644 index 0000000000..95d4265bb4 --- /dev/null +++ b/indra/newview/llfloaterregionrestarting.cpp @@ -0,0 +1,176 @@ +/** + * @file llfloaterregionrestarting.cpp + * @brief Shows countdown timer during region restart + * + * $LicenseInfo:firstyear=2006&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 "llfloaterregionrestarting.h" + +#include "llfloaterreg.h" +#include "lluictrl.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llviewerwindow.h" + +static S32 sSeconds; +static U32 sShakeState; + +LLFloaterRegionRestarting::LLFloaterRegionRestarting(const LLSD& key) : + LLFloater(key), + LLEventTimer(1) +{ + mName = (std::string)key["NAME"]; + sSeconds = (LLSD::Integer)key["SECONDS"]; +} + +LLFloaterRegionRestarting::~LLFloaterRegionRestarting() +{ + mRegionChangedConnection.disconnect(); +} + +BOOL LLFloaterRegionRestarting::postBuild() +{ + mRegionChangedConnection = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterRegionRestarting::regionChange, this)); + + LLStringUtil::format_map_t args; + std::string text; + + args["[NAME]"] = mName; + text = getString("RegionName", args); + LLTextBox* textbox = getChild<LLTextBox>("region_name"); + textbox->setValue(text); + + sShakeState = SHAKE_START; + + refresh(); + + return TRUE; +} + +void LLFloaterRegionRestarting::regionChange() +{ + close(); +} + +BOOL LLFloaterRegionRestarting::tick() +{ + refresh(); + + return FALSE; +} + +void LLFloaterRegionRestarting::refresh() +{ + LLStringUtil::format_map_t args; + std::string text; + + args["[SECONDS]"] = llformat("%d", sSeconds); + getChild<LLTextBox>("restart_seconds")->setValue(getString("RestartSeconds", args)); + + sSeconds = sSeconds - 1; + if(sSeconds < 0.0) + { + sSeconds = 0; + } +} + +void LLFloaterRegionRestarting::draw() +{ + LLFloater::draw(); + + const F32 SHAKE_INTERVAL = 0.025; + const F32 SHAKE_TOTAL_DURATION = 1.8; // the length of the default alert tone for this + const F32 SHAKE_INITIAL_MAGNITUDE = 1.5; + const F32 SHAKE_HORIZONTAL_BIAS = 0.25; + F32 time_shaking; + + if(SHAKE_START == sShakeState) + { + mShakeTimer.setTimerExpirySec(SHAKE_INTERVAL); + sShakeState = SHAKE_LEFT; + mShakeIterations = 0; + mShakeMagnitude = SHAKE_INITIAL_MAGNITUDE; + } + + if(SHAKE_DONE != sShakeState && mShakeTimer.hasExpired()) + { + gAgentCamera.unlockView(); + + switch(sShakeState) + { + case SHAKE_LEFT: + gAgentCamera.setPanLeftKey(mShakeMagnitude * SHAKE_HORIZONTAL_BIAS); + sShakeState = SHAKE_UP; + break; + + case SHAKE_UP: + gAgentCamera.setPanUpKey(mShakeMagnitude); + sShakeState = SHAKE_RIGHT; + break; + + case SHAKE_RIGHT: + gAgentCamera.setPanRightKey(mShakeMagnitude * SHAKE_HORIZONTAL_BIAS); + sShakeState = SHAKE_DOWN; + break; + + case SHAKE_DOWN: + gAgentCamera.setPanDownKey(mShakeMagnitude); + mShakeIterations++; + time_shaking = SHAKE_INTERVAL * (mShakeIterations * 4 /* left, up, right, down */); + if(SHAKE_TOTAL_DURATION <= time_shaking) + { + sShakeState = SHAKE_DONE; + mShakeMagnitude = 0.0; + } + else + { + sShakeState = SHAKE_LEFT; + F32 percent_done_shaking = (SHAKE_TOTAL_DURATION - time_shaking) / SHAKE_TOTAL_DURATION; + mShakeMagnitude = SHAKE_INITIAL_MAGNITUDE * (percent_done_shaking * percent_done_shaking); // exponential decay + } + break; + + default: + break; + } + mShakeTimer.setTimerExpirySec(SHAKE_INTERVAL); + } +} + +void LLFloaterRegionRestarting::close() +{ + LLFloaterRegionRestarting* floaterp = LLFloaterReg::findTypedInstance<LLFloaterRegionRestarting>("region_restarting"); + + if (floaterp) + { + floaterp->closeFloater(); + } +} + +void LLFloaterRegionRestarting::updateTime(S32 time) +{ + sSeconds = time; + sShakeState = SHAKE_START; +} diff --git a/indra/newview/llfloaterregionrestarting.h b/indra/newview/llfloaterregionrestarting.h new file mode 100644 index 0000000000..46416db2c8 --- /dev/null +++ b/indra/newview/llfloaterregionrestarting.h @@ -0,0 +1,69 @@ +/** + * @file llfloaterregionrestarting.h + * @brief Shows countdown timer during region restart + * + * $LicenseInfo:firstyear=2006&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_LLFLOATERREGIONRESTARTING_H +#define LL_LLFLOATERREGIONRESTARTING_H + +#include "llfloater.h" +#include "lltextbox.h" +#include "lleventtimer.h" + +class LLFloaterRegionRestarting : public LLFloater, public LLEventTimer +{ + friend class LLFloaterReg; + +public: + static void close(); + static void updateTime(S32 time); + +private: + LLFloaterRegionRestarting(const LLSD& key); + virtual ~LLFloaterRegionRestarting(); + virtual BOOL postBuild(); + virtual BOOL tick(); + virtual void refresh(); + virtual void draw(); + virtual void regionChange(); + + std::string mName; + U32 mShakeIterations; + F32 mShakeMagnitude; + LLTimer mShakeTimer; + + boost::signals2::connection mRegionChangedConnection; + + enum + { + SHAKE_START, + SHAKE_LEFT, + SHAKE_UP, + SHAKE_RIGHT, + SHAKE_DOWN, + SHAKE_DONE + }; +}; + +#endif // LL_LLFLOATERREGIONRESTARTING_H diff --git a/indra/newview/llfloaterwebcontent.cpp b/indra/newview/llfloaterwebcontent.cpp index 76b73fcf29..68dbb5ae33 100755 --- a/indra/newview/llfloaterwebcontent.cpp +++ b/indra/newview/llfloaterwebcontent.cpp @@ -372,7 +372,10 @@ void LLFloaterWebContent::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent } else if(event == MEDIA_EVENT_GEOMETRY_CHANGE) { - geometryChanged(self->getGeometryX(), self->getGeometryY(), self->getGeometryWidth(), self->getGeometryHeight()); + if (mCurrentURL.find("facebook.com/dialog/oauth") == std::string::npos) // HACK to fix ACME-1317 - Cho + { + geometryChanged(self->getGeometryX(), self->getGeometryY(), self->getGeometryWidth(), self->getGeometryHeight()); + } } else if(event == MEDIA_EVENT_STATUS_TEXT_CHANGED ) { diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp index 137b5446cf..cb637c7162 100755 --- a/indra/newview/llfloaterworldmap.cpp +++ b/indra/newview/llfloaterworldmap.cpp @@ -627,8 +627,8 @@ void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global) if (!sim_info) { // We haven't found a region for that point yet, leave the tracking to the world map - LLWorldMap::getInstance()->setTracking(pos_global); LLTracker::stopTracking(NULL); + LLWorldMap::getInstance()->setTracking(pos_global); S32 world_x = S32(pos_global.mdV[0] / 256); S32 world_y = S32(pos_global.mdV[1] / 256); LLWorldMapMessage::getInstance()->sendMapBlockRequest(world_x, world_y, world_x, world_y, true); @@ -643,9 +643,9 @@ void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global) { // Down region. Show the blue circle of death! // i.e. let the world map that this and tell it it's invalid + LLTracker::stopTracking(NULL); LLWorldMap::getInstance()->setTracking(pos_global); LLWorldMap::getInstance()->setTrackingInvalid(); - LLTracker::stopTracking(NULL); setDefaultBtn(""); // clicked on a down region - turn off coord display @@ -665,8 +665,8 @@ void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global) std::string tooltip(""); mTrackedStatus = LLTracker::TRACKING_LOCATION; - LLTracker::trackLocation(pos_global, full_name, tooltip); LLWorldMap::getInstance()->cancelTracking(); // The floater is taking over the tracking + LLTracker::trackLocation(pos_global, full_name, tooltip); LLVector3d coord_pos = LLTracker::getTrackedPositionGlobal(); updateTeleportCoordsDisplay( coord_pos ); diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 80ef506272..44943d8722 100755 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -74,6 +74,7 @@ #include "llvoavatarself.h" #include "llwearablelist.h" #include "lllandmarkactions.h" +#include "llpanellandmarks.h" void copy_slurl_to_clipboard_callback_inv(const std::string& slurl); @@ -1449,6 +1450,38 @@ void LLItemBridge::performAction(LLInventoryModel* model, std::string action) } } } + else if ("show_on_map" == action) + { + doActionOnCurSelectedLandmark(boost::bind(&LLItemBridge::doShowOnMap, this, _1)); + } +} + +void LLItemBridge::doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb) +{ + LLViewerInventoryItem* cur_item = getItem(); + if(cur_item && cur_item->getInventoryType() == LLInventoryType::IT_LANDMARK) + { + LLLandmark* landmark = LLLandmarkActions::getLandmark(cur_item->getUUID(), cb); + if (landmark) + { + cb(landmark); + } + } +} + +void LLItemBridge::doShowOnMap(LLLandmark* landmark) +{ + LLVector3d landmark_global_pos; + // landmark has already been tested for NULL by calling routine + if (landmark->getGlobalPos(landmark_global_pos)) + { + LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); + if (!landmark_global_pos.isExactlyZero() && worldmap_instance) + { + worldmap_instance->trackLocation(landmark_global_pos); + LLFloaterReg::showInstance("world_map", "center"); + } + } } void copy_slurl_to_clipboard_callback_inv(const std::string& slurl) @@ -4580,6 +4613,7 @@ void LLLandmarkBridge::buildContextMenu(LLMenuGL& menu, U32 flags) items.push_back(std::string("Landmark Separator")); items.push_back(std::string("url_copy")); items.push_back(std::string("About Landmark")); + items.push_back(std::string("show_on_map")); } // Disable "About Landmark" menu item for diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index 517153e171..bc875e8f37 100755 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -36,6 +36,7 @@ #include "llviewercontrol.h" #include "llviewerwearable.h" #include "lltooldraganddrop.h" +#include "lllandmarklist.h" class LLInventoryFilter; class LLInventoryPanel; @@ -239,7 +240,10 @@ protected: BOOL confirmRemoveItem(const LLSD& notification, const LLSD& response); virtual BOOL isItemPermissive() const; virtual void buildDisplayName() const; + void doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb); +private: + void doShowOnMap(LLLandmark* landmark); }; class LLFolderBridge : public LLInvFVBridge diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 2e02805c02..5afd2cb329 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -5,7 +5,7 @@ * * $LicenseInfo:firstyear=2005&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2010-2014, 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 @@ -38,11 +38,13 @@ #include "llcallbacklist.h" #include "llcurl.h" #include "lldatapacker.h" +#include "lldeadmantimer.h" #include "llfloatermodelpreview.h" #include "llfloaterperms.h" #include "lleconomy.h" #include "llimagej2c.h" #include "llhost.h" +#include "llmath.h" #include "llnotificationsutil.h" #include "llsd.h" #include "llsdutil_math.h" @@ -52,6 +54,7 @@ #include "llviewercontrol.h" #include "llviewerinventory.h" #include "llviewermenufile.h" +#include "llviewermessage.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llviewertexturelist.h" @@ -65,6 +68,9 @@ #include "llfoldertype.h" #include "llviewerparcelmgr.h" #include "lluploadfloaterobservers.h" +#include "bufferarray.h" +#include "bufferstream.h" +#include "llfasttimer.h" #include "boost/lexical_cast.hpp" @@ -72,11 +78,296 @@ #include "netdb.h" #endif -#include <queue> + +// Purpose +// +// The purpose of this module is to provide access between the viewer +// and the asset system as regards to mesh objects. +// +// * High-throughput download of mesh assets from servers while +// following best industry practices for network profile. +// * Reliable expensing and upload of new mesh assets. +// * Recovery and retry from errors when appropriate. +// * Decomposition of mesh assets for preview and uploads. +// * And most important: all of the above without exposing the +// main thread to stalls due to deep processing or thread +// locking actions. In particular, the following operations +// on LLMeshRepository are very averse to any stalls: +// * loadMesh +// * getMeshHeader (For structural details, see: +// http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format) +// * notifyLoadedMeshes +// * getSkinInfo +// +// Threads +// +// main Main rendering thread, very sensitive to locking and other stalls +// repo Overseeing worker thread associated with the LLMeshRepoThread class +// decom Worker thread for mesh decomposition requests +// core HTTP worker thread: does the work but doesn't intrude here +// uploadN 0-N temporary mesh upload threads (0-1 in practice) +// +// Sequence of Operations +// +// What follows is a description of the retrieval of one LOD for +// a new mesh object. Work is performed by a series of short, quick +// actions distributed over a number of threads. Each is meant +// to proceed without stalling and the whole forms a deep request +// pipeline to achieve throughput. Ellipsis indicates a return +// or break in processing which is resumed elsewhere. +// +// main thread repo thread (run() method) +// +// loadMesh() invoked to request LOD +// append LODRequest to mPendingRequests +// ... +// other mesh requests may be made +// ... +// notifyLoadedMeshes() invoked to stage work +// append HeaderRequest to mHeaderReqQ +// ... +// scan mHeaderReqQ +// issue 4096-byte GET for header +// ... +// onCompleted() invoked for GET +// data copied +// headerReceived() invoked +// LLSD parsed +// mMeshHeader, mMeshHeaderSize updated +// scan mPendingLOD for LOD request +// push LODRequest to mLODReqQ +// ... +// scan mLODReqQ +// fetchMeshLOD() invoked +// issue Byte-Range GET for LOD +// ... +// onCompleted() invoked for GET +// data copied +// lodReceived() invoked +// unpack data into LLVolume +// append LoadedMesh to mLoadedQ +// ... +// notifyLoadedMeshes() invoked again +// scan mLoadedQ +// notifyMeshLoaded() for LOD +// setMeshAssetLoaded() invoked for system volume +// notifyMeshLoaded() invoked for each interested object +// ... +// +// Mutexes +// +// LLMeshRepository::mMeshMutex +// LLMeshRepoThread::mMutex +// LLMeshRepoThread::mHeaderMutex +// LLMeshRepoThread::mSignal (LLCondition) +// LLPhysicsDecomp::mSignal (LLCondition) +// LLPhysicsDecomp::mMutex +// LLMeshUploadThread::mMutex +// +// Mutex Order Rules +// +// 1. LLMeshRepoThread::mMutex before LLMeshRepoThread::mHeaderMutex +// 2. LLMeshRepository::mMeshMutex before LLMeshRepoThread::mMutex +// (There are more rules, haven't been extracted.) +// +// Data Member Access/Locking +// +// Description of how shared access to static and instance data +// members is performed. Each member is followed by the name of +// the mutex, if any, covering the data and then a list of data +// access models each of which is a triplet of the following form: +// +// {ro, wo, rw}.{main, repo, any}.{mutex, none} +// Type of access: read-only, write-only, read-write. +// Accessing thread or 'any' +// Relevant mutex held during access (several may be held) or 'none' +// +// A careful eye will notice some unsafe operations. Many of these +// have an alibi of some form. Several types of alibi are identified +// and listed here: +// +// [0] No alibi. Probably unsafe. +// [1] Single-writer, self-consistent readers. Old data must +// be tolerated by any reader but data will come true eventually. +// [2] Like [1] but provides a hint about thread state. These +// may be unsafe. +// [3] empty() check outside of lock. Can me made safish when +// done in double-check lock style. But this depends on +// std:: implementation and memory model. +// [4] Appears to be covered by a mutex but doesn't need one. +// [5] Read of a double-checked lock. +// +// So, in addition to documentation, take this as a to-do/review +// list and see if you can improve things. For porters to non-x86 +// architectures, the weaker memory models will make these platforms +// probabilistically more susceptible to hitting race conditions. +// True here and in other multi-thread code such as texture fetching. +// (Strong memory models make weak programmers. Weak memory models +// make strong programmers. Ref: arm, ppc, mips, alpha) +// +// LLMeshRepository: +// +// sBytesReceived none rw.repo.none, ro.main.none [1] +// sMeshRequestCount " +// sHTTPRequestCount " +// sHTTPLargeRequestCount " +// sHTTPRetryCount " +// sHTTPErrorCount " +// sLODPending mMeshMutex [4] rw.main.mMeshMutex +// sLODProcessing Repo::mMutex rw.any.Repo::mMutex +// sCacheBytesRead none rw.repo.none, ro.main.none [1] +// sCacheBytesWritten " +// sCacheReads " +// sCacheWrites " +// mLoadingMeshes mMeshMutex [4] rw.main.none, rw.any.mMeshMutex +// mSkinMap none rw.main.none +// mDecompositionMap none rw.main.none +// mPendingRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingSkins mMeshMutex [4] rw.main.mMeshMutex +// mPendingSkinRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingDecompositions mMeshMutex [4] rw.main.mMeshMutex +// mPendingDecompositionRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingPhysicsShapes mMeshMutex [4] rw.main.mMeshMutex +// mPendingPhysicsShapeRequests mMeshMutex [4] rw.main.mMeshMutex +// mUploads none rw.main.none (upload thread accessing objects) +// mUploadWaitList none rw.main.none (upload thread accessing objects) +// mInventoryQ mMeshMutex [4] rw.main.mMeshMutex, ro.main.none [5] +// mUploadErrorQ mMeshMutex rw.main.mMeshMutex, rw.any.mMeshMutex +// mGetMeshVersion none rw.main.none +// +// LLMeshRepoThread: +// +// sActiveHeaderRequests mMutex rw.any.mMutex, ro.repo.none [1] +// sActiveLODRequests mMutex rw.any.mMutex, ro.repo.none [1] +// sMaxConcurrentRequests mMutex wo.main.none, ro.repo.none, ro.main.mMutex +// mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0] +// mMeshHeaderSize mHeaderMutex rw.repo.mHeaderMutex +// mSkinRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mSkinInfoQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0]) +// mDecompositionRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mPhysicsShapeRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mDecompositionQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0]) +// mHeaderReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex +// mLODReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex +// mUnavailableQ mMutex rw.repo.none [0], ro.main.none [5], rw.main.mMutex +// mLoadedQ mMutex rw.repo.mMutex, ro.main.none [5], rw.main.mMutex +// mPendingLOD mMutex rw.repo.mMutex, rw.any.mMutex +// mGetMeshCapability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0]) +// mGetMesh2Capability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0]) +// mGetMeshVersion mMutex rw.main.mMutex, ro.repo.mMutex +// mHttp* none rw.repo.none +// +// LLMeshUploadThread: +// +// mDiscarded mMutex rw.main.mMutex, ro.uploadN.none [1] +// ... more ... +// +// QA/Development Testing +// +// Debug variable 'MeshUploadFakeErrors' takes a mask of bits that will +// simulate an error on fee query or upload. Defined bits are: +// +// 0x01 Simulate application error on fee check reading +// response body from file "fake_upload_error.xml" +// 0x02 Same as 0x01 but for actual upload attempt. +// 0x04 Simulate a transport problem on fee check with a +// locally-generated 500 status. +// 0x08 As with 0x04 but for the upload operation. +// +// For major changes, see the LL_MESH_FASTTIMER_ENABLE below and +// instructions for looking for frame stalls using fast timers. +// +// *TODO: Work list for followup actions: +// * Review anything marked as unsafe above, verify if there are real issues. +// * See if we can put ::run() into a hard sleep. May not actually perform better +// than the current scheme so be prepared for disappointment. You'll likely +// need to introduce a condition variable class that references a mutex in +// methods rather than derives from mutex which isn't correct. +// * On upload failures, make more information available to the alerting +// dialog. Get the structured information going into the log into a +// tree there. +// * Header parse failures come without much explanation. Elaborate. +// * Work queue for uploads? Any need for this or is the current scheme good +// enough? +// * Various temp buffers used in VFS I/O might be allocated once or even +// statically. Look for some wins here. +// * Move data structures holding mesh data used by main thread into main- +// thread-only access so that no locking is needed. May require duplication +// of some data so that worker thread has a minimal data set to guide +// operations. +// +// -------------------------------------------------------------------------- +// Development/Debug/QA Tools +// +// Enable here or in build environment to get fasttimer data on mesh fetches. +// +// Typically, this is used to perform A/B testing using the +// fasttimer console (shift-ctrl-9). This is done by looking +// for stalls due to lock contention between the main thread +// and the repository and HTTP code. In a release viewer, +// these appear as ping-time or worse spikes in frame time. +// With this instrumentation enabled, a stall will appear +// under the 'Mesh Fetch' timer which will be either top-level +// or under 'Render' time. + +#ifndef LL_MESH_FASTTIMER_ENABLE +#define LL_MESH_FASTTIMER_ENABLE 1 +#endif +#if LL_MESH_FASTTIMER_ENABLE +static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); + +#define MESH_FASTTIMER_DEFBLOCK LLFastTimer meshtimer(FTM_MESH_FETCH) +#else +#define MESH_FASTTIMER_DEFBLOCK +#endif // LL_MESH_FASTTIMER_ENABLE + + +// Random failure testing for development/QA. +// +// Set the MESH_*_FAILED macros to either 'false' or to +// an invocation of MESH_RANDOM_NTH_TRUE() with some +// suitable number. In production, all must be false. +// +// Example: +// #define MESH_HTTP_RESPONSE_FAILED MESH_RANDOM_NTH_TRUE(9) + +// 1-in-N calls will test true +#define MESH_RANDOM_NTH_TRUE(_N) ( ll_rand(S32(_N)) == 0 ) + +#define MESH_HTTP_RESPONSE_FAILED false +#define MESH_HEADER_PROCESS_FAILED false +#define MESH_LOD_PROCESS_FAILED false +#define MESH_SKIN_INFO_PROCESS_FAILED false +#define MESH_DECOMP_PROCESS_FAILED false +#define MESH_PHYS_SHAPE_PROCESS_FAILED false + +// -------------------------------------------------------------------------- + LLMeshRepository gMeshRepo; -const U32 MAX_MESH_REQUESTS_PER_SECOND = 100; +const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space +const S32 REQUEST_HIGH_WATER_MIN = 32; // Limits for GetMesh regions +const S32 REQUEST_HIGH_WATER_MAX = 150; // Should remain under 2X throttle +const S32 REQUEST_LOW_WATER_MIN = 16; +const S32 REQUEST_LOW_WATER_MAX = 75; +const S32 REQUEST2_HIGH_WATER_MIN = 32; // Limits for GetMesh2 regions +const S32 REQUEST2_HIGH_WATER_MAX = 80; +const S32 REQUEST2_LOW_WATER_MIN = 16; +const S32 REQUEST2_LOW_WATER_MAX = 40; +const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue +const long SMALL_MESH_XFER_TIMEOUT = 120L; // Seconds to complete xfer, small mesh downloads +const long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large downloads + +// Would normally like to retry on uploads as some +// retryable failures would be recoverable. Unfortunately, +// the mesh service is using 500 (retryable) rather than +// 400/bad request (permanent) for a bad payload and +// retrying that just leads to revocation of the one-shot +// cap which then produces a 404 on retry destroying some +// (occasionally) useful error information. We'll leave +// upload retries to the user as in the past. SH-4667. +const long UPLOAD_RETRY_LIMIT = 0L; // Maximum mesh version to support. Three least significant digits are reserved for the minor version, // with major version changes indicating a format change that is not backwards compatible and should not @@ -87,35 +378,45 @@ const U32 MAX_MESH_REQUESTS_PER_SECOND = 100; const S32 MAX_MESH_VERSION = 999; U32 LLMeshRepository::sBytesReceived = 0; +U32 LLMeshRepository::sMeshRequestCount = 0; U32 LLMeshRepository::sHTTPRequestCount = 0; +U32 LLMeshRepository::sHTTPLargeRequestCount = 0; U32 LLMeshRepository::sHTTPRetryCount = 0; +U32 LLMeshRepository::sHTTPErrorCount = 0; U32 LLMeshRepository::sLODProcessing = 0; U32 LLMeshRepository::sLODPending = 0; U32 LLMeshRepository::sCacheBytesRead = 0; U32 LLMeshRepository::sCacheBytesWritten = 0; -U32 LLMeshRepository::sPeakKbps = 0; - +U32 LLMeshRepository::sCacheReads = 0; +U32 LLMeshRepository::sCacheWrites = 0; +U32 LLMeshRepository::sMaxLockHoldoffs = 0; -const U32 MAX_TEXTURE_UPLOAD_RETRIES = 5; +LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, false); // true -> gather cpu metrics + static S32 dump_num = 0; std::string make_dump_name(std::string prefix, S32 num) { return prefix + boost::lexical_cast<std::string>(num) + std::string(".xml"); - } void dump_llsd_to_file(const LLSD& content, std::string filename); LLSD llsd_from_file(std::string filename); -std::string header_lod[] = +const std::string header_lod[] = { "lowest_lod", "low_lod", "medium_lod", "high_lod" }; +const char * const LOG_MESH = "Mesh"; +// Static data and functions to measure mesh load +// time metrics for a new region scene. +static unsigned int metrics_teleport_start_count = 0; +boost::signals2::connection metrics_teleport_started_signal; +static void teleport_started(); //get the number of bytes resident in memory for given volume U32 get_volume_memory_size(const LLVolume* volume) @@ -197,200 +498,228 @@ void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res, } } -S32 LLMeshRepoThread::sActiveHeaderRequests = 0; -S32 LLMeshRepoThread::sActiveLODRequests = 0; +volatile S32 LLMeshRepoThread::sActiveHeaderRequests = 0; +volatile S32 LLMeshRepoThread::sActiveLODRequests = 0; U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; - -class LLMeshHeaderResponder : public LLCurl::Responder +S32 LLMeshRepoThread::sRequestLowWater = REQUEST2_LOW_WATER_MIN; +S32 LLMeshRepoThread::sRequestHighWater = REQUEST2_HIGH_WATER_MIN; +S32 LLMeshRepoThread::sRequestWaterLevel = 0; + +// Base handler class for all mesh users of llcorehttp. +// This is roughly equivalent to a Responder class in +// traditional LL code. The base is going to perform +// common response/data handling in the inherited +// onCompleted() method. Derived classes, one for each +// type of HTTP action, define processData() and +// processFailure() methods to customize handling and +// error messages. +// +// LLCore::HttpHandler +// LLMeshHandlerBase +// LLMeshHeaderHandler +// LLMeshLODHandler +// LLMeshSkinInfoHandler +// LLMeshDecompositionHandler +// LLMeshPhysicsShapeHandler +// LLMeshUploadThread + +class LLMeshHandlerBase : public LLCore::HttpHandler { public: - LLVolumeParams mMeshParams; - bool mProcessed; + LLMeshHandlerBase() + : LLCore::HttpHandler(), + mMeshParams(), + mProcessed(false), + mHttpHandle(LLCORE_HTTP_HANDLE_INVALID) + {} + + virtual ~LLMeshHandlerBase() + {} + +protected: + LLMeshHandlerBase(const LLMeshHandlerBase &); // Not defined + void operator=(const LLMeshHandlerBase &); // Not defined + +public: + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size) = 0; + virtual void processFailure(LLCore::HttpStatus status) = 0; + +public: + LLVolumeParams mMeshParams; + bool mProcessed; + LLCore::HttpHandle mHttpHandle; +}; - LLMeshHeaderResponder(const LLVolumeParams& mesh_params) - : mMeshParams(mesh_params) - { - LLMeshRepoThread::incActiveHeaderRequests(); - mProcessed = false; - } - ~LLMeshHeaderResponder() +// Subclass for header fetches. +// +// Thread: repo +class LLMeshHeaderHandler : public LLMeshHandlerBase +{ +public: + LLMeshHeaderHandler(const LLVolumeParams & mesh_params) + : LLMeshHandlerBase() { - if (!LLApp::isQuitting()) - { - if (!mProcessed) - { //something went wrong, retry - llwarns << "Timeout or service unavailable, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; - LLMeshRepoThread::HeaderRequest req(mMeshParams); - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mHeaderReqQ.push(req); - } - - LLMeshRepoThread::decActiveHeaderRequests(); - } + mMeshParams = mesh_params; + LLMeshRepoThread::incActiveHeaderRequests(); } + virtual ~LLMeshHeaderHandler(); - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); - +protected: + LLMeshHeaderHandler(const LLMeshHeaderHandler &); // Not defined + void operator=(const LLMeshHeaderHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); }; -class LLMeshLODResponder : public LLCurl::Responder + +// Subclass for LOD fetches. +// +// Thread: repo +class LLMeshLODHandler : public LLMeshHandlerBase { public: - LLVolumeParams mMeshParams; - S32 mLOD; - U32 mRequestedBytes; - U32 mOffset; - bool mProcessed; - - LLMeshLODResponder(const LLVolumeParams& mesh_params, S32 lod, U32 offset, U32 requested_bytes) - : mMeshParams(mesh_params), mLOD(lod), mOffset(offset), mRequestedBytes(requested_bytes) + LLMeshLODHandler(const LLVolumeParams & mesh_params, S32 lod, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(), + mLOD(lod), + mRequestedBytes(requested_bytes), + mOffset(offset) { + mMeshParams = mesh_params; LLMeshRepoThread::incActiveLODRequests(); - mProcessed = false; } + virtual ~LLMeshLODHandler(); + +protected: + LLMeshLODHandler(const LLMeshLODHandler &); // Not defined + void operator=(const LLMeshLODHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); - ~LLMeshLODResponder() - { - if (!LLApp::isQuitting()) - { - if (!mProcessed) - { - llwarns << "Killed without being processed, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD); - } - LLMeshRepoThread::decActiveLODRequests(); - } - } - - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); - -}; - -class LLMeshSkinInfoResponder : public LLCurl::Responder -{ public: - LLUUID mMeshID; + S32 mLOD; U32 mRequestedBytes; U32 mOffset; - bool mProcessed; - - LLMeshSkinInfoResponder(const LLUUID& id, U32 offset, U32 size) - : mMeshID(id), mRequestedBytes(size), mOffset(offset) - { - mProcessed = false; - } - - ~LLMeshSkinInfoResponder() - { - if (!LLApp::isQuitting() && - !mProcessed && - mMeshID.notNull()) - { // Something went wrong, retry - llwarns << "Timeout or service unavailable, retrying loadMeshSkinInfo() for " << mMeshID << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->loadMeshSkinInfo(mMeshID); - } - } - - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); - }; -class LLMeshDecompositionResponder : public LLCurl::Responder + +// Subclass for skin info fetches. +// +// Thread: repo +class LLMeshSkinInfoHandler : public LLMeshHandlerBase { public: + LLMeshSkinInfoHandler(const LLUUID& id, U32 offset, U32 size) + : LLMeshHandlerBase(), + mMeshID(id), + mRequestedBytes(size), + mOffset(offset) + {} + virtual ~LLMeshSkinInfoHandler(); + +protected: + LLMeshSkinInfoHandler(const LLMeshSkinInfoHandler &); // Not defined + void operator=(const LLMeshSkinInfoHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: LLUUID mMeshID; U32 mRequestedBytes; U32 mOffset; - bool mProcessed; - - LLMeshDecompositionResponder(const LLUUID& id, U32 offset, U32 size) - : mMeshID(id), mRequestedBytes(size), mOffset(offset) - { - mProcessed = false; - } - - ~LLMeshDecompositionResponder() - { - if (!LLApp::isQuitting() && - !mProcessed && - mMeshID.notNull()) - { // Something went wrong, retry - llwarns << "Timeout or service unavailable, retrying loadMeshDecomposition() for " << mMeshID << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->loadMeshDecomposition(mMeshID); - } - } - - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); - }; -class LLMeshPhysicsShapeResponder : public LLCurl::Responder + +// Subclass for decomposition fetches. +// +// Thread: repo +class LLMeshDecompositionHandler : public LLMeshHandlerBase { public: + LLMeshDecompositionHandler(const LLUUID& id, U32 offset, U32 size) + : LLMeshHandlerBase(), + mMeshID(id), + mRequestedBytes(size), + mOffset(offset) + {} + virtual ~LLMeshDecompositionHandler(); + +protected: + LLMeshDecompositionHandler(const LLMeshDecompositionHandler &); // Not defined + void operator=(const LLMeshDecompositionHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: LLUUID mMeshID; U32 mRequestedBytes; U32 mOffset; - bool mProcessed; +}; - LLMeshPhysicsShapeResponder(const LLUUID& id, U32 offset, U32 size) - : mMeshID(id), mRequestedBytes(size), mOffset(offset) - { - mProcessed = false; - } - ~LLMeshPhysicsShapeResponder() - { - if (!LLApp::isQuitting() && - !mProcessed && - mMeshID.notNull()) - { // Something went wrong, retry - llwarns << "Timeout or service unavailable, retrying loadMeshPhysicsShape() for " << mMeshID << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID); - } - } - - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); +// Subclass for physics shape fetches. +// +// Thread: repo +class LLMeshPhysicsShapeHandler : public LLMeshHandlerBase +{ +public: + LLMeshPhysicsShapeHandler(const LLUUID& id, U32 offset, U32 size) + : LLMeshHandlerBase(), + mMeshID(id), + mRequestedBytes(size), + mOffset(offset) + {} + virtual ~LLMeshPhysicsShapeHandler(); + +protected: + LLMeshPhysicsShapeHandler(const LLMeshPhysicsShapeHandler &); // Not defined + void operator=(const LLMeshPhysicsShapeHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); +public: + LLUUID mMeshID; + U32 mRequestedBytes; + U32 mOffset; }; -void log_upload_error(S32 status, const LLSD& content, std::string stage, std::string model_name) + +void log_upload_error(LLCore::HttpStatus status, const LLSD& content, + const char * const stage, const std::string & model_name) { // Add notification popup. LLSD args; - std::string message = content["error"]["message"]; - std::string identifier = content["error"]["identifier"]; + std::string message = content["error"]["message"].asString(); + std::string identifier = content["error"]["identifier"].asString(); args["MESSAGE"] = message; args["IDENTIFIER"] = identifier; args["LABEL"] = model_name; gMeshRepo.uploadError(args); // Log details. - llwarns << "stage: " << stage << " http status: " << status << llendl; + LL_WARNS(LOG_MESH) << "Error in stage: " << stage + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << ")" << LL_ENDL; if (content.has("error")) { const LLSD& err = content["error"]; - llwarns << "err: " << err << llendl; - llwarns << "mesh upload failed, stage '" << stage - << "' error '" << err["error"].asString() - << "', message '" << err["message"].asString() - << "', id '" << err["identifier"].asString() - << "'" << llendl; + LL_WARNS(LOG_MESH) << "error: " << err << LL_ENDL; + LL_WARNS(LOG_MESH) << " mesh upload failed, stage '" << stage + << "', error '" << err["error"].asString() + << "', message '" << err["message"].asString() + << "', id '" << err["identifier"].asString() + << "'" << LL_ENDL; if (err.has("errors")) { S32 error_num = 0; @@ -400,13 +729,13 @@ void log_upload_error(S32 status, const LLSD& content, std::string stage, std::s ++it) { const LLSD& err_entry = *it; - llwarns << "error[" << error_num << "]:" << llendl; + LL_WARNS(LOG_MESH) << " error[" << error_num << "]:" << LL_ENDL; for (LLSD::map_const_iterator map_it = err_entry.beginMap(); map_it != err_entry.endMap(); ++map_it) { - llwarns << "\t" << map_it->first << ": " - << map_it->second << llendl; + LL_WARNS(LOG_MESH) << " " << map_it->first << ": " + << map_it->second << LL_ENDL; } error_num++; } @@ -414,153 +743,72 @@ void log_upload_error(S32 status, const LLSD& content, std::string stage, std::s } else { - llwarns << "bad mesh, no error information available" << llendl; + LL_WARNS(LOG_MESH) << "Bad response to mesh request, no additional error information available." << LL_ENDL; } } -class LLWholeModelFeeResponder: public LLCurl::Responder -{ - LLMeshUploadThread* mThread; - LLSD mModelData; - LLHandle<LLWholeModelFeeObserver> mObserverHandle; -public: - LLWholeModelFeeResponder(LLMeshUploadThread* thread, LLSD& model_data, LLHandle<LLWholeModelFeeObserver> observer_handle): - mThread(thread), - mModelData(model_data), - mObserverHandle(observer_handle) - { - if (mThread) - { - mThread->startRequest(); - } - } - - ~LLWholeModelFeeResponder() - { - if (mThread) - { - mThread->stopRequest(); - } - } - - virtual void completed(U32 status, - const std::string& reason, - const LLSD& content) - { - LLSD cc = content; - if (gSavedSettings.getS32("MeshUploadFakeErrors")&1) - { - cc = llsd_from_file("fake_upload_error.xml"); - } - - dump_llsd_to_file(cc,make_dump_name("whole_model_fee_response_",dump_num)); - LLWholeModelFeeObserver* observer = mObserverHandle.get(); +LLMeshRepoThread::LLMeshRepoThread() +: LLThread("mesh repo"), + mHttpRequest(NULL), + mHttpOptions(NULL), + mHttpLargeOptions(NULL), + mHttpHeaders(NULL), + mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpLegacyPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpLargePolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpPriority(0), + mGetMeshVersion(2) +{ + mMutex = new LLMutex(NULL); + mHeaderMutex = new LLMutex(NULL); + mSignal = new LLCondition(NULL); + mHttpRequest = new LLCore::HttpRequest; + mHttpOptions = new LLCore::HttpOptions; + mHttpOptions->setTransferTimeout(SMALL_MESH_XFER_TIMEOUT); + mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); + mHttpLargeOptions = new LLCore::HttpOptions; + mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT); + mHttpLargeOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); + mHttpHeaders = new LLCore::HttpHeaders; + mHttpHeaders->append("Accept", "application/vnd.ll.mesh"); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH2); + mHttpLegacyPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH1); + mHttpLargePolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_LARGE_MESH); +} - if (isGoodStatus(status) && - cc["state"].asString() == "upload") - { - mThread->mWholeModelUploadURL = cc["uploader"].asString(); - if (observer) - { - cc["data"]["upload_price"] = cc["upload_price"]; - observer->onModelPhysicsFeeReceived(cc["data"], mThread->mWholeModelUploadURL); - } - } - else - { - llwarns << "fee request failed" << llendl; - log_upload_error(status,cc,"fee",mModelData["name"]); - mThread->mWholeModelUploadURL = ""; +LLMeshRepoThread::~LLMeshRepoThread() +{ + LL_INFOS(LOG_MESH) << "Small GETs issued: " << LLMeshRepository::sHTTPRequestCount + << ", Large GETs issued: " << LLMeshRepository::sHTTPLargeRequestCount + << ", Max Lock Holdoffs: " << LLMeshRepository::sMaxLockHoldoffs + << LL_ENDL; - if (observer) - { - observer->setModelPhysicsFeeErrorStatus(status, reason); - } - } + for (http_request_set::iterator iter(mHttpRequestSet.begin()); + iter != mHttpRequestSet.end(); + ++iter) + { + delete *iter; } - -}; - -class LLWholeModelUploadResponder: public LLCurl::Responder -{ - LLMeshUploadThread* mThread; - LLSD mModelData; - LLHandle<LLWholeModelUploadObserver> mObserverHandle; - -public: - LLWholeModelUploadResponder(LLMeshUploadThread* thread, LLSD& model_data, LLHandle<LLWholeModelUploadObserver> observer_handle): - mThread(thread), - mModelData(model_data), - mObserverHandle(observer_handle) + mHttpRequestSet.clear(); + if (mHttpHeaders) { - if (mThread) - { - mThread->startRequest(); - } + mHttpHeaders->release(); + mHttpHeaders = NULL; } - - ~LLWholeModelUploadResponder() + if (mHttpOptions) { - if (mThread) - { - mThread->stopRequest(); - } + mHttpOptions->release(); + mHttpOptions = NULL; } - - virtual void completed(U32 status, - const std::string& reason, - const LLSD& content) + if (mHttpLargeOptions) { - LLSD cc = content; - if (gSavedSettings.getS32("MeshUploadFakeErrors")&2) - { - cc = llsd_from_file("fake_upload_error.xml"); - } - - dump_llsd_to_file(cc,make_dump_name("whole_model_upload_response_",dump_num)); - - LLWholeModelUploadObserver* observer = mObserverHandle.get(); - - // requested "mesh" asset type isn't actually the type - // of the resultant object, fix it up here. - if (isGoodStatus(status) && - cc["state"].asString() == "complete") - { - mModelData["asset_type"] = "object"; - gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData,cc)); - - if (observer) - { - doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer)); - } - } - else - { - llwarns << "upload failed" << llendl; - std::string model_name = mModelData["name"].asString(); - log_upload_error(status,cc,"upload",model_name); - - if (observer) - { - doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); - } - } + mHttpLargeOptions->release(); + mHttpLargeOptions = NULL; } -}; - -LLMeshRepoThread::LLMeshRepoThread() -: LLThread("mesh repo") -{ - mWaiting = false; - mMutex = new LLMutex(NULL); - mHeaderMutex = new LLMutex(NULL); - mSignal = new LLCondition(NULL); -} - -LLMeshRepoThread::~LLMeshRepoThread() -{ + delete mHttpRequest; + mHttpRequest = NULL; delete mMutex; mMutex = NULL; delete mHeaderMutex; @@ -571,109 +819,180 @@ LLMeshRepoThread::~LLMeshRepoThread() void LLMeshRepoThread::run() { - mCurlRequest = new LLCurlRequest(); LLCDResult res = LLConvexDecomposition::initThread(); if (res != LLCD_OK) { - llwarns << "convex decomposition unable to be loaded" << llendl; + LL_WARNS(LOG_MESH) << "Convex decomposition unable to be loaded. Expect severe problems." << LL_ENDL; } while (!LLApp::isQuitting()) { - mWaiting = true; + // *TODO: Revise sleep/wake strategy and try to move away + // from polling operations in this thread. We can sleep + // this thread hard when: + // * All Http requests are serviced + // * LOD request queue empty + // * Header request queue empty + // * Skin info request queue empty + // * Decomposition request queue empty + // * Physics shape request queue empty + // We wake the thread when any of the above become untrue. + // Will likely need a correctly-implemented condition variable to do this. + // On the other hand, this may actually be an effective and efficient scheme... + mSignal->wait(); - mWaiting = false; - if (!LLApp::isQuitting()) + if (LLApp::isQuitting()) { - static U32 count = 0; + break; + } + + if (! mHttpRequestSet.empty()) + { + // Dispatch all HttpHandler notifications + mHttpRequest->update(0L); + } + sRequestWaterLevel = mHttpRequestSet.size(); // Stats data update + + // NOTE: order of queue processing intentionally favors LOD requests over header requests - static F32 last_hundred = gFrameTimeSeconds; + while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + if (! mMutex) + { + break; + } + mMutex->lock(); + LODRequest req = mLODReqQ.front(); + mLODReqQ.pop(); + LLMeshRepository::sLODProcessing--; + mMutex->unlock(); + if (!fetchMeshLOD(req.mMeshParams, req.mLOD)) // failed, resubmit + { + mMutex->lock(); + mLODReqQ.push(req) ; + ++LLMeshRepository::sLODProcessing; + mMutex->unlock(); + } + } - if (gFrameTimeSeconds - last_hundred > 1.f) - { //a second has gone by, clear count - last_hundred = gFrameTimeSeconds; - count = 0; + while (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + if (! mMutex) + { + break; + } + mMutex->lock(); + HeaderRequest req = mHeaderReqQ.front(); + mHeaderReqQ.pop(); + mMutex->unlock(); + if (!fetchMeshHeader(req.mMeshParams))//failed, resubmit + { + mMutex->lock(); + mHeaderReqQ.push(req) ; + mMutex->unlock(); } + } - // NOTE: throttling intentionally favors LOD requests over header requests - - while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveLODRequests < sMaxConcurrentRequests) + // For the final three request lists, similar goal to above but + // slightly different queue structures. Stay off the mutex when + // performing long-duration actions. + + if (mHttpRequestSet.size() < sRequestHighWater + && (! mSkinRequests.empty() + || ! mDecompositionRequests.empty() + || ! mPhysicsShapeRequests.empty())) + { + // Something to do probably, lock and double-check. We don't want + // to hold the lock long here. That will stall main thread activities + // so we bounce it. + + mMutex->lock(); + if (! mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) { - if (mMutex) + std::set<LLUUID> incomplete; + std::set<LLUUID>::iterator iter(mSkinRequests.begin()); + while (iter != mSkinRequests.end() && mHttpRequestSet.size() < sRequestHighWater) { - mMutex->lock(); - LODRequest req = mLODReqQ.front(); - mLODReqQ.pop(); - LLMeshRepository::sLODProcessing--; + LLUUID mesh_id = *iter; + mSkinRequests.erase(iter); mMutex->unlock(); - if (!fetchMeshLOD(req.mMeshParams, req.mLOD, count))//failed, resubmit + + if (! fetchMeshSkinInfo(mesh_id)) { - mMutex->lock(); - mLODReqQ.push(req); - mMutex->unlock(); + incomplete.insert(mesh_id); } + + mMutex->lock(); + iter = mSkinRequests.begin(); } - } - while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveHeaderRequests < sMaxConcurrentRequests) - { - if (mMutex) + if (! incomplete.empty()) { - mMutex->lock(); - HeaderRequest req = mHeaderReqQ.front(); - mHeaderReqQ.pop(); - mMutex->unlock(); - if (!fetchMeshHeader(req.mMeshParams, count))//failed, resubmit - { - mMutex->lock(); - mHeaderReqQ.push(req) ; - mMutex->unlock(); - } + mSkinRequests.insert(incomplete.begin(), incomplete.end()); } } - { //mSkinRequests is protected by mSignal + // holding lock, try next list + // *TODO: For UI/debug-oriented lists, we might drop the fine- + // grained locking as there's a lowered expectation of smoothness + // in these cases. + if (! mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) + { std::set<LLUUID> incomplete; - for (std::set<LLUUID>::iterator iter = mSkinRequests.begin(); iter != mSkinRequests.end(); ++iter) + std::set<LLUUID>::iterator iter(mDecompositionRequests.begin()); + while (iter != mDecompositionRequests.end() && mHttpRequestSet.size() < sRequestHighWater) { LLUUID mesh_id = *iter; - if (!fetchMeshSkinInfo(mesh_id)) + mDecompositionRequests.erase(iter); + mMutex->unlock(); + + if (! fetchMeshDecomposition(mesh_id)) { incomplete.insert(mesh_id); } + + mMutex->lock(); + iter = mDecompositionRequests.begin(); } - mSkinRequests = incomplete; - } - { //mDecompositionRequests is protected by mSignal - std::set<LLUUID> incomplete; - for (std::set<LLUUID>::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter) + if (! incomplete.empty()) { - LLUUID mesh_id = *iter; - if (!fetchMeshDecomposition(mesh_id)) - { - incomplete.insert(mesh_id); - } + mDecompositionRequests.insert(incomplete.begin(), incomplete.end()); } - mDecompositionRequests = incomplete; } - { //mPhysicsShapeRequests is protected by mSignal + // holding lock, final list + if (! mPhysicsShapeRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) + { std::set<LLUUID> incomplete; - for (std::set<LLUUID>::iterator iter = mPhysicsShapeRequests.begin(); iter != mPhysicsShapeRequests.end(); ++iter) + std::set<LLUUID>::iterator iter(mPhysicsShapeRequests.begin()); + while (iter != mPhysicsShapeRequests.end() && mHttpRequestSet.size() < sRequestHighWater) { LLUUID mesh_id = *iter; - if (!fetchMeshPhysicsShape(mesh_id)) + mPhysicsShapeRequests.erase(iter); + mMutex->unlock(); + + if (! fetchMeshPhysicsShape(mesh_id)) { incomplete.insert(mesh_id); } + + mMutex->lock(); + iter = mPhysicsShapeRequests.begin(); } - mPhysicsShapeRequests = incomplete; - } - mCurlRequest->process(); + if (! incomplete.empty()) + { + mPhysicsShapeRequests.insert(incomplete.begin(), incomplete.end()); + } + } + mMutex->unlock(); } + + // For dev purposes only. A dynamic change could make this false + // and that shouldn't assert. + // llassert_always(mHttpRequestSet.size() <= sRequestHighWater); } if (mSignal->isLocked()) @@ -684,25 +1003,25 @@ void LLMeshRepoThread::run() res = LLConvexDecomposition::quitThread(); if (res != LLCD_OK) { - llwarns << "convex decomposition unable to be quit" << llendl; + LL_WARNS(LOG_MESH) << "Convex decomposition unable to be quit." << LL_ENDL; } - - delete mCurlRequest; - mCurlRequest = NULL; } +// Mutex: LLMeshRepoThread::mMutex must be held on entry void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id) -{ //protected by mSignal, no locking needed here +{ mSkinRequests.insert(mesh_id); } +// Mutex: LLMeshRepoThread::mMutex must be held on entry void LLMeshRepoThread::loadMeshDecomposition(const LLUUID& mesh_id) -{ //protected by mSignal, no locking needed here +{ mDecompositionRequests.insert(mesh_id); } +// Mutex: LLMeshRepoThread::mMutex must be held on entry void LLMeshRepoThread::loadMeshPhysicsShape(const LLUUID& mesh_id) -{ //protected by mSignal, no locking needed here +{ mPhysicsShapeRequests.insert(mesh_id); } @@ -715,7 +1034,6 @@ void LLMeshRepoThread::lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 } - void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) { //could be called from any thread LLMutexLock lock(mMutex); @@ -747,31 +1065,122 @@ void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) } } -//static -std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id) +// Mutex: must be holding mMutex when called +void LLMeshRepoThread::setGetMeshCaps(const std::string & get_mesh1, + const std::string & get_mesh2, + int pref_version) { - std::string http_url; + mGetMeshCapability = get_mesh1; + mGetMesh2Capability = get_mesh2; + mGetMeshVersion = pref_version; +} + + +// Constructs a Cap URL for the mesh. Prefers a GetMesh2 cap +// over a GetMesh cap. +// +// Mutex: acquires mMutex +void LLMeshRepoThread::constructUrl(LLUUID mesh_id, std::string * url, int * version) +{ + std::string res_url; + int res_version(2); if (gAgent.getRegion()) { - http_url = gMeshRepo.mGetMeshCapability; + LLMutexLock lock(mMutex); + + // Get a consistent pair of (cap string, version). The + // locking could be eliminated here without loss of safety + // by using a set of staging values in setGetMeshCaps(). + + if (! mGetMesh2Capability.empty() && mGetMeshVersion > 1) + { + res_url = mGetMesh2Capability; + res_version = 2; + } + else + { + res_url = mGetMeshCapability; + res_version = 1; + } } - if (!http_url.empty()) + if (! res_url.empty()) { - http_url += "/?mesh_id="; - http_url += mesh_id.asString().c_str(); + res_url += "/?mesh_id="; + res_url += mesh_id.asString().c_str(); } else { - llwarns << "Current region does not have GetMesh capability! Cannot load " << mesh_id << ".mesh" << llendl; + LL_WARNS_ONCE(LOG_MESH) << "Current region does not have GetMesh capability! Cannot load " + << mesh_id << ".mesh" << LL_ENDL; } - return http_url; + *url = res_url; + *version = res_version; +} + +// Issue an HTTP GET request with byte range using the right +// policy class. Large requests go to the large request class. +// If the current region supports GetMesh2, we prefer that for +// smaller requests otherwise we try to use the traditional +// GetMesh capability and connection concurrency. +// +// @return Valid handle or LLCORE_HTTP_HANDLE_INVALID. +// If the latter, actual status is found in +// mHttpStatus member which is valid until the +// next call to this method. +// +// Thread: repo +LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, int cap_version, + size_t offset, size_t len, + LLCore::HttpHandler * handler) +{ + LLCore::HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + if (len < LARGE_MESH_FETCH_THRESHOLD) + { + handle = mHttpRequest->requestGetByteRange((2 == cap_version + ? mHttpPolicyClass + : mHttpLegacyPolicyClass), + mHttpPriority, + url, + offset, + len, + mHttpOptions, + mHttpHeaders, + handler); + if (LLCORE_HTTP_HANDLE_INVALID != handle) + { + ++LLMeshRepository::sHTTPRequestCount; + } + } + else + { + handle = mHttpRequest->requestGetByteRange(mHttpLargePolicyClass, + mHttpPriority, + url, + offset, + len, + mHttpLargeOptions, + mHttpHeaders, + handler); + if (LLCORE_HTTP_HANDLE_INVALID != handle) + { + ++LLMeshRepository::sHTTPLargeRequestCount; + } + } + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + // Something went wrong, capture the error code for caller. + mHttpStatus = mHttpRequest->getStatus(); + } + return handle; } + bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) -{ //protected by mMutex +{ if (!mHeaderMutex) { @@ -786,7 +1195,8 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) return false; } - bool ret = true ; + ++LLMeshRepository::sMeshRequestCount; + bool ret = true; U32 header_size = mMeshHeaderSize[mesh_id]; if (header_size > 0) @@ -804,6 +1214,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; file.seek(offset); U8* buffer = new U8[size]; file.read(buffer, size); @@ -828,17 +1239,28 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::vector<std::string> headers; - headers.push_back("Accept: application/octet-stream"); + int cap_version(2); + std::string http_url; + constructUrl(mesh_id, &http_url, &cap_version); - std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) - { - ret = mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshSkinInfoResponder(mesh_id, offset, size)); - if(ret) + { + LLMeshSkinInfoHandler * handler = new LLMeshSkinInfoHandler(mesh_id, offset, size); + LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for skin info on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + delete handler; + ret = false; + + } + else { - LLMeshRepository::sHTTPRequestCount++; + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); } } } @@ -853,7 +1275,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) } bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) -{ //protected by mMutex +{ if (!mHeaderMutex) { return false; @@ -867,8 +1289,9 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) return false; } + ++LLMeshRepository::sMeshRequestCount; U32 header_size = mMeshHeaderSize[mesh_id]; - bool ret = true ; + bool ret = true; if (header_size > 0) { @@ -885,6 +1308,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; file.seek(offset); U8* buffer = new U8[size]; @@ -910,17 +1334,27 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::vector<std::string> headers; - headers.push_back("Accept: application/octet-stream"); - - std::string http_url = constructUrl(mesh_id); + int cap_version(2); + std::string http_url; + constructUrl(mesh_id, &http_url, &cap_version); + if (!http_url.empty()) - { - ret = mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshDecompositionResponder(mesh_id, offset, size)); - if(ret) + { + LLMeshDecompositionHandler * handler = new LLMeshDecompositionHandler(mesh_id, offset, size); + LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for decomposition mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + delete handler; + ret = false; + } + else { - LLMeshRepository::sHTTPRequestCount++; + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); } } } @@ -935,7 +1369,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) -{ //protected by mMutex +{ if (!mHeaderMutex) { return false; @@ -949,8 +1383,9 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) return false; } + ++LLMeshRepository::sMeshRequestCount; U32 header_size = mMeshHeaderSize[mesh_id]; - bool ret = true ; + bool ret = true; if (header_size > 0) { @@ -967,6 +1402,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; file.seek(offset); U8* buffer = new U8[size]; file.read(buffer, size); @@ -991,18 +1427,27 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::vector<std::string> headers; - headers.push_back("Accept: application/octet-stream"); - - std::string http_url = constructUrl(mesh_id); + int cap_version(2); + std::string http_url; + constructUrl(mesh_id, &http_url, &cap_version); + if (!http_url.empty()) - { - ret = mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshPhysicsShapeResponder(mesh_id, offset, size)); - - if(ret) + { + LLMeshPhysicsShapeHandler * handler = new LLMeshPhysicsShapeHandler(mesh_id, offset, size); + LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) { - LLMeshRepository::sHTTPRequestCount++; + LL_WARNS(LOG_MESH) << "HTTP GET request failed for physics shape on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + delete handler; + ret = false; + } + else + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); } } } @@ -1049,8 +1494,10 @@ void LLMeshRepoThread::decActiveHeaderRequests() } //return false if failed to get header -bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& count) +bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params) { + ++LLMeshRepository::sMeshRequestCount; + { //look for mesh in asset in vfs LLVFile file(gVFS, mesh_params.getSculptID(), LLAssetType::AT_MESH); @@ -1058,43 +1505,57 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c S32 size = file.getSize(); if (size > 0) - { //NOTE -- if the header size is ever more than 4KB, this will break - U8 buffer[4096]; - S32 bytes = llmin(size, 4096); + { + // *NOTE: if the header size is ever more than 4KB, this will break + U8 buffer[MESH_HEADER_SIZE]; + S32 bytes = llmin(size, MESH_HEADER_SIZE); LLMeshRepository::sCacheBytesRead += bytes; + ++LLMeshRepository::sCacheReads; file.read(buffer, bytes); if (headerReceived(mesh_params, buffer, bytes)) - { //did not do an HTTP request, return false + { + // Found mesh in VFS cache return true; } } } //either cache entry doesn't exist or is corrupt, request header from simulator - bool retval = true ; - std::vector<std::string> headers; - headers.push_back("Accept: application/octet-stream"); - - std::string http_url = constructUrl(mesh_params.getSculptID()); + bool retval = true; + int cap_version(2); + std::string http_url; + constructUrl(mesh_params.getSculptID(), &http_url, &cap_version); + if (!http_url.empty()) { //grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits //within the first 4KB //NOTE -- this will break of headers ever exceed 4KB - retval = mCurlRequest->getByteRange(http_url, headers, 0, 4096, new LLMeshHeaderResponder(mesh_params)); - if(retval) + + LLMeshHeaderHandler * handler = new LLMeshHeaderHandler(mesh_params); + LLCore::HttpHandle handle = getByteRange(http_url, cap_version, 0, MESH_HEADER_SIZE, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for mesh header " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + delete handler; + retval = false; + } + else { - LLMeshRepository::sHTTPRequestCount++; + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); } - count++; } return retval; } //return false if failed to get mesh lod. -bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count) -{ //protected by mMutex +bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod) +{ if (!mHeaderMutex) { return false; @@ -1102,6 +1563,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, mHeaderMutex->lock(); + ++LLMeshRepository::sMeshRequestCount; bool retval = true; LLUUID mesh_id = mesh_params.getSculptID(); @@ -1123,6 +1585,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; file.seek(offset); U8* buffer = new U8[size]; file.read(buffer, size); @@ -1147,20 +1610,29 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, } //reading from VFS failed for whatever reason, fetch from sim - std::vector<std::string> headers; - headers.push_back("Accept: application/octet-stream"); - - std::string http_url = constructUrl(mesh_id); + int cap_version(2); + std::string http_url; + constructUrl(mesh_id, &http_url, &cap_version); + if (!http_url.empty()) - { - retval = mCurlRequest->getByteRange(constructUrl(mesh_id), headers, offset, size, - new LLMeshLODResponder(mesh_params, lod, offset, size)); - - if(retval) + { + LLMeshLODHandler * handler = new LLMeshLODHandler(mesh_params, lod, offset, size); + LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for LOD on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + delete handler; + retval = false; + } + else { - LLMeshRepository::sHTTPRequestCount++; + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + // *NOTE: Allowing a re-request, not marking as unavailable. Is that correct? } - count++; } else { @@ -1182,6 +1654,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size) { + const LLUUID mesh_id = mesh_params.getSculptID(); LLSD header; U32 header_size = 0; @@ -1202,7 +1675,8 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat if (!LLSDSerialize::fromBinary(header, stream, data_size)) { - llwarns << "Mesh header parse error. Not a valid mesh asset!" << llendl; + LL_WARNS(LOG_MESH) << "Mesh header parse error. Not a valid mesh asset! ID: " << mesh_id + << LL_ENDL; return false; } @@ -1210,13 +1684,12 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat } else { - llinfos - << "Marking header as non-existent, will not retry." << llendl; + LL_INFOS(LOG_MESH) << "Non-positive data size. Marking header as non-existent, will not retry. ID: " << mesh_id + << LL_ENDL; header["404"] = 1; } { - LLUUID mesh_id = mesh_params.getSculptID(); { LLMutexLock lock(mHeaderMutex); @@ -1224,6 +1697,7 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat mMeshHeader[mesh_id] = header; } + LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time. //check for pending requests @@ -1277,7 +1751,8 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat if (!unzip_llsd(skin, stream, data_size)) { - llwarns << "Mesh skin info parse error. Not a valid mesh asset!" << llendl; + LL_WARNS(LOG_MESH) << "Mesh skin info parse error. Not a valid mesh asset! ID: " << mesh_id + << LL_ENDL; return false; } } @@ -1286,8 +1761,11 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat LLMeshSkinInfo info(skin); info.mMeshID = mesh_id; - //llinfos<<"info pelvis offset"<<info.mPelvisOffset<<llendl; - mSkinInfoQ.push(info); + // LL_DEBUGS(LOG_MESH) << "info pelvis offset" << info.mPelvisOffset << LL_ENDL; + { + LLMutexLock lock(mMutex); + mSkinInfoQ.push_back(info); + } } return true; @@ -1305,7 +1783,8 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3 if (!unzip_llsd(decomp, stream, data_size)) { - llwarns << "Mesh decomposition parse error. Not a valid mesh asset!" << llendl; + LL_WARNS(LOG_MESH) << "Mesh decomposition parse error. Not a valid mesh asset! ID: " << mesh_id + << LL_ENDL; return false; } } @@ -1313,7 +1792,10 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3 { LLModel::Decomposition* d = new LLModel::Decomposition(decomp); d->mMeshID = mesh_id; - mDecompositionQ.push(d); + { + LLMutexLock lock(mMutex); + mDecompositionQ.push_back(d); + } } return true; @@ -1372,15 +1854,20 @@ bool LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 } } - mDecompositionQ.push(d); + { + LLMutexLock lock(mMutex); + mDecompositionQ.push_back(d); + } return true; } LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures, - bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload, - LLHandle<LLWholeModelFeeObserver> fee_observer, LLHandle<LLWholeModelUploadObserver> upload_observer) -: LLThread("mesh upload"), - mDiscarded(FALSE), + bool upload_skin, bool upload_joints, const std::string & upload_url, bool do_upload, + LLHandle<LLWholeModelFeeObserver> fee_observer, + LLHandle<LLWholeModelUploadObserver> upload_observer) + : LLThread("mesh upload"), + LLCore::HttpHandler(), + mDiscarded(false), mDoUpload(do_upload), mWholeModelUploadURL(upload_url), mFeeObserverHandle(fee_observer), @@ -1391,7 +1878,6 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, mUploadSkin = upload_skin; mUploadJoints = upload_joints; mMutex = new LLMutex(NULL); - mCurlRequest = NULL; mPendingUploads = 0; mFinished = false; mOrigin = gAgent.getPositionAgent(); @@ -1401,12 +1887,33 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, mOrigin += gAgent.getAtAxis() * scale.magVec(); - mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut") ; + mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut"); + + mHttpRequest = new LLCore::HttpRequest; + mHttpOptions = new LLCore::HttpOptions; + mHttpOptions->setTransferTimeout(mMeshUploadTimeOut); + mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); + mHttpOptions->setRetries(UPLOAD_RETRY_LIMIT); + mHttpHeaders = new LLCore::HttpHeaders; + mHttpHeaders->append("Content-Type", "application/llsd+xml"); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_UPLOADS); + mHttpPriority = 0; } LLMeshUploadThread::~LLMeshUploadThread() { - + if (mHttpHeaders) + { + mHttpHeaders->release(); + mHttpHeaders = NULL; + } + if (mHttpOptions) + { + mHttpOptions->release(); + mHttpOptions = NULL; + } + delete mHttpRequest; + mHttpRequest = NULL; } LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread) @@ -1448,14 +1955,14 @@ void LLMeshUploadThread::preStart() void LLMeshUploadThread::discard() { - LLMutexLock lock(mMutex) ; - mDiscarded = TRUE ; + LLMutexLock lock(mMutex); + mDiscarded = true; } -BOOL LLMeshUploadThread::isDiscarded() +bool LLMeshUploadThread::isDiscarded() const { - LLMutexLock lock(mMutex) ; - return mDiscarded ; + LLMutexLock lock(mMutex); + return mDiscarded; } void LLMeshUploadThread::run() @@ -1706,9 +2213,15 @@ void LLMeshUploadThread::generateHulls() } } - if(has_valid_requests) - { - while (!mPhysicsComplete) + if (has_valid_requests) + { + // *NOTE: Interesting livelock condition on shutdown. If there + // is an upload request in generateHulls() when shutdown starts, + // the main thread isn't available to manage communication between + // the decomposition thread and the upload thread and this loop + // wouldn't complete in turn stalling the main thread. The check + // on isDiscarded() prevents that. + while (! mPhysicsComplete && ! isDiscarded()) { apr_sleep(100); } @@ -1717,86 +2230,266 @@ void LLMeshUploadThread::generateHulls() void LLMeshUploadThread::doWholeModelUpload() { - mCurlRequest = new LLCurlRequest(); + LL_DEBUGS(LOG_MESH) << "Starting model upload. Instances: " << mInstance.size() << LL_ENDL; if (mWholeModelUploadURL.empty()) { - llinfos << "unable to upload, fee request failed" << llendl; + LL_WARNS(LOG_MESH) << "Missing mesh upload capability, unable to upload, fee request failed." + << LL_ENDL; } else { generateHulls(); - - LLSD full_model_data; - wholeModelToLLSD(full_model_data, true); - LLSD body = full_model_data["asset_resources"]; - dump_llsd_to_file(body,make_dump_name("whole_model_body_",dump_num)); - LLCurlRequest::headers_t headers; - + LL_DEBUGS(LOG_MESH) << "Hull generation completed." << LL_ENDL; + + mModelData = LLSD::emptyMap(); + wholeModelToLLSD(mModelData, true); + LLSD body = mModelData["asset_resources"]; + dump_llsd_to_file(body, make_dump_name("whole_model_body_", dump_num)); + + LLCore::BufferArray * ba = new LLCore::BufferArray; + LLCore::BufferArrayStream bas(ba); + LLSDSerialize::toXML(body, bas); + // LLSDSerialize::toXML(mModelData, bas); // <- Enabling this will generate a convenient upload error + LLCore::HttpHandle handle = mHttpRequest->requestPost(mHttpPolicyClass, + mHttpPriority, + mWholeModelUploadURL, + ba, + mHttpOptions, + mHttpHeaders, + this); + ba->release(); + + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + mHttpStatus = mHttpRequest->getStatus(); + + LL_WARNS(LOG_MESH) << "Couldn't issue request for full model upload. Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + } + else { - LLCurl::ResponderPtr responder = new LLWholeModelUploadResponder(this, full_model_data, mUploadObserverHandle) ; + U32 sleep_time(10); + + LL_DEBUGS(LOG_MESH) << "POST request issued." << LL_ENDL; + + mHttpRequest->update(0); + while (! LLApp::isQuitting() && ! finished() && ! isDiscarded()) + { + ms_sleep(sleep_time); + sleep_time = llmin(250U, sleep_time + sleep_time); + mHttpRequest->update(0); + } - while(!mCurlRequest->post(mWholeModelUploadURL, headers, body, responder, mMeshUploadTimeOut)) + if (isDiscarded()) + { + LL_DEBUGS(LOG_MESH) << "Mesh upload operation discarded." << LL_ENDL; + } + else { - //sleep for 10ms to prevent eating a whole core - apr_sleep(10000); + LL_DEBUGS(LOG_MESH) << "Mesh upload operation completed." << LL_ENDL; } } - - do - { - mCurlRequest->process(); - //sleep for 10ms to prevent eating a whole core - apr_sleep(10000); - } while (!LLAppViewer::isQuitting() && mPendingUploads > 0); } - - delete mCurlRequest; - mCurlRequest = NULL; - - // Currently a no-op. - mFinished = true; } void LLMeshUploadThread::requestWholeModelFee() { dump_num++; - mCurlRequest = new LLCurlRequest(); - generateHulls(); - LLSD model_data; - wholeModelToLLSD(model_data,false); - dump_llsd_to_file(model_data,make_dump_name("whole_model_fee_request_",dump_num)); - - LLCurlRequest::headers_t headers; + mModelData = LLSD::emptyMap(); + wholeModelToLLSD(mModelData, false); + dump_llsd_to_file(mModelData, make_dump_name("whole_model_fee_request_", dump_num)); + LLCore::BufferArray * ba = new LLCore::BufferArray; + LLCore::BufferArrayStream bas(ba); + LLSDSerialize::toXML(mModelData, bas); + + LLCore::HttpHandle handle = mHttpRequest->requestPost(mHttpPolicyClass, + mHttpPriority, + mWholeModelFeeCapability, + ba, + mHttpOptions, + mHttpHeaders, + this); + ba->release(); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + mHttpStatus = mHttpRequest->getStatus(); + + LL_WARNS(LOG_MESH) << "Couldn't issue request for model fee. Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + } + else { - LLCurl::ResponderPtr responder = new LLWholeModelFeeResponder(this,model_data, mFeeObserverHandle) ; - while(!mCurlRequest->post(mWholeModelFeeCapability, headers, model_data, responder, mMeshUploadTimeOut)) + U32 sleep_time(10); + + mHttpRequest->update(0); + while (! LLApp::isQuitting() && ! finished() && ! isDiscarded()) + { + ms_sleep(sleep_time); + sleep_time = llmin(250U, sleep_time + sleep_time); + mHttpRequest->update(0); + } + if (isDiscarded()) { - //sleep for 10ms to prevent eating a whole core - apr_sleep(10000); + LL_DEBUGS(LOG_MESH) << "Mesh fee query operation discarded." << LL_ENDL; } } +} - do - { - mCurlRequest->process(); - //sleep for 10ms to prevent eating a whole core - apr_sleep(10000); - } while (!LLApp::isQuitting() && mPendingUploads > 0); - delete mCurlRequest; - mCurlRequest = NULL; +// Does completion duty for both fee queries and actual uploads. +void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + // QA/Devel: 0x2 to enable fake error import on upload, 0x1 on fee check + const S32 fake_error(gSavedSettings.getS32("MeshUploadFakeErrors") & (mDoUpload ? 0xa : 0x5)); + LLCore::HttpStatus status(response->getStatus()); + if (fake_error) + { + status = (fake_error & 0x0c) ? LLCore::HttpStatus(500) : LLCore::HttpStatus(200); + } + std::string reason(status.toString()); + LLSD body; - // Currently a no-op. mFinished = true; + + if (mDoUpload) + { + // model upload case + LLWholeModelUploadObserver * observer(mUploadObserverHandle.get()); + + if (! status) + { + LL_WARNS(LOG_MESH) << "Upload failed. Reason: " << reason + << " (" << status.toTerseString() << ")" + << LL_ENDL; + + // Build a fake body for the alert generator + body["error"] = LLSD::emptyMap(); + body["error"]["message"] = reason; + body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py + log_upload_error(status, body, "upload", mModelData["name"].asString()); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); + } + } + else + { + if (fake_error & 0x2) + { + body = llsd_from_file("fake_upload_error.xml"); + } + else + { + LLCore::BufferArray * ba(response->getBody()); + if (ba && ba->size()) + { + LLCore::BufferArrayStream bas(ba); + LLSDSerialize::fromXML(body, bas); + } + } + dump_llsd_to_file(body, make_dump_name("whole_model_upload_response_", dump_num)); + + if (body["state"].asString() == "complete") + { + // requested "mesh" asset type isn't actually the type + // of the resultant object, fix it up here. + mModelData["asset_type"] = "object"; + gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData, body)); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer)); + } + } + else + { + LL_WARNS(LOG_MESH) << "Upload failed. Not in expected 'complete' state." << LL_ENDL; + log_upload_error(status, body, "upload", mModelData["name"].asString()); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); + } + } + } + } + else + { + // model fee case + LLWholeModelFeeObserver* observer(mFeeObserverHandle.get()); + mWholeModelUploadURL.clear(); + + if (! status) + { + LL_WARNS(LOG_MESH) << "Fee request failed. Reason: " << reason + << " (" << status.toTerseString() << ")" + << LL_ENDL; + + // Build a fake body for the alert generator + body["error"] = LLSD::emptyMap(); + body["error"]["message"] = reason; + body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py + log_upload_error(status, body, "fee", mModelData["name"].asString()); + + if (observer) + { + observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason); + } + } + else + { + if (fake_error & 0x1) + { + body = llsd_from_file("fake_upload_error.xml"); + } + else + { + LLCore::BufferArray * ba(response->getBody()); + if (ba && ba->size()) + { + LLCore::BufferArrayStream bas(ba); + LLSDSerialize::fromXML(body, bas); + } + } + dump_llsd_to_file(body, make_dump_name("whole_model_fee_response_", dump_num)); + + if (body["state"].asString() == "upload") + { + mWholeModelUploadURL = body["uploader"].asString(); + + if (observer) + { + body["data"]["upload_price"] = body["upload_price"]; + observer->onModelPhysicsFeeReceived(body["data"], mWholeModelUploadURL); + } + } + else + { + LL_WARNS(LOG_MESH) << "Fee request failed. Not in expected 'upload' state." << LL_ENDL; + log_upload_error(status, body, "fee", mModelData["name"].asString()); + + if (observer) + { + observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason); + } + } + } + } } + void LLMeshRepoThread::notifyLoadedMeshes() { + bool update_metrics(false); + if (!mMutex) { return; @@ -1805,10 +2498,16 @@ void LLMeshRepoThread::notifyLoadedMeshes() while (!mLoadedQ.empty()) { mMutex->lock(); + if (mLoadedQ.empty()) + { + mMutex->unlock(); + break; + } LoadedMesh mesh = mLoadedQ.front(); mLoadedQ.pop(); mMutex->unlock(); + update_metrics = true; if (mesh.mVolume && mesh.mVolume->getNumVolumeFaces() > 0) { gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume); @@ -1823,24 +2522,59 @@ void LLMeshRepoThread::notifyLoadedMeshes() while (!mUnavailableQ.empty()) { mMutex->lock(); + if (mUnavailableQ.empty()) + { + mMutex->unlock(); + break; + } + LODRequest req = mUnavailableQ.front(); mUnavailableQ.pop(); mMutex->unlock(); - + + update_metrics = true; gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD); } - while (!mSkinInfoQ.empty()) + if (! mSkinInfoQ.empty() || ! mDecompositionQ.empty()) { - gMeshRepo.notifySkinInfoReceived(mSkinInfoQ.front()); - mSkinInfoQ.pop(); + if (mMutex->trylock()) + { + std::list<LLMeshSkinInfo> skin_info_q; + std::list<LLModel::Decomposition*> decomp_q; + + if (! mSkinInfoQ.empty()) + { + skin_info_q.swap(mSkinInfoQ); + } + if (! mDecompositionQ.empty()) + { + decomp_q.swap(mDecompositionQ); + } + + mMutex->unlock(); + + // Process the elements free of the lock + while (! skin_info_q.empty()) + { + gMeshRepo.notifySkinInfoReceived(skin_info_q.front()); + skin_info_q.pop_front(); + } + + while (! decomp_q.empty()) + { + gMeshRepo.notifyDecompositionReceived(decomp_q.front()); + decomp_q.pop_front(); + } + } } - while (!mDecompositionQ.empty()) + if (update_metrics) { - gMeshRepo.notifyDecompositionReceived(mDecompositionQ.front()); - mDecompositionQ.pop(); + // Ping time-to-load metrics for mesh download operations. + LLMeshRepository::metricsProgress(0); } + } S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) @@ -1919,245 +2653,250 @@ void LLMeshRepository::cacheOutgoingMesh(LLMeshUploadData& data, LLSD& header) } -void LLMeshLODResponder::completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) +void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) { mProcessed = true; - - // thread could have already be destroyed during logout - if( !gMeshRepo.mThread ) - { - return; - } - - S32 data_size = buffer->countAfter(channels.in(), NULL); - if (status < 200 || status > 400) + unsigned int retries(0U); + response->getRetries(NULL, &retries); + LLMeshRepository::sHTTPRetryCount += retries; + + LLCore::HttpStatus status(response->getStatus()); + if (! status || MESH_HTTP_RESPONSE_FAILED) { - llwarns << status << ": " << reason << llendl; + processFailure(status); + ++LLMeshRepository::sHTTPErrorCount; } - - if (data_size < mRequestedBytes) + else { - if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE) - { //timeout or service unavailable, try again - llwarns << "Timeout or service unavailable, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD); - } - else + // From texture fetch code and may apply here: + // + // A warning about partial (HTTP 206) data. Some grid services + // do *not* return a 'Content-Range' header in the response to + // Range requests with a 206 status. We're forced to assume + // we get what we asked for in these cases until we can fix + // the services. + // + // May also need to deal with 200 status (full asset returned + // rather than partial) and 416 (request completely unsatisfyable). + // Always been exposed to these but are less likely here where + // speculative loads aren't done. + static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); + + if (par_status != status) { - llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint - llwarns << "Unhandled status " << status << llendl; + LL_WARNS_ONCE(LOG_MESH) << "Non-206 successful status received for fetch: " + << status.toTerseString() << LL_ENDL; } - return; - } + + LLCore::BufferArray * body(response->getBody()); + S32 data_size(body ? body->size() : 0); + U8 * data(NULL); - LLMeshRepository::sBytesReceived += mRequestedBytes; + if (data_size > 0) + { + // *TODO: Try to get rid of data copying and add interfaces + // that support BufferArray directly. Introduce a two-phase + // handler, optional first that takes a body, fallback second + // that requires a temporary allocation and data copy. + data = new U8[data_size]; + body->read(0, (char *) data, data_size); + LLMeshRepository::sBytesReceived += data_size; + } - U8* data = NULL; + processData(body, data, data_size); - if (data_size > 0) - { - data = new U8[data_size]; - buffer->readAfter(channels.in(), NULL, data, data_size); + delete [] data; } - if (gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size)) - { - //good fetch from sim, write to VFS for caching - LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE); + // Release handler + gMeshRepo.mThread->mHttpRequestSet.erase(this); + delete this; // Must be last statement +} - S32 offset = mOffset; - S32 size = mRequestedBytes; - if (file.getSize() >= offset+size) +LLMeshHeaderHandler::~LLMeshHeaderHandler() +{ + if (!LLApp::isQuitting()) + { + if (! mProcessed) { - file.seek(offset); - file.write(data, size); - LLMeshRepository::sCacheBytesWritten += size; + // something went wrong, retry + LL_WARNS(LOG_MESH) << "Mesh header fetch canceled unexpectedly, retrying." << LL_ENDL; + LLMeshRepoThread::HeaderRequest req(mMeshParams); + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mHeaderReqQ.push(req); } + LLMeshRepoThread::decActiveHeaderRequests(); } - - delete [] data; } -void LLMeshSkinInfoResponder::completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) +void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) { - mProcessed = true; + LL_WARNS(LOG_MESH) << "Error during mesh header handling. ID: " << mMeshParams.getSculptID() + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; - // thread could have already be destroyed during logout - if( !gMeshRepo.mThread ) + // Can't get the header so none of the LODs will be available + LLMutexLock lock(gMeshRepo.mThread->mMutex); + for (int i(0); i < 4; ++i) { - return; + gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, i)); } +} - S32 data_size = buffer->countAfter(channels.in(), NULL); - - if (status < 200 || status > 400) +void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ + LLUUID mesh_id = mMeshParams.getSculptID(); + bool success = (! MESH_HEADER_PROCESS_FAILED) && gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); + llassert(success); + if (! success) { - llwarns << status << ": " << reason << llendl; - } + // *TODO: Get real reason for parse failure here. Might we want to retry? + LL_WARNS(LOG_MESH) << "Unable to parse mesh header. ID: " << mesh_id + << ", Unknown reason. Not retrying." + << LL_ENDL; - if (data_size < mRequestedBytes) - { - if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE) - { //timeout or service unavailable, try again - llwarns << "Timeout or service unavailable, retrying loadMeshSkinInfo() for " << mMeshID << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->loadMeshSkinInfo(mMeshID); - } - else + // Can't get the header so none of the LODs will be available + LLMutexLock lock(gMeshRepo.mThread->mMutex); + for (int i(0); i < 4; ++i) { - llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint - llwarns << "Unhandled status " << status << llendl; + gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, i)); } - return; } + else if (data && data_size > 0) + { + // header was successfully retrieved from sim, cache in vfs + LLSD header = gMeshRepo.mThread->mMeshHeader[mesh_id]; - LLMeshRepository::sBytesReceived += mRequestedBytes; + S32 version = header["version"].asInteger(); - U8* data = NULL; + if (version <= MAX_MESH_VERSION) + { + std::stringstream str; - if (data_size > 0) - { - data = new U8[data_size]; - buffer->readAfter(channels.in(), NULL, data, data_size); - } + S32 lod_bytes = 0; - if (gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) - { - //good fetch from sim, write to VFS for caching - LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); + for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i) + { + // figure out how many bytes we'll need to reserve in the file + const std::string & lod_name = header_lod[i]; + lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger()); + } + + // just in case skin info or decomposition is at the end of the file (which it shouldn't be) + lod_bytes = llmax(lod_bytes, header["skin"]["offset"].asInteger() + header["skin"]["size"].asInteger()); + lod_bytes = llmax(lod_bytes, header["physics_convex"]["offset"].asInteger() + header["physics_convex"]["size"].asInteger()); - S32 offset = mOffset; - S32 size = mRequestedBytes; + S32 header_bytes = (S32) gMeshRepo.mThread->mMeshHeaderSize[mesh_id]; + S32 bytes = lod_bytes + header_bytes; - if (file.getSize() >= offset+size) - { - LLMeshRepository::sCacheBytesWritten += size; - file.seek(offset); - file.write(data, size); - } - } + + // It's possible for the remote asset to have more data than is needed for the local cache + // only allocate as much space in the VFS as is needed for the local cache + data_size = llmin(data_size, bytes); - delete [] data; -} + LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH, LLVFile::WRITE); + if (file.getMaxSize() >= bytes || file.setMaxSize(bytes)) + { + LLMeshRepository::sCacheBytesWritten += data_size; + ++LLMeshRepository::sCacheWrites; -void LLMeshDecompositionResponder::completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) -{ - mProcessed = true; - - if( !gMeshRepo.mThread ) - { - return; - } + file.write(data, data_size); + + // zero out the rest of the file + U8 block[MESH_HEADER_SIZE]; + memset(block, 0, sizeof(block)); - S32 data_size = buffer->countAfter(channels.in(), NULL); + while (bytes-file.tell() > sizeof(block)) + { + file.write(block, sizeof(block)); + } - if (status < 200 || status > 400) - { - llwarns << status << ": " << reason << llendl; + S32 remaining = bytes-file.tell(); + if (remaining > 0) + { + file.write(block, remaining); + } + } + } } +} - if (data_size < mRequestedBytes) +LLMeshLODHandler::~LLMeshLODHandler() +{ + if (! LLApp::isQuitting()) { - if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE) - { //timeout or service unavailable, try again - llwarns << "Timeout or service unavailable, retrying loadMeshDecomposition() for " << mMeshID << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->loadMeshDecomposition(mMeshID); - } - else + if (! mProcessed) { - llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint - llwarns << "Unhandled status " << status << llendl; + LL_WARNS(LOG_MESH) << "Mesh LOD fetch canceled unexpectedly, retrying." << LL_ENDL; + gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD); } - return; + LLMeshRepoThread::decActiveLODRequests(); } +} - LLMeshRepository::sBytesReceived += mRequestedBytes; - - U8* data = NULL; +void LLMeshLODHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh LOD handling. ID: " << mMeshParams.getSculptID() + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; - if (data_size > 0) - { - data = new U8[data_size]; - buffer->readAfter(channels.in(), NULL, data, data_size); - } + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); +} - if (gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size)) +void LLMeshLODHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ + if ((! MESH_LOD_PROCESS_FAILED) && gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size)) { - //good fetch from sim, write to VFS for caching - LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); + // good fetch from sim, write to VFS for caching + LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE); S32 offset = mOffset; S32 size = mRequestedBytes; if (file.getSize() >= offset+size) { - LLMeshRepository::sCacheBytesWritten += size; file.seek(offset); file.write(data, size); + LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; } } - - delete [] data; -} - -void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) -{ - mProcessed = true; - - // thread could have already be destroyed during logout - if( !gMeshRepo.mThread ) - { - return; - } - - S32 data_size = buffer->countAfter(channels.in(), NULL); - - if (status < 200 || status > 400) - { - llwarns << status << ": " << reason << llendl; - } - - if (data_size < mRequestedBytes) + else { - if (status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE) - { //timeout or service unavailable, try again - llwarns << "Timeout or service unavailable, retrying loadMeshPhysicsShape() for " << mMeshID << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID); - } - else - { - llassert(status == HTTP_INTERNAL_ERROR || status == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint - llwarns << "Unhandled status " << status << llendl; - } - return; + LL_WARNS(LOG_MESH) << "Error during mesh LOD processing. ID: " << mMeshParams.getSculptID() + << ", Unknown reason. Not retrying." + << LL_ENDL; + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); } +} - LLMeshRepository::sBytesReceived += mRequestedBytes; +LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() +{ + llassert(mProcessed); +} - U8* data = NULL; +void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; - if (data_size > 0) - { - data = new U8[data_size]; - buffer->readAfter(channels.in(), NULL, data, data_size); - } + // *TODO: Mark mesh unavailable on error. For now, simply leave + // request unfulfilled rather than retry forever. +} - if (gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size)) +void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ + if ((! MESH_SKIN_INFO_PROCESS_FAILED) && gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) { - //good fetch from sim, write to VFS for caching + // good fetch from sim, write to VFS for caching LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); S32 offset = mOffset; @@ -2166,145 +2905,108 @@ void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& re if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; file.seek(offset); file.write(data, size); } } - - delete [] data; + else + { + LL_WARNS(LOG_MESH) << "Error during mesh skin info processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error + } } -void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) +LLMeshDecompositionHandler::~LLMeshDecompositionHandler() { - mProcessed = true; + llassert(mProcessed); +} - // thread could have already be destroyed during logout - if( !gMeshRepo.mThread ) - { - return; - } +void LLMeshDecompositionHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error. For now, simply leave + // request unfulfilled rather than retry forever. +} - if (status < 200 || status > 400) +void LLMeshDecompositionHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ + if ((! MESH_DECOMP_PROCESS_FAILED) && gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size)) { - //llwarns - // << "Header responder failed with status: " - // << status << ": " << reason << llendl; - - // 503 (service unavailable) or 499 (internal Linden-generated error) - // can be due to server load and can be retried - - // TODO*: Add maximum retry logic, exponential backoff - // and (somewhat more optional than the others) retries - // again after some set period of time - - llassert(status == HTTP_NOT_FOUND || status == HTTP_SERVICE_UNAVAILABLE || status == HTTP_REQUEST_TIME_OUT || status == HTTP_INTERNAL_ERROR); + // good fetch from sim, write to VFS for caching + LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); - if (status == HTTP_SERVICE_UNAVAILABLE || status == HTTP_REQUEST_TIME_OUT || status == HTTP_INTERNAL_ERROR) - { //retry - llwarns << "Timeout or service unavailable, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; - LLMeshRepoThread::HeaderRequest req(mMeshParams); - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mHeaderReqQ.push(req); + S32 offset = mOffset; + S32 size = mRequestedBytes; - return; - } - else + if (file.getSize() >= offset+size) { - llwarns << "Unhandled status: " << status << llendl; + LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; + file.seek(offset); + file.write(data, size); } } - - S32 data_size = buffer->countAfter(channels.in(), NULL); - - U8* data = NULL; - - if (data_size > 0) + else { - data = new U8[data_size]; - buffer->readAfter(channels.in(), NULL, data, data_size); + LL_WARNS(LOG_MESH) << "Error during mesh decomposition processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error } +} - LLMeshRepository::sBytesReceived += llmin(data_size, 4096); +LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler() +{ + llassert(mProcessed); +} - bool success = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); - - llassert(success); +void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error +} - if (!success) - { - llwarns - << "Unable to parse mesh header: " - << status << ": " << reason << llendl; - } - else if (data && data_size > 0) +void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ + if ((! MESH_PHYS_SHAPE_PROCESS_FAILED) && gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size)) { - //header was successfully retrieved from sim, cache in vfs - LLUUID mesh_id = mMeshParams.getSculptID(); - LLSD header = gMeshRepo.mThread->mMeshHeader[mesh_id]; + // good fetch from sim, write to VFS for caching + LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); - S32 version = header["version"].asInteger(); + S32 offset = mOffset; + S32 size = mRequestedBytes; - if (version <= MAX_MESH_VERSION) + if (file.getSize() >= offset+size) { - std::stringstream str; - - S32 lod_bytes = 0; - - for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i) - { //figure out how many bytes we'll need to reserve in the file - std::string lod_name = header_lod[i]; - lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger()); - } - - //just in case skin info or decomposition is at the end of the file (which it shouldn't be) - lod_bytes = llmax(lod_bytes, header["skin"]["offset"].asInteger() + header["skin"]["size"].asInteger()); - lod_bytes = llmax(lod_bytes, header["physics_convex"]["offset"].asInteger() + header["physics_convex"]["size"].asInteger()); - - S32 header_bytes = (S32) gMeshRepo.mThread->mMeshHeaderSize[mesh_id]; - S32 bytes = lod_bytes + header_bytes; - - - //it's possible for the remote asset to have more data than is needed for the local cache - //only allocate as much space in the VFS as is needed for the local cache - data_size = llmin(data_size, bytes); - - LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH, LLVFile::WRITE); - if (file.getMaxSize() >= bytes || file.setMaxSize(bytes)) - { - LLMeshRepository::sCacheBytesWritten += data_size; - - file.write((const U8*) data, data_size); - - //zero out the rest of the file - U8 block[4096]; - memset(block, 0, 4096); - - while (bytes-file.tell() > 4096) - { - file.write(block, 4096); - } - - S32 remaining = bytes-file.tell(); - - if (remaining > 0) - { - file.write(block, remaining); - } - } + LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; + file.seek(offset); + file.write(data, size); } } - - delete [] data; + else + { + LL_WARNS(LOG_MESH) << "Error during mesh physics shape processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error + } } - LLMeshRepository::LLMeshRepository() : mMeshMutex(NULL), mMeshThreadCount(0), - mThread(NULL) + mThread(NULL), + mGetMeshVersion(2) { } @@ -2323,7 +3025,7 @@ void LLMeshRepository::init() apr_sleep(100); } - + metrics_teleport_started_signal = LLViewerMessage::getInstance()->setTeleportStartedCallback(teleport_started); mThread = new LLMeshRepoThread(); mThread->start(); @@ -2331,11 +3033,13 @@ void LLMeshRepository::init() void LLMeshRepository::shutdown() { - llinfos << "Shutting down mesh repository." << llendl; + LL_INFOS(LOG_MESH) << "Shutting down mesh repository." << LL_ENDL; + + metrics_teleport_started_signal.disconnect(); for (U32 i = 0; i < mUploads.size(); ++i) { - llinfos << "Discard the pending mesh uploads " << llendl; + LL_INFOS(LOG_MESH) << "Discard the pending mesh uploads." << LL_ENDL; mUploads[i]->discard() ; //discard the uploading requests. } @@ -2350,7 +3054,7 @@ void LLMeshRepository::shutdown() for (U32 i = 0; i < mUploads.size(); ++i) { - llinfos << "Waiting for pending mesh upload " << i << "/" << mUploads.size() << llendl; + LL_INFOS(LOG_MESH) << "Waiting for pending mesh upload " << (i + 1) << "/" << mUploads.size() << LL_ENDL; while (!mUploads[i]->isStopped()) { apr_sleep(10); @@ -2363,7 +3067,7 @@ void LLMeshRepository::shutdown() delete mMeshMutex; mMeshMutex = NULL; - llinfos << "Shutting down decomposition system." << llendl; + LL_INFOS(LOG_MESH) << "Shutting down decomposition system." << LL_ENDL; if (mDecompThread) { @@ -2378,6 +3082,9 @@ void LLMeshRepository::shutdown() //called in the main thread. S32 LLMeshRepository::update() { + // Conditionally log a mesh metrics event + metricsUpdate(); + if(mUploadWaitList.empty()) { return 0 ; @@ -2397,7 +3104,12 @@ S32 LLMeshRepository::update() S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod) { - if (detail < 0 || detail > 4) + MESH_FASTTIMER_DEFBLOCK; + + // Manage time-to-load metrics for mesh download operations. + metricsProgress(1); + + if (detail < 0 || detail >= 4) { return detail; } @@ -2475,9 +3187,32 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para void LLMeshRepository::notifyLoadedMeshes() { //called from main thread + MESH_FASTTIMER_DEFBLOCK; - LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests"); - + if (1 == mGetMeshVersion) + { + // Legacy GetMesh operation with high connection concurrency + LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests"); + LLMeshRepoThread::sRequestHighWater = llclamp(2 * S32(LLMeshRepoThread::sMaxConcurrentRequests), + REQUEST_HIGH_WATER_MIN, + REQUEST_HIGH_WATER_MAX); + LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2, + REQUEST_LOW_WATER_MIN, + REQUEST_LOW_WATER_MAX); + } + else + { + // GetMesh2 operation with keepalives, etc. With pipelining, + // we'll increase this. + LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("Mesh2MaxConcurrentRequests"); + LLMeshRepoThread::sRequestHighWater = llclamp(5 * S32(LLMeshRepoThread::sMaxConcurrentRequests), + REQUEST2_HIGH_WATER_MIN, + REQUEST2_HIGH_WATER_MAX); + LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2, + REQUEST2_LOW_WATER_MIN, + REQUEST2_LOW_WATER_MAX); + } + //clean up completed upload threads for (std::vector<LLMeshUploadThread*>::iterator iter = mUploads.begin(); iter != mUploads.end(); ) { @@ -2554,26 +3289,46 @@ void LLMeshRepository::notifyLoadedMeshes() //call completed callbacks on finished decompositions mDecompThread->notifyCompleted(); - - if (!mThread->mWaiting) - { //curl thread is churning, wait for it to go idle - return; - } - static std::string region_name("never name a region this"); + // For major operations, attempt to get the required locks + // without blocking and punt if they're not available. The + // longest run of holdoffs is kept in sMaxLockHoldoffs just + // to collect the data. In testing, I've never seen a value + // greater than 2 (written to log on exit). + { + LLMutexTrylock lock1(mMeshMutex); + LLMutexTrylock lock2(mThread->mMutex); - if (gAgent.getRegion()) - { //update capability url - if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived()) + static U32 hold_offs(0); + if (! lock1.isLocked() || ! lock2.isLocked()) { - region_name = gAgent.getRegion()->getName(); - mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh"); + // If we can't get the locks, skip and pick this up later. + ++hold_offs; + sMaxLockHoldoffs = llmax(sMaxLockHoldoffs, hold_offs); + return; + } + hold_offs = 0; + + if (gAgent.getRegion()) + { + // Update capability urls + static std::string region_name("never name a region this"); + + if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived()) + { + region_name = gAgent.getRegion()->getName(); + const bool use_v1(gSavedSettings.getBOOL("MeshUseGetMesh1")); + const std::string mesh1(gAgent.getRegion()->getCapability("GetMesh")); + const std::string mesh2(gAgent.getRegion()->getCapability("GetMesh2")); + mGetMeshVersion = (mesh2.empty() || use_v1) ? 1 : 2; + mThread->setGetMeshCaps(mesh1, mesh2, mGetMeshVersion); + LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name + << "', GetMesh2: " << mesh2 + << ", GetMesh: " << mesh1 + << ", using version: " << mGetMeshVersion + << LL_ENDL; + } } - } - - { - LLMutexLock lock1(mMeshMutex); - LLMutexLock lock2(mThread->mMutex); //popup queued error messages from background threads while (!mUploadErrorQ.empty()) @@ -2582,47 +3337,55 @@ void LLMeshRepository::notifyLoadedMeshes() mUploadErrorQ.pop(); } - S32 push_count = LLMeshRepoThread::sMaxConcurrentRequests-(LLMeshRepoThread::sActiveHeaderRequests+LLMeshRepoThread::sActiveLODRequests); - - if (push_count > 0) + S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests; + if (active_count < LLMeshRepoThread::sRequestLowWater) { - //calculate "score" for pending requests - - //create score map - std::map<LLUUID, F32> score_map; + S32 push_count = LLMeshRepoThread::sRequestHighWater - active_count; - for (U32 i = 0; i < 4; ++i) + if (mPendingRequests.size() > push_count) { - for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter) + // More requests than the high-water limit allows so + // sort and forward the most important. + + //calculate "score" for pending requests + + //create score map + std::map<LLUUID, F32> score_map; + + for (U32 i = 0; i < 4; ++i) { - F32 max_score = 0.f; - for (std::set<LLUUID>::iterator obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter) + for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter) { - LLViewerObject* object = gObjectList.findObject(*obj_iter); - - if (object) + F32 max_score = 0.f; + for (std::set<LLUUID>::iterator obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter) { - LLDrawable* drawable = object->mDrawable; - if (drawable) + LLViewerObject* object = gObjectList.findObject(*obj_iter); + + if (object) { - F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f); - max_score = llmax(max_score, cur_score); + LLDrawable* drawable = object->mDrawable; + if (drawable) + { + F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f); + max_score = llmax(max_score, cur_score); + } } } - } - score_map[iter->first.getSculptID()] = max_score; + score_map[iter->first.getSculptID()] = max_score; + } } - } - //set "score" for pending requests - for (std::vector<LLMeshRepoThread::LODRequest>::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter) - { - iter->mScore = score_map[iter->mMeshParams.getSculptID()]; - } + //set "score" for pending requests + for (std::vector<LLMeshRepoThread::LODRequest>::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter) + { + iter->mScore = score_map[iter->mMeshParams.getSculptID()]; + } - //sort by "score" - std::sort(mPendingRequests.begin(), mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater()); + //sort by "score" + std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count, + mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater()); + } while (!mPendingRequests.empty() && push_count > 0) { @@ -2676,9 +3439,8 @@ void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo& info) vobj->notifyMeshLoaded(); } } + mLoadingSkins.erase(info.mMeshID); } - - mLoadingSkins.erase(info.mMeshID); } void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decomp) @@ -2687,14 +3449,14 @@ void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decom if (iter == mDecompositionMap.end()) { //just insert decomp into map mDecompositionMap[decomp->mMeshID] = decomp; + mLoadingDecompositions.erase(decomp->mMeshID); } else { //merge decomp with existing entry iter->second->merge(decomp); + mLoadingDecompositions.erase(decomp->mMeshID); delete decomp; } - - mLoadingDecompositions.erase(decomp->mMeshID); } void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume) @@ -2709,7 +3471,8 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol //make sure target volume is still valid if (volume->getNumVolumeFaces() <= 0) { - llwarns << "Mesh loading returned empty volume." << llendl; + LL_WARNS(LOG_MESH) << "Mesh loading returned empty volume. ID: " << mesh_params.getSculptID() + << LL_ENDL; } { //update system volume @@ -2722,7 +3485,8 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol } else { - llwarns << "Couldn't find system volume for given mesh." << llendl; + LL_WARNS(LOG_MESH) << "Couldn't find system volume for mesh " << mesh_params.getSculptID() + << LL_ENDL; } } @@ -2776,6 +3540,8 @@ S32 LLMeshRepository::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lo const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, const LLVOVolume* requesting_obj) { + MESH_FASTTIMER_DEFBLOCK; + if (mesh_id.notNull()) { skin_map::iterator iter = mSkinMap.find(mesh_id); @@ -2802,6 +3568,8 @@ const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, const void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id) { + MESH_FASTTIMER_DEFBLOCK; + if (mesh_id.notNull()) { LLModel::Decomposition* decomp = NULL; @@ -2819,6 +3587,7 @@ void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id) std::set<LLUUID>::iterator iter = mLoadingPhysicsShapes.find(mesh_id); if (iter == mLoadingPhysicsShapes.end()) { //no request pending for this skin info + // *FIXME: Nothing ever deletes entries, can't be right mLoadingPhysicsShapes.insert(mesh_id); mPendingPhysicsShapeRequests.push(mesh_id); } @@ -2829,6 +3598,8 @@ void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id) LLModel::Decomposition* LLMeshRepository::getDecomposition(const LLUUID& mesh_id) { + MESH_FASTTIMER_DEFBLOCK; + LLModel::Decomposition* ret = NULL; if (mesh_id.notNull()) @@ -2891,6 +3662,8 @@ bool LLMeshRepository::hasPhysicsShape(const LLUUID& mesh_id) LLSD& LLMeshRepository::getMeshHeader(const LLUUID& mesh_id) { + MESH_FASTTIMER_DEFBLOCK; + return mThread->getMeshHeader(mesh_id); } @@ -2912,7 +3685,7 @@ LLSD& LLMeshRepoThread::getMeshHeader(const LLUUID& mesh_id) void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, LLVector3& scale, bool upload_textures, - bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload, + bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload, LLHandle<LLWholeModelFeeObserver> fee_observer, LLHandle<LLWholeModelUploadObserver> upload_observer) { LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures, upload_skin, upload_joints, upload_url, @@ -2941,7 +3714,6 @@ S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod) } return -1; - } void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation, @@ -3206,7 +3978,7 @@ void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh, bool vertex_based) if (ret) { - llerrs << "Convex Decomposition thread valid but could not set mesh data" << llendl; + LL_ERRS(LOG_MESH) << "Convex Decomposition thread valid but could not set mesh data." << LL_ENDL; } } } @@ -3282,7 +4054,8 @@ void LLPhysicsDecomp::doDecomposition() if (ret) { - llwarns << "Convex Decomposition thread valid but could not execute stage " << stage << llendl; + LL_WARNS(LOG_MESH) << "Convex Decomposition thread valid but could not execute stage " << stage << "." + << LL_ENDL; LLMutexLock lock(mMutex); mCurRequest->mHull.clear(); @@ -3411,9 +4184,9 @@ void LLPhysicsDecomp::doDecompositionSingleHull() setMeshData(mesh, true); LLCDResult ret = decomp->buildSingleHull() ; - if(ret) + if (ret) { - llwarns << "Could not execute decomposition stage when attempting to create single hull." << llendl; + LL_WARNS(LOG_MESH) << "Could not execute decomposition stage when attempting to create single hull." << LL_ENDL; make_box(mCurRequest); } else @@ -3718,3 +4491,63 @@ bool LLMeshRepository::meshRezEnabled() } return false; } + +// Threading: main thread only +// static +void LLMeshRepository::metricsStart() +{ + ++metrics_teleport_start_count; + sQuiescentTimer.start(0); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsStop() +{ + sQuiescentTimer.stop(0); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsProgress(unsigned int this_count) +{ + static bool first_start(true); + + if (first_start) + { + metricsStart(); + first_start = false; + } + sQuiescentTimer.ringBell(0, this_count); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsUpdate() +{ + F64 started, stopped; + U64 total_count(U64L(0)), user_cpu(U64L(0)), sys_cpu(U64L(0)); + + if (sQuiescentTimer.isExpired(0, started, stopped, total_count, user_cpu, sys_cpu)) + { + LLSD metrics; + + metrics["reason"] = "Mesh Download Quiescent"; + metrics["scope"] = metrics_teleport_start_count > 1 ? "Teleport" : "Login"; + metrics["start"] = started; + metrics["stop"] = stopped; + metrics["fetches"] = LLSD::Integer(total_count); + metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count); + metrics["user_cpu"] = double(user_cpu) / 1.0e6; + metrics["sys_cpu"] = double(sys_cpu) / 1.0e6; + LL_INFOS(LOG_MESH) << "EventMarker " << metrics << LL_ENDL; + } +} + +// Threading: main thread only +// static +void teleport_started() +{ + LLMeshRepository::metricsStart(); +} + diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 8eaf691d6f..39280bea3a 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2010-2013, 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 @@ -32,6 +32,12 @@ #include "lluuid.h" #include "llviewertexture.h" #include "llvolume.h" +#include "lldeadmantimer.h" +#include "httpcommon.h" +#include "httprequest.h" +#include "httpoptions.h" +#include "httpheaders.h" +#include "httphandler.h" #define LLCONVEXDECOMPINTER_STATIC 1 @@ -39,8 +45,6 @@ #include "lluploadfloaterobservers.h" class LLVOVolume; -class LLMeshResponder; -class LLCurlRequest; class LLMutex; class LLCondition; class LLVFS; @@ -215,17 +219,17 @@ class LLMeshRepoThread : public LLThread { public: - static S32 sActiveHeaderRequests; - static S32 sActiveLODRequests; + volatile static S32 sActiveHeaderRequests; + volatile static S32 sActiveLODRequests; static U32 sMaxConcurrentRequests; + static S32 sRequestLowWater; + static S32 sRequestHighWater; + static S32 sRequestWaterLevel; // Stats-use only, may read outside of thread - LLCurlRequest* mCurlRequest; LLMutex* mMutex; LLMutex* mHeaderMutex; LLCondition* mSignal; - bool mWaiting; - //map of known mesh headers typedef std::map<LLUUID, LLSD> mesh_header_map; mesh_header_map mMeshHeader; @@ -287,8 +291,8 @@ public: //set of requested skin info std::set<LLUUID> mSkinRequests; - //queue of completed skin info requests - std::queue<LLMeshSkinInfo> mSkinInfoQ; + // list of completed skin info requests + std::list<LLMeshSkinInfo> mSkinInfoQ; //set of requested decompositions std::set<LLUUID> mDecompositionRequests; @@ -296,8 +300,8 @@ public: //set of requested physics shapes std::set<LLUUID> mPhysicsShapeRequests; - //queue of completed Decomposition info requests - std::queue<LLModel::Decomposition*> mDecompositionQ; + // list of completed Decomposition info requests + std::list<LLModel::Decomposition*> mDecompositionQ; //queue of requested headers std::queue<HeaderRequest> mHeaderReqQ; @@ -315,7 +319,23 @@ public: typedef std::map<LLVolumeParams, std::vector<S32> > pending_lod_map; pending_lod_map mPendingLOD; - static std::string constructUrl(LLUUID mesh_id); + // llcorehttp library interface objects. + LLCore::HttpStatus mHttpStatus; + LLCore::HttpRequest * mHttpRequest; + LLCore::HttpOptions * mHttpOptions; + LLCore::HttpOptions * mHttpLargeOptions; + LLCore::HttpHeaders * mHttpHeaders; + LLCore::HttpRequest::policy_t mHttpPolicyClass; + LLCore::HttpRequest::policy_t mHttpLegacyPolicyClass; + LLCore::HttpRequest::policy_t mHttpLargePolicyClass; + LLCore::HttpRequest::priority_t mHttpPriority; + + typedef std::set<LLCore::HttpHandler *> http_request_set; + http_request_set mHttpRequestSet; // Outstanding HTTP requests + + std::string mGetMeshCapability; + std::string mGetMesh2Capability; + int mGetMeshVersion; LLMeshRepoThread(); ~LLMeshRepoThread(); @@ -325,8 +345,8 @@ public: void lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); void loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); - bool fetchMeshHeader(const LLVolumeParams& mesh_params, U32& count); - bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count); + bool fetchMeshHeader(const LLVolumeParams& mesh_params); + bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod); bool headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size); bool lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size); bool skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size); @@ -358,9 +378,37 @@ public: static void incActiveHeaderRequests(); static void decActiveHeaderRequests(); + // Set the caps strings and preferred version for constructing + // mesh fetch URLs. + // + // Mutex: must be holding mMutex when called + void setGetMeshCaps(const std::string & get_mesh1, + const std::string & get_mesh2, + int pref_version); + + // Mutex: acquires mMutex + void constructUrl(LLUUID mesh_id, std::string * url, int * version); + +private: + // Issue a GET request to a URL with 'Range' header using + // the correct policy class and other attributes. If an invalid + // handle is returned, the request failed and caller must retry + // or dispose of handler. + // + // Threads: Repo thread only + LLCore::HttpHandle getByteRange(const std::string & url, int cap_version, + size_t offset, size_t len, + LLCore::HttpHandler * handler); }; -class LLMeshUploadThread : public LLThread + +// Class whose instances represent a single upload-type request for +// meshes: one fee query or one actual upload attempt. Yes, it creates +// a unique thread for that single request. As it is 1:1, it can also +// trivially serve as the HttpHandler object for request completion +// notifications. + +class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler { private: S32 mMeshUploadTimeOut ; //maximum time in seconds to execute an uploading request. @@ -381,44 +429,41 @@ public: }; LLPointer<DecompRequest> mFinalDecomp; - bool mPhysicsComplete; + volatile bool mPhysicsComplete; typedef std::map<LLPointer<LLModel>, std::vector<LLVector3> > hull_map; - hull_map mHullMap; + hull_map mHullMap; typedef std::vector<LLModelInstance> instance_list; - instance_list mInstanceList; + instance_list mInstanceList; typedef std::map<LLPointer<LLModel>, instance_list> instance_map; - instance_map mInstance; + instance_map mInstance; - LLMutex* mMutex; - LLCurlRequest* mCurlRequest; + LLMutex* mMutex; S32 mPendingUploads; LLVector3 mOrigin; bool mFinished; bool mUploadTextures; bool mUploadSkin; bool mUploadJoints; - BOOL mDiscarded ; + volatile bool mDiscarded; LLHost mHost; std::string mWholeModelFeeCapability; std::string mWholeModelUploadURL; LLMeshUploadThread(instance_list& data, LLVector3& scale, bool upload_textures, - bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload = true, - LLHandle<LLWholeModelFeeObserver> fee_observer= (LLHandle<LLWholeModelFeeObserver>()), LLHandle<LLWholeModelUploadObserver> upload_observer = (LLHandle<LLWholeModelUploadObserver>())); + bool upload_skin, bool upload_joints, const std::string & upload_url, bool do_upload = true, + LLHandle<LLWholeModelFeeObserver> fee_observer = (LLHandle<LLWholeModelFeeObserver>()), + LLHandle<LLWholeModelUploadObserver> upload_observer = (LLHandle<LLWholeModelUploadObserver>())); ~LLMeshUploadThread(); - void startRequest() { ++mPendingUploads; } - void stopRequest() { --mPendingUploads; } - - bool finished() { return mFinished; } + bool finished() const { return mFinished; } virtual void run(); void preStart(); void discard() ; - BOOL isDiscarded(); + bool isDiscarded() const; void generateHulls(); @@ -435,11 +480,23 @@ public: void setFeeObserverHandle(LLHandle<LLWholeModelFeeObserver> observer_handle) { mFeeObserverHandle = observer_handle; } void setUploadObserverHandle(LLHandle<LLWholeModelUploadObserver> observer_handle) { mUploadObserverHandle = observer_handle; } + // Inherited from LLCore::HttpHandler + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + private: LLHandle<LLWholeModelFeeObserver> mFeeObserverHandle; LLHandle<LLWholeModelUploadObserver> mUploadObserverHandle; bool mDoUpload; // if FALSE only model data will be requested, otherwise the model will be uploaded + LLSD mModelData; + + // llcorehttp library interface objects. + LLCore::HttpStatus mHttpStatus; + LLCore::HttpRequest * mHttpRequest; + LLCore::HttpOptions * mHttpOptions; + LLCore::HttpHeaders * mHttpHeaders; + LLCore::HttpRequest::policy_t mHttpPolicyClass; + LLCore::HttpRequest::priority_t mHttpPriority; }; class LLMeshRepository @@ -448,21 +505,28 @@ public: //metrics static U32 sBytesReceived; - static U32 sHTTPRequestCount; - static U32 sHTTPRetryCount; + static U32 sMeshRequestCount; // Total request count, http or cached, all component types + static U32 sHTTPRequestCount; // Http GETs issued (not large) + static U32 sHTTPLargeRequestCount; // Http GETs issued for large requests + static U32 sHTTPRetryCount; // Total request retries whether successful or failed + static U32 sHTTPErrorCount; // Requests ending in error static U32 sLODPending; static U32 sLODProcessing; static U32 sCacheBytesRead; static U32 sCacheBytesWritten; - static U32 sPeakKbps; + static U32 sCacheReads; + static U32 sCacheWrites; + static U32 sMaxLockHoldoffs; // Maximum sequential locking failures + static LLDeadmanTimer sQuiescentTimer; // Time-to-complete-mesh-downloads after significant events + static F32 getStreamingCost(LLSD& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL); LLMeshRepository(); void init(); void shutdown(); - S32 update() ; + S32 update(); //mesh management functions S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 detail = 0, S32 last_lod = -1); @@ -495,6 +559,12 @@ public: S32 getMeshSize(const LLUUID& mesh_id, S32 lod); + // Quiescent timer management, main thread only. + static void metricsStart(); + static void metricsStop(); + static void metricsProgress(unsigned int count); + static void metricsUpdate(); + typedef std::map<LLVolumeParams, std::set<LLUUID> > mesh_load_map; mesh_load_map mLoadingMeshes[4]; @@ -556,8 +626,7 @@ public: void uploadError(LLSD& args); void updateInventory(inventory_data data); - std::string mGetMeshCapability; - + int mGetMeshVersion; // Shadows value in LLMeshRepoThread }; extern LLMeshRepository gMeshRepo; diff --git a/indra/newview/llpanelteleporthistory.cpp b/indra/newview/llpanelteleporthistory.cpp index 0756faf5c0..9c380f63bd 100755 --- a/indra/newview/llpanelteleporthistory.cpp +++ b/indra/newview/llpanelteleporthistory.cpp @@ -359,6 +359,11 @@ void LLTeleportHistoryPanel::ContextMenu::onInfo() void LLTeleportHistoryPanel::ContextMenu::gotSLURLCallback(const std::string& slurl) { LLClipboard::instance().copyToClipboard(utf8str_to_wstring(slurl),0,slurl.size()); + + LLSD args; + args["SLURL"] = slurl; + + LLNotificationsUtil::add("CopySLURL", args); } void LLTeleportHistoryPanel::ContextMenu::onCopyToClipboard() diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index def26b3885..e5f2ca7e5c 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, 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 @@ -1552,7 +1552,7 @@ bool LLTextureFetchWorker::doWork(S32 param) else { llinfos << "HTTP GET failed for: " << mUrl - << " Status: " << mGetStatus.toHex() + << " Status: " << mGetStatus.toTerseString() << " Reason: '" << mGetReason << "'" << llendl; } @@ -1896,7 +1896,7 @@ void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRe LLCore::HttpStatus status(response->getStatus()); LL_DEBUGS("Texture") << "HTTP COMPLETE: " << mID - << " status: " << status.toHex() + << " status: " << status.toTerseString() << " '" << status.toString() << "'" << llendl; // unsigned int offset(0), length(0), full_length(0); @@ -1912,7 +1912,7 @@ void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRe success = false; std::string reason(status.toString()); setGetStatus(status, reason); - llwarns << "CURL GET FAILED, status: " << status.toHex() + llwarns << "CURL GET FAILED, status: " << status.toTerseString() << " reason: " << reason << llendl; } else @@ -2376,6 +2376,7 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image mQAMode(qa_mode), mHttpRequest(NULL), mHttpOptions(NULL), + mHttpOptionsWithHeaders(NULL), mHttpHeaders(NULL), mHttpMetricsHeaders(NULL), mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), @@ -2406,11 +2407,13 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image mHttpRequest = new LLCore::HttpRequest; mHttpOptions = new LLCore::HttpOptions; + mHttpOptionsWithHeaders = new LLCore::HttpOptions; + mHttpOptionsWithHeaders->setWantHeaders(true); mHttpHeaders = new LLCore::HttpHeaders; - mHttpHeaders->mHeaders.push_back("Accept: image/x-j2c"); + mHttpHeaders->append("Accept", "image/x-j2c"); mHttpMetricsHeaders = new LLCore::HttpHeaders; - mHttpMetricsHeaders->mHeaders.push_back("Content-Type: application/llsd+xml"); - mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicyDefault(); + mHttpMetricsHeaders->append("Content-Type", "application/llsd+xml"); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_TEXTURE); } LLTextureFetch::~LLTextureFetch() @@ -2430,6 +2433,12 @@ LLTextureFetch::~LLTextureFetch() mHttpOptions = NULL; } + if (mHttpOptionsWithHeaders) + { + mHttpOptionsWithHeaders->release(); + mHttpOptionsWithHeaders = NULL; + } + if (mHttpHeaders) { mHttpHeaders->release(); @@ -3772,7 +3781,7 @@ public: else { LL_WARNS("Texture") << "Error delivering asset metrics to grid. Status: " - << status.toHex() + << status.toTerseString() << ", Reason: " << status.toString() << LL_ENDL; } } @@ -4041,7 +4050,7 @@ void LLTextureFetchDebugger::init() if (! mHttpHeaders) { mHttpHeaders = new LLCore::HttpHeaders; - mHttpHeaders->mHeaders.push_back("Accept: image/x-j2c"); + mHttpHeaders->append("Accept", "image/x-j2c"); } } @@ -4461,7 +4470,7 @@ S32 LLTextureFetchDebugger::fillCurlQueue() LL_WARNS("Texture") << "Couldn't issue HTTP request in debugger for texture " << mFetchingHistory[i].mID - << ", status: " << status.toHex() + << ", status: " << status.toTerseString() << " reason: " << status.toString() << LL_ENDL; mFetchingHistory[i].mCurlState = FetchEntry::CURL_DONE; @@ -4854,7 +4863,7 @@ void LLTextureFetchDebugger::callbackHTTP(FetchEntry & fetch, LLCore::HttpRespon else //failed { llinfos << "Fetch Debugger : CURL GET FAILED, ID = " << fetch.mID - << ", status: " << status.toHex() + << ", status: " << status.toTerseString() << " reason: " << status.toString() << llendl; } } diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index 902a3d7a25..3c79a5a24d 100755 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, 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 @@ -351,6 +351,7 @@ private: // LLCurl interfaces used in the past. LLCore::HttpRequest * mHttpRequest; // Ttf LLCore::HttpOptions * mHttpOptions; // Ttf + LLCore::HttpOptions * mHttpOptionsWithHeaders; // Ttf LLCore::HttpHeaders * mHttpHeaders; // Ttf LLCore::HttpHeaders * mHttpMetricsHeaders; // Ttf LLCore::HttpRequest::policy_t mHttpPolicyClass; // T* diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index e80136b286..50edbb61a8 100755 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, 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 @@ -49,6 +49,7 @@ #include "llviewertexturelist.h" #include "llvovolume.h" #include "llviewerstats.h" +#include "llmeshrepository.h" // For avatar texture view #include "llvoavatarself.h" @@ -517,6 +518,8 @@ void LLGLTexMemBar::draw() F32 total_texture_downloaded = (F32)gTotalTextureBytes / (1024 * 1024); F32 total_object_downloaded = (F32)gTotalObjectBytes / (1024 * 1024); U32 total_http_requests = LLAppViewer::getTextureFetch()->getTotalNumHTTPRequests(); + F32 x_right = 0.0; + //---------------------------------------------------------------------------- LLGLSUIDefault gls_ui; LLColor4 text_color(1.f, 1.f, 1.f, 0.75f); @@ -543,7 +546,7 @@ void LLGLTexMemBar::draw() cache_max_usage); //, cache_entries, cache_max_entries - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*4, + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*5, text_color, LLFontGL::LEFT, LLFontGL::TOP); U32 cache_read(0U), cache_write(0U), res_wait(0U); @@ -557,13 +560,12 @@ void LLGLTexMemBar::draw() cache_write, res_wait); - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3, + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*4, text_color, LLFontGL::LEFT, LLFontGL::TOP); - S32 left = 0 ; //---------------------------------------------------------------------------- - text = llformat("Textures: %d Fetch: %d(%d) Pkts:%d(%d) Cache R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d", + text = llformat("Textures: %d Fetch: %d(%d) Pkts:%d(%d) Cache R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d ", gTextureList.getNumImages(), LLAppViewer::getTextureFetch()->getNumRequests(), LLAppViewer::getTextureFetch()->getNumDeletes(), LLAppViewer::getTextureFetch()->mPacketCount, LLAppViewer::getTextureFetch()->mBadPacketCount, @@ -574,19 +576,30 @@ void LLGLTexMemBar::draw() LLAppViewer::getImageDecodeThread()->getPending(), gTextureList.mCreateTextureList.size()); - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*2, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - + x_right = 550.0; + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3, + text_color, LLFontGL::LEFT, LLFontGL::TOP, + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, + &x_right, FALSE); - left = 550; F32 bandwidth = LLAppViewer::getTextureFetch()->getTextureBandwidth(); F32 max_bandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS"); - color = bandwidth > max_bandwidth ? LLColor4::red : bandwidth > max_bandwidth*.75f ? LLColor4::yellow : text_color; + color = bandwidth > max_bandwidth ? LLColor4::red : bandwidth > max_bandwidth * .75f ? LLColor4::yellow : text_color; color[VALPHA] = text_color[VALPHA]; - text = llformat("BW:%.0f/%.0f",bandwidth, max_bandwidth); - LLFontGL::getFontMonospace()->renderUTF8(text, 0, left, v_offset + line_height*2, + text = llformat("BW:%.0f/%.0f", bandwidth, max_bandwidth); + LLFontGL::getFontMonospace()->renderUTF8(text, 0, x_right, v_offset + line_height*3, color, LLFontGL::LEFT, LLFontGL::TOP); - + + // Mesh status line + text = llformat("Mesh: Reqs(Tot/Htp/Big): %u/%u/%u Rtr/Err: %u/%u Cread/Cwrite: %u/%u Low/At/High: %d/%d/%d", + LLMeshRepository::sMeshRequestCount, LLMeshRepository::sHTTPRequestCount, LLMeshRepository::sHTTPLargeRequestCount, + LLMeshRepository::sHTTPRetryCount, LLMeshRepository::sHTTPErrorCount, + LLMeshRepository::sCacheReads, LLMeshRepository::sCacheWrites, + LLMeshRepoThread::sRequestLowWater, LLMeshRepoThread::sRequestWaterLevel, LLMeshRepoThread::sRequestHighWater); + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*2, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + + // Header for texture table columns S32 dx1 = 0; if (LLAppViewer::getTextureFetch()->mDebugPause) { @@ -629,7 +642,7 @@ BOOL LLGLTexMemBar::handleMouseDown(S32 x, S32 y, MASK mask) LLRect LLGLTexMemBar::getRequiredRect() { LLRect rect; - rect.mTop = 50; //LLFontGL::getFontMonospace()->getLineHeight() * 6; + rect.mTop = 68; //LLFontGL::getFontMonospace()->getLineHeight() * 6; return rect; } diff --git a/indra/newview/lltracker.cpp b/indra/newview/lltracker.cpp index cbd16e873d..73ceb783b5 100755 --- a/indra/newview/lltracker.cpp +++ b/indra/newview/lltracker.cpp @@ -167,6 +167,7 @@ void LLTracker::render3D() } static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); + static LLUIColor map_track_color_under = LLUIColorTable::instance().getColor("MapTrackColorUnder", LLColor4::white); // Arbitary location beacon if( instance()->mIsTrackingLocation ) @@ -187,7 +188,7 @@ void LLTracker::render3D() } else { - renderBeacon( instance()->mTrackedPositionGlobal, map_track_color, + renderBeacon( instance()->mTrackedPositionGlobal, map_track_color, map_track_color_under, instance()->mBeaconText, instance()->mTrackedLocationName ); } } @@ -229,7 +230,7 @@ void LLTracker::render3D() // and back again instance()->mHasReachedLandmark = FALSE; } - renderBeacon( instance()->mTrackedPositionGlobal, map_track_color, + renderBeacon( instance()->mTrackedPositionGlobal, map_track_color, map_track_color_under, instance()->mBeaconText, instance()->mTrackedLandmarkName ); } } @@ -258,7 +259,7 @@ void LLTracker::render3D() } else { - renderBeacon( av_tracker.getGlobalPos(), map_track_color, + renderBeacon( av_tracker.getGlobalPos(), map_track_color, map_track_color_under, instance()->mBeaconText, av_tracker.getName() ); } } @@ -412,7 +413,7 @@ const std::string& LLTracker::getTrackedLocationName() return instance()->mTrackedLocationName; } -F32 pulse_func(F32 t, F32 z) +F32 pulse_func(F32 t, F32 z, bool tracking_avatar, std::string direction) { if (!LLTracker::sCheesyBeacon) { @@ -420,8 +421,15 @@ F32 pulse_func(F32 t, F32 z) } t *= F_PI; - z -= t*64.f - 256.f; - + if ("DOWN" == direction) + { + z += t*64.f - 256.f; + } + else + { + z -= t*64.f - 256.f; + } + F32 a = cosf(z*F_PI/512.f)*10.0f; a = llmax(a, 9.9f); a -= 9.9f; @@ -474,10 +482,78 @@ void draw_shockwave(F32 center_z, F32 t, S32 steps, LLColor4 color) gGL.end(); } +void LLTracker::drawBeacon(LLVector3 pos_agent, std::string direction, LLColor4 fogged_color, F32 dist) +{ + const U32 BEACON_VERTS = 256; + F32 step; + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + + if ("DOWN" == direction) + { + gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], pos_agent.mV[2]); + draw_shockwave(1024.f, gRenderStartTime.getElapsedTimeF32(), 32, fogged_color); + step = (5020.0f - pos_agent.mV[2]) / BEACON_VERTS; + } + else + { + gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], 0); + step = pos_agent.mV[2] / BEACON_VERTS; + } + + gGL.color4fv(fogged_color.mV); + + LLVector3 x_axis = LLViewerCamera::getInstance()->getLeftAxis(); + F32 t = gRenderStartTime.getElapsedTimeF32(); + + for (U32 i = 0; i < BEACON_VERTS; i++) + { + F32 x = x_axis.mV[0]; + F32 y = x_axis.mV[1]; + + F32 z = i * step; + F32 z_next = (i+1)*step; + + bool tracking_avatar = getTrackingStatus() == TRACKING_AVATAR; + F32 a = pulse_func(t, z, tracking_avatar, direction); + F32 an = pulse_func(t, z_next, tracking_avatar, direction); + + LLColor4 c_col = fogged_color + LLColor4(a,a,a,a); + LLColor4 col_next = fogged_color + LLColor4(an,an,an,an); + LLColor4 col_edge = fogged_color * LLColor4(a,a,a,0.0f); + LLColor4 col_edge_next = fogged_color * LLColor4(an,an,an,0.0f); + + a *= 2.f; + a += 1.0f; + + an *= 2.f; + an += 1.0f; + + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.color4fv(col_edge.mV); + gGL.vertex3f(-x*a, -y*a, z); + gGL.color4fv(col_edge_next.mV); + gGL.vertex3f(-x*an, -y*an, z_next); + + gGL.color4fv(c_col.mV); + gGL.vertex3f(0, 0, z); + gGL.color4fv(col_next.mV); + gGL.vertex3f(0, 0, z_next); + + gGL.color4fv(col_edge.mV); + gGL.vertex3f(x*a,y*a,z); + gGL.color4fv(col_edge_next.mV); + gGL.vertex3f(x*an,y*an,z_next); + gGL.end(); + } + gGL.popMatrix(); +} // static void LLTracker::renderBeacon(LLVector3d pos_global, - const LLColor4& color, + const LLColor4& color, + const LLColor4& color_under, LLHUDText* hud_textp, const std::string& label ) { @@ -497,9 +573,11 @@ void LLTracker::renderBeacon(LLVector3d pos_global, } LLColor4 fogged_color = color_frac * color + (1 - color_frac)*gSky.getFogColor(); + LLColor4 fogged_color_under = color_frac * color_under + (1 - color_frac) * gSky.getFogColor(); F32 FADE_DIST = 3.f; fogged_color.mV[3] = llmax(0.2f, llmin(0.5f,(dist-FADE_DIST)/FADE_DIST)); + fogged_color_under.mV[3] = llmax(0.2f, llmin(0.5f,(dist-FADE_DIST)/FADE_DIST)); LLVector3 pos_agent = gAgent.getPosAgentFromGlobal(pos_global); @@ -508,64 +586,8 @@ void LLTracker::renderBeacon(LLVector3d pos_global, LLGLDisable cull_face(GL_CULL_FACE); LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - { - gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], pos_agent.mV[2]); - - draw_shockwave(1024.f, gRenderStartTime.getElapsedTimeF32(), 32, fogged_color); - - gGL.color4fv(fogged_color.mV); - const U32 BEACON_VERTS = 256; - const F32 step = 1024.0f/BEACON_VERTS; - - LLVector3 x_axis = LLViewerCamera::getInstance()->getLeftAxis(); - F32 t = gRenderStartTime.getElapsedTimeF32(); - F32 dr = dist/LLViewerCamera::getInstance()->getFar(); - - for (U32 i = 0; i < BEACON_VERTS; i++) - { - F32 x = x_axis.mV[0]; - F32 y = x_axis.mV[1]; - - F32 z = i * step; - F32 z_next = (i+1)*step; - - F32 a = pulse_func(t, z); - F32 an = pulse_func(t, z_next); - - LLColor4 c_col = fogged_color + LLColor4(a,a,a,a); - LLColor4 col_next = fogged_color + LLColor4(an,an,an,an); - LLColor4 col_edge = fogged_color * LLColor4(a,a,a,0.0f); - LLColor4 col_edge_next = fogged_color * LLColor4(an,an,an,0.0f); - - a *= 2.f; - a += 1.0f+dr; - - an *= 2.f; - an += 1.0f+dr; - - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.color4fv(col_edge.mV); - gGL.vertex3f(-x*a, -y*a, z); - gGL.color4fv(col_edge_next.mV); - gGL.vertex3f(-x*an, -y*an, z_next); - - gGL.color4fv(c_col.mV); - gGL.vertex3f(0, 0, z); - gGL.color4fv(col_next.mV); - gGL.vertex3f(0, 0, z_next); - - gGL.color4fv(col_edge.mV); - gGL.vertex3f(x*a,y*a,z); - gGL.color4fv(col_edge_next.mV); - gGL.vertex3f(x*an,y*an,z_next); - - gGL.end(); - } - } - gGL.popMatrix(); + LLTracker::drawBeacon(pos_agent, "DOWN", fogged_color, dist); + LLTracker::drawBeacon(pos_agent, "UP", fogged_color_under, dist); std::string text; text = llformat( "%.0f m", to_vec.magVec()); diff --git a/indra/newview/lltracker.h b/indra/newview/lltracker.h index 8e916af315..d8d5803787 100755 --- a/indra/newview/lltracker.h +++ b/indra/newview/lltracker.h @@ -108,8 +108,10 @@ protected: LLTracker(); ~LLTracker(); + static void drawBeacon(LLVector3 pos_agent, std::string direction, LLColor4 fogged_color, F32 dist); static void renderBeacon( LLVector3d pos_global, const LLColor4& color, + const LLColor4& color_under, LLHUDText* hud_textp, const std::string& label ); diff --git a/indra/newview/llvieweraudio.cpp b/indra/newview/llvieweraudio.cpp index 3da934b148..826d296117 100755 --- a/indra/newview/llvieweraudio.cpp +++ b/indra/newview/llvieweraudio.cpp @@ -368,6 +368,7 @@ void init_audio() gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTyping"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowClose"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowOpen"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndRestart"))); } audio_update_volume(true); diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 4ce049df03..a8eeddb798 100755 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -95,6 +95,7 @@ #include "llfloaterproperties.h" #include "llfloaterregiondebugconsole.h" #include "llfloaterregioninfo.h" +#include "llfloaterregionrestarting.h" #include "llfloaterreporter.h" #include "llfloaterscriptdebug.h" #include "llfloaterscriptlimits.h" @@ -296,6 +297,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("reset_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterResetQueue>); LLFloaterReg::add("region_debug_console", "floater_region_debug_console.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionDebugConsole>); LLFloaterReg::add("region_info", "floater_region_info.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionInfo>); + LLFloaterReg::add("region_restarting", "floater_region_restarting.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionRestarting>); LLFloaterReg::add("script_debug", "floater_script_debug.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterScriptDebug>); LLFloaterReg::add("script_debug_output", "floater_script_debug_panel.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterScriptDebugOutput>); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index ac2940fcfc..fb07ab8fbe 100755 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -1025,6 +1025,10 @@ U32 info_display_from_string(std::string info_display) { return LLPipeline::RENDER_DEBUG_AVATAR_VOLUME; } + else if ("joints" == info_display) + { + return LLPipeline::RENDER_DEBUG_AVATAR_JOINTS; + } else if ("raycast" == info_display) { return LLPipeline::RENDER_DEBUG_RAYCAST; diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 3574d37adf..267aa9532c 100755 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -111,6 +111,7 @@ #include "llpanelblockedlist.h" #include "llpanelplaceprofile.h" #include "llviewerregion.h" +#include "llfloaterregionrestarting.h" #include <boost/algorithm/string/split.hpp> // #include <boost/regex.hpp> @@ -5741,7 +5742,6 @@ bool handle_special_notification(std::string notificationID, LLSD& llsdBlock) std::string regionMaturity = LLViewerRegion::accessToString(regionAccess); LLStringUtil::toLower(regionMaturity); llsdBlock["REGIONMATURITY"] = regionMaturity; - bool returnValue = false; LLNotificationPtr maturityLevelNotification; std::string notifySuffix = "_Notify"; @@ -5911,6 +5911,7 @@ bool attempt_standard_notification(LLMessageSystem* msgsystem) (notificationID == "RegionEntryAccessBlocked") || (notificationID == "LandClaimAccessBlocked") || (notificationID == "LandBuyAccessBlocked") + ) { /*--------------------------------------------------------------------- @@ -5952,7 +5953,41 @@ bool attempt_standard_notification(LLMessageSystem* msgsystem) snap_filename += SCREEN_HOME_FILENAME; gViewerWindow->saveSnapshot(snap_filename, gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw(), FALSE, FALSE); } - + + if (notificationID == "RegionRestartMinutes" || + notificationID == "RegionRestartSeconds") + { + S32 seconds; + if (notificationID == "RegionRestartMinutes") + { + seconds = 60 * static_cast<S32>(llsdBlock["MINUTES"].asInteger()); + } + else + { + seconds = static_cast<S32>(llsdBlock["SECONDS"].asInteger()); + } + + LLFloaterRegionRestarting* floaterp = LLFloaterReg::findTypedInstance<LLFloaterRegionRestarting>("region_restarting"); + + if (floaterp) + { + LLFloaterRegionRestarting::updateTime(seconds); + } + else + { + LLSD params; + params["NAME"] = llsdBlock["NAME"]; + params["SECONDS"] = (LLSD::Integer)seconds; + LLFloaterRegionRestarting* restarting_floater = dynamic_cast<LLFloaterRegionRestarting*>(LLFloaterReg::showInstance("region_restarting", params)); + if(restarting_floater) + { + restarting_floater->center(); + } + } + + send_sound_trigger(LLUUID(gSavedSettings.getString("UISndRestart")), 1.0f); + } + LLNotificationsUtil::add(notificationID, llsdBlock); return true; } @@ -6012,7 +6047,6 @@ void process_alert_message(LLMessageSystem *msgsystem, void **user_data) std::string message; msgsystem->getStringFast(_PREHASH_AlertData, _PREHASH_Message, message); - process_special_alert_messages(message); if (!attempt_standard_notification(msgsystem)) @@ -6036,7 +6070,6 @@ bool handle_not_age_verified_alert(const std::string &pAlertName) bool handle_special_alerts(const std::string &pAlertName) { bool isHandled = false; - if (LLStringUtil::compareStrings(pAlertName, "NotAgeVerified") == 0) { @@ -6072,26 +6105,17 @@ void process_alert_core(const std::string& message, BOOL modal) // System message is important, show in upper-right box not tip std::string text(message.substr(1)); LLSD args; - if (text.substr(0,17) == "RESTART_X_MINUTES") - { - S32 mins = 0; - LLStringUtil::convertToS32(text.substr(18), mins); - args["MINUTES"] = llformat("%d",mins); - LLNotificationsUtil::add("RegionRestartMinutes", args); - } - else if (text.substr(0,17) == "RESTART_X_SECONDS") - { - S32 secs = 0; - LLStringUtil::convertToS32(text.substr(18), secs); - args["SECONDS"] = llformat("%d",secs); - LLNotificationsUtil::add("RegionRestartSeconds", args); - } - else + + // *NOTE: If the text from the server ever changes this line will need to be adjusted. + std::string restart_cancelled = "Region restart cancelled."; + if (text.substr(0, restart_cancelled.length()) == restart_cancelled) { - std::string new_msg =LLNotifications::instance().getGlobalString(text); - args["MESSAGE"] = new_msg; - LLNotificationsUtil::add("SystemMessage", args); + LLFloaterRegionRestarting::close(); } + + std::string new_msg =LLNotifications::instance().getGlobalString(text); + args["MESSAGE"] = new_msg; + LLNotificationsUtil::add("SystemMessage", args); } else if (modal) { diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index a067603bf0..c2e9769fc7 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2010-2013, 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 @@ -1601,6 +1601,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("GetDisplayNames"); capabilityNames.append("GetMesh"); + capabilityNames.append("GetMesh2"); capabilityNames.append("GetObjectCost"); capabilityNames.append("GetObjectPhysicsData"); capabilityNames.append("GetTexture"); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index c3c1edb0a3..1e7d1644b2 100755 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -1392,9 +1392,11 @@ void LLVOAvatar::getSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) //----------------------------------------------------------------------------- void LLVOAvatar::renderCollisionVolumes() { + std::ostringstream ostr; for (S32 i = 0; i < mNumCollisionVolumes; i++) { mCollisionVolumes[i].renderCollision(); + ostr << mCollisionVolumes[i].getName() << ", "; } if (mNameText.notNull()) @@ -1403,6 +1405,96 @@ void LLVOAvatar::renderCollisionVolumes() mNameText->lineSegmentIntersect(unused, unused, unused, TRUE); } + + mDebugText.clear(); + addDebugText(ostr.str()); +} + +void LLVOAvatar::renderJoints() +{ + std::ostringstream ostr; + std::ostringstream nullstr; + + for (joint_map_t::iterator iter = mJointMap.begin(); iter != mJointMap.end(); ++iter) + { + LLJoint* jointp = iter->second; + if (!jointp) + { + nullstr << iter->first << " is NULL" << std::endl; + continue; + } + + ostr << jointp->getName() << ", "; + + jointp->updateWorldMatrix(); + + gGL.pushMatrix(); + gGL.multMatrix( &jointp->getXform()->getWorldMatrix().mMatrix[0][0] ); + + gGL.diffuseColor3f( 1.f, 0.f, 1.f ); + + gGL.begin(LLRender::LINES); + + LLVector3 v[] = + { + LLVector3(1,0,0), + LLVector3(-1,0,0), + LLVector3(0,1,0), + LLVector3(0,-1,0), + + LLVector3(0,0,-1), + LLVector3(0,0,1), + }; + + //sides + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[2].mV); + + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[3].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[2].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[3].mV); + + + //top + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[4].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[4].mV); + + gGL.vertex3fv(v[2].mV); + gGL.vertex3fv(v[4].mV); + + gGL.vertex3fv(v[3].mV); + gGL.vertex3fv(v[4].mV); + + + //bottom + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[5].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[5].mV); + + gGL.vertex3fv(v[2].mV); + gGL.vertex3fv(v[5].mV); + + gGL.vertex3fv(v[3].mV); + gGL.vertex3fv(v[5].mV); + + gGL.end(); + + gGL.popMatrix(); + } + + mDebugText.clear(); + addDebugText(ostr.str()); + addDebugText(nullstr.str()); } BOOL LLVOAvatar::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, @@ -3077,9 +3169,6 @@ void LLVOAvatar::forceUpdateVisualMuteSettings() //------------------------------------------------------------------------ BOOL LLVOAvatar::updateCharacter(LLAgent &agent) { - // clear debug text - mDebugText.clear(); - if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) { S32 central_bake_version = -1; @@ -3588,6 +3677,7 @@ BOOL LLVOAvatar::updateCharacter(LLAgent &agent) { setDebugText(mDebugText); } + mDebugText.clear(); //mesh vertices need to be reskinned mNeedsSkin = TRUE; diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 9d45a74ecc..0e4121f1c4 100755 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -406,6 +406,7 @@ public: F32 getLastSkinTime() { return mLastSkinTime; } U32 renderTransparent(BOOL first_pass); void renderCollisionVolumes(); + void renderJoints(); static void deleteCachedImages(bool clearAll=true); static void destroyGL(); static void restoreGL(); diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h index 9aeb2d4978..1c7154d413 100755 --- a/indra/newview/pipeline.h +++ b/indra/newview/pipeline.h @@ -535,15 +535,16 @@ public: RENDER_DEBUG_SHADOW_FRUSTA = 0x00040000, RENDER_DEBUG_SCULPTED = 0x00080000, RENDER_DEBUG_AVATAR_VOLUME = 0x00100000, - RENDER_DEBUG_BUILD_QUEUE = 0x00200000, - RENDER_DEBUG_AGENT_TARGET = 0x00400000, - RENDER_DEBUG_UPDATE_TYPE = 0x00800000, - RENDER_DEBUG_PHYSICS_SHAPES = 0x01000000, - RENDER_DEBUG_NORMALS = 0x02000000, - RENDER_DEBUG_LOD_INFO = 0x04000000, - RENDER_DEBUG_RENDER_COMPLEXITY = 0x08000000, - RENDER_DEBUG_ATTACHMENT_BYTES = 0x10000000, - RENDER_DEBUG_TEXEL_DENSITY = 0x20000000 + RENDER_DEBUG_AVATAR_JOINTS = 0x00200000, + RENDER_DEBUG_BUILD_QUEUE = 0x00400000, + RENDER_DEBUG_AGENT_TARGET = 0x00800000, + RENDER_DEBUG_UPDATE_TYPE = 0x01000000, + RENDER_DEBUG_PHYSICS_SHAPES = 0x02000000, + RENDER_DEBUG_NORMALS = 0x04000000, + RENDER_DEBUG_LOD_INFO = 0x08000000, + RENDER_DEBUG_RENDER_COMPLEXITY = 0x10000000, + RENDER_DEBUG_ATTACHMENT_BYTES = 0x20000000, + RENDER_DEBUG_TEXEL_DENSITY = 0x40000000 }; public: diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml index dab4c5eb3b..dfd7b5ee39 100755 --- a/indra/newview/skins/default/colors.xml +++ b/indra/newview/skins/default/colors.xml @@ -122,6 +122,9 @@ <color name="Blue_80" value="0 0 1 0.8" /> + <color + name="Orange" + value="1 .82 .46 1" /> <!-- This color name makes potentially unused colors show up bright purple. Leave this here until all Unused? are removed below, otherwise @@ -511,6 +514,9 @@ name="MapTrackColor" reference="Red" /> <color + name="MapTrackColorUnder" + reference="Blue" /> + <color name="MapTrackDisabledColor" value="0.5 0 0 1" /> <color diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index bb891996c9..94c187e21a 100755 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -153,8 +153,6 @@ with the same filename but different name <texture name="Command_Speak_Icon" file_name="toolbar_icons/speak.png" preload="true" /> <texture name="Command_View_Icon" file_name="toolbar_icons/view.png" preload="true" /> <texture name="Command_Voice_Icon" file_name="toolbar_icons/nearbyvoice.png" preload="true" /> - <texture name="Command_Highlighting_Icon" file_name="toolbar_icons/highlighting.png" preload="true" scale.left="4" scale.top="19" scale.right="28" scale.bottom="4" /> - <texture name="Command_Highlighting_Selected_Icon" file_name="toolbar_icons/highlighting_selected.png" preload="true" scale.left="4" scale.top="19" scale.right="28" scale.bottom="4" /> <texture name="Caret_Bottom_Icon" file_name="toolbar_icons/caret_bottom.png" preload="true" scale.left="1" scale.top="23" scale.right="15" scale.bottom="1" /> <texture name="Caret_Right_Icon" file_name="toolbar_icons/caret_right.png" preload="true" scale.left="5" scale.top="15" scale.right="28" scale.bottom="1" /> <texture name="Caret_Left_Icon" file_name="toolbar_icons/caret_left.png" preload="true" scale.left="1" scale.top="15" scale.right="23" scale.bottom="1" /> @@ -165,7 +163,6 @@ with the same filename but different name <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="ComboButton_Hovered" file_name="widgets/ComboButton_Hover.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/en/floater_im_session.xml b/indra/newview/skins/default/xui/en/floater_im_session.xml index 43d0f2fb18..7076de55e3 100755 --- a/indra/newview/skins/default/xui/en/floater_im_session.xml +++ b/indra/newview/skins/default/xui/en/floater_im_session.xml @@ -210,7 +210,7 @@ default_tab_group="3" tab_group="2" name="right_part_holder" - min_width="172"> + min_width="230"> <layout_stack animate="true" default_tab_group="2" diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 5e92a12251..9fa416012c 100755 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -212,11 +212,11 @@ follows="top|left" height="20" layout="topleft" - left="215" + left="222" name="lod_mode_high" top_delta="0" visible="false" - width="135"> + width="130"> <item name="Triangle Limit" value="Triangle Limit" /> @@ -230,7 +230,7 @@ height="20" increment="10" layout="topleft" - left_pad="5" + left_pad="3" name="lod_triangle_limit_high" visible="false" width="55" /> @@ -342,10 +342,10 @@ follows="top|left" height="20" layout="topleft" - left="215" + left="222" name="lod_mode_medium" top_delta="0" - width="135"> + width="130"> <item name="Triangle Limit" value="Triangle Limit" /> @@ -359,7 +359,7 @@ height="20" increment="10" layout="topleft" - left_pad="5" + left_pad="3" name="lod_triangle_limit_medium" width="55" /> <spinner @@ -470,10 +470,10 @@ follows="top|left" height="20" layout="topleft" - left="215" + left="222" name="lod_mode_low" top_delta="0" - width="135"> + width="130"> <item name="Triangle Limit" value="Triangle Limit" /> @@ -487,7 +487,7 @@ height="20" increment="10" layout="topleft" - left_pad="5" + left_pad="3" name="lod_triangle_limit_low" width="55" /> <spinner @@ -598,10 +598,10 @@ follows="top|left" height="20" layout="topleft" - left="215" + left="222" name="lod_mode_lowest" top_delta="0" - width="135"> + width="130"> <item name="Triangle Limit" value="Triangle Limit" /> @@ -615,7 +615,7 @@ height="20" increment="10" layout="topleft" - left_pad="5" + left_pad="3" name="lod_triangle_limit_lowest" width="55" /> <spinner diff --git a/indra/newview/skins/default/xui/en/floater_region_restarting.xml b/indra/newview/skins/default/xui/en/floater_region_restarting.xml new file mode 100644 index 0000000000..2fe4d0190a --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_region_restarting.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + height="150" + width="290" + layout="topleft" + name="region_restarting" + help_topic="floater_region_restarting" + single_instance="true" + reuse_instance="false" + title="REGION RESTARTING"> + <string name="RegionName"> + The region you are in now ([NAME]) is about to restart. + +If you stay in this region you will be logged out. + </string> + <string name="RestartSeconds"> + Seconds until restart +[SECONDS] + </string> + <panel + name="layout_panel_1" + height="150" + width="290" + follows="right|top" + top="0" + left="0" + background_visible="true" + bg_opaque_color="Orange" + bg_alpha_color="Orange"> + + <icon color="1.0 1.0 1.0 1.0" + tab_stop="false" + mouse_opaque="false" + name="icon" + width="32" + height="32" + image_name="notify_caution_icon.tga" + follows="left|top"> + </icon> + + <text + type="string" + length="1" + follows="top|left" + layout="topleft" + name="region_name" + text_color="Black" + font="SansSerifBold" + word_wrap="true" + height="100" + top="5" + left="40" + width="230"> + The region you are in now (-The longest region name-) is about to restart. + +If you stay in this region you will be logged out. + </text> + <text + type="string" + length="1" + follows="top|left" + layout="topleft" + name="restart_seconds" + text_color="Black" + font="SansSerifLargeBold" + height="40" + top="110" + left="0" + halign="center" + width="290"> + Seconds until restart + 32767 + </text> + </panel> + </floater> diff --git a/indra/newview/skins/default/xui/en/menu_inventory.xml b/indra/newview/skins/default/xui/en/menu_inventory.xml index 512205ba43..6fa45d7d66 100755 --- a/indra/newview/skins/default/xui/en/menu_inventory.xml +++ b/indra/newview/skins/default/xui/en/menu_inventory.xml @@ -552,6 +552,14 @@ function="Inventory.DoToSelected" parameter="about" /> </menu_item_call> + <menu_item_call + label="Show on Map" + layout="topleft" + name="show_on_map"> + <menu_item_call.on_click + function="Inventory.DoToSelected" + parameter="show_on_map" /> + </menu_item_call> <menu_item_separator layout="topleft" name="Animation Separator" /> diff --git a/indra/newview/skins/default/xui/en/menu_teleport_history_item.xml b/indra/newview/skins/default/xui/en/menu_teleport_history_item.xml index 0160d52b17..f939c3996d 100755 --- a/indra/newview/skins/default/xui/en/menu_teleport_history_item.xml +++ b/indra/newview/skins/default/xui/en/menu_teleport_history_item.xml @@ -17,7 +17,7 @@ function="TeleportHistory.MoreInformation" /> </menu_item_call> <menu_item_call - label="Copy to Clipboard" + label="Copy SLurl" layout="topleft" name="CopyToClipboard"> <menu_item_call.on_click diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index e5e2bd4c11..64de010eb5 100755 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -2564,6 +2564,16 @@ parameter="collision skeleton" /> </menu_item_check> <menu_item_check + label="Joints" + name="Joints"> + <menu_item_check.on_check + function="Advanced.CheckInfoDisplay" + parameter="joints" /> + <menu_item_check.on_click + function="Advanced.ToggleInfoDisplay" + parameter="joints" /> + </menu_item_check> + <menu_item_check label="Raycast" name="Raycast"> <menu_item_check.on_check diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 35f2e7b31e..a93601f5fd 100755 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -6893,8 +6893,8 @@ This will add a bookmark in your inventory so you can quickly IM this Resident. <notification icon="notify.tga" name="RegionRestartMinutes" + show_toast="false" priority="high" - sound="UISndAlert" type="notify"> The region "[NAME]" will restart in [MINUTES] minutes. If you stay in this region you will be logged out. @@ -6903,8 +6903,8 @@ If you stay in this region you will be logged out. <notification icon="notify.tga" name="RegionRestartSeconds" + show_toast="false" priority="high" - sound="UISndAlert" type="notify"> The region "[NAME]" will restart in [SECONDS] seconds. If you stay in this region you will be logged out. diff --git a/indra/newview/skins/default/xui/en/panel_people.xml b/indra/newview/skins/default/xui/en/panel_people.xml index 3caf2b3d7e..d2caf63052 100755 --- a/indra/newview/skins/default/xui/en/panel_people.xml +++ b/indra/newview/skins/default/xui/en/panel_people.xml @@ -66,8 +66,7 @@ Looking for people to hang out with? Try the [secondlife:///app/worldmap World M tab_position="top" top="0" halign="center" - right="-5" - use_highlighting_on_hover="true"> + right="-5"> <!-- ================================= NEARBY tab =========================== --> @@ -507,6 +506,7 @@ Looking for people to hang out with? Try the [secondlife:///app/worldmap World M right="-10" top_pad="4" left="3" + use_ellipses="true" name="groupcount"> You belong to [COUNT] groups, and can join [REMAINING] more. </text> diff --git a/indra/newview/skins/default/xui/en/widgets/location_input.xml b/indra/newview/skins/default/xui/en/widgets/location_input.xml index 4ea1aa6efb..61ec046649 100755 --- a/indra/newview/skins/default/xui/en/widgets/location_input.xml +++ b/indra/newview/skins/default/xui/en/widgets/location_input.xml @@ -150,7 +150,6 @@ <combo_button name="Location History" label="" - image_hover_unselected="ComboButton_Hovered" pad_right="0"/> <combo_list bg_writeable_color="MenuDefaultBgColor" diff --git a/indra/newview/skins/default/xui/en/widgets/tab_container.xml b/indra/newview/skins/default/xui/en/widgets/tab_container.xml index 9559be214a..0586119681 100755 --- a/indra/newview/skins/default/xui/en/widgets/tab_container.xml +++ b/indra/newview/skins/default/xui/en/widgets/tab_container.xml @@ -24,26 +24,17 @@ label_pad_left - padding to the left of tab button labels tab_bottom_image_unselected="Toolbar_Left_Off" tab_bottom_image_selected="Toolbar_Left_Selected" tab_left_image_unselected="SegmentedBtn_Left_Disabled" - tab_left_image_selected="SegmentedBtn_Left_Selected_Over" - tab_top_image_hovered="TabTop_Left_Selected" - tab_button_image_hovered="Toolbar_Left_Selected" - tab_left_image_hovered="SegmentedBtn_Left_Selected_Over"/> + tab_left_image_selected="SegmentedBtn_Left_Selected_Over"/> <middle_tab tab_top_image_unselected="TabTop_Middle_Off" tab_top_image_selected="TabTop_Middle_Selected" tab_bottom_image_unselected="Toolbar_Middle_Off" tab_bottom_image_selected="Toolbar_Middle_Selected" tab_left_image_unselected="SegmentedBtn_Left_Disabled" - tab_left_image_selected="SegmentedBtn_Left_Selected_Over" - tab_top_image_hovered="TabTop_Middle_Selected" - tab_button_image_hovered="Toolbar_Middle_Selected" - tab_left_image_hovered="SegmentedBtn_Left_Selected_Over"/> + tab_left_image_selected="SegmentedBtn_Left_Selected_Over"/> <last_tab tab_top_image_unselected="TabTop_Right_Off" tab_top_image_selected="TabTop_Right_Selected" tab_bottom_image_unselected="Toolbar_Right_Off" tab_bottom_image_selected="Toolbar_Right_Selected" tab_left_image_unselected="SegmentedBtn_Left_Disabled" - tab_left_image_selected="SegmentedBtn_Left_Selected_Over" - tab_top_image_hovered="TabTop_Right_Selected" - tab_button_image_hovered="Toolbar_Right_Selected" - tab_left_image_hovered="SegmentedBtn_Left_Selected_Over"/> + tab_left_image_selected="SegmentedBtn_Left_Selected_Over"/> </tab_container> |