Pull and merge from ssh://hg@bitbucket.org/lindenlab/viewer-release.
commit
3e038cd71b
|
|
@ -1,5 +1,6 @@
|
|||
syntax: glob
|
||||
|
||||
|
||||
# WinMerge temp files
|
||||
*.bak
|
||||
# Compiled python bytecode
|
||||
|
|
@ -24,6 +25,7 @@ indra/lib/mono/indra/*.exe
|
|||
indra/lib/mono/indra/*.pdb
|
||||
indra/lib/python/eventlet/
|
||||
indra/llwindow/glh/glh_linear.h
|
||||
indra/newview/app_settings/dictionaries
|
||||
indra/newview/app_settings/mozilla
|
||||
indra/newview/app_settings/mozilla-runtime-*
|
||||
indra/newview/app_settings/mozilla_debug
|
||||
|
|
|
|||
49
.hgtags
49
.hgtags
|
|
@ -110,6 +110,7 @@ bb1075286b3b147b1dae2e3d6b2d56f04ff03f35 DRTVWR-52_2.6.6-beta1
|
|||
bb1075286b3b147b1dae2e3d6b2d56f04ff03f35 2.6.6-beta1
|
||||
5e349dbe9cc84ea5795af8aeb6d473a0af9d4953 2.6.8-start
|
||||
beafa8a9bd1d1b670b7523d865204dc4a4b38eef DRTVWR-55_2.6.8-beta1
|
||||
bb9932a7a5fd00edf52d95f354e3b37ae6a942db DRTVWR-156
|
||||
beafa8a9bd1d1b670b7523d865204dc4a4b38eef 2.6.8-beta1
|
||||
11d5d8080e67c3955914caf98f2eb116af30e55a 2.6.9-start
|
||||
e67da2c6e3125966dd49eef98b36317afac1fcfe 2.6.9-start
|
||||
|
|
@ -149,6 +150,7 @@ a9abb9633a266c8d2fe62411cfd1c86d32da72bf 2.7.1-release
|
|||
09984bfa6cae17e0f72d02b75c1b7393c65eecfc 2.7.5-beta1
|
||||
e1ed60913230dd64269a7f7fc52cbc6004f6d52c 2.8.0-start
|
||||
502f6a5deca9365ddae57db4f1e30172668e171e 2.8.1-start
|
||||
2a3965b3ad202df7ea25d2be689291bb14a1280e DRTVWR-155
|
||||
6866d9df6efbd441c66451debd376d21211de39c DRTVWR-68_2.7.5-release
|
||||
6866d9df6efbd441c66451debd376d21211de39c 2.7.5-release
|
||||
e1ed60913230dd64269a7f7fc52cbc6004f6d52c DRTVWR-71_2.8.0-beta1
|
||||
|
|
@ -260,12 +262,13 @@ c6175c955a19e9b9353d242889ec1779b5762522 3.2.5-release
|
|||
3d75c836d178c7c7e788f256afe195f6cab764a2 3.2.7-beta1
|
||||
89980333c99dbaf1787fe20784f1d8849e9b5d4f 3.2.8-start
|
||||
16f8e2915f3f2e4d732fb3125daf229cb0fd1875 DRTVWR-114_3.2.8-beta1
|
||||
37dd400ad721e2a89ee820ffc1e7e433c68f3ca2 3.2.9-start
|
||||
16f8e2915f3f2e4d732fb3125daf229cb0fd1875 3.2.8-beta1
|
||||
089e5c84b2dece68f2b016c842ef9b5de4786842 DRTVWR-161
|
||||
987425b1acf4752379b2e1eb20944b4b35d67a85 DRTVWR-115_3.2.8-beta2
|
||||
987425b1acf4752379b2e1eb20944b4b35d67a85 3.2.8-beta2
|
||||
51b2fd52e36aab8f670e0874e7e1472434ec4b4a DRTVWR-113_3.2.8-release
|
||||
51b2fd52e36aab8f670e0874e7e1472434ec4b4a 3.2.8-release
|
||||
37dd400ad721e2a89ee820ffc1e7e433c68f3ca2 3.2.9-start
|
||||
e9c82fca5ae6fb8a8af29012d78fb194a29323f3 DRTVWR-117_3.2.9-beta1
|
||||
e9c82fca5ae6fb8a8af29012d78fb194a29323f3 3.2.9-beta1
|
||||
a01ef9bed28627f4ca543fbc1d70c79cc297a90f DRTVWR-118_3.2.9-beta2
|
||||
|
|
@ -284,6 +287,9 @@ d5f263687f43f278107363365938f0a214920a4b 3.3.0-beta1
|
|||
28b95a6a28dca3338d9a1f4f204b96678df9f6a5 viewer-beta-candidate
|
||||
b43cd25be49e3984ff5361cefad020e069131d98 3.3.1-start
|
||||
3e2fca4ed1a0dc9fe6d8a6664e71098bb035a367 DRTVWR-125
|
||||
dffd0457ee0745de65bf95f0642a5c9e46b8e2f0 viewer-beta-candidate
|
||||
3e2fca4ed1a0dc9fe6d8a6664e71098bb035a367 viewer-beta-candidate
|
||||
3e2fca4ed1a0dc9fe6d8a6664e71098bb035a367 viewer-beta-candidate
|
||||
3e2fca4ed1a0dc9fe6d8a6664e71098bb035a367 3.3.1-start
|
||||
28b95a6a28dca3338d9a1f4f204b96678df9f6a5 3.3.1-beta1
|
||||
1dc545e44617975da2a4a32fe303386c687a6ca1 viewer-beta-candidate
|
||||
|
|
@ -297,3 +303,44 @@ d29a260119f8d5a5d168e25fed0c7ea6b3f40161 3.3.2-beta1
|
|||
c623bbc854b6f7ee1b33a3718f76715046aa2937 viewer-release-candidate
|
||||
675668bd24d3bea570814f71762a2a806f7e1b8d viewer-release-candidate
|
||||
675668bd24d3bea570814f71762a2a806f7e1b8d 3.3.2-release
|
||||
675668bd24d3bea570814f71762a2a806f7e1b8d viewer-release-candidate
|
||||
050e48759337249130f684b4a21080b683f61732 DRTVWR-168
|
||||
b9d0170b62eb1c7c3adaa37a0b13a833e5e659f9 DRTVWR-171
|
||||
c08e2ac17a99973b2a94477659220b99b8847ae2 DRTVWR-163
|
||||
600f3b3920d94de805ac6dc8bb6def9c069dd360 DRTVWR-162
|
||||
600f3b3920d94de805ac6dc8bb6def9c069dd360 DRTVWR-162
|
||||
9a78ac13f047056f788c4734dd91aebfe30970e3 DRTVWR-157
|
||||
a716684aa7c07c440b1de5815b8a1f3dd3fd8bfb DRTVWR-159
|
||||
24a7281bef42bd4430ceb25db8b195449c2c7de3 DRTVWR-153
|
||||
15e90b52dc0297921b022b90d10d797436b8a1bd viewer-release-candidate
|
||||
6414ecdabc5d89515b08d1f872cf923ed3a5523a DRTVWR-148
|
||||
1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c DRTVWR-144
|
||||
1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c 3.3.3-beta1
|
||||
5910f8063a7e1ddddf504c2f35ca831cc5e8f469 DRTVWR-160
|
||||
1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c 3.3.3-beta1
|
||||
f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 3.3.3-beta1
|
||||
1b7f311b5a5dbfbed3dcbb4ed44afa20f89cad4c DRTVWR-144
|
||||
f0a174c2adb4bc39b16722a61d7eeb4f2a1d4843 DRTVWR-144
|
||||
2d6c0634b11e6f3df11002b8510a72a0433da00a DRTVWR-164
|
||||
80b5e5e9775966d3839331ffa7a16a60f9d7c930 DRTVWR-165
|
||||
fdcc08a4f20ae9bb060f4693c8980d216534efdf 3.3.3-beta2
|
||||
af5f3e43e6e4424b1da19d9e16f6b853a7b822ed DRTVWR-169
|
||||
4b3c68199a86cabaa5d9466d7b0f7e141e901d7a 3.3.3-beta3
|
||||
6428242e124b523813bfaf4c45b3d422f0298c81 3.3.3-release
|
||||
57d221de3df94f90b55204313c2cef044a3c0ae2 DRTVWR-176
|
||||
09ef7fd1b0781f33b8a3a9af6236b7bcb4831910 DRTVWR-170
|
||||
f87bfbe0b62d26f451d02a47c80ebef6b9168fc2 3.3.4-beta1
|
||||
f87bfbe0b62d26f451d02a47c80ebef6b9168fc2 DRTVWR-158
|
||||
f87bfbe0b62d26f451d02a47c80ebef6b9168fc2 3.3.4-beta1
|
||||
cbea6356ce9cb0c313b6777f10c5c14783264fcc DRTVWR-174
|
||||
bce218b2b45b730b22cc51e4807aa8b571cadef3 DRTVWR-173
|
||||
f91d003091a61937a044652c4c674447f7dcbb7a 3.3.4-beta1
|
||||
82b5330bc8b17d0d4b598832e9c5a92e90075682 3.3.4-beta2
|
||||
eb539c65e6ee26eea2bf373af2d0f4b52dc91289 DRTVWR-177
|
||||
a8057e1b9a1246b434a27405be35e030f7d28b0c 3.3.4-beta3
|
||||
4281aa899fb2cedb7a9ca7ce91c5c29d4aa69594 DRTVWR-180
|
||||
9cd174d3a54d93d409a7c346a15b8bfb40fc58f4 DRTVWR-184
|
||||
5c08e1d8edd871807153603b690e3ee9dbb548aa DRTVWR-183
|
||||
6c75f220b103db1420919c8b635fe53e2177f318 3.3.4-beta4
|
||||
ab2ffc547c8a8950ff187c4f6c95e5334fab597b 3.3.4-beta5
|
||||
28e100d0379a2b0710c57647a28fc5239d3d7b99 3.3.4-release
|
||||
|
|
|
|||
46
BuildParams
46
BuildParams
|
|
@ -112,6 +112,17 @@ viewer-mesh.login_channel = "Project Viewer - Mesh"
|
|||
viewer-mesh.viewer_grid = aditi
|
||||
viewer-mesh.email = shining@lists.lindenlab.com
|
||||
|
||||
# ========================================
|
||||
# viewer-adult-check
|
||||
# ========================================
|
||||
|
||||
viewer-adult-check.viewer_channel = "Project Viewer - AdultCheck"
|
||||
viewer-adult-check.login_channel = "Project Viewer - AdultCheck"
|
||||
viewer-adult-check.viewer_grid = agni
|
||||
viewer-adult-check.build_debug_release_separately = true
|
||||
viewer-adult-check.build_CYGWIN_Debug = false
|
||||
viewer-adult-check.build_viewer_update_version_manager = false
|
||||
|
||||
# ========================================
|
||||
# viewer-chui
|
||||
#
|
||||
|
|
@ -150,6 +161,10 @@ oz_viewer-beta-review.viewer_channel = "Second Life Beta Viewer"
|
|||
oz_viewer-beta-review.login_channel = "Second Life Beta Viewer"
|
||||
oz_viewer-beta-review.email = oz@lindenlab.com
|
||||
|
||||
oz_project-7.build_debug_release_separately = true
|
||||
oz_project-7.codeticket_add_context = false
|
||||
oz_project-7.email = "sldev@catznip.com oz@lindenlab.com"
|
||||
|
||||
# =================================================================
|
||||
# asset delivery 2010 projects
|
||||
# =================================================================
|
||||
|
|
@ -180,4 +195,35 @@ simon_viewer-dev-private.email_status_this_is_os = false
|
|||
vir-project-1.viewer_channel = "Second Life Release"
|
||||
vir-project-1.login_channel = "Second Life Release"
|
||||
|
||||
# ========================================
|
||||
# THX-1138 / Runway projects
|
||||
# ========================================
|
||||
viewer-thx1138-runway-shared.viewer_channel = "Project Viewer - THX-1138 Runway"
|
||||
viewer-thx1138-runway-shared.login_channel = "Project Viewer - THX-1138 Runway"
|
||||
viewer-thx1138-runway-shared.viewer_grid = uma
|
||||
viewer-thx1138-runway-shared.build_debug_release_separately = true
|
||||
viewer-thx1138-runway-shared.build_CYGWIN_Debug = false
|
||||
viewer-thx1138-runway-shared.build_viewer_update_version_manager = false
|
||||
|
||||
viewer-thx1138.viewer_channel = "Project Viewer - THX-1138"
|
||||
viewer-thx1138.login_channel = "Project Viewer - THX-1138"
|
||||
viewer-thx1138.viewer_grid = uma
|
||||
viewer-thx1138.build_debug_release_separately = true
|
||||
viewer-thx1138.build_CYGWIN_Debug = false
|
||||
viewer-thx1138.build_viewer_update_version_manager = false
|
||||
|
||||
runway-merge.viewer_channel = "Project Viewer - Runway Merge"
|
||||
runway-merge.login_channel = "Project Viewer - Runway Merge"
|
||||
runway-merge.viewer_grid = agni
|
||||
runway-merge.build_debug_release_separately = true
|
||||
runway-merge.build_CYGWIN_Debug = false
|
||||
runway-merge.build_viewer_update_version_manager = false
|
||||
|
||||
runway.viewer_channel = "Project Viewer - Runway"
|
||||
runway.login_channel = "Project Viewer - Runway"
|
||||
runway.viewer_grid = agni
|
||||
runway.build_debug_release_separately = true
|
||||
runway.build_CYGWIN_Debug = false
|
||||
runway.build_viewer_update_version_manager = false
|
||||
|
||||
# eof
|
||||
|
|
|
|||
118
autobuild.xml
118
autobuild.xml
|
|
@ -90,9 +90,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>9868bfa0b6954e4884c49c6f30068c80</string>
|
||||
<string>2dfcd809e747f714b3fe0bf82a175812</string>
|
||||
<key>url</key>
|
||||
<string>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/apr_suite-1.4.2-darwin-20110217.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259951/arch/Darwin/installer/apr_suite-1.4.5-darwin-20120618.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin</string>
|
||||
|
|
@ -102,9 +102,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>ff62946c518a247c86e1066c1e9a5855</string>
|
||||
<string>f38c966a430012dc157fdc104f23a59b</string>
|
||||
<key>url</key>
|
||||
<string>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/apr_suite-1.4.2-linux-20110309.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259951/arch/Linux/installer/apr_suite-1.4.5-linux-20120618.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>linux</string>
|
||||
|
|
@ -114,9 +114,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>73785c200a5b4ef74a1230b028bb680d</string>
|
||||
<string>4a9d040582342699c58c886c5ccd2caf</string>
|
||||
<key>url</key>
|
||||
<string>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/apr_suite-1.4.2-windows-20110217.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-apr/rev/259951/arch/CYGWIN/installer/apr_suite-1.4.5-windows-20120618.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
|
|
@ -363,6 +363,54 @@
|
|||
</map>
|
||||
</map>
|
||||
</map>
|
||||
<key>dictionaries</key>
|
||||
<map>
|
||||
<key>license</key>
|
||||
<string>various open</string>
|
||||
<key>license_file</key>
|
||||
<string>LICENSES/dictionaries.txt</string>
|
||||
<key>name</key>
|
||||
<string>dictionaries</string>
|
||||
<key>platforms</key>
|
||||
<map>
|
||||
<key>darwin</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>06a6c49eb1873e95623d3d2d07aee903</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-dictionaries/rev/259873/arch/Darwin/installer/dictionaries-1-darwin-20120616.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin</string>
|
||||
</map>
|
||||
<key>linux</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>4f0ca21d27e0cd0b002149062b0a4b25</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-dictionaries/rev/259873/arch/Linux/installer/dictionaries-1-linux-20120616.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>linux</string>
|
||||
</map>
|
||||
<key>windows</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>7520d75f6af325328322201c888191d4</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-dictionaries/rev/259873/arch/CYGWIN/installer/dictionaries-1-windows-20120616.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
</map>
|
||||
</map>
|
||||
</map>
|
||||
<key>elfio</key>
|
||||
<map>
|
||||
<key>license</key>
|
||||
|
|
@ -999,6 +1047,54 @@
|
|||
</map>
|
||||
</map>
|
||||
</map>
|
||||
<key>libhunspell</key>
|
||||
<map>
|
||||
<key>license</key>
|
||||
<string>libhunspell</string>
|
||||
<key>license_file</key>
|
||||
<string>LICENSES/hunspell.txt</string>
|
||||
<key>name</key>
|
||||
<string>libhunspell</string>
|
||||
<key>platforms</key>
|
||||
<map>
|
||||
<key>darwin</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>6f5db0ef258df6e5c93c843ec559db6d</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-hunspell/rev/259874/arch/Darwin/installer/libhunspell-1.3.2-darwin-20120616.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin</string>
|
||||
</map>
|
||||
<key>linux</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>0c432d2626aea2e91a56335879c92965</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-hunspell/rev/259874/arch/Linux/installer/libhunspell-1.3.2-linux-20120616.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>linux</string>
|
||||
</map>
|
||||
<key>windows</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>6a140e5620826aa5e587b4157f57b389</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-hunspell/rev/259874/arch/CYGWIN/installer/libhunspell-1.3.2-windows-20120616.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
</map>
|
||||
</map>
|
||||
</map>
|
||||
<key>libpng</key>
|
||||
<map>
|
||||
<key>license</key>
|
||||
|
|
@ -1099,8 +1195,6 @@
|
|||
<map>
|
||||
<key>license</key>
|
||||
<string>havok</string>
|
||||
<key>license_file</key>
|
||||
<string>on_file</string>
|
||||
<key>name</key>
|
||||
<string>llconvexdecomposition</string>
|
||||
<key>platforms</key>
|
||||
|
|
@ -1762,8 +1856,12 @@
|
|||
</map>
|
||||
<key>package_description</key>
|
||||
<map>
|
||||
<key>description</key>
|
||||
<string>Spell checking dictionaries</string>
|
||||
<key>license</key>
|
||||
<string>various open</string>
|
||||
<key>name</key>
|
||||
<string>viewer_development</string>
|
||||
<string>dictionaries</string>
|
||||
<key>platforms</key>
|
||||
<map>
|
||||
<key>common</key>
|
||||
|
|
@ -2473,6 +2571,8 @@
|
|||
<string>windows</string>
|
||||
</map>
|
||||
</map>
|
||||
<key>version</key>
|
||||
<string>1.0</string>
|
||||
</map>
|
||||
<key>type</key>
|
||||
<string>autobuild</string>
|
||||
|
|
|
|||
21
build.sh
21
build.sh
|
|
@ -15,6 +15,12 @@
|
|||
# * The basic convention is that the build name can be mapped onto a mercurial URL,
|
||||
# which is also used as the "branch" name.
|
||||
|
||||
check_for()
|
||||
{
|
||||
if [ -e "$2" ]; then found_dict='FOUND'; else found_dict='MISSING'; fi
|
||||
echo "$1 ${found_dict} '$2' " 1>&2
|
||||
}
|
||||
|
||||
build_dir_Darwin()
|
||||
{
|
||||
echo build-darwin-i386
|
||||
|
|
@ -59,6 +65,8 @@ pre_build()
|
|||
&& [ -r "$master_message_template_checkout/message_template.msg" ] \
|
||||
&& template_verifier_master_url="-DTEMPLATE_VERIFIER_MASTER_URL=file://$master_message_template_checkout/message_template.msg"
|
||||
|
||||
check_for "Before 'autobuild configure'" ${build_dir}/packages/dictionaries
|
||||
|
||||
"$AUTOBUILD" configure -c $variant -- \
|
||||
-DPACKAGE:BOOL=ON \
|
||||
-DRELEASE_CRASH_REPORTING:BOOL=ON \
|
||||
|
|
@ -67,7 +75,10 @@ pre_build()
|
|||
-DGRID:STRING="\"$viewer_grid\"" \
|
||||
-DLL_TESTS:BOOL="$run_tests" \
|
||||
-DTEMPLATE_VERIFIER_OPTIONS:STRING="$template_verifier_options" $template_verifier_master_url
|
||||
end_section "Pre$variant"
|
||||
|
||||
check_for "After 'autobuild configure'" ${build_dir}/packages/dictionaries
|
||||
|
||||
end_section "Pre$variant"
|
||||
}
|
||||
|
||||
build()
|
||||
|
|
@ -76,12 +87,17 @@ build()
|
|||
if $build_viewer
|
||||
then
|
||||
begin_section "Viewer$variant"
|
||||
|
||||
check_for "Before 'autobuild build'" ${build_dir}/packages/dictionaries
|
||||
|
||||
if "$AUTOBUILD" build --no-configure -c $variant
|
||||
then
|
||||
echo true >"$build_dir"/build_ok
|
||||
else
|
||||
echo false >"$build_dir"/build_ok
|
||||
fi
|
||||
check_for "After 'autobuild configure'" ${build_dir}/packages/dictionaries
|
||||
|
||||
end_section "Viewer$variant"
|
||||
fi
|
||||
}
|
||||
|
|
@ -172,7 +188,10 @@ eval "$("$AUTOBUILD" source_environment)"
|
|||
# dump environment variables for debugging
|
||||
env|sort
|
||||
|
||||
check_for "Before 'autobuild install'" ${build_dir}/packages/dictionaries
|
||||
|
||||
|
||||
check_for "After 'autobuild install'" ${build_dir}/packages/dictionaries
|
||||
# Now run the build
|
||||
succeeded=true
|
||||
build_processes=
|
||||
|
|
|
|||
|
|
@ -297,6 +297,8 @@ Cherry Cheevers
|
|||
ChickyBabes Zuzu
|
||||
Christopher Organiser
|
||||
Ciaran Laval
|
||||
Cinder Roxley
|
||||
STORM-1703
|
||||
Clara Young
|
||||
Coaldust Numbers
|
||||
VWR-1095
|
||||
|
|
@ -471,6 +473,7 @@ Hiro Sommambulist
|
|||
VWR-143
|
||||
Hitomi Tiponi
|
||||
STORM-1741
|
||||
STORM-1862
|
||||
Holger Gilruth
|
||||
Horatio Freund
|
||||
Hoze Menges
|
||||
|
|
@ -623,12 +626,22 @@ Jonathan Yap
|
|||
STORM-1799
|
||||
STORM-1796
|
||||
STORM-1807
|
||||
STORM-1812
|
||||
STORM-1820
|
||||
STORM-1839
|
||||
STORM-1842
|
||||
STORM-1808
|
||||
STORM-637
|
||||
STORM-1822
|
||||
STORM-1809
|
||||
STORM-1793
|
||||
STORM-1810
|
||||
STORM-1860
|
||||
STORM-1852
|
||||
STORM-1870
|
||||
STORM-1872
|
||||
STORM-1858
|
||||
STORM-1862
|
||||
Kadah Coba
|
||||
STORM-1060
|
||||
Jondan Lundquist
|
||||
|
|
@ -725,6 +738,8 @@ Marc2 Sands
|
|||
Marianne McCann
|
||||
Marine Kelley
|
||||
STORM-281
|
||||
MartinRJ Fayray
|
||||
STORM-1845
|
||||
Matthew Anthony
|
||||
Matthew Dowd
|
||||
VWR-1344
|
||||
|
|
@ -1052,6 +1067,8 @@ Simon Nolan
|
|||
Sini Nubalo
|
||||
Sitearm Madonna
|
||||
SLB Wirefly
|
||||
Slee Mayo
|
||||
SEC-1075
|
||||
snowy Sidran
|
||||
SpacedOut Frye
|
||||
VWR-34
|
||||
|
|
@ -1160,9 +1177,9 @@ Tofu Buzzard
|
|||
CTS-411
|
||||
STORM-546
|
||||
VWR-24509
|
||||
STORM-1684
|
||||
SH-2477
|
||||
STORM-1684
|
||||
STORM-1819
|
||||
Tony Kembia
|
||||
Torben Trautman
|
||||
TouchaHoney Perhaps
|
||||
|
|
@ -1287,6 +1304,7 @@ Zi Ree
|
|||
VWR-24017
|
||||
VWR-25588
|
||||
STORM-1790
|
||||
STORM-1842
|
||||
Zipherius Turas
|
||||
VWR-76
|
||||
VWR-77
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ if (VIEWER)
|
|||
add_subdirectory(${LIBS_OPEN_PREFIX}llcrashlogger)
|
||||
add_subdirectory(${LIBS_OPEN_PREFIX}llplugin)
|
||||
add_subdirectory(${LIBS_OPEN_PREFIX}llui)
|
||||
add_subdirectory(${LIBS_OPEN_PREFIX}llxuixml)
|
||||
add_subdirectory(${LIBS_OPEN_PREFIX}viewer_components)
|
||||
|
||||
# Legacy C++ tests. Build always, run if LL_TESTS is true.
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ set(cmake_SOURCE_FILES
|
|||
GLOD.cmake
|
||||
GStreamer010Plugin.cmake
|
||||
GooglePerfTools.cmake
|
||||
Hunspell.cmake
|
||||
JPEG.cmake
|
||||
LLAddBuildTest.cmake
|
||||
LLAudio.cmake
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ if(WINDOWS)
|
|||
libeay32.dll
|
||||
libcollada14dom22-d.dll
|
||||
glod.dll
|
||||
libhunspell.dll
|
||||
)
|
||||
|
||||
set(release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}")
|
||||
|
|
@ -53,6 +54,7 @@ if(WINDOWS)
|
|||
libeay32.dll
|
||||
libcollada14dom22.dll
|
||||
glod.dll
|
||||
libhunspell.dll
|
||||
)
|
||||
|
||||
if(USE_GOOGLE_PERFTOOLS)
|
||||
|
|
@ -212,11 +214,12 @@ elseif(DARWIN)
|
|||
libexpat.1.5.2.dylib
|
||||
libexpat.dylib
|
||||
libGLOD.dylib
|
||||
libllqtwebkit.dylib
|
||||
libminizip.a
|
||||
libllqtwebkit.dylib
|
||||
libminizip.a
|
||||
libndofdev.dylib
|
||||
libhunspell-1.3.0.dylib
|
||||
libexception_handler.dylib
|
||||
libcollada14dom.dylib
|
||||
libcollada14dom.dylib
|
||||
)
|
||||
|
||||
# fmod is statically linked on darwin
|
||||
|
|
@ -257,14 +260,15 @@ elseif(LINUX)
|
|||
libdb-5.1.so
|
||||
libexpat.so
|
||||
libexpat.so.1
|
||||
libglod.so
|
||||
libglod.so
|
||||
libgmock_main.so
|
||||
libgmock.so.0
|
||||
libgmodule-2.0.so
|
||||
libgobject-2.0.so
|
||||
libgtest_main.so
|
||||
libgtest.so.0
|
||||
libminizip.so
|
||||
libhunspell-1.3.so.0.0.0
|
||||
libminizip.so
|
||||
libopenal.so
|
||||
libopenjpeg.so
|
||||
libssl.so
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
# -*- cmake -*-
|
||||
|
||||
# - Find HUNSPELL
|
||||
# This module defines
|
||||
# HUNSPELL_INCLUDE_DIR, where to find libhunspell.h, etc.
|
||||
# HUNSPELL_LIBRARY, the library needed to use HUNSPELL.
|
||||
# HUNSPELL_FOUND, If false, do not try to use HUNSPELL.
|
||||
|
||||
find_path(HUNSPELL_INCLUDE_DIR hunspell.h
|
||||
PATH_SUFFIXES hunspell
|
||||
)
|
||||
|
||||
set(HUNSPELL_NAMES ${HUNSPELL_NAMES} libhunspell-1.3.0 libhunspell)
|
||||
find_library(HUNSPELL_LIBRARY
|
||||
NAMES ${HUNSPELL_NAMES}
|
||||
)
|
||||
|
||||
if (HUNSPELL_LIBRARY AND HUNSPELL_INCLUDE_DIR)
|
||||
set(HUNSPELL_FOUND "YES")
|
||||
else (HUNSPELL_LIBRARY AND HUNSPELL_INCLUDE_DIR)
|
||||
set(HUNSPELL_FOUND "NO")
|
||||
endif (HUNSPELL_LIBRARY AND HUNSPELL_INCLUDE_DIR)
|
||||
|
||||
|
||||
if (HUNSPELL_FOUND)
|
||||
if (NOT HUNSPELL_FIND_QUIETLY)
|
||||
message(STATUS "Found Hunspell: Library in '${HUNSPELL_LIBRARY}' and header in '${HUNSPELL_INCLUDE_DIR}' ")
|
||||
endif (NOT HUNSPELL_FIND_QUIETLY)
|
||||
else (HUNSPELL_FOUND)
|
||||
if (HUNSPELL_FIND_REQUIRED)
|
||||
message(FATAL_ERROR " * * *\nCould not find HUNSPELL library! * * *")
|
||||
endif (HUNSPELL_FIND_REQUIRED)
|
||||
endif (HUNSPELL_FOUND)
|
||||
|
||||
mark_as_advanced(
|
||||
HUNSPELL_LIBRARY
|
||||
HUNSPELL_INCLUDE_DIR
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# -*- cmake -*-
|
||||
include(Prebuilt)
|
||||
|
||||
set(HUNSPELL_FIND_QUIETLY ON)
|
||||
set(HUNSPELL_FIND_REQUIRED ON)
|
||||
|
||||
if (STANDALONE)
|
||||
include(FindHUNSPELL)
|
||||
else (STANDALONE)
|
||||
use_prebuilt_binary(libhunspell)
|
||||
if (WINDOWS)
|
||||
set(HUNSPELL_LIBRARY libhunspell)
|
||||
elseif(DARWIN)
|
||||
set(HUNSPELL_LIBRARY hunspell-1.3.0)
|
||||
elseif(LINUX)
|
||||
set(HUNSPELL_LIBRARY hunspell-1.3)
|
||||
else()
|
||||
message(FATAL_ERROR "Invalid platform")
|
||||
endif()
|
||||
set(HUNSPELL_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include/hunspell)
|
||||
use_prebuilt_binary(dictionaries)
|
||||
endif (STANDALONE)
|
||||
|
|
@ -9,6 +9,9 @@ MACRO(LL_TEST_COMMAND OUTVAR LD_LIBRARY_PATH)
|
|||
FOREACH(dir ${LD_LIBRARY_PATH})
|
||||
LIST(APPEND value "-l${dir}")
|
||||
ENDFOREACH(dir)
|
||||
# Enough different tests want to be able to find CMake's PYTHON_EXECUTABLE
|
||||
# that we should just pop it into the environment for everybody.
|
||||
LIST(APPEND value "-DPYTHON=${PYTHON_EXECUTABLE}")
|
||||
LIST(APPEND value ${ARGN})
|
||||
SET(${OUTVAR} ${value})
|
||||
##IF(LL_TEST_VERBOSE)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
include(Prebuilt)
|
||||
|
||||
if (NOT STANDALONE)
|
||||
use_prebuilt_binary(libhunspell)
|
||||
use_prebuilt_binary(libuuid)
|
||||
use_prebuilt_binary(slvoice)
|
||||
use_prebuilt_binary(fontconfig)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ $/LicenseInfo$
|
|||
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import subprocess
|
||||
|
||||
def main(command, libpath=[], vars={}):
|
||||
|
|
@ -113,6 +114,33 @@ def main(command, libpath=[], vars={}):
|
|||
sys.stdout.flush()
|
||||
return subprocess.call(command)
|
||||
|
||||
# swiped from vita, sigh, seems like a Bad Idea to introduce dependency
|
||||
def translate_rc(rc):
|
||||
"""
|
||||
Accept an rc encoded as for subprocess.Popen.returncode:
|
||||
None means still running
|
||||
int >= 0 means terminated voluntarily with specified rc
|
||||
int < 0 means terminated by signal (-rc)
|
||||
|
||||
Return a string explaining the outcome. In case of a signal, try to
|
||||
name the corresponding symbol from the 'signal' module.
|
||||
"""
|
||||
if rc is None:
|
||||
return "still running"
|
||||
|
||||
if rc >= 0:
|
||||
return "terminated with rc %s" % rc
|
||||
|
||||
# Negative rc means the child was terminated by signal -rc.
|
||||
rc = -rc
|
||||
for attr in dir(signal):
|
||||
if attr.startswith('SIG') and getattr(signal, attr) == rc:
|
||||
strc = attr
|
||||
break
|
||||
else:
|
||||
strc = str(rc)
|
||||
return "terminated by signal %s" % strc
|
||||
|
||||
if __name__ == "__main__":
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(usage="usage: %prog [options] command args...")
|
||||
|
|
@ -140,5 +168,5 @@ if __name__ == "__main__":
|
|||
vars=dict([(pair.split('=', 1) + [""])[:2] for pair in opts.vars]))
|
||||
if rc not in (None, 0):
|
||||
print >>sys.stderr, "Failure running: %s" % " ".join(args)
|
||||
print >>sys.stderr, "Error: %s" % rc
|
||||
print >>sys.stderr, "Error %s: %s" % (rc, translate_rc(rc))
|
||||
sys.exit((rc < 0) and 255 or rc)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,11 @@ static const char USAGE[] = "\n"
|
|||
" -o, --output <file1 .. file2> OR <type>\n"
|
||||
" List of image files to create (assumes same order as for input files)\n"
|
||||
" OR 3 letters file type extension to convert each input file into.\n"
|
||||
" -load, --load_size <n>\n"
|
||||
" Portion of the input file to load, in bytes."
|
||||
" If (load == 0), it will load the whole file."
|
||||
" If (load == -1), it will load the size relevant to reach the requested discard level (see -d)."
|
||||
" Only valid for j2c images. Default is 0 (load whole file).\n"
|
||||
" -r, --region <x0, y0, x1, y1>\n"
|
||||
" Crop region applied to the input files in pixels.\n"
|
||||
" Only used for j2c images. Default is no region cropping.\n"
|
||||
|
|
@ -104,22 +109,52 @@ void output_image_stats(LLPointer<LLImageFormatted> image, const std::string &fi
|
|||
// Print out some statistical data on the image
|
||||
std::cout << "Image stats for : " << filename << ", extension : " << image->getExtension() << std::endl;
|
||||
|
||||
std::cout << " with : " << (int)(image->getWidth()) << ", height : " << (int)(image->getHeight()) << std::endl;
|
||||
std::cout << " comp : " << (int)(image->getComponents()) << ", levels : " << (int)(image->getDiscardLevel()) << std::endl;
|
||||
std::cout << " head : " << (int)(image->calcHeaderSize()) << ", data : " << (int)(image->getDataSize()) << std::endl;
|
||||
std::cout << " with : " << (int)(image->getWidth()) << ", height : " << (int)(image->getHeight()) << std::endl;
|
||||
std::cout << " comp : " << (int)(image->getComponents()) << ", levels : " << (int)(image->getLevels()) << std::endl;
|
||||
std::cout << " head : " << (int)(image->calcHeaderSize()) << ", data : " << (int)(image->getDataSize()) << std::endl;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Load an image from file and return a raw (decompressed) instance of its data
|
||||
LLPointer<LLImageRaw> load_image(const std::string &src_filename, int discard_level, int* region, bool output_stats)
|
||||
LLPointer<LLImageRaw> load_image(const std::string &src_filename, int discard_level, int* region, int load_size, bool output_stats)
|
||||
{
|
||||
LLPointer<LLImageFormatted> image = create_image(src_filename);
|
||||
|
||||
// This just loads the image file stream into a buffer. No decoding done.
|
||||
if (!image->load(src_filename))
|
||||
// We support partial loading only for j2c images
|
||||
if (image->getCodec() == IMG_CODEC_J2C)
|
||||
{
|
||||
return NULL;
|
||||
// Load the header
|
||||
if (!image->load(src_filename, 600))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
S32 h = ((LLImageJ2C*)(image.get()))->calcHeaderSize();
|
||||
S32 d = (load_size > 0 ? ((LLImageJ2C*)(image.get()))->calcDiscardLevelBytes(load_size) : 0);
|
||||
S8 r = ((LLImageJ2C*)(image.get()))->getRawDiscardLevel();
|
||||
std::cout << "Merov debug : header = " << h << ", load_size = " << load_size << ", discard level = " << d << ", raw discard level = " << r << std::endl;
|
||||
for (d = 0; d < MAX_DISCARD_LEVEL; d++)
|
||||
{
|
||||
S32 data_size = ((LLImageJ2C*)(image.get()))->calcDataSize(d);
|
||||
std::cout << "Merov debug : discard_level = " << d << ", data_size = " << data_size << std::endl;
|
||||
}
|
||||
if (load_size < 0)
|
||||
{
|
||||
load_size = (discard_level != -1 ? ((LLImageJ2C*)(image.get()))->calcDataSize(discard_level) : 0);
|
||||
}
|
||||
// Load the requested byte range
|
||||
if (!image->load(src_filename, load_size))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This just loads the image file stream into a buffer. No decoding done.
|
||||
if (!image->load(src_filename))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if( (image->getComponents() != 3) && (image->getComponents() != 4) )
|
||||
|
|
@ -310,6 +345,7 @@ int main(int argc, char** argv)
|
|||
bool image_stats = false;
|
||||
int* region = NULL;
|
||||
int discard_level = -1;
|
||||
int load_size = 0;
|
||||
int precincts_size = -1;
|
||||
int blocks_size = -1;
|
||||
int levels = 0;
|
||||
|
|
@ -396,6 +432,22 @@ int main(int argc, char** argv)
|
|||
discard_level = llclamp(discard_level,0,5);
|
||||
}
|
||||
}
|
||||
else if (!strcmp(argv[arg], "--load_size") || !strcmp(argv[arg], "-load"))
|
||||
{
|
||||
std::string value_str;
|
||||
if ((arg + 1) < argc)
|
||||
{
|
||||
value_str = argv[arg+1];
|
||||
}
|
||||
if (((arg + 1) >= argc) || (value_str[0] == '-'))
|
||||
{
|
||||
std::cout << "No valid --load_size argument given, load_size ignored" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
load_size = atoi(value_str.c_str());
|
||||
}
|
||||
}
|
||||
else if (!strcmp(argv[arg], "--precincts") || !strcmp(argv[arg], "-p"))
|
||||
{
|
||||
std::string value_str;
|
||||
|
|
@ -510,7 +562,7 @@ int main(int argc, char** argv)
|
|||
for (; in_file != in_end; ++in_file, ++out_file)
|
||||
{
|
||||
// Load file
|
||||
LLPointer<LLImageRaw> raw_image = load_image(*in_file, discard_level, region, image_stats);
|
||||
LLPointer<LLImageRaw> raw_image = load_image(*in_file, discard_level, region, load_size, image_stats);
|
||||
if (!raw_image)
|
||||
{
|
||||
std::cout << "Error: Image " << *in_file << " could not be loaded" << std::endl;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ include(LLWindow)
|
|||
include(LLUI)
|
||||
include(LLVFS) # ugh, needed for LLDir
|
||||
include(LLXML)
|
||||
include(LLXUIXML)
|
||||
include(Hunspell)
|
||||
include(Linking)
|
||||
# include(Tut)
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ include_directories(
|
|||
${LLVFS_INCLUDE_DIRS}
|
||||
${LLWINDOW_INCLUDE_DIRS}
|
||||
${LLXML_INCLUDE_DIRS}
|
||||
${LLXUIXML_INCLUDE_DIRS}
|
||||
${LIBS_PREBUILD_DIR}/include/hunspell
|
||||
)
|
||||
|
||||
set(llui_libtest_SOURCE_FILES
|
||||
|
|
@ -80,6 +80,7 @@ target_link_libraries(llui_libtest
|
|||
${LLIMAGEJ2COJ_LIBRARIES}
|
||||
${OS_LIBRARIES}
|
||||
${GOOGLE_PERFTOOLS_LIBRARIES}
|
||||
${HUNSPELL_LIBRARY}
|
||||
)
|
||||
|
||||
if (WINDOWS)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,14 @@ import tarfile
|
|||
import errno
|
||||
import subprocess
|
||||
|
||||
class ManifestError(RuntimeError):
|
||||
"""Use an exception more specific than generic Python RuntimeError"""
|
||||
pass
|
||||
|
||||
class MissingError(ManifestError):
|
||||
"""You specified a file that doesn't exist"""
|
||||
pass
|
||||
|
||||
def path_ancestors(path):
|
||||
drive, path = os.path.splitdrive(os.path.normpath(path))
|
||||
result = []
|
||||
|
|
@ -180,6 +188,9 @@ def usage(srctree=""):
|
|||
arg['description'] % nd)
|
||||
|
||||
def main():
|
||||
## import itertools
|
||||
## print ' '.join((("'%s'" % item) if ' ' in item else item)
|
||||
## for item in itertools.chain([sys.executable], sys.argv))
|
||||
option_names = [arg['name'] + '=' for arg in ARGUMENTS]
|
||||
option_names.append('help')
|
||||
options, remainder = getopt.getopt(sys.argv[1:], "", option_names)
|
||||
|
|
@ -385,7 +396,7 @@ class LLManifest(object):
|
|||
child.stdout.close()
|
||||
status = child.wait()
|
||||
if status:
|
||||
raise RuntimeError(
|
||||
raise ManifestError(
|
||||
"Command %s returned non-zero status (%s) \noutput:\n%s"
|
||||
% (command, status, output) )
|
||||
return output
|
||||
|
|
@ -395,7 +406,7 @@ class LLManifest(object):
|
|||
a) verify that you really have created it
|
||||
b) schedule it for cleanup"""
|
||||
if not os.path.exists(path):
|
||||
raise RuntimeError, "Should be something at path " + path
|
||||
raise ManifestError, "Should be something at path " + path
|
||||
self.created_paths.append(path)
|
||||
|
||||
def put_in_file(self, contents, dst):
|
||||
|
|
@ -550,7 +561,7 @@ class LLManifest(object):
|
|||
except (IOError, os.error), why:
|
||||
errors.append((srcname, dstname, why))
|
||||
if errors:
|
||||
raise RuntimeError, errors
|
||||
raise ManifestError, errors
|
||||
|
||||
|
||||
def cmakedirs(self, path):
|
||||
|
|
@ -598,11 +609,10 @@ class LLManifest(object):
|
|||
|
||||
def check_file_exists(self, path):
|
||||
if not os.path.exists(path) and not os.path.islink(path):
|
||||
raise RuntimeError("Path %s doesn't exist" % (
|
||||
os.path.normpath(os.path.join(os.getcwd(), path)),))
|
||||
raise MissingError("Path %s doesn't exist" % (os.path.abspath(path),))
|
||||
|
||||
|
||||
wildcard_pattern = re.compile('\*')
|
||||
wildcard_pattern = re.compile(r'\*')
|
||||
def expand_globs(self, src, dst):
|
||||
src_list = glob.glob(src)
|
||||
src_re, d_template = self.wildcard_regex(src.replace('\\', '/'),
|
||||
|
|
@ -615,7 +625,7 @@ class LLManifest(object):
|
|||
sys.stdout.write("Processing %s => %s ... " % (src, dst))
|
||||
sys.stdout.flush()
|
||||
if src == None:
|
||||
raise RuntimeError("No source file, dst is " + dst)
|
||||
raise ManifestError("No source file, dst is " + dst)
|
||||
if dst == None:
|
||||
dst = src
|
||||
dst = os.path.join(self.get_dst_prefix(), dst)
|
||||
|
|
@ -637,13 +647,23 @@ class LLManifest(object):
|
|||
else:
|
||||
count += self.process_file(src, dst)
|
||||
return count
|
||||
try:
|
||||
count = try_path(os.path.join(self.get_src_prefix(), src))
|
||||
except RuntimeError:
|
||||
|
||||
for pfx in self.get_src_prefix(), self.get_artwork_prefix(), self.get_build_prefix():
|
||||
try:
|
||||
count = try_path(os.path.join(self.get_artwork_prefix(), src))
|
||||
except RuntimeError:
|
||||
count = try_path(os.path.join(self.get_build_prefix(), src))
|
||||
count = try_path(os.path.join(pfx, src))
|
||||
except MissingError:
|
||||
# If src isn't a wildcard, and if that file doesn't exist in
|
||||
# this pfx, try next pfx.
|
||||
count = 0
|
||||
continue
|
||||
|
||||
# Here try_path() didn't raise MissingError. Did it process any files?
|
||||
if count:
|
||||
break
|
||||
# Even though try_path() didn't raise MissingError, it returned 0
|
||||
# files. src is probably a wildcard meant for some other pfx. Loop
|
||||
# back to try the next.
|
||||
|
||||
print "%d files" % count
|
||||
|
||||
def do(self, *actions):
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ include(UI)
|
|||
include(LLCommon)
|
||||
include(LLVFS)
|
||||
include(LLXML)
|
||||
include(LLXUIXML)
|
||||
include(LLUI)
|
||||
include(Linking)
|
||||
|
||||
include_directories(
|
||||
${LLCOMMON_INCLUDE_DIRS}
|
||||
${LLVFS_INCLUDE_DIRS}
|
||||
${LLXML_INCLUDE_DIRS}
|
||||
${LLXUIXML_INCLUDE_DIRS}
|
||||
${LLUI_INCLUDE_DIRS}
|
||||
${CURL_INCLUDE_DIRS}
|
||||
${CARES_INCLUDE_DIRS}
|
||||
${OPENSSL_INCLUDE_DIRS}
|
||||
|
|
@ -42,7 +42,7 @@ target_link_libraries(linux-updater
|
|||
${CRYPTO_LIBRARIES}
|
||||
${UI_LIBRARIES}
|
||||
${LLXML_LIBRARIES}
|
||||
${LLXUIXML_LIBRARIES}
|
||||
${LLUI_LIBRARIES}
|
||||
${LLVFS_LIBRARIES}
|
||||
${LLCOMMON_LIBRARIES}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/**
|
||||
* @file linux_updater.cpp
|
||||
* @author Kyle Ambroff <ambroff@lindenlab.com>, Tofu Linden
|
||||
* @brief Viewer update program for unix platforms that support GTK+
|
||||
|
|
@ -6,21 +6,21 @@
|
|||
* $LicenseInfo:firstyear=2008&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$
|
||||
*/
|
||||
|
|
@ -34,10 +34,30 @@
|
|||
#include "llfile.h"
|
||||
#include "lldir.h"
|
||||
#include "lldiriterator.h"
|
||||
|
||||
/*==========================================================================*|
|
||||
// IQA-490: Use of LLTrans -- by this program at least -- appears to be buggy.
|
||||
// With it, the 3.3.2 beta 1 linux-updater.bin crashes; without it seems stable.
|
||||
#include "llxmlnode.h"
|
||||
#include "lltrans.h"
|
||||
|*==========================================================================*/
|
||||
|
||||
static class LLTrans
|
||||
{
|
||||
public:
|
||||
LLTrans();
|
||||
static std::string getString(const std::string& key);
|
||||
|
||||
private:
|
||||
std::string _getString(const std::string& key) const;
|
||||
|
||||
typedef std::map<std::string, std::string> MessageMap;
|
||||
MessageMap mMessages;
|
||||
} sLLTransInstance;
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <map>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <gtk/gtk.h>
|
||||
|
|
@ -85,6 +105,8 @@ void init_default_trans_args()
|
|||
bool translate_init(std::string comma_delim_path_list,
|
||||
std::string base_xml_name)
|
||||
{
|
||||
return true;
|
||||
/*==========================================================================*|
|
||||
init_default_trans_args();
|
||||
|
||||
// extract paths string vector from comma-delimited flat string
|
||||
|
|
@ -112,6 +134,7 @@ bool translate_init(std::string comma_delim_path_list,
|
|||
LLTrans::parseStrings(root, default_trans_args);
|
||||
return true;
|
||||
}
|
||||
|*==========================================================================*/
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -151,7 +174,7 @@ void updater_app_ui_init(UpdaterAppState *app_state)
|
|||
GTK_WIN_POS_CENTER_ALWAYS);
|
||||
|
||||
gtk_container_set_border_width(GTK_CONTAINER(app_state->window), 12);
|
||||
g_signal_connect(G_OBJECT(app_state->window), "delete-event",
|
||||
g_signal_connect(G_OBJECT(app_state->window), "delete-event",
|
||||
G_CALLBACK(on_window_closed), app_state);
|
||||
|
||||
vbox = gtk_vbox_new(FALSE, 6);
|
||||
|
|
@ -165,7 +188,7 @@ void updater_app_ui_init(UpdaterAppState *app_state)
|
|||
|
||||
summary_label = gtk_label_new(NULL);
|
||||
gtk_label_set_use_markup(GTK_LABEL(summary_label), TRUE);
|
||||
gtk_label_set_markup(GTK_LABEL(summary_label),
|
||||
gtk_label_set_markup(GTK_LABEL(summary_label),
|
||||
label_ostr.str().c_str());
|
||||
gtk_misc_set_alignment(GTK_MISC(summary_label), 0, 0.5);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), summary_label, FALSE, FALSE, 0);
|
||||
|
|
@ -195,9 +218,9 @@ void updater_app_ui_init(UpdaterAppState *app_state)
|
|||
|
||||
// set up progress bar, and update it roughly every 1/10 of a second
|
||||
app_state->progress_bar = gtk_progress_bar_new();
|
||||
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar),
|
||||
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app_state->progress_bar),
|
||||
LLTrans::getString("UpdaterProgressBarTextWithEllipses").c_str());
|
||||
gtk_box_pack_start(GTK_BOX(vbox),
|
||||
gtk_box_pack_start(GTK_BOX(vbox),
|
||||
app_state->progress_bar, FALSE, TRUE, 0);
|
||||
app_state->progress_update_timeout_id = g_timeout_add
|
||||
(UPDATE_PROGRESS_TIMEOUT, progress_update_timeout, app_state);
|
||||
|
|
@ -299,7 +322,7 @@ gpointer worker_thread_cb(gpointer data)
|
|||
g_error_free(error);
|
||||
throw 0;
|
||||
}
|
||||
|
||||
|
||||
if(tmp_local_filename != NULL)
|
||||
{
|
||||
app_state->file = tmp_local_filename;
|
||||
|
|
@ -342,7 +365,7 @@ gpointer worker_thread_cb(gpointer data)
|
|||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, package_file);
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
|
||||
&download_progress_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, app_state);
|
||||
|
||||
|
|
@ -352,8 +375,8 @@ gpointer worker_thread_cb(gpointer data)
|
|||
|
||||
if (result)
|
||||
{
|
||||
llerrs << "Failed to download update: "
|
||||
<< app_state->url
|
||||
llerrs << "Failed to download update: "
|
||||
<< app_state->url
|
||||
<< llendl;
|
||||
|
||||
gdk_threads_enter();
|
||||
|
|
@ -365,7 +388,7 @@ gpointer worker_thread_cb(gpointer data)
|
|||
throw 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// now pulse the progres bar back and forth while the package is
|
||||
// being unpacked
|
||||
gdk_threads_enter();
|
||||
|
|
@ -386,8 +409,8 @@ gpointer worker_thread_cb(gpointer data)
|
|||
|
||||
gdk_threads_enter();
|
||||
display_error(app_state->window,
|
||||
LLTrans::getString("UpdaterFailInstallTitle"),
|
||||
LLTrans::getString("UpdaterFailUpdateDescriptive"));
|
||||
LLTrans::getString("UpdaterFailInstallTitle"),
|
||||
LLTrans::getString("UpdaterFailUpdateDescriptive"));
|
||||
//"Failed to update " + app_state->app_name,
|
||||
gdk_threads_leave();
|
||||
throw 0;
|
||||
|
|
@ -402,8 +425,8 @@ gpointer worker_thread_cb(gpointer data)
|
|||
|
||||
gdk_threads_enter();
|
||||
display_error(app_state->window,
|
||||
LLTrans::getString("UpdaterFailStartTitle"),
|
||||
LLTrans::getString("UpdaterFailUpdateDescriptive"));
|
||||
LLTrans::getString("UpdaterFailStartTitle"),
|
||||
LLTrans::getString("UpdaterFailUpdateDescriptive"));
|
||||
gdk_threads_leave();
|
||||
throw 0;
|
||||
}
|
||||
|
|
@ -448,7 +471,7 @@ gboolean less_anal_gspawnsync(gchar **argv,
|
|||
|
||||
// restore SIGCHLD handler
|
||||
sigaction(SIGCHLD, &sigchld_backup, NULL);
|
||||
|
||||
|
||||
return rtn;
|
||||
}
|
||||
|
||||
|
|
@ -477,7 +500,7 @@ rename_with_sudo_fallback(const std::string& filename, const std::string& newnam
|
|||
{
|
||||
char *src_string_copy = g_strdup(filename.c_str());
|
||||
char *dst_string_copy = g_strdup(newname.c_str());
|
||||
char* argv[] =
|
||||
char* argv[] =
|
||||
{
|
||||
sudo_cmd,
|
||||
mv_cmd,
|
||||
|
|
@ -492,8 +515,8 @@ rename_with_sudo_fallback(const std::string& filename, const std::string& newnam
|
|||
if (!less_anal_gspawnsync(argv, &stderr_output,
|
||||
&child_exit_status, &spawn_error))
|
||||
{
|
||||
llwarns << "Failed to spawn child process: "
|
||||
<< spawn_error->message
|
||||
llwarns << "Failed to spawn child process: "
|
||||
<< spawn_error->message
|
||||
<< llendl;
|
||||
}
|
||||
else if (child_exit_status)
|
||||
|
|
@ -506,7 +529,7 @@ rename_with_sudo_fallback(const std::string& filename, const std::string& newnam
|
|||
{
|
||||
// everything looks good, clear the error code
|
||||
rtncode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
g_free(src_string_copy);
|
||||
g_free(dst_string_copy);
|
||||
|
|
@ -531,7 +554,7 @@ gboolean install_package(std::string package_file, std::string destination)
|
|||
}
|
||||
llinfos << "Found tar command: " << tar_cmd << llendl;
|
||||
|
||||
// Unpack the tarball in a temporary place first, then move it to
|
||||
// Unpack the tarball in a temporary place first, then move it to
|
||||
// its final destination
|
||||
std::string tmp_dest_dir = gDirUtilp->getTempFilename();
|
||||
if (LLFile::mkdir(tmp_dest_dir, 0744))
|
||||
|
|
@ -571,8 +594,8 @@ gboolean install_package(std::string package_file, std::string destination)
|
|||
if (!less_anal_gspawnsync(argv, &stderr_output,
|
||||
&child_exit_status, &untar_error))
|
||||
{
|
||||
llwarns << "Failed to spawn child process: "
|
||||
<< untar_error->message
|
||||
llwarns << "Failed to spawn child process: "
|
||||
<< untar_error->message
|
||||
<< llendl;
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -605,8 +628,8 @@ gboolean install_package(std::string package_file, std::string destination)
|
|||
|
||||
if (rename_with_sudo_fallback(destination, backup_dir))
|
||||
{
|
||||
llwarns << "Failed to move directory: '"
|
||||
<< destination << "' -> '" << backup_dir
|
||||
llwarns << "Failed to move directory: '"
|
||||
<< destination << "' -> '" << backup_dir
|
||||
<< llendl;
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -617,7 +640,7 @@ gboolean install_package(std::string package_file, std::string destination)
|
|||
if (rename_with_sudo_fallback(tmp_dest_dir, destination))
|
||||
{
|
||||
llwarns << "Failed to move installation to the destination: "
|
||||
<< destination
|
||||
<< destination
|
||||
<< llendl;
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -713,7 +736,7 @@ BOOL spawn_viewer(UpdaterAppState *app_state)
|
|||
|
||||
if (!success)
|
||||
{
|
||||
llwarns << "Failed to launch viewer: " << error->message
|
||||
llwarns << "Failed to launch viewer: " << error->message
|
||||
<< llendl;
|
||||
}
|
||||
|
||||
|
|
@ -751,7 +774,7 @@ void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state)
|
|||
else if ((!strcmp(argv[i], "--image-dir")) && (++i < argc))
|
||||
{
|
||||
app_state->image_dir = argv[i];
|
||||
app_state->image_dir_iter = new LLDirIterator(argv[i], "/*.jpg");
|
||||
app_state->image_dir_iter = new LLDirIterator(argv[i], "*.jpg");
|
||||
}
|
||||
else if ((!strcmp(argv[i], "--dest")) && (++i < argc))
|
||||
{
|
||||
|
|
@ -772,8 +795,8 @@ void parse_args_and_init(int argc, char **argv, UpdaterAppState *app_state)
|
|||
}
|
||||
}
|
||||
|
||||
if (app_state->app_name.empty()
|
||||
|| (app_state->url.empty() && app_state->file.empty())
|
||||
if (app_state->app_name.empty()
|
||||
|| (app_state->url.empty() && app_state->file.empty())
|
||||
|| app_state->dest_dir.empty())
|
||||
{
|
||||
show_usage_and_exit();
|
||||
|
|
@ -799,7 +822,7 @@ int main(int argc, char **argv)
|
|||
(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, ""));
|
||||
std::string old_log_file = gDirUtilp->getExpandedFilename
|
||||
(LL_PATH_LOGS, "updater.log.old");
|
||||
std::string log_file =
|
||||
std::string log_file =
|
||||
gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log");
|
||||
LLFile::rename(log_file, old_log_file);
|
||||
LLError::logToFile(log_file);
|
||||
|
|
@ -841,3 +864,63 @@ int main(int argc, char **argv)
|
|||
return success ? 0 : 1;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Dummy LLTrans implementation (IQA-490)
|
||||
*****************************************************************************/
|
||||
static LLTrans sStaticStrings;
|
||||
|
||||
// lookup
|
||||
std::string LLTrans::_getString(const std::string& key) const
|
||||
{
|
||||
MessageMap::const_iterator found = mMessages.find(key);
|
||||
if (found != mMessages.end())
|
||||
{
|
||||
return found->second;
|
||||
}
|
||||
LL_WARNS("linux_updater") << "No message for key '" << key
|
||||
<< "' -- add to LLTrans::LLTrans() in linux_updater.cpp"
|
||||
<< LL_ENDL;
|
||||
return key;
|
||||
}
|
||||
|
||||
// static lookup
|
||||
std::string LLTrans::getString(const std::string& key)
|
||||
{
|
||||
return sLLTransInstance._getString(key);
|
||||
}
|
||||
|
||||
// initialization
|
||||
LLTrans::LLTrans()
|
||||
{
|
||||
typedef std::pair<const char*, const char*> Pair;
|
||||
static const Pair data[] =
|
||||
{
|
||||
Pair("UpdaterFailDownloadTitle",
|
||||
"Failed to download update"),
|
||||
Pair("UpdaterFailInstallTitle",
|
||||
"Failed to install update"),
|
||||
Pair("UpdaterFailStartTitle",
|
||||
"Failed to start viewer"),
|
||||
Pair("UpdaterFailUpdateDescriptive",
|
||||
"An error occurred while updating Second Life. "
|
||||
"Please download the latest version from www.secondlife.com."),
|
||||
Pair("UpdaterNowInstalling",
|
||||
"Installing Second Life..."),
|
||||
Pair("UpdaterNowUpdating",
|
||||
"Now updating Second Life..."),
|
||||
Pair("UpdaterProgressBarText",
|
||||
"Downloading update"),
|
||||
Pair("UpdaterProgressBarTextWithEllipses",
|
||||
"Downloading update..."),
|
||||
Pair("UpdaterUpdatingDescriptive",
|
||||
"Your Second Life Viewer is being updated to the latest release. "
|
||||
"This may take some time, so please be patient."),
|
||||
Pair("UpdaterWindowTitle",
|
||||
"Second Life Update")
|
||||
};
|
||||
|
||||
BOOST_FOREACH(Pair pair, data)
|
||||
{
|
||||
mMessages[pair.first] = pair.second;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -571,7 +571,8 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs)
|
|||
llwarns << mCurrentDecodep->getUUID() << " has invalid vorbis data, aborting decode" << llendl;
|
||||
mCurrentDecodep->flushBadFile();
|
||||
LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID());
|
||||
adp->setHasValidData(FALSE);
|
||||
adp->setHasValidData(false);
|
||||
adp->setHasCompletedDecode(true);
|
||||
mCurrentDecodep = NULL;
|
||||
done = TRUE;
|
||||
}
|
||||
|
|
@ -586,11 +587,16 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs)
|
|||
if (mCurrentDecodep->finishDecode())
|
||||
{
|
||||
// We finished!
|
||||
if (mCurrentDecodep->isValid() && mCurrentDecodep->isDone())
|
||||
LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID());
|
||||
if (!adp)
|
||||
{
|
||||
LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID());
|
||||
adp->setHasDecodedData(TRUE);
|
||||
adp->setHasValidData(TRUE);
|
||||
llwarns << "Missing LLAudioData for decode of " << mCurrentDecodep->getUUID() << llendl;
|
||||
}
|
||||
else if (mCurrentDecodep->isValid() && mCurrentDecodep->isDone())
|
||||
{
|
||||
adp->setHasCompletedDecode(true);
|
||||
adp->setHasDecodedData(true);
|
||||
adp->setHasValidData(true);
|
||||
|
||||
// At this point, we could see if anyone needs this sound immediately, but
|
||||
// I'm not sure that there's a reason to - we need to poll all of the playing
|
||||
|
|
@ -599,7 +605,8 @@ void LLAudioDecodeMgr::Impl::processQueue(const F32 num_secs)
|
|||
}
|
||||
else
|
||||
{
|
||||
llinfos << "Vorbis decode failed!!!" << llendl;
|
||||
adp->setHasCompletedDecode(true);
|
||||
llinfos << "Vorbis decode failed for " << mCurrentDecodep->getUUID() << llendl;
|
||||
}
|
||||
mCurrentDecodep = NULL;
|
||||
}
|
||||
|
|
@ -667,16 +674,19 @@ BOOL LLAudioDecodeMgr::addDecodeRequest(const LLUUID &uuid)
|
|||
if (gAudiop->hasDecodedFile(uuid))
|
||||
{
|
||||
// Already have a decoded version, don't need to decode it.
|
||||
//llinfos << "addDecodeRequest for " << uuid << " has decoded file already" << llendl;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND))
|
||||
{
|
||||
// Just put it on the decode queue.
|
||||
//llinfos << "addDecodeRequest for " << uuid << " has local asset file already" << llendl;
|
||||
mImpl->mDecodeQueue.push(uuid);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//llinfos << "addDecodeRequest for " << uuid << " no file available" << llendl;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1221,10 +1221,11 @@ void LLAudioEngine::assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::E
|
|||
// Need to mark data as bad to avoid constant rerequests.
|
||||
LLAudioData *adp = gAudiop->getAudioData(uuid);
|
||||
if (adp)
|
||||
{
|
||||
{ // Make sure everything is cleared
|
||||
adp->setHasValidData(false);
|
||||
adp->setHasLocalData(false);
|
||||
adp->setHasDecodedData(false);
|
||||
adp->setHasCompletedDecode(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -1237,6 +1238,7 @@ void LLAudioEngine::assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::E
|
|||
}
|
||||
else
|
||||
{
|
||||
// llinfos << "Got asset callback with good audio data for " << uuid << ", making decode request" << llendl;
|
||||
adp->setHasValidData(true);
|
||||
adp->setHasLocalData(true);
|
||||
gAudioDecodeMgrp->addDecodeRequest(uuid);
|
||||
|
|
@ -1304,16 +1306,18 @@ void LLAudioSource::update()
|
|||
|
||||
if (!getCurrentBuffer())
|
||||
{
|
||||
if (getCurrentData())
|
||||
LLAudioData *adp = getCurrentData();
|
||||
if (adp)
|
||||
{
|
||||
// Hack - try and load the sound. Will do this as a callback
|
||||
// on decode later.
|
||||
if (getCurrentData()->load() && getCurrentData()->getBuffer())
|
||||
if (adp->load() && adp->getBuffer())
|
||||
{
|
||||
play(getCurrentData()->getID());
|
||||
play(adp->getID());
|
||||
}
|
||||
else
|
||||
else if (adp->hasCompletedDecode()) // Only mark corrupted after decode is done
|
||||
{
|
||||
llwarns << "Marking LLAudioSource corrupted for " << adp->getID() << llendl;
|
||||
mCorrupted = true ;
|
||||
}
|
||||
}
|
||||
|
|
@ -1731,6 +1735,7 @@ LLAudioData::LLAudioData(const LLUUID &uuid) :
|
|||
mBufferp(NULL),
|
||||
mHasLocalData(false),
|
||||
mHasDecodedData(false),
|
||||
mHasCompletedDecode(false),
|
||||
mHasValidData(true)
|
||||
{
|
||||
if (uuid.isNull())
|
||||
|
|
@ -1742,12 +1747,13 @@ LLAudioData::LLAudioData(const LLUUID &uuid) :
|
|||
if (gAudiop && gAudiop->hasDecodedFile(uuid))
|
||||
{
|
||||
// Already have a decoded version, don't need to decode it.
|
||||
mHasLocalData = true;
|
||||
mHasDecodedData = true;
|
||||
setHasLocalData(true);
|
||||
setHasDecodedData(true);
|
||||
setHasCompletedDecode(true);
|
||||
}
|
||||
else if (gAssetStorage && gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND))
|
||||
{
|
||||
mHasLocalData = true;
|
||||
setHasLocalData(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -372,10 +372,12 @@ public:
|
|||
|
||||
bool hasLocalData() const { return mHasLocalData; }
|
||||
bool hasDecodedData() const { return mHasDecodedData; }
|
||||
bool hasCompletedDecode() const { return mHasCompletedDecode; }
|
||||
bool hasValidData() const { return mHasValidData; }
|
||||
|
||||
void setHasLocalData(const bool hld) { mHasLocalData = hld; }
|
||||
void setHasDecodedData(const bool hdd) { mHasDecodedData = hdd; }
|
||||
void setHasCompletedDecode(const bool hcd) { mHasCompletedDecode = hcd; }
|
||||
void setHasValidData(const bool hvd) { mHasValidData = hvd; }
|
||||
|
||||
friend class LLAudioEngine; // Severe laziness, bad.
|
||||
|
|
@ -383,9 +385,10 @@ public:
|
|||
protected:
|
||||
LLUUID mID;
|
||||
LLAudioBuffer *mBufferp; // If this data is being used by the audio system, a pointer to the buffer will be set here.
|
||||
bool mHasLocalData;
|
||||
bool mHasDecodedData;
|
||||
bool mHasValidData;
|
||||
bool mHasLocalData; // Set true if the sound asset file is available locally
|
||||
bool mHasDecodedData; // Set true if the sound file has been decoded
|
||||
bool mHasCompletedDecode; // Set true when the sound is decoded
|
||||
bool mHasValidData; // Set false if decoding failed, meaning the sound asset is bad
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,10 @@ set(llcommon_SOURCE_FILES
|
|||
llformat.cpp
|
||||
llframetimer.cpp
|
||||
llheartbeat.cpp
|
||||
llinitparam.cpp
|
||||
llinstancetracker.cpp
|
||||
llleap.cpp
|
||||
llleaplistener.cpp
|
||||
llliveappconfig.cpp
|
||||
lllivefile.cpp
|
||||
lllog.cpp
|
||||
|
|
@ -74,13 +77,14 @@ set(llcommon_SOURCE_FILES
|
|||
llmortician.cpp
|
||||
lloptioninterface.cpp
|
||||
llptrto.cpp
|
||||
llprocesslauncher.cpp
|
||||
llprocess.cpp
|
||||
llprocessor.cpp
|
||||
llqueuedthread.cpp
|
||||
llrand.cpp
|
||||
llrefcount.cpp
|
||||
llrun.cpp
|
||||
llsd.cpp
|
||||
llsdparam.cpp
|
||||
llsdserialize.cpp
|
||||
llsdserialize_xml.cpp
|
||||
llsdutil.cpp
|
||||
|
|
@ -88,6 +92,7 @@ set(llcommon_SOURCE_FILES
|
|||
llsingleton.cpp
|
||||
llstat.cpp
|
||||
llstacktrace.cpp
|
||||
llstreamqueue.cpp
|
||||
llstreamtools.cpp
|
||||
llstring.cpp
|
||||
llstringtable.cpp
|
||||
|
|
@ -173,9 +178,12 @@ set(llcommon_HEADER_FILES
|
|||
llheartbeat.h
|
||||
llhttpstatuscodes.h
|
||||
llindexedqueue.h
|
||||
llinitparam.h
|
||||
llinstancetracker.h
|
||||
llkeythrottle.h
|
||||
lllazy.h
|
||||
llleap.h
|
||||
llleaplistener.h
|
||||
lllistenerwrapper.h
|
||||
lllinkedqueue.h
|
||||
llliveappconfig.h
|
||||
|
|
@ -196,7 +204,7 @@ set(llcommon_HEADER_FILES
|
|||
llpointer.h
|
||||
llpreprocessor.h
|
||||
llpriqueuemap.h
|
||||
llprocesslauncher.h
|
||||
llprocess.h
|
||||
llprocessor.h
|
||||
llptrskiplist.h
|
||||
llptrskipmap.h
|
||||
|
|
@ -204,10 +212,12 @@ set(llcommon_HEADER_FILES
|
|||
llqueuedthread.h
|
||||
llrand.h
|
||||
llrefcount.h
|
||||
llregistry.h
|
||||
llrun.h
|
||||
llrefcount.h
|
||||
llsafehandle.h
|
||||
llsd.h
|
||||
llsdparam.h
|
||||
llsdserialize.h
|
||||
llsdserialize_xml.h
|
||||
llsdutil.h
|
||||
|
|
@ -216,11 +226,13 @@ set(llcommon_HEADER_FILES
|
|||
llsingleton.h
|
||||
llskiplist.h
|
||||
llskipmap.h
|
||||
llsortedvector.h
|
||||
llstack.h
|
||||
llstacktrace.h
|
||||
llstat.h
|
||||
llstatenums.h
|
||||
llstl.h
|
||||
llstreamqueue.h
|
||||
llstreamtools.h
|
||||
llstrider.h
|
||||
llstring.h
|
||||
|
|
@ -230,6 +242,7 @@ set(llcommon_HEADER_FILES
|
|||
llthreadsafequeue.h
|
||||
lltimer.h
|
||||
lltreeiterators.h
|
||||
lltypeinfolookup.h
|
||||
lluri.h
|
||||
lluuid.h
|
||||
lluuidhashmap.h
|
||||
|
|
@ -317,8 +330,7 @@ if (LL_TESTS)
|
|||
LL_ADD_INTEGRATION_TEST(lllazy "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}"
|
||||
"${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py")
|
||||
LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llstring "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(lltreeiterators "" "${test_libs}")
|
||||
|
|
@ -326,6 +338,9 @@ if (LL_TESTS)
|
|||
LL_ADD_INTEGRATION_TEST(reflection "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")
|
||||
|
||||
# *TODO - reenable these once tcmalloc libs no longer break the build.
|
||||
#ADD_BUILD_TEST(llallocator llcommon)
|
||||
|
|
|
|||
|
|
@ -654,9 +654,7 @@ namespace LLError
|
|||
g.invalidateCallSites();
|
||||
s.tagLevelMap[tag_name] = level;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
LLError::ELevel decodeLevel(std::string name)
|
||||
{
|
||||
static LevelMap level_names;
|
||||
|
|
@ -681,7 +679,9 @@ namespace {
|
|||
|
||||
return i->second;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level)
|
||||
{
|
||||
LLSD::array_const_iterator i, end;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/**
|
||||
* @file llerrorcontrol.h
|
||||
* @date December 2006
|
||||
* @brief error message system control
|
||||
|
|
@ -6,21 +6,21 @@
|
|||
* $LicenseInfo:firstyear=2007&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$
|
||||
*/
|
||||
|
|
@ -38,7 +38,7 @@ class LLSD;
|
|||
This is the part of the LLError namespace that manages the messages
|
||||
produced by the logging. The logging support is defined in llerror.h.
|
||||
Most files do not need to include this.
|
||||
|
||||
|
||||
These implementations are in llerror.cpp.
|
||||
*/
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ namespace LLError
|
|||
Settings that control what is logged.
|
||||
Setting a level means log messages at that level or above.
|
||||
*/
|
||||
|
||||
|
||||
LL_COMMON_API void setPrintLocation(bool);
|
||||
LL_COMMON_API void setDefaultLevel(LLError::ELevel);
|
||||
LL_COMMON_API ELevel getDefaultLevel();
|
||||
|
|
@ -80,7 +80,8 @@ namespace LLError
|
|||
LL_COMMON_API void setClassLevel(const std::string& class_name, LLError::ELevel);
|
||||
LL_COMMON_API void setFileLevel(const std::string& file_name, LLError::ELevel);
|
||||
LL_COMMON_API void setTagLevel(const std::string& file_name, LLError::ELevel);
|
||||
|
||||
|
||||
LL_COMMON_API LLError::ELevel decodeLevel(std::string name);
|
||||
LL_COMMON_API void configure(const LLSD&);
|
||||
// the LLSD can configure all of the settings
|
||||
// usually read automatically from the live errorlog.xml file
|
||||
|
|
@ -100,31 +101,31 @@ namespace LLError
|
|||
// (by, for example, setting a class level to LEVEL_NONE), will keep
|
||||
// the that message from causing the fatal funciton to be invoked.
|
||||
|
||||
LL_COMMON_API FatalFunction getFatalFunction();
|
||||
// Retrieve the previously-set FatalFunction
|
||||
LL_COMMON_API FatalFunction getFatalFunction();
|
||||
// Retrieve the previously-set FatalFunction
|
||||
|
||||
/// temporarily override the FatalFunction for the duration of a
|
||||
/// particular scope, e.g. for unit tests
|
||||
class LL_COMMON_API OverrideFatalFunction
|
||||
{
|
||||
public:
|
||||
OverrideFatalFunction(const FatalFunction& func):
|
||||
mPrev(getFatalFunction())
|
||||
{
|
||||
setFatalFunction(func);
|
||||
}
|
||||
~OverrideFatalFunction()
|
||||
{
|
||||
setFatalFunction(mPrev);
|
||||
}
|
||||
/// temporarily override the FatalFunction for the duration of a
|
||||
/// particular scope, e.g. for unit tests
|
||||
class LL_COMMON_API OverrideFatalFunction
|
||||
{
|
||||
public:
|
||||
OverrideFatalFunction(const FatalFunction& func):
|
||||
mPrev(getFatalFunction())
|
||||
{
|
||||
setFatalFunction(func);
|
||||
}
|
||||
~OverrideFatalFunction()
|
||||
{
|
||||
setFatalFunction(mPrev);
|
||||
}
|
||||
|
||||
private:
|
||||
FatalFunction mPrev;
|
||||
};
|
||||
private:
|
||||
FatalFunction mPrev;
|
||||
};
|
||||
|
||||
typedef std::string (*TimeFunction)();
|
||||
LL_COMMON_API std::string utcTime();
|
||||
|
||||
|
||||
LL_COMMON_API void setTimeFunction(TimeFunction);
|
||||
// The function is use to return the current time, formatted for
|
||||
// display by those error recorders that want the time included.
|
||||
|
|
@ -136,19 +137,27 @@ namespace LLError
|
|||
// An object that handles the actual output or error messages.
|
||||
public:
|
||||
virtual ~Recorder();
|
||||
|
||||
|
||||
virtual void recordMessage(LLError::ELevel, const std::string& message) = 0;
|
||||
// use the level for better display, not for filtering
|
||||
|
||||
|
||||
virtual bool wantsTime(); // default returns false
|
||||
// override and return true if the recorder wants the time string
|
||||
// included in the text of the message
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @NOTE: addRecorder() conveys ownership to the underlying Settings
|
||||
* object -- when destroyed, it will @em delete the passed Recorder*!
|
||||
*/
|
||||
LL_COMMON_API void addRecorder(Recorder*);
|
||||
/**
|
||||
* @NOTE: removeRecorder() reclaims ownership of the Recorder*: its
|
||||
* lifespan becomes the caller's problem.
|
||||
*/
|
||||
LL_COMMON_API void removeRecorder(Recorder*);
|
||||
// each error message is passed to each recorder via recordMessage()
|
||||
|
||||
|
||||
LL_COMMON_API void logToFile(const std::string& filename);
|
||||
LL_COMMON_API void logToFixedBuffer(LLLineBuffer*);
|
||||
// Utilities to add recorders for logging to a file or a fixed buffer
|
||||
|
|
@ -166,10 +175,9 @@ namespace LLError
|
|||
class Settings;
|
||||
LL_COMMON_API Settings* saveAndResetSettings();
|
||||
LL_COMMON_API void restoreSettings(Settings *);
|
||||
|
||||
|
||||
LL_COMMON_API std::string abbreviateFile(const std::string& filePath);
|
||||
LL_COMMON_API int shouldLogCallCount();
|
||||
|
||||
};
|
||||
|
||||
#endif // LL_LLERRORCONTROL_H
|
||||
|
|
|
|||
|
|
@ -112,13 +112,8 @@ void LLErrorThread::run()
|
|||
#if !LL_WINDOWS
|
||||
U32 last_sig_child_count = 0;
|
||||
#endif
|
||||
while (1)
|
||||
while (! (LLApp::isError() || LLApp::isStopped()))
|
||||
{
|
||||
if (LLApp::isError() || LLApp::isStopped())
|
||||
{
|
||||
// The application has stopped running, time to take action (maybe)
|
||||
break;
|
||||
}
|
||||
#if !LL_WINDOWS
|
||||
// Check whether or not the main thread had a sig child we haven't handled.
|
||||
U32 current_sig_child_count = LLApp::getSigChildCount();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/**
|
||||
* @file llfile.cpp
|
||||
* @author Michael Schlachter
|
||||
* @date 2006-03-23
|
||||
|
|
@ -8,60 +8,194 @@
|
|||
* $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$
|
||||
*/
|
||||
|
||||
#if LL_WINDOWS
|
||||
#include <windows.h>
|
||||
#include <stdlib.h> // Windows errno
|
||||
#else
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
#include "linden_common.h"
|
||||
#include "llfile.h"
|
||||
#include "llstring.h"
|
||||
#include "llerror.h"
|
||||
#include "stringize.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static std::string empty;
|
||||
|
||||
// Many of the methods below use OS-level functions that mess with errno. Wrap
|
||||
// variants of strerror() to report errors.
|
||||
|
||||
#if LL_WINDOWS
|
||||
// On Windows, use strerror_s().
|
||||
std::string strerr(int errn)
|
||||
{
|
||||
char buffer[256];
|
||||
strerror_s(buffer, errn); // infers sizeof(buffer) -- love it!
|
||||
return buffer;
|
||||
}
|
||||
|
||||
#else
|
||||
// On Posix we want to call strerror_r(), but alarmingly, there are two
|
||||
// different variants. The one that returns int always populates the passed
|
||||
// buffer (except in case of error), whereas the other one always returns a
|
||||
// valid char* but might or might not populate the passed buffer. How do we
|
||||
// know which one we're getting? Define adapters for each and let the compiler
|
||||
// select the applicable adapter.
|
||||
|
||||
// strerror_r() returns char*
|
||||
std::string message_from(int /*orig_errno*/, const char* /*buffer*/, size_t /*bufflen*/,
|
||||
const char* strerror_ret)
|
||||
{
|
||||
return strerror_ret;
|
||||
}
|
||||
|
||||
// strerror_r() returns int
|
||||
std::string message_from(int orig_errno, const char* buffer, size_t bufflen,
|
||||
int strerror_ret)
|
||||
{
|
||||
if (strerror_ret == 0)
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
// Here strerror_r() has set errno. Since strerror_r() has already failed,
|
||||
// seems like a poor bet to call it again to diagnose its own error...
|
||||
int stre_errno = errno;
|
||||
if (stre_errno == ERANGE)
|
||||
{
|
||||
return STRINGIZE("strerror_r() can't explain errno " << orig_errno
|
||||
<< " (" << bufflen << "-byte buffer too small)");
|
||||
}
|
||||
if (stre_errno == EINVAL)
|
||||
{
|
||||
return STRINGIZE("unknown errno " << orig_errno);
|
||||
}
|
||||
// Here we don't even understand the errno from strerror_r()!
|
||||
return STRINGIZE("strerror_r() can't explain errno " << orig_errno
|
||||
<< " (error " << stre_errno << ')');
|
||||
}
|
||||
|
||||
std::string strerr(int errn)
|
||||
{
|
||||
char buffer[256];
|
||||
// Select message_from() function matching the strerror_r() we have on hand.
|
||||
return message_from(errn, buffer, sizeof(buffer),
|
||||
strerror_r(errn, buffer, sizeof(buffer)));
|
||||
}
|
||||
#endif // ! LL_WINDOWS
|
||||
|
||||
// On either system, shorthand call just infers global 'errno'.
|
||||
std::string strerr()
|
||||
{
|
||||
return strerr(errno);
|
||||
}
|
||||
|
||||
int warnif(const std::string& desc, const std::string& filename, int rc, int accept=0)
|
||||
{
|
||||
if (rc < 0)
|
||||
{
|
||||
// Capture errno before we start emitting output
|
||||
int errn = errno;
|
||||
// For certain operations, a particular errno value might be
|
||||
// acceptable -- e.g. stat() could permit ENOENT, mkdir() could permit
|
||||
// EEXIST. Don't warn if caller explicitly says this errno is okay.
|
||||
if (errn != accept)
|
||||
{
|
||||
LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename
|
||||
<< "' (errno " << errn << "): " << strerr(errn) << LL_ENDL;
|
||||
}
|
||||
#if 0 && LL_WINDOWS // turn on to debug file-locking problems
|
||||
// If the problem is "Permission denied," maybe it's because another
|
||||
// process has the file open. Try to find out.
|
||||
if (errn == EACCES) // *not* EPERM
|
||||
{
|
||||
// Only do any of this stuff (before LL_ENDL) if it will be logged.
|
||||
LL_DEBUGS("LLFile") << empty;
|
||||
const char* TEMP = getenv("TEMP");
|
||||
if (! TEMP)
|
||||
{
|
||||
LL_CONT << "No $TEMP, not running 'handle'";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string tf(TEMP);
|
||||
tf += "\\handle.tmp";
|
||||
// http://technet.microsoft.com/en-us/sysinternals/bb896655
|
||||
std::string cmd(STRINGIZE("handle \"" << filename
|
||||
// "openfiles /query /v | fgrep -i \"" << filename
|
||||
<< "\" > \"" << tf << '"'));
|
||||
LL_CONT << cmd;
|
||||
if (system(cmd.c_str()) != 0)
|
||||
{
|
||||
LL_CONT << "\nDownload 'handle.exe' from http://technet.microsoft.com/en-us/sysinternals/bb896655";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ifstream inf(tf);
|
||||
std::string line;
|
||||
while (std::getline(inf, line))
|
||||
{
|
||||
LL_CONT << '\n' << line;
|
||||
}
|
||||
}
|
||||
LLFile::remove(tf);
|
||||
}
|
||||
LL_CONT << LL_ENDL;
|
||||
}
|
||||
#endif // LL_WINDOWS hack to identify processes holding file open
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
// static
|
||||
int LLFile::mkdir(const std::string& dirname, int perms)
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
#if LL_WINDOWS
|
||||
// permissions are ignored on Windows
|
||||
std::string utf8dirname = dirname;
|
||||
llutf16string utf16dirname = utf8str_to_utf16str(utf8dirname);
|
||||
return _wmkdir(utf16dirname.c_str());
|
||||
int rc = _wmkdir(utf16dirname.c_str());
|
||||
#else
|
||||
return ::mkdir(dirname.c_str(), (mode_t)perms);
|
||||
int rc = ::mkdir(dirname.c_str(), (mode_t)perms);
|
||||
#endif
|
||||
// We often use mkdir() to ensure the existence of a directory that might
|
||||
// already exist. Don't spam the log if it does.
|
||||
return warnif("mkdir", dirname, rc, EEXIST);
|
||||
}
|
||||
|
||||
// static
|
||||
int LLFile::rmdir(const std::string& dirname)
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
#if LL_WINDOWS
|
||||
// permissions are ignored on Windows
|
||||
std::string utf8dirname = dirname;
|
||||
llutf16string utf16dirname = utf8str_to_utf16str(utf8dirname);
|
||||
return _wrmdir(utf16dirname.c_str());
|
||||
int rc = _wrmdir(utf16dirname.c_str());
|
||||
#else
|
||||
return ::rmdir(dirname.c_str());
|
||||
int rc = ::rmdir(dirname.c_str());
|
||||
#endif
|
||||
return warnif("rmdir", dirname, rc);
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
@ -108,10 +242,11 @@ int LLFile::remove(const std::string& filename)
|
|||
#if LL_WINDOWS
|
||||
std::string utf8filename = filename;
|
||||
llutf16string utf16filename = utf8str_to_utf16str(utf8filename);
|
||||
return _wremove(utf16filename.c_str());
|
||||
int rc = _wremove(utf16filename.c_str());
|
||||
#else
|
||||
return ::remove(filename.c_str());
|
||||
int rc = ::remove(filename.c_str());
|
||||
#endif
|
||||
return warnif("remove", filename, rc);
|
||||
}
|
||||
|
||||
int LLFile::rename(const std::string& filename, const std::string& newname)
|
||||
|
|
@ -121,10 +256,11 @@ int LLFile::rename(const std::string& filename, const std::string& newname)
|
|||
std::string utf8newname = newname;
|
||||
llutf16string utf16filename = utf8str_to_utf16str(utf8filename);
|
||||
llutf16string utf16newname = utf8str_to_utf16str(utf8newname);
|
||||
return _wrename(utf16filename.c_str(),utf16newname.c_str());
|
||||
int rc = _wrename(utf16filename.c_str(),utf16newname.c_str());
|
||||
#else
|
||||
return ::rename(filename.c_str(),newname.c_str());
|
||||
int rc = ::rename(filename.c_str(),newname.c_str());
|
||||
#endif
|
||||
return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc);
|
||||
}
|
||||
|
||||
int LLFile::stat(const std::string& filename, llstat* filestatus)
|
||||
|
|
@ -132,23 +268,26 @@ int LLFile::stat(const std::string& filename, llstat* filestatus)
|
|||
#if LL_WINDOWS
|
||||
std::string utf8filename = filename;
|
||||
llutf16string utf16filename = utf8str_to_utf16str(utf8filename);
|
||||
return _wstat(utf16filename.c_str(),filestatus);
|
||||
int rc = _wstat(utf16filename.c_str(),filestatus);
|
||||
#else
|
||||
return ::stat(filename.c_str(),filestatus);
|
||||
int rc = ::stat(filename.c_str(),filestatus);
|
||||
#endif
|
||||
// We use stat() to determine existence (see isfile(), isdir()).
|
||||
// Don't spam the log if the subject pathname doesn't exist.
|
||||
return warnif("stat", filename, rc, ENOENT);
|
||||
}
|
||||
|
||||
bool LLFile::isdir(const std::string& filename)
|
||||
{
|
||||
llstat st;
|
||||
|
||||
|
||||
return stat(filename, &st) == 0 && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
bool LLFile::isfile(const std::string& filename)
|
||||
{
|
||||
llstat st;
|
||||
|
||||
|
||||
return stat(filename, &st) == 0 && S_ISREG(st.st_mode);
|
||||
}
|
||||
|
||||
|
|
@ -260,7 +399,7 @@ void llifstream::open(const std::string& _Filename, /* Flawfinder: ignore */
|
|||
ios_base::openmode _Mode,
|
||||
int _Prot)
|
||||
{ // open a C stream with specified mode
|
||||
|
||||
|
||||
LLFILE* filep = LLFile::_Fiopen(_Filename,_Mode | ios_base::in, _Prot);
|
||||
if(filep == NULL)
|
||||
{
|
||||
|
|
@ -280,7 +419,7 @@ bool llifstream::is_open() const
|
|||
return false;
|
||||
}
|
||||
llifstream::~llifstream()
|
||||
{
|
||||
{
|
||||
if (_ShouldClose)
|
||||
{
|
||||
close();
|
||||
|
|
@ -309,7 +448,7 @@ bool llofstream::is_open() const
|
|||
|
||||
void llofstream::open(const std::string& _Filename, /* Flawfinder: ignore */
|
||||
ios_base::openmode _Mode,
|
||||
int _Prot)
|
||||
int _Prot)
|
||||
{ // open a C stream with specified mode
|
||||
|
||||
LLFILE* filep = LLFile::_Fiopen(_Filename,_Mode | ios_base::out, _Prot);
|
||||
|
|
@ -340,14 +479,14 @@ void llofstream::close()
|
|||
|
||||
llofstream::llofstream(const std::string& _Filename,
|
||||
std::ios_base::openmode _Mode,
|
||||
int _Prot)
|
||||
int _Prot)
|
||||
: std::basic_ostream<char,std::char_traits < char > >(NULL,true),_Filebuffer(NULL),_ShouldClose(false)
|
||||
{ // construct with named file and specified mode
|
||||
open(_Filename, _Mode , _Prot); /* Flawfinder: ignore */
|
||||
}
|
||||
|
||||
llofstream::~llofstream()
|
||||
{
|
||||
{
|
||||
// destroy the object
|
||||
if (_ShouldClose)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include "llerror.h"
|
||||
#include "lltypeinfolookup.h"
|
||||
|
||||
namespace LLTypeTags
|
||||
{
|
||||
|
|
@ -467,7 +468,7 @@ namespace LLInitParam
|
|||
};
|
||||
|
||||
// parser base class with mechanisms for registering readers/writers/inspectors of different types
|
||||
class Parser
|
||||
class LL_COMMON_API Parser
|
||||
{
|
||||
LOG_CLASS(Parser);
|
||||
|
||||
|
|
@ -489,9 +490,9 @@ namespace LLInitParam
|
|||
typedef bool (*parser_write_func_t)(Parser& parser, const void*, name_stack_t&);
|
||||
typedef boost::function<void (name_stack_t&, S32, S32, const possible_values_t*)> parser_inspect_func_t;
|
||||
|
||||
typedef std::map<const std::type_info*, parser_read_func_t, CompareTypeID> parser_read_func_map_t;
|
||||
typedef std::map<const std::type_info*, parser_write_func_t, CompareTypeID> parser_write_func_map_t;
|
||||
typedef std::map<const std::type_info*, parser_inspect_func_t, CompareTypeID> parser_inspect_func_map_t;
|
||||
typedef LLTypeInfoLookup<parser_read_func_t> parser_read_func_map_t;
|
||||
typedef LLTypeInfoLookup<parser_write_func_t> parser_write_func_map_t;
|
||||
typedef LLTypeInfoLookup<parser_inspect_func_t> parser_inspect_func_map_t;
|
||||
|
||||
Parser(parser_read_func_map_t& read_map, parser_write_func_map_t& write_map, parser_inspect_func_map_t& inspect_map)
|
||||
: mParseSilently(false),
|
||||
|
|
@ -563,7 +564,7 @@ namespace LLInitParam
|
|||
class Param;
|
||||
|
||||
// various callbacks and constraints associated with an individual param
|
||||
struct ParamDescriptor
|
||||
struct LL_COMMON_API ParamDescriptor
|
||||
{
|
||||
struct UserData
|
||||
{
|
||||
|
|
@ -603,7 +604,7 @@ namespace LLInitParam
|
|||
typedef boost::shared_ptr<ParamDescriptor> ParamDescriptorPtr;
|
||||
|
||||
// each derived Block class keeps a static data structure maintaining offsets to various params
|
||||
class BlockDescriptor
|
||||
class LL_COMMON_API BlockDescriptor
|
||||
{
|
||||
public:
|
||||
BlockDescriptor();
|
||||
|
|
@ -632,38 +633,38 @@ namespace LLInitParam
|
|||
class BaseBlock* mCurrentBlockPtr; // pointer to block currently being constructed
|
||||
};
|
||||
|
||||
//TODO: implement in terms of owned_ptr
|
||||
template<typename T>
|
||||
//TODO: implement in terms of owned_ptr
|
||||
template<typename T>
|
||||
class LazyValue
|
||||
{
|
||||
{
|
||||
public:
|
||||
LazyValue()
|
||||
: mPtr(NULL)
|
||||
{}
|
||||
: mPtr(NULL)
|
||||
{}
|
||||
|
||||
~LazyValue()
|
||||
{
|
||||
delete mPtr;
|
||||
}
|
||||
{
|
||||
delete mPtr;
|
||||
}
|
||||
|
||||
LazyValue(const T& value)
|
||||
{
|
||||
{
|
||||
mPtr = new T(value);
|
||||
}
|
||||
|
||||
LazyValue(const LazyValue& other)
|
||||
: mPtr(NULL)
|
||||
{
|
||||
{
|
||||
*this = other;
|
||||
}
|
||||
}
|
||||
|
||||
LazyValue& operator = (const LazyValue& other)
|
||||
{
|
||||
if (!other.mPtr)
|
||||
{
|
||||
{
|
||||
delete mPtr;
|
||||
mPtr = NULL;
|
||||
}
|
||||
mPtr = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mPtr)
|
||||
|
|
@ -674,9 +675,9 @@ namespace LLInitParam
|
|||
{
|
||||
*mPtr = *(other.mPtr);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const LazyValue& other) const
|
||||
{
|
||||
|
|
@ -684,13 +685,13 @@ namespace LLInitParam
|
|||
return *mPtr == *other.mPtr;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return mPtr == NULL;
|
||||
}
|
||||
bool empty() const
|
||||
{
|
||||
return mPtr == NULL;
|
||||
}
|
||||
|
||||
void set(const T& other)
|
||||
{
|
||||
void set(const T& other)
|
||||
{
|
||||
if (!mPtr)
|
||||
{
|
||||
mPtr = new T(other);
|
||||
|
|
@ -701,40 +702,40 @@ namespace LLInitParam
|
|||
}
|
||||
}
|
||||
|
||||
const T& get() const
|
||||
{
|
||||
const T& get() const
|
||||
{
|
||||
return *ensureInstance();
|
||||
}
|
||||
}
|
||||
|
||||
T& get()
|
||||
{
|
||||
T& get()
|
||||
{
|
||||
return *ensureInstance();
|
||||
}
|
||||
|
||||
operator const T&() const
|
||||
{
|
||||
return get();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// lazily allocate an instance of T
|
||||
T* ensureInstance() const
|
||||
{
|
||||
if (mPtr == NULL)
|
||||
private:
|
||||
// lazily allocate an instance of T
|
||||
T* ensureInstance() const
|
||||
{
|
||||
mPtr = new T();
|
||||
}
|
||||
return mPtr;
|
||||
}
|
||||
if (mPtr == NULL)
|
||||
{
|
||||
mPtr = new T();
|
||||
}
|
||||
return mPtr;
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
|
||||
mutable T* mPtr;
|
||||
};
|
||||
mutable T* mPtr;
|
||||
};
|
||||
|
||||
// root class of all parameter blocks
|
||||
|
||||
class BaseBlock
|
||||
class LL_COMMON_API BaseBlock
|
||||
{
|
||||
public:
|
||||
// lift block tags into baseblock namespace so derived classes do not need to qualify them
|
||||
|
|
@ -880,7 +881,7 @@ namespace LLInitParam
|
|||
const std::string& getParamName(const BlockDescriptor& block_data, const Param* paramp) const;
|
||||
};
|
||||
|
||||
class Param
|
||||
class LL_COMMON_API Param
|
||||
{
|
||||
public:
|
||||
void setProvided(bool is_provided = true)
|
||||
|
|
@ -912,7 +913,7 @@ namespace LLInitParam
|
|||
}
|
||||
|
||||
U32 getEnclosingBlockOffset() const
|
||||
{
|
||||
{
|
||||
return ((U32)mEnclosingBlockOffsetHigh << 16) | (U32)mEnclosingBlockOffsetLow;
|
||||
}
|
||||
|
||||
|
|
@ -2061,7 +2062,7 @@ namespace LLInitParam
|
|||
// dummy writer interfaces
|
||||
template<typename T>
|
||||
Deprecated& operator =(const T& val)
|
||||
{
|
||||
{
|
||||
// do nothing
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -2184,7 +2185,7 @@ namespace LLInitParam
|
|||
resetToDefault();
|
||||
}
|
||||
return mValue.deserializeBlock(p, name_stack_range, new_name);
|
||||
}
|
||||
}
|
||||
|
||||
void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const self_t* diff_block = NULL) const
|
||||
{
|
||||
|
|
@ -2275,7 +2276,7 @@ namespace LLInitParam
|
|||
if (new_name)
|
||||
{
|
||||
mCurParam = getBlockDescriptor().mAllParams.begin();
|
||||
}
|
||||
}
|
||||
if (name_stack_range.first == name_stack_range.second
|
||||
&& mCurParam != getBlockDescriptor().mAllParams.end())
|
||||
{
|
||||
|
|
@ -2287,7 +2288,7 @@ namespace LLInitParam
|
|||
if (deserialize_func
|
||||
&& paramp
|
||||
&& deserialize_func(*paramp, p, name_stack_range, new_name))
|
||||
{
|
||||
{
|
||||
++mCurParam;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -2299,7 +2300,7 @@ namespace LLInitParam
|
|||
else
|
||||
{
|
||||
return mValue.deserializeBlock(p, name_stack_range, new_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const self_t* diff_block = NULL) const
|
||||
|
|
@ -2352,7 +2353,7 @@ namespace LLInitParam
|
|||
: T(),
|
||||
mValidated(false)
|
||||
{}
|
||||
|
||||
|
||||
ParamValue(const default_value_t& value)
|
||||
: T(value.getValue()),
|
||||
mValidated(false)
|
||||
|
|
@ -2424,7 +2425,7 @@ namespace LLInitParam
|
|||
{
|
||||
return source.mValue.empty() || mValue.get().mergeBlock(block_data, source.getValue(), overwrite);
|
||||
}
|
||||
|
||||
|
||||
bool validateBlock(bool emit_errors = true) const
|
||||
{
|
||||
return mValue.empty() || mValue.get().validateBlock(emit_errors);
|
||||
|
|
@ -2509,8 +2510,8 @@ namespace LLInitParam
|
|||
LLSD& getValue() { return mValue; }
|
||||
|
||||
// block param interface
|
||||
bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name);
|
||||
void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const;
|
||||
LL_COMMON_API bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name);
|
||||
LL_COMMON_API void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const;
|
||||
bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const
|
||||
{
|
||||
//TODO: implement LLSD params as schema type Any
|
||||
|
|
@ -2539,10 +2540,10 @@ namespace LLInitParam
|
|||
} EValueAge;
|
||||
|
||||
typedef ParamValue<T> derived_t;
|
||||
typedef CustomParamValue<T> self_t;
|
||||
typedef Block<derived_t> block_t;
|
||||
typedef CustomParamValue<T> self_t;
|
||||
typedef Block<derived_t> block_t;
|
||||
typedef T default_value_t;
|
||||
typedef T value_t;
|
||||
typedef T value_t;
|
||||
typedef void baseblock_base_class_t;
|
||||
|
||||
|
||||
|
|
@ -167,8 +167,9 @@ public:
|
|||
|
||||
static T* getInstance(const KEY& k)
|
||||
{
|
||||
typename InstanceMap::const_iterator found = getMap_().find(k);
|
||||
return (found == getMap_().end()) ? NULL : found->second;
|
||||
const InstanceMap& map(getMap_());
|
||||
typename InstanceMap::const_iterator found = map.find(k);
|
||||
return (found == map.end()) ? NULL : found->second;
|
||||
}
|
||||
|
||||
static instance_iter beginInstances()
|
||||
|
|
@ -242,8 +243,20 @@ class LLInstanceTracker<T, T*> : public LLInstanceTrackerBase
|
|||
|
||||
public:
|
||||
|
||||
/// for completeness of analogy with the generic implementation
|
||||
static T* getInstance(T* k) { return k; }
|
||||
/**
|
||||
* Does a particular instance still exist? Of course, if you already have
|
||||
* a T* in hand, you need not call getInstance() to @em locate the
|
||||
* instance -- unlike the case where getInstance() accepts some kind of
|
||||
* key. Nonetheless this method is still useful to @em validate a
|
||||
* particular T*, since each instance's destructor removes itself from the
|
||||
* underlying set.
|
||||
*/
|
||||
static T* getInstance(T* k)
|
||||
{
|
||||
const InstanceSet& set(getSet_());
|
||||
typename InstanceSet::const_iterator found = set.find(k);
|
||||
return (found == set.end())? NULL : *found;
|
||||
}
|
||||
static S32 instanceCount() { return getSet_().size(); }
|
||||
|
||||
class instance_iter : public boost::iterator_facade<instance_iter, T, boost::forward_traversal_tag>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,459 @@
|
|||
/**
|
||||
* @file llleap.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-02-20
|
||||
* @brief Implementation for llleap.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llleap.h"
|
||||
// STL headers
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/tokenizer.hpp>
|
||||
// other Linden headers
|
||||
#include "llerror.h"
|
||||
#include "llstring.h"
|
||||
#include "llprocess.h"
|
||||
#include "llevents.h"
|
||||
#include "stringize.h"
|
||||
#include "llsdutil.h"
|
||||
#include "llsdserialize.h"
|
||||
#include "llerrorcontrol.h"
|
||||
#include "lltimer.h"
|
||||
#include "lluuid.h"
|
||||
#include "llleaplistener.h"
|
||||
|
||||
#if LL_MSVC
|
||||
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
|
||||
#endif
|
||||
|
||||
LLLeap::LLLeap() {}
|
||||
LLLeap::~LLLeap() {}
|
||||
|
||||
class LLLeapImpl: public LLLeap
|
||||
{
|
||||
LOG_CLASS(LLLeap);
|
||||
public:
|
||||
// Called only by LLLeap::create()
|
||||
LLLeapImpl(const std::string& desc, const std::vector<std::string>& plugin):
|
||||
// We might reassign mDesc in the constructor body if it's empty here.
|
||||
mDesc(desc),
|
||||
// We expect multiple LLLeapImpl instances. Definitely tweak
|
||||
// mDonePump's name for uniqueness.
|
||||
mDonePump("LLLeap", true),
|
||||
// Troubling thought: what if one plugin intentionally messes with
|
||||
// another plugin? LLEventPump names are in a single global namespace.
|
||||
// Try to make that more difficult by generating a UUID for the reply-
|
||||
// pump name -- so it should NOT need tweaking for uniqueness.
|
||||
mReplyPump(LLUUID::generateNewID().asString()),
|
||||
mExpect(0),
|
||||
mPrevFatalFunction(LLError::getFatalFunction()),
|
||||
// Instantiate a distinct LLLeapListener for this plugin. (Every
|
||||
// plugin will want its own collection of managed listeners, etc.)
|
||||
// Pass it a callback to our connect() method, so it can send events
|
||||
// from a particular LLEventPump to the plugin without having to know
|
||||
// this class or method name.
|
||||
mListener(new LLLeapListener(boost::bind(&LLLeapImpl::connect, this, _1, _2)))
|
||||
{
|
||||
// Rule out empty vector
|
||||
if (plugin.empty())
|
||||
{
|
||||
throw Error("no plugin command");
|
||||
}
|
||||
|
||||
// Don't leave desc empty either, but in this case, if we weren't
|
||||
// given one, we'll fake one.
|
||||
if (desc.empty())
|
||||
{
|
||||
mDesc = LLProcess::basename(plugin[0]);
|
||||
// how about a toLower() variant that returns the transformed string?!
|
||||
std::string desclower(mDesc);
|
||||
LLStringUtil::toLower(desclower);
|
||||
// If we're running a Python script, use the script name for the
|
||||
// desc instead of just 'python'. Arguably we should check for
|
||||
// more different interpreters as well, but there's a reason to
|
||||
// notice Python specially: we provide Python LLSD serialization
|
||||
// support, so there's a pretty good reason to implement plugins
|
||||
// in that language.
|
||||
if (plugin.size() >= 2 && (desclower == "python" || desclower == "python.exe"))
|
||||
{
|
||||
mDesc = LLProcess::basename(plugin[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for child "termination" right away to catch launch errors.
|
||||
mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::bad_launch, this, _1));
|
||||
|
||||
// Okay, launch child.
|
||||
LLProcess::Params params;
|
||||
params.desc = mDesc;
|
||||
std::vector<std::string>::const_iterator pi(plugin.begin()), pend(plugin.end());
|
||||
params.executable = *pi++;
|
||||
for ( ; pi != pend; ++pi)
|
||||
{
|
||||
params.args.add(*pi);
|
||||
}
|
||||
params.files.add(LLProcess::FileParam("pipe")); // stdin
|
||||
params.files.add(LLProcess::FileParam("pipe")); // stdout
|
||||
params.files.add(LLProcess::FileParam("pipe")); // stderr
|
||||
params.postend = mDonePump.getName();
|
||||
mChild = LLProcess::create(params);
|
||||
// If that didn't work, no point in keeping this LLLeap object.
|
||||
if (! mChild)
|
||||
{
|
||||
throw Error(STRINGIZE("failed to run " << mDesc));
|
||||
}
|
||||
|
||||
// Okay, launch apparently worked. Change our mDonePump listener.
|
||||
mDonePump.stopListening("LLLeap");
|
||||
mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::done, this, _1));
|
||||
|
||||
// Child might pump large volumes of data through either stdout or
|
||||
// stderr. Don't bother copying all that data into notification event.
|
||||
LLProcess::ReadPipe
|
||||
&childout(mChild->getReadPipe(LLProcess::STDOUT)),
|
||||
&childerr(mChild->getReadPipe(LLProcess::STDERR));
|
||||
childout.setLimit(20);
|
||||
childerr.setLimit(20);
|
||||
|
||||
// Serialize any event received on mReplyPump to our child's stdin.
|
||||
mStdinConnection = connect(mReplyPump, "LLLeap");
|
||||
|
||||
// Listening on stdout is stateful. In general, we're either waiting
|
||||
// for the length prefix or waiting for the specified length of data.
|
||||
// We address that with two different listener methods -- one of which
|
||||
// is blocked at any given time.
|
||||
mStdoutConnection = childout.getPump()
|
||||
.listen("prefix", boost::bind(&LLLeapImpl::rstdout, this, _1));
|
||||
mStdoutDataConnection = childout.getPump()
|
||||
.listen("data", boost::bind(&LLLeapImpl::rstdoutData, this, _1));
|
||||
mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
|
||||
|
||||
// Log anything sent up through stderr. When a typical program
|
||||
// encounters an error, it writes its error message to stderr and
|
||||
// terminates with nonzero exit code. In particular, the Python
|
||||
// interpreter behaves that way. More generally, though, a plugin
|
||||
// author can log whatever s/he wants to the viewer log using stderr.
|
||||
mStderrConnection = childerr.getPump()
|
||||
.listen("LLLeap", boost::bind(&LLLeapImpl::rstderr, this, _1));
|
||||
|
||||
// For our lifespan, intercept any LL_ERRS so we can notify plugin
|
||||
LLError::setFatalFunction(boost::bind(&LLLeapImpl::fatalFunction, this, _1));
|
||||
|
||||
// Send child a preliminary event reporting our own reply-pump name --
|
||||
// which would otherwise be pretty tricky to guess!
|
||||
wstdin(mReplyPump.getName(),
|
||||
LLSDMap
|
||||
("command", mListener->getName())
|
||||
// Include LLLeap features -- this may be important for child to
|
||||
// construct (or recognize) current protocol.
|
||||
("features", LLLeapListener::getFeatures()));
|
||||
}
|
||||
|
||||
// Normally we'd expect to arrive here only via done()
|
||||
virtual ~LLLeapImpl()
|
||||
{
|
||||
LL_DEBUGS("LLLeap") << "destroying LLLeap(\"" << mDesc << "\")" << LL_ENDL;
|
||||
// Restore original FatalFunction
|
||||
LLError::setFatalFunction(mPrevFatalFunction);
|
||||
}
|
||||
|
||||
// Listener for failed launch attempt
|
||||
bool bad_launch(const LLSD& data)
|
||||
{
|
||||
LL_WARNS("LLLeap") << data["string"].asString() << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Listener for child-process termination
|
||||
bool done(const LLSD& data)
|
||||
{
|
||||
// Log the termination
|
||||
LL_INFOS("LLLeap") << data["string"].asString() << LL_ENDL;
|
||||
|
||||
// Any leftover data at this moment are because protocol was not
|
||||
// satisfied. Possibly the child was interrupted in the middle of
|
||||
// sending a message, possibly the child didn't flush stdout before
|
||||
// terminating, possibly it's just garbage. Log its existence but
|
||||
// discard it.
|
||||
LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT));
|
||||
if (childout.size())
|
||||
{
|
||||
LLProcess::ReadPipe::size_type
|
||||
peeklen((std::min)(LLProcess::ReadPipe::size_type(50), childout.size()));
|
||||
LL_WARNS("LLLeap") << "Discarding final " << childout.size() << " bytes: "
|
||||
<< childout.peek(0, peeklen) << "..." << LL_ENDL;
|
||||
}
|
||||
|
||||
// Kill this instance. MUST BE LAST before return!
|
||||
delete this;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Listener for events on mReplyPump: send to child stdin
|
||||
bool wstdin(const std::string& pump, const LLSD& data)
|
||||
{
|
||||
LLSD packet(LLSDMap("pump", pump)("data", data));
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << LLSDNotationStreamer(packet);
|
||||
|
||||
/*==========================================================================*|
|
||||
// DEBUGGING ONLY: don't copy str() if we can avoid it.
|
||||
std::string strdata(buffer.str());
|
||||
if (std::size_t(buffer.tellp()) != strdata.length())
|
||||
{
|
||||
LL_ERRS("LLLeap") << "tellp() -> " << buffer.tellp() << " != "
|
||||
<< "str().length() -> " << strdata.length() << LL_ENDL;
|
||||
}
|
||||
// DEBUGGING ONLY: reading back is terribly inefficient.
|
||||
std::istringstream readback(strdata);
|
||||
LLSD echo;
|
||||
LLPointer<LLSDParser> parser(new LLSDNotationParser());
|
||||
S32 parse_status(parser->parse(readback, echo, strdata.length()));
|
||||
if (parse_status == LLSDParser::PARSE_FAILURE)
|
||||
{
|
||||
LL_ERRS("LLLeap") << "LLSDNotationParser() cannot parse output of "
|
||||
<< "LLSDNotationStreamer()" << LL_ENDL;
|
||||
}
|
||||
if (! llsd_equals(echo, packet))
|
||||
{
|
||||
LL_ERRS("LLLeap") << "LLSDNotationParser() produced different LLSD "
|
||||
<< "than passed to LLSDNotationStreamer()" << LL_ENDL;
|
||||
}
|
||||
|*==========================================================================*/
|
||||
|
||||
LL_DEBUGS("EventHost") << "Sending: " << buffer.tellp() << ':';
|
||||
std::string::size_type truncate(80);
|
||||
if (buffer.tellp() <= truncate)
|
||||
{
|
||||
LL_CONT << buffer.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_CONT << buffer.str().substr(0, truncate) << "...";
|
||||
}
|
||||
LL_CONT << LL_ENDL;
|
||||
|
||||
LLProcess::WritePipe& childin(mChild->getWritePipe(LLProcess::STDIN));
|
||||
childin.get_ostream() << buffer.tellp() << ':' << buffer.str() << std::flush;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initial state of stateful listening on child stdout: wait for a length
|
||||
// prefix, followed by ':'.
|
||||
bool rstdout(const LLSD& data)
|
||||
{
|
||||
LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT));
|
||||
// It's possible we got notified of a couple digit characters without
|
||||
// seeing the ':' -- unlikely, but still. Until we see ':', keep
|
||||
// waiting.
|
||||
if (childout.contains(':'))
|
||||
{
|
||||
std::istream& childstream(childout.get_istream());
|
||||
// Saw ':', read length prefix and store in mExpect.
|
||||
size_t expect;
|
||||
childstream >> expect;
|
||||
int colon(childstream.get());
|
||||
if (colon != ':')
|
||||
{
|
||||
// Protocol failure. Clear out the rest of the pending data in
|
||||
// childout (well, up to a max length) to log what was wrong.
|
||||
LLProcess::ReadPipe::size_type
|
||||
readlen((std::min)(childout.size(), LLProcess::ReadPipe::size_type(80)));
|
||||
bad_protocol(STRINGIZE(expect << char(colon) << childout.read(readlen)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Saw length prefix, saw colon, life is good. Now wait for
|
||||
// that length of data to arrive.
|
||||
mExpect = expect;
|
||||
LL_DEBUGS("LLLeap") << "got length, waiting for "
|
||||
<< mExpect << " bytes of data" << LL_ENDL;
|
||||
// Block calls to this method; resetting mBlocker unblocks
|
||||
// calls to the other method.
|
||||
mBlocker.reset(new LLEventPump::Blocker(mStdoutConnection));
|
||||
// Go check if we've already received all the advertised data.
|
||||
if (childout.size())
|
||||
{
|
||||
LLSD updata(data);
|
||||
updata["len"] = LLSD::Integer(childout.size());
|
||||
rstdoutData(updata);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (childout.contains('\n'))
|
||||
{
|
||||
// Since this is the initial listening state, this is where we'd
|
||||
// arrive if the child isn't following protocol at all -- say
|
||||
// because the user specified 'ls' or some darn thing.
|
||||
bad_protocol(childout.getline());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// State in which we listen on stdout for the specified length of data to
|
||||
// arrive.
|
||||
bool rstdoutData(const LLSD& data)
|
||||
{
|
||||
LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT));
|
||||
// Until we've accumulated the promised length of data, keep waiting.
|
||||
if (childout.size() >= mExpect)
|
||||
{
|
||||
// Ready to rock and roll.
|
||||
LL_DEBUGS("LLLeap") << "needed " << mExpect << " bytes, got "
|
||||
<< childout.size() << ", parsing LLSD" << LL_ENDL;
|
||||
LLSD data;
|
||||
LLPointer<LLSDParser> parser(new LLSDNotationParser());
|
||||
S32 parse_status(parser->parse(childout.get_istream(), data, mExpect));
|
||||
if (parse_status == LLSDParser::PARSE_FAILURE)
|
||||
{
|
||||
bad_protocol("unparseable LLSD data");
|
||||
}
|
||||
else if (! (data.isMap() && data["pump"].isString() && data.has("data")))
|
||||
{
|
||||
// we got an LLSD object, but it lacks required keys
|
||||
bad_protocol("missing 'pump' or 'data'");
|
||||
}
|
||||
else
|
||||
{
|
||||
// The LLSD object we got from our stream contains the keys we
|
||||
// need.
|
||||
LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
|
||||
// Block calls to this method; resetting mBlocker unblocks calls
|
||||
// to the other method.
|
||||
mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
|
||||
// Go check for any more pending events in the buffer.
|
||||
if (childout.size())
|
||||
{
|
||||
LLSD updata(data);
|
||||
data["len"] = LLSD::Integer(childout.size());
|
||||
rstdout(updata);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void bad_protocol(const std::string& data)
|
||||
{
|
||||
LL_WARNS("LLLeap") << mDesc << ": invalid protocol: " << data << LL_ENDL;
|
||||
// No point in continuing to run this child.
|
||||
mChild->kill();
|
||||
}
|
||||
|
||||
// Listen on child stderr and log everything that arrives
|
||||
bool rstderr(const LLSD& data)
|
||||
{
|
||||
LLProcess::ReadPipe& childerr(mChild->getReadPipe(LLProcess::STDERR));
|
||||
// We might have gotten a notification involving only a partial line
|
||||
// -- or multiple lines. Read all complete lines; stop when there's
|
||||
// only a partial line left.
|
||||
while (childerr.contains('\n'))
|
||||
{
|
||||
// DO NOT make calls with side effects in a logging statement! If
|
||||
// that log level is suppressed, your side effects WON'T HAPPEN.
|
||||
std::string line(childerr.getline());
|
||||
// Log the received line. Prefix it with the desc so we know which
|
||||
// plugin it's from. This method name rstderr() is intentionally
|
||||
// chosen to further qualify the log output.
|
||||
LL_INFOS("LLLeap") << mDesc << ": " << line << LL_ENDL;
|
||||
}
|
||||
// What if child writes a final partial line to stderr?
|
||||
if (data["eof"].asBoolean() && childerr.size())
|
||||
{
|
||||
std::string rest(childerr.read(childerr.size()));
|
||||
// Read all remaining bytes and log.
|
||||
LL_INFOS("LLLeap") << mDesc << ": " << rest << LL_ENDL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void fatalFunction(const std::string& error)
|
||||
{
|
||||
// Notify plugin
|
||||
LLSD event;
|
||||
event["type"] = "error";
|
||||
event["error"] = error;
|
||||
mReplyPump.post(event);
|
||||
|
||||
// All the above really accomplished was to buffer the serialized
|
||||
// event in our WritePipe. Have to pump mainloop a couple times to
|
||||
// really write it out there... but time out in case we can't write.
|
||||
LLProcess::WritePipe& childin(mChild->getWritePipe(LLProcess::STDIN));
|
||||
LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop"));
|
||||
LLSD nop;
|
||||
F64 until(LLTimer::getElapsedSeconds() + 2);
|
||||
while (childin.size() && LLTimer::getElapsedSeconds() < until)
|
||||
{
|
||||
mainloop.post(nop);
|
||||
}
|
||||
|
||||
// forward the call to the previous FatalFunction
|
||||
mPrevFatalFunction(error);
|
||||
}
|
||||
|
||||
private:
|
||||
/// We always want to listen on mReplyPump with wstdin(); under some
|
||||
/// circumstances we'll also echo other LLEventPumps to the plugin.
|
||||
LLBoundListener connect(LLEventPump& pump, const std::string& listener)
|
||||
{
|
||||
// Serialize any event received on the specified LLEventPump to our
|
||||
// child's stdin, suitably enriched with the pump name on which it was
|
||||
// received.
|
||||
return pump.listen(listener,
|
||||
boost::bind(&LLLeapImpl::wstdin, this, pump.getName(), _1));
|
||||
}
|
||||
|
||||
std::string mDesc;
|
||||
LLEventStream mDonePump;
|
||||
LLEventStream mReplyPump;
|
||||
LLProcessPtr mChild;
|
||||
LLTempBoundListener
|
||||
mStdinConnection, mStdoutConnection, mStdoutDataConnection, mStderrConnection;
|
||||
boost::scoped_ptr<LLEventPump::Blocker> mBlocker;
|
||||
LLProcess::ReadPipe::size_type mExpect;
|
||||
LLError::FatalFunction mPrevFatalFunction;
|
||||
boost::scoped_ptr<LLLeapListener> mListener;
|
||||
};
|
||||
|
||||
// This must follow the declaration of LLLeapImpl, so it may as well be last.
|
||||
LLLeap* LLLeap::create(const std::string& desc, const std::vector<std::string>& plugin, bool exc)
|
||||
{
|
||||
// If caller is willing to permit exceptions, just instantiate.
|
||||
if (exc)
|
||||
return new LLLeapImpl(desc, plugin);
|
||||
|
||||
// Caller insists on suppressing LLLeap::Error. Very well, catch it.
|
||||
try
|
||||
{
|
||||
return new LLLeapImpl(desc, plugin);
|
||||
}
|
||||
catch (const LLLeap::Error&)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
LLLeap* LLLeap::create(const std::string& desc, const std::string& plugin, bool exc)
|
||||
{
|
||||
// Use LLStringUtil::getTokens() to parse the command line
|
||||
return create(desc,
|
||||
LLStringUtil::getTokens(plugin,
|
||||
" \t\r\n", // drop_delims
|
||||
"", // no keep_delims
|
||||
"\"'", // either kind of quotes
|
||||
"\\"), // backslash escape
|
||||
exc);
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* @file llleap.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-02-20
|
||||
* @brief Class that implements "LLSD Event API Plugin"
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLLEAP_H)
|
||||
#define LL_LLLEAP_H
|
||||
|
||||
#include "llinstancetracker.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
/**
|
||||
* LLSD Event API Plugin class. Because instances are managed by
|
||||
* LLInstanceTracker, you can instantiate LLLeap and forget the instance
|
||||
* unless you need it later. Each instance manages an LLProcess; when the
|
||||
* child process terminates, LLLeap deletes itself. We don't require a unique
|
||||
* LLInstanceTracker key.
|
||||
*
|
||||
* The fact that a given LLLeap instance vanishes when its child process
|
||||
* terminates makes it problematic to store an LLLeap* anywhere. Any stored
|
||||
* LLLeap* pointer should be validated before use by
|
||||
* LLLeap::getInstance(LLLeap*) (see LLInstanceTracker).
|
||||
*/
|
||||
class LL_COMMON_API LLLeap: public LLInstanceTracker<LLLeap>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Pass a brief string description, mostly for logging purposes. The desc
|
||||
* need not be unique, but obviously the clearer we can make it, the
|
||||
* easier these things will be to debug. The strings are the command line
|
||||
* used to launch the desired plugin process.
|
||||
*
|
||||
* Pass exc=false to suppress LLLeap::Error exception. Obviously in that
|
||||
* case the caller cannot discover the nature of the error, merely that an
|
||||
* error of some kind occurred (because create() returned NULL). Either
|
||||
* way, the error is logged.
|
||||
*/
|
||||
static LLLeap* create(const std::string& desc, const std::vector<std::string>& plugin,
|
||||
bool exc=true);
|
||||
|
||||
/**
|
||||
* Pass a brief string description, mostly for logging purposes. The desc
|
||||
* need not be unique, but obviously the clearer we can make it, the
|
||||
* easier these things will be to debug. Pass a command-line string
|
||||
* to launch the desired plugin process.
|
||||
*
|
||||
* Pass exc=false to suppress LLLeap::Error exception. Obviously in that
|
||||
* case the caller cannot discover the nature of the error, merely that an
|
||||
* error of some kind occurred (because create() returned NULL). Either
|
||||
* way, the error is logged.
|
||||
*/
|
||||
static LLLeap* create(const std::string& desc, const std::string& plugin,
|
||||
bool exc=true);
|
||||
|
||||
/**
|
||||
* Exception thrown for invalid create() arguments, e.g. no plugin
|
||||
* program. This is more resiliant than an LL_ERRS failure, because the
|
||||
* string(s) passed to create() might come from an external source. This
|
||||
* way the caller can catch LLLeap::Error and try to recover.
|
||||
*/
|
||||
struct Error: public std::runtime_error
|
||||
{
|
||||
Error(const std::string& what): std::runtime_error(what) {}
|
||||
};
|
||||
|
||||
virtual ~LLLeap();
|
||||
|
||||
protected:
|
||||
LLLeap();
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLLEAP_H) */
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
/**
|
||||
* @file llleaplistener.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-03-16
|
||||
* @brief Implementation for llleaplistener.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llleaplistener.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/foreach.hpp>
|
||||
// other Linden headers
|
||||
#include "lluuid.h"
|
||||
#include "llsdutil.h"
|
||||
#include "stringize.h"
|
||||
|
||||
/*****************************************************************************
|
||||
* LEAP FEATURE STRINGS
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* Implement "getFeatures" command. The LLSD map thus obtained is intended to
|
||||
* be machine-readable (read: easily-parsed, if parsing be necessary) and to
|
||||
* highlight the differences between this version of the LEAP protocol and
|
||||
* the baseline version. A client may thus determine whether or not the
|
||||
* running viewer supports some recent feature of interest.
|
||||
*
|
||||
* This method is defined at the top of this implementation file so it's easy
|
||||
* to find, easy to spot, easy to update as we enhance the LEAP protocol.
|
||||
*/
|
||||
/*static*/ LLSD LLLeapListener::getFeatures()
|
||||
{
|
||||
static LLSD features;
|
||||
if (features.isUndefined())
|
||||
{
|
||||
features = LLSD::emptyMap();
|
||||
|
||||
// This initial implementation IS the baseline LEAP protocol; thus the
|
||||
// set of differences is empty; thus features is initially empty.
|
||||
// features["featurename"] = "value";
|
||||
}
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
LLLeapListener::LLLeapListener(const ConnectFunc& connect):
|
||||
// Each LEAP plugin has an instance of this listener. Make the command
|
||||
// pump name difficult for other such plugins to guess.
|
||||
LLEventAPI(LLUUID::generateNewID().asString(),
|
||||
"Operations relating to the LLSD Event API Plugin (LEAP) protocol"),
|
||||
mConnect(connect)
|
||||
{
|
||||
LLSD need_name(LLSDMap("name", LLSD()));
|
||||
add("newpump",
|
||||
"Instantiate a new LLEventPump named like [\"name\"] and listen to it.\n"
|
||||
"If [\"type\"] == \"LLEventQueue\", make LLEventQueue, else LLEventStream.\n"
|
||||
"Events sent through new LLEventPump will be decorated with [\"pump\"]=name.\n"
|
||||
"Returns actual name in [\"name\"] (may be different if collision).",
|
||||
&LLLeapListener::newpump,
|
||||
need_name);
|
||||
add("killpump",
|
||||
"Delete LLEventPump [\"name\"] created by \"newpump\".\n"
|
||||
"Returns [\"status\"] boolean indicating whether such a pump existed.",
|
||||
&LLLeapListener::killpump,
|
||||
need_name);
|
||||
LLSD need_source_listener(LLSDMap("source", LLSD())("listener", LLSD()));
|
||||
add("listen",
|
||||
"Listen to an existing LLEventPump named [\"source\"], with listener name\n"
|
||||
"[\"listener\"].\n"
|
||||
"By default, send events on [\"source\"] to the plugin, decorated\n"
|
||||
"with [\"pump\"]=[\"source\"].\n"
|
||||
"If [\"dest\"] specified, send undecorated events on [\"source\"] to the\n"
|
||||
"LLEventPump named [\"dest\"].\n"
|
||||
"Returns [\"status\"] boolean indicating whether the connection was made.",
|
||||
&LLLeapListener::listen,
|
||||
need_source_listener);
|
||||
add("stoplistening",
|
||||
"Disconnect a connection previously established by \"listen\".\n"
|
||||
"Pass same [\"source\"] and [\"listener\"] arguments.\n"
|
||||
"Returns [\"status\"] boolean indicating whether such a listener existed.",
|
||||
&LLLeapListener::stoplistening,
|
||||
need_source_listener);
|
||||
add("ping",
|
||||
"No arguments, just a round-trip sanity check.",
|
||||
&LLLeapListener::ping);
|
||||
add("getAPIs",
|
||||
"Enumerate all LLEventAPI instances by name and description.",
|
||||
&LLLeapListener::getAPIs);
|
||||
add("getAPI",
|
||||
"Get name, description, dispatch key and operations for LLEventAPI [\"api\"].",
|
||||
&LLLeapListener::getAPI,
|
||||
LLSD().with("api", LLSD()));
|
||||
add("getFeatures",
|
||||
"Return an LLSD map of feature strings (deltas from baseline LEAP protocol)",
|
||||
static_cast<void (LLLeapListener::*)(const LLSD&) const>(&LLLeapListener::getFeatures));
|
||||
add("getFeature",
|
||||
"Return the feature value with key [\"feature\"]",
|
||||
&LLLeapListener::getFeature,
|
||||
LLSD().with("feature", LLSD()));
|
||||
}
|
||||
|
||||
LLLeapListener::~LLLeapListener()
|
||||
{
|
||||
// We'd have stored a map of LLTempBoundListener instances, save that the
|
||||
// operation of inserting into a std::map necessarily copies the
|
||||
// value_type, and Bad Things would happen if you copied an
|
||||
// LLTempBoundListener. (Destruction of the original would disconnect the
|
||||
// listener, invalidating every stored connection.)
|
||||
BOOST_FOREACH(ListenersMap::value_type& pair, mListeners)
|
||||
{
|
||||
pair.second.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::newpump(const LLSD& request)
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
std::string name = request["name"];
|
||||
LLSD const & type = request["type"];
|
||||
|
||||
LLEventPump * new_pump = NULL;
|
||||
if (type.asString() == "LLEventQueue")
|
||||
{
|
||||
new_pump = new LLEventQueue(name, true); // tweak name for uniqueness
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! (type.isUndefined() || type.asString() == "LLEventStream"))
|
||||
{
|
||||
reply.warn(STRINGIZE("unknown 'type' " << type << ", using LLEventStream"));
|
||||
}
|
||||
new_pump = new LLEventStream(name, true); // tweak name for uniqueness
|
||||
}
|
||||
|
||||
name = new_pump->getName();
|
||||
|
||||
mEventPumps.insert(name, new_pump);
|
||||
|
||||
// Now listen on this new pump with our plugin listener
|
||||
std::string myname("llleap");
|
||||
saveListener(name, myname, mConnect(*new_pump, myname));
|
||||
|
||||
reply["name"] = name;
|
||||
}
|
||||
|
||||
void LLLeapListener::killpump(const LLSD& request)
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
std::string name = request["name"];
|
||||
// success == (nonzero number of entries were erased)
|
||||
reply["status"] = bool(mEventPumps.erase(name));
|
||||
}
|
||||
|
||||
void LLLeapListener::listen(const LLSD& request)
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
std::string source_name = request["source"];
|
||||
std::string dest_name = request["dest"];
|
||||
std::string listener_name = request["listener"];
|
||||
|
||||
LLEventPump & source = LLEventPumps::instance().obtain(source_name);
|
||||
|
||||
reply["status"] = false;
|
||||
if (mListeners.find(ListenersMap::key_type(source_name, listener_name)) == mListeners.end())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (request["dest"].isDefined())
|
||||
{
|
||||
// If we're asked to connect the "source" pump to a
|
||||
// specific "dest" pump, find dest pump and connect it.
|
||||
LLEventPump & dest = LLEventPumps::instance().obtain(dest_name);
|
||||
saveListener(source_name, listener_name,
|
||||
source.listen(listener_name,
|
||||
boost::bind(&LLEventPump::post, &dest, _1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// "dest" unspecified means to direct events on "source"
|
||||
// to our plugin listener.
|
||||
saveListener(source_name, listener_name, mConnect(source, listener_name));
|
||||
}
|
||||
reply["status"] = true;
|
||||
}
|
||||
catch (const LLEventPump::DupListenerName &)
|
||||
{
|
||||
// pass - status already set to false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::stoplistening(const LLSD& request)
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
std::string source_name = request["source"];
|
||||
std::string listener_name = request["listener"];
|
||||
|
||||
ListenersMap::iterator finder =
|
||||
mListeners.find(ListenersMap::key_type(source_name, listener_name));
|
||||
|
||||
reply["status"] = false;
|
||||
if(finder != mListeners.end())
|
||||
{
|
||||
reply["status"] = true;
|
||||
finder->second.disconnect();
|
||||
mListeners.erase(finder);
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::ping(const LLSD& request) const
|
||||
{
|
||||
// do nothing, default reply suffices
|
||||
Response(LLSD(), request);
|
||||
}
|
||||
|
||||
void LLLeapListener::getAPIs(const LLSD& request) const
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
for (LLEventAPI::instance_iter eai(LLEventAPI::beginInstances()),
|
||||
eaend(LLEventAPI::endInstances());
|
||||
eai != eaend; ++eai)
|
||||
{
|
||||
LLSD info;
|
||||
info["desc"] = eai->getDesc();
|
||||
reply[eai->getName()] = info;
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::getAPI(const LLSD& request) const
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
LLEventAPI* found = LLEventAPI::getInstance(request["api"]);
|
||||
if (found)
|
||||
{
|
||||
reply["name"] = found->getName();
|
||||
reply["desc"] = found->getDesc();
|
||||
reply["key"] = found->getDispatchKey();
|
||||
LLSD ops;
|
||||
for (LLEventAPI::const_iterator oi(found->begin()), oend(found->end());
|
||||
oi != oend; ++oi)
|
||||
{
|
||||
ops.append(found->getMetadata(oi->first));
|
||||
}
|
||||
reply["ops"] = ops;
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::getFeatures(const LLSD& request) const
|
||||
{
|
||||
// Merely constructing and destroying a Response object suffices here.
|
||||
// Giving it a name would only produce fatal 'unreferenced variable'
|
||||
// warnings.
|
||||
Response(getFeatures(), request);
|
||||
}
|
||||
|
||||
void LLLeapListener::getFeature(const LLSD& request) const
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
LLSD::String feature_name(request["feature"]);
|
||||
LLSD features(getFeatures());
|
||||
if (features[feature_name].isDefined())
|
||||
{
|
||||
reply["feature"] = features[feature_name];
|
||||
}
|
||||
}
|
||||
|
||||
void LLLeapListener::saveListener(const std::string& pump_name,
|
||||
const std::string& listener_name,
|
||||
const LLBoundListener& listener)
|
||||
{
|
||||
mListeners.insert(ListenersMap::value_type(ListenersMap::key_type(pump_name, listener_name),
|
||||
listener));
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* @file llleaplistener.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-03-16
|
||||
* @brief LLEventAPI supporting LEAP plugins
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLLEAPLISTENER_H)
|
||||
#define LL_LLLEAPLISTENER_H
|
||||
|
||||
#include "lleventapi.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/ptr_container/ptr_map.hpp>
|
||||
|
||||
/// Listener class implementing LLLeap query/control operations.
|
||||
/// See https://jira.lindenlab.com/jira/browse/DEV-31978.
|
||||
class LLLeapListener: public LLEventAPI
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Decouple LLLeap by dependency injection. Certain LLLeapListener
|
||||
* operations must be able to cause LLLeap to listen on a specified
|
||||
* LLEventPump with the LLLeap listener that wraps incoming events in an
|
||||
* outer (pump=, data=) map and forwards them to the plugin. Very well,
|
||||
* define the signature for a function that will perform that, and make
|
||||
* our constructor accept such a function.
|
||||
*/
|
||||
typedef boost::function<LLBoundListener(LLEventPump&, const std::string& listener)>
|
||||
ConnectFunc;
|
||||
LLLeapListener(const ConnectFunc& connect);
|
||||
~LLLeapListener();
|
||||
|
||||
static LLSD getFeatures();
|
||||
|
||||
private:
|
||||
void newpump(const LLSD&);
|
||||
void killpump(const LLSD&);
|
||||
void listen(const LLSD&);
|
||||
void stoplistening(const LLSD&);
|
||||
void ping(const LLSD&) const;
|
||||
void getAPIs(const LLSD&) const;
|
||||
void getAPI(const LLSD&) const;
|
||||
void getFeatures(const LLSD&) const;
|
||||
void getFeature(const LLSD&) const;
|
||||
|
||||
void saveListener(const std::string& pump_name, const std::string& listener_name,
|
||||
const LLBoundListener& listener);
|
||||
|
||||
ConnectFunc mConnect;
|
||||
|
||||
// In theory, listen() could simply call the relevant LLEventPump's
|
||||
// listen() method, stoplistening() likewise. Lifespan issues make us
|
||||
// capture the LLBoundListener objects: when this object goes away, all
|
||||
// those listeners should be disconnected. But what if the client listens,
|
||||
// stops, listens again on the same LLEventPump with the same listener
|
||||
// name? Merely collecting LLBoundListeners wouldn't adequately track
|
||||
// that. So capture the latest LLBoundListener for this LLEventPump name
|
||||
// and listener name.
|
||||
typedef std::map<std::pair<std::string, std::string>, LLBoundListener> ListenersMap;
|
||||
ListenersMap mListeners;
|
||||
// Similar lifespan reasoning applies to LLEventPumps instantiated by
|
||||
// newpump() operations.
|
||||
typedef boost::ptr_map<std::string, LLEventPump> EventPumpsMap;
|
||||
EventPumpsMap mEventPumps;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLLEAPLISTENER_H) */
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,553 @@
|
|||
/**
|
||||
* @file llprocess.h
|
||||
* @brief Utility class for launching, terminating, and tracking child processes.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&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_LLPROCESS_H
|
||||
#define LL_LLPROCESS_H
|
||||
|
||||
#include "llinitparam.h"
|
||||
#include "llsdparam.h"
|
||||
#include "apr_thread_proc.h"
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/ptr_container/ptr_vector.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <iosfwd> // std::ostream
|
||||
#include <stdexcept>
|
||||
|
||||
#if LL_WINDOWS
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h> // HANDLE (eye roll)
|
||||
#elif LL_LINUX
|
||||
#if defined(Status)
|
||||
#undef Status
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class LLEventPump;
|
||||
|
||||
class LLProcess;
|
||||
/// LLProcess instances are created on the heap by static factory methods and
|
||||
/// managed by ref-counted pointers.
|
||||
typedef boost::shared_ptr<LLProcess> LLProcessPtr;
|
||||
|
||||
/**
|
||||
* LLProcess handles launching an external process with specified command line
|
||||
* arguments. It also keeps track of whether the process is still running, and
|
||||
* can kill it if required.
|
||||
*
|
||||
* In discussing LLProcess, we use the term "parent" to refer to this process
|
||||
* (the process invoking LLProcess), versus "child" to refer to the process
|
||||
* spawned by LLProcess.
|
||||
*
|
||||
* LLProcess relies on periodic post() calls on the "mainloop" LLEventPump: an
|
||||
* LLProcess object's Status won't update until the next "mainloop" tick. For
|
||||
* instance, the Second Life viewer's main loop already posts to an
|
||||
* LLEventPump by that name once per iteration. See
|
||||
* indra/llcommon/tests/llprocess_test.cpp for an example of waiting for
|
||||
* child-process termination in a standalone test context.
|
||||
*/
|
||||
class LL_COMMON_API LLProcess: public boost::noncopyable
|
||||
{
|
||||
LOG_CLASS(LLProcess);
|
||||
public:
|
||||
/**
|
||||
* Specify what to pass for each of child stdin, stdout, stderr.
|
||||
* @see LLProcess::Params::files.
|
||||
*/
|
||||
struct FileParam: public LLInitParam::Block<FileParam>
|
||||
{
|
||||
/**
|
||||
* type of file handle to pass to child process
|
||||
*
|
||||
* - "" (default): let the child inherit the same file handle used by
|
||||
* this process. For instance, if passed as stdout, child stdout
|
||||
* will be interleaved with stdout from this process. In this case,
|
||||
* @a name is moot and should be left "".
|
||||
*
|
||||
* - "file": open an OS filesystem file with the specified @a name.
|
||||
* <i>Not yet implemented.</i>
|
||||
*
|
||||
* - "pipe" or "tpipe" or "npipe": depends on @a name
|
||||
*
|
||||
* - @a name.empty(): construct an OS pipe used only for this slot
|
||||
* of the forthcoming child process.
|
||||
*
|
||||
* - ! @a name.empty(): in a global registry, find or create (using
|
||||
* the specified @a name) an OS pipe. The point of the (purely
|
||||
* internal) @a name is that passing the same @a name in more than
|
||||
* one slot for a given LLProcess -- or for slots in different
|
||||
* LLProcess instances -- means the same pipe. For example, you
|
||||
* might pass the same @a name value as both stdout and stderr to
|
||||
* make the child process produce both on the same actual pipe. Or
|
||||
* you might pass the same @a name as the stdout for one LLProcess
|
||||
* and the stdin for another to connect the two child processes.
|
||||
* Use LLProcess::getPipeName() to generate a unique name
|
||||
* guaranteed not to already exist in the registry. <i>Not yet
|
||||
* implemented.</i>
|
||||
*
|
||||
* The difference between "pipe", "tpipe" and "npipe" is as follows.
|
||||
*
|
||||
* - "pipe": direct LLProcess to monitor the parent end of the pipe,
|
||||
* pumping nonblocking I/O every frame. The expectation (at least
|
||||
* for stdout or stderr) is that the caller will listen for
|
||||
* incoming data and consume it as it arrives. It's important not
|
||||
* to neglect such a pipe, because it's buffered in memory. If you
|
||||
* suspect the child may produce a great volume of output between
|
||||
* frames, consider directing the child to write to a filesystem
|
||||
* file instead, then read the file later.
|
||||
*
|
||||
* - "tpipe": do not engage LLProcess machinery to monitor the
|
||||
* parent end of the pipe. A "tpipe" is used only to connect
|
||||
* different child processes. As such, it makes little sense to
|
||||
* pass an empty @a name. <i>Not yet implemented.</i>
|
||||
*
|
||||
* - "npipe": like "tpipe", but use an OS named pipe with a
|
||||
* generated name. Note that @a name is the @em internal name of
|
||||
* the pipe in our global registry -- it doesn't necessarily have
|
||||
* anything to do with the pipe's name in the OS filesystem. Use
|
||||
* LLProcess::getPipeName() to obtain the named pipe's OS
|
||||
* filesystem name, e.g. to pass it as the @a name to another
|
||||
* LLProcess instance using @a type "file". This supports usage
|
||||
* like bash's <(subcommand...) or >(subcommand...)
|
||||
* constructs. <i>Not yet implemented.</i>
|
||||
*
|
||||
* In all cases the open mode (read, write) is determined by the child
|
||||
* slot you're filling. Child stdin means select the "read" end of a
|
||||
* pipe, or open a filesystem file for reading; child stdout or stderr
|
||||
* means select the "write" end of a pipe, or open a filesystem file
|
||||
* for writing.
|
||||
*
|
||||
* Confusion such as passing the same pipe as the stdin of two
|
||||
* processes (rather than stdout for one and stdin for the other) is
|
||||
* explicitly permitted: it's up to the caller to construct meaningful
|
||||
* LLProcess pipe graphs.
|
||||
*/
|
||||
Optional<std::string> type;
|
||||
Optional<std::string> name;
|
||||
|
||||
FileParam(const std::string& tp="", const std::string& nm=""):
|
||||
type("type"),
|
||||
name("name")
|
||||
{
|
||||
// If caller wants to specify values, use explicit assignment to
|
||||
// set them rather than initialization.
|
||||
if (! tp.empty()) type = tp;
|
||||
if (! nm.empty()) name = nm;
|
||||
}
|
||||
};
|
||||
|
||||
/// Param block definition
|
||||
struct Params: public LLInitParam::Block<Params>
|
||||
{
|
||||
Params():
|
||||
executable("executable"),
|
||||
args("args"),
|
||||
cwd("cwd"),
|
||||
autokill("autokill", true),
|
||||
files("files"),
|
||||
postend("postend"),
|
||||
desc("desc")
|
||||
{}
|
||||
|
||||
/// pathname of executable
|
||||
Mandatory<std::string> executable;
|
||||
/**
|
||||
* zero or more additional command-line arguments. Arguments are
|
||||
* passed through as exactly as we can manage, whitespace and all.
|
||||
* @note On Windows we manage this by implicitly double-quoting each
|
||||
* argument while assembling the command line.
|
||||
*/
|
||||
Multiple<std::string> args;
|
||||
/// current working directory, if need it changed
|
||||
Optional<std::string> cwd;
|
||||
/// implicitly kill process on destruction of LLProcess object
|
||||
/// (default true)
|
||||
Optional<bool> autokill;
|
||||
/**
|
||||
* Up to three FileParam items: for child stdin, stdout, stderr.
|
||||
* Passing two FileParam entries means default treatment for stderr,
|
||||
* and so forth.
|
||||
*
|
||||
* @note LLInitParam::Block permits usage like this:
|
||||
* @code
|
||||
* LLProcess::Params params;
|
||||
* ...
|
||||
* params.files
|
||||
* .add(LLProcess::FileParam()) // stdin
|
||||
* .add(LLProcess::FileParam().type("pipe") // stdout
|
||||
* .add(LLProcess::FileParam().type("file").name("error.log"));
|
||||
* @endcode
|
||||
*
|
||||
* @note While it's theoretically plausible to pass additional open
|
||||
* file handles to a child specifically written to expect them, our
|
||||
* underlying implementation doesn't yet support that.
|
||||
*/
|
||||
Multiple<FileParam, AtMost<3> > files;
|
||||
/**
|
||||
* On child-process termination, if this LLProcess object still
|
||||
* exists, post LLSD event to LLEventPump with specified name (default
|
||||
* no event). Event contains at least:
|
||||
*
|
||||
* - "id" as obtained from getProcessID()
|
||||
* - "desc" short string description of child (executable + pid)
|
||||
* - "state" @c state enum value, from Status.mState
|
||||
* - "data" if "state" is EXITED, exit code; if KILLED, on Posix,
|
||||
* signal number
|
||||
* - "string" English text describing "state" and "data" (e.g. "exited
|
||||
* with code 0")
|
||||
*/
|
||||
Optional<std::string> postend;
|
||||
/**
|
||||
* Description of child process for logging purposes. It need not be
|
||||
* unique; the logged description string will contain the PID as well.
|
||||
* If this is omitted, a description will be derived from the
|
||||
* executable name.
|
||||
*/
|
||||
Optional<std::string> desc;
|
||||
};
|
||||
typedef LLSDParamAdapter<Params> LLSDOrParams;
|
||||
|
||||
/**
|
||||
* Factory accepting either plain LLSD::Map or Params block.
|
||||
* MAY RETURN DEFAULT-CONSTRUCTED LLProcessPtr if params invalid!
|
||||
*/
|
||||
static LLProcessPtr create(const LLSDOrParams& params);
|
||||
virtual ~LLProcess();
|
||||
|
||||
/// Is child process still running?
|
||||
bool isRunning() const;
|
||||
// static isRunning(LLProcessPtr), getStatus(LLProcessPtr),
|
||||
// getStatusString(LLProcessPtr), kill(LLProcessPtr) handle the case in
|
||||
// which the passed LLProcessPtr might be NULL (default-constructed).
|
||||
static bool isRunning(const LLProcessPtr&);
|
||||
|
||||
/**
|
||||
* State of child process
|
||||
*/
|
||||
enum state
|
||||
{
|
||||
UNSTARTED, ///< initial value, invisible to consumer
|
||||
RUNNING, ///< child process launched
|
||||
EXITED, ///< child process terminated voluntarily
|
||||
KILLED ///< child process terminated involuntarily
|
||||
};
|
||||
|
||||
/**
|
||||
* Status info
|
||||
*/
|
||||
struct Status
|
||||
{
|
||||
Status():
|
||||
mState(UNSTARTED),
|
||||
mData(0)
|
||||
{}
|
||||
|
||||
state mState; ///< @see state
|
||||
/**
|
||||
* - for mState == EXITED: mData is exit() code
|
||||
* - for mState == KILLED: mData is signal number (Posix)
|
||||
* - otherwise: mData is undefined
|
||||
*/
|
||||
int mData;
|
||||
};
|
||||
|
||||
/// Status query
|
||||
Status getStatus() const;
|
||||
static Status getStatus(const LLProcessPtr&);
|
||||
/// English Status string query, for logging etc.
|
||||
std::string getStatusString() const;
|
||||
static std::string getStatusString(const std::string& desc, const LLProcessPtr&);
|
||||
/// English Status string query for previously-captured Status
|
||||
std::string getStatusString(const Status& status) const;
|
||||
/// static English Status string query
|
||||
static std::string getStatusString(const std::string& desc, const Status& status);
|
||||
|
||||
// Attempt to kill the process -- returns true if the process is no longer running when it returns.
|
||||
// Note that even if this returns false, the process may exit some time after it's called.
|
||||
bool kill(const std::string& who="");
|
||||
static bool kill(const LLProcessPtr& p, const std::string& who="");
|
||||
|
||||
#if LL_WINDOWS
|
||||
typedef int id; ///< as returned by getProcessID()
|
||||
typedef HANDLE handle; ///< as returned by getProcessHandle()
|
||||
#else
|
||||
typedef pid_t id;
|
||||
typedef pid_t handle;
|
||||
#endif
|
||||
/**
|
||||
* Get an int-like id value. This is primarily intended for a human reader
|
||||
* to differentiate processes.
|
||||
*/
|
||||
id getProcessID() const;
|
||||
/**
|
||||
* Get a "handle" of a kind that you might pass to platform-specific API
|
||||
* functions to engage features not directly supported by LLProcess.
|
||||
*/
|
||||
handle getProcessHandle() const;
|
||||
|
||||
/**
|
||||
* Test if a process (@c handle obtained from getProcessHandle()) is still
|
||||
* running. Return same nonzero @c handle value if still running, else
|
||||
* zero, so you can test it like a bool. But if you want to update a
|
||||
* stored variable as a side effect, you can write code like this:
|
||||
* @code
|
||||
* hchild = LLProcess::isRunning(hchild);
|
||||
* @endcode
|
||||
* @note This method is intended as a unit-test hook, not as the first of
|
||||
* a whole set of operations supported on freestanding @c handle values.
|
||||
* New functionality should be added as nonstatic members operating on
|
||||
* the same data as getProcessHandle().
|
||||
*
|
||||
* In particular, if child termination is detected by this static isRunning()
|
||||
* rather than by nonstatic isRunning(), the LLProcess object won't be
|
||||
* aware of the child's changed status and may encounter OS errors trying
|
||||
* to obtain it. This static isRunning() is only intended for after the
|
||||
* launching LLProcess object has been destroyed.
|
||||
*/
|
||||
static handle isRunning(handle, const std::string& desc="");
|
||||
|
||||
/// Provide symbolic access to child's file slots
|
||||
enum FILESLOT { STDIN=0, STDOUT=1, STDERR=2, NSLOTS=3 };
|
||||
|
||||
/**
|
||||
* For a pipe constructed with @a type "npipe", obtain the generated OS
|
||||
* filesystem name for the specified pipe. Otherwise returns the empty
|
||||
* string. @see LLProcess::FileParam::type
|
||||
*/
|
||||
std::string getPipeName(FILESLOT) const;
|
||||
|
||||
/// base of ReadPipe, WritePipe
|
||||
class LL_COMMON_API BasePipe
|
||||
{
|
||||
public:
|
||||
virtual ~BasePipe() = 0;
|
||||
|
||||
typedef std::size_t size_type;
|
||||
static const size_type npos;
|
||||
|
||||
/**
|
||||
* Get accumulated buffer length.
|
||||
*
|
||||
* For WritePipe, is there still pending data to send to child?
|
||||
*
|
||||
* For ReadPipe, we often need to refrain from actually reading the
|
||||
* std::istream returned by get_istream() until we've accumulated
|
||||
* enough data to make it worthwhile. For instance, if we're expecting
|
||||
* a number from the child, but the child happens to flush "12" before
|
||||
* emitting "3\n", get_istream() >> myint could return 12 rather than
|
||||
* 123!
|
||||
*/
|
||||
virtual size_type size() const = 0;
|
||||
};
|
||||
|
||||
/// As returned by getWritePipe() or getOptWritePipe()
|
||||
class WritePipe: public BasePipe
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Get ostream& on which to write to child's stdin.
|
||||
*
|
||||
* @usage
|
||||
* @code
|
||||
* myProcess->getWritePipe().get_ostream() << "Hello, child!" << std::endl;
|
||||
* @endcode
|
||||
*/
|
||||
virtual std::ostream& get_ostream() = 0;
|
||||
};
|
||||
|
||||
/// As returned by getReadPipe() or getOptReadPipe()
|
||||
class ReadPipe: public BasePipe
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Get istream& on which to read from child's stdout or stderr.
|
||||
*
|
||||
* @usage
|
||||
* @code
|
||||
* std::string stuff;
|
||||
* myProcess->getReadPipe().get_istream() >> stuff;
|
||||
* @endcode
|
||||
*
|
||||
* You should be sure in advance that the ReadPipe in question can
|
||||
* fill the request. @see getPump()
|
||||
*/
|
||||
virtual std::istream& get_istream() = 0;
|
||||
|
||||
/**
|
||||
* Like std::getline(get_istream(), line), but trims off trailing '\r'
|
||||
* to make calling code less platform-sensitive.
|
||||
*/
|
||||
virtual std::string getline() = 0;
|
||||
|
||||
/**
|
||||
* Like get_istream().read(buffer, n), but returns std::string rather
|
||||
* than requiring caller to construct a buffer, etc.
|
||||
*/
|
||||
virtual std::string read(size_type len) = 0;
|
||||
|
||||
/**
|
||||
* Peek at accumulated buffer data without consuming it. Optional
|
||||
* parameters give you substr() functionality.
|
||||
*
|
||||
* @note You can discard buffer data using get_istream().ignore(n).
|
||||
*/
|
||||
virtual std::string peek(size_type offset=0, size_type len=npos) const = 0;
|
||||
|
||||
/**
|
||||
* Detect presence of a substring (or char) in accumulated buffer data
|
||||
* without retrieving it. Optional offset allows you to search from
|
||||
* specified position.
|
||||
*/
|
||||
template <typename SEEK>
|
||||
bool contains(SEEK seek, size_type offset=0) const
|
||||
{ return find(seek, offset) != npos; }
|
||||
|
||||
/**
|
||||
* Search for a substring in accumulated buffer data without
|
||||
* retrieving it. Returns size_type position at which found, or npos
|
||||
* meaning not found. Optional offset allows you to search from
|
||||
* specified position.
|
||||
*/
|
||||
virtual size_type find(const std::string& seek, size_type offset=0) const = 0;
|
||||
|
||||
/**
|
||||
* Search for a char in accumulated buffer data without retrieving it.
|
||||
* Returns size_type position at which found, or npos meaning not
|
||||
* found. Optional offset allows you to search from specified
|
||||
* position.
|
||||
*/
|
||||
virtual size_type find(char seek, size_type offset=0) const = 0;
|
||||
|
||||
/**
|
||||
* Get LLEventPump& on which to listen for incoming data. The posted
|
||||
* LLSD::Map event will contain:
|
||||
*
|
||||
* - "data" part of pending data; see setLimit()
|
||||
* - "len" entire length of pending data, regardless of setLimit()
|
||||
* - "slot" this ReadPipe's FILESLOT, e.g. LLProcess::STDOUT
|
||||
* - "name" e.g. "stdout"
|
||||
* - "desc" e.g. "SLPlugin (pid) stdout"
|
||||
* - "eof" @c true means there no more data will arrive on this pipe,
|
||||
* therefore no more events on this pump
|
||||
*
|
||||
* If the child sends "abc", and this ReadPipe posts "data"="abc", but
|
||||
* you don't consume it by reading the std::istream returned by
|
||||
* get_istream(), and the child next sends "def", ReadPipe will post
|
||||
* "data"="abcdef".
|
||||
*/
|
||||
virtual LLEventPump& getPump() = 0;
|
||||
|
||||
/**
|
||||
* Set maximum length of buffer data that will be posted in the LLSD
|
||||
* announcing arrival of new data from the child. If you call
|
||||
* setLimit(5), and the child sends "abcdef", the LLSD event will
|
||||
* contain "data"="abcde". However, you may still read the entire
|
||||
* "abcdef" from get_istream(): this limit affects only the size of
|
||||
* the data posted with the LLSD event. If you don't call this method,
|
||||
* @em no data will be posted: the default is 0 bytes.
|
||||
*/
|
||||
virtual void setLimit(size_type limit) = 0;
|
||||
|
||||
/**
|
||||
* Query the current setLimit() limit.
|
||||
*/
|
||||
virtual size_type getLimit() const = 0;
|
||||
};
|
||||
|
||||
/// Exception thrown by getWritePipe(), getReadPipe() if you didn't ask to
|
||||
/// create a pipe at the corresponding FILESLOT.
|
||||
struct NoPipe: public std::runtime_error
|
||||
{
|
||||
NoPipe(const std::string& what): std::runtime_error(what) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a reference to the (only) WritePipe for this LLProcess. @a slot, if
|
||||
* specified, must be STDIN. Throws NoPipe if you did not request a "pipe"
|
||||
* for child stdin. Use this method when you know how you created the
|
||||
* LLProcess in hand.
|
||||
*/
|
||||
WritePipe& getWritePipe(FILESLOT slot=STDIN);
|
||||
|
||||
/**
|
||||
* Get a boost::optional<WritePipe&> to the (only) WritePipe for this
|
||||
* LLProcess. @a slot, if specified, must be STDIN. The return value is
|
||||
* empty if you did not request a "pipe" for child stdin. Use this method
|
||||
* for inspecting an LLProcess you did not create.
|
||||
*/
|
||||
boost::optional<WritePipe&> getOptWritePipe(FILESLOT slot=STDIN);
|
||||
|
||||
/**
|
||||
* Get a reference to one of the ReadPipes for this LLProcess. @a slot, if
|
||||
* specified, must be STDOUT or STDERR. Throws NoPipe if you did not
|
||||
* request a "pipe" for child stdout or stderr. Use this method when you
|
||||
* know how you created the LLProcess in hand.
|
||||
*/
|
||||
ReadPipe& getReadPipe(FILESLOT slot);
|
||||
|
||||
/**
|
||||
* Get a boost::optional<ReadPipe&> to one of the ReadPipes for this
|
||||
* LLProcess. @a slot, if specified, must be STDOUT or STDERR. The return
|
||||
* value is empty if you did not request a "pipe" for child stdout or
|
||||
* stderr. Use this method for inspecting an LLProcess you did not create.
|
||||
*/
|
||||
boost::optional<ReadPipe&> getOptReadPipe(FILESLOT slot);
|
||||
|
||||
/// little utilities that really should already be somewhere else in the
|
||||
/// code base
|
||||
static std::string basename(const std::string& path);
|
||||
static std::string getline(std::istream&);
|
||||
|
||||
private:
|
||||
/// constructor is private: use create() instead
|
||||
LLProcess(const LLSDOrParams& params);
|
||||
void autokill();
|
||||
// Classic-C-style APR callback
|
||||
static void status_callback(int reason, void* data, int status);
|
||||
// Object-oriented callback
|
||||
void handle_status(int reason, int status);
|
||||
// implementation for get[Opt][Read|Write]Pipe()
|
||||
template <class PIPETYPE>
|
||||
PIPETYPE& getPipe(FILESLOT slot);
|
||||
template <class PIPETYPE>
|
||||
boost::optional<PIPETYPE&> getOptPipe(FILESLOT slot);
|
||||
template <class PIPETYPE>
|
||||
PIPETYPE* getPipePtr(std::string& error, FILESLOT slot);
|
||||
|
||||
std::string mDesc;
|
||||
std::string mPostend;
|
||||
apr_proc_t mProcess;
|
||||
bool mAutokill;
|
||||
Status mStatus;
|
||||
// explicitly want this ptr_vector to be able to store NULLs
|
||||
typedef boost::ptr_vector< boost::nullable<BasePipe> > PipeVector;
|
||||
PipeVector mPipes;
|
||||
};
|
||||
|
||||
/// for logging
|
||||
LL_COMMON_API std::ostream& operator<<(std::ostream&, const LLProcess::Params&);
|
||||
|
||||
#endif // LL_LLPROCESS_H
|
||||
|
|
@ -1,357 +0,0 @@
|
|||
/**
|
||||
* @file llprocesslauncher.cpp
|
||||
* @brief Utility class for launching, terminating, and tracking the state of processes.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llprocesslauncher.h"
|
||||
|
||||
#include <iostream>
|
||||
#if LL_DARWIN || LL_LINUX
|
||||
// not required or present on Win32
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
LLProcessLauncher::LLProcessLauncher()
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
mProcessHandle = 0;
|
||||
#else
|
||||
mProcessID = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
LLProcessLauncher::~LLProcessLauncher()
|
||||
{
|
||||
kill();
|
||||
}
|
||||
|
||||
void LLProcessLauncher::setExecutable(const std::string &executable)
|
||||
{
|
||||
mExecutable = executable;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::setWorkingDirectory(const std::string &dir)
|
||||
{
|
||||
mWorkingDir = dir;
|
||||
}
|
||||
|
||||
const std::string& LLProcessLauncher::getExecutable() const
|
||||
{
|
||||
return mExecutable;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::clearArguments()
|
||||
{
|
||||
mLaunchArguments.clear();
|
||||
}
|
||||
|
||||
void LLProcessLauncher::addArgument(const std::string &arg)
|
||||
{
|
||||
mLaunchArguments.push_back(arg);
|
||||
}
|
||||
|
||||
void LLProcessLauncher::addArgument(const char *arg)
|
||||
{
|
||||
mLaunchArguments.push_back(std::string(arg));
|
||||
}
|
||||
|
||||
#if LL_WINDOWS
|
||||
|
||||
int LLProcessLauncher::launch(void)
|
||||
{
|
||||
// If there was already a process associated with this object, kill it.
|
||||
kill();
|
||||
orphan();
|
||||
|
||||
int result = 0;
|
||||
|
||||
PROCESS_INFORMATION pinfo;
|
||||
STARTUPINFOA sinfo;
|
||||
memset(&sinfo, 0, sizeof(sinfo));
|
||||
|
||||
std::string args = mExecutable;
|
||||
for(int i = 0; i < (int)mLaunchArguments.size(); i++)
|
||||
{
|
||||
args += " ";
|
||||
args += mLaunchArguments[i];
|
||||
}
|
||||
|
||||
// So retarded. Windows requires that the second parameter to CreateProcessA be a writable (non-const) string...
|
||||
char *args2 = new char[args.size() + 1];
|
||||
strcpy(args2, args.c_str());
|
||||
|
||||
const char * working_directory = 0;
|
||||
if(!mWorkingDir.empty()) working_directory = mWorkingDir.c_str();
|
||||
if( ! CreateProcessA( NULL, args2, NULL, NULL, FALSE, 0, NULL, working_directory, &sinfo, &pinfo ) )
|
||||
{
|
||||
result = GetLastError();
|
||||
|
||||
LPTSTR error_str = 0;
|
||||
if(
|
||||
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
||||
NULL,
|
||||
result,
|
||||
0,
|
||||
(LPTSTR)&error_str,
|
||||
0,
|
||||
NULL)
|
||||
!= 0)
|
||||
{
|
||||
char message[256];
|
||||
wcstombs(message, error_str, 256);
|
||||
message[255] = 0;
|
||||
llwarns << "CreateProcessA failed: " << message << llendl;
|
||||
LocalFree(error_str);
|
||||
}
|
||||
|
||||
if(result == 0)
|
||||
{
|
||||
// Make absolutely certain we return a non-zero value on failure.
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
|
||||
// CloseHandle(pinfo.hProcess); // stops leaks - nothing else
|
||||
mProcessHandle = pinfo.hProcess;
|
||||
CloseHandle(pinfo.hThread); // stops leaks - nothing else
|
||||
}
|
||||
|
||||
delete[] args2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LLProcessLauncher::isRunning(void)
|
||||
{
|
||||
if(mProcessHandle != 0)
|
||||
{
|
||||
DWORD waitresult = WaitForSingleObject(mProcessHandle, 0);
|
||||
if(waitresult == WAIT_OBJECT_0)
|
||||
{
|
||||
// the process has completed.
|
||||
mProcessHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (mProcessHandle != 0);
|
||||
}
|
||||
bool LLProcessLauncher::kill(void)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if(mProcessHandle != 0)
|
||||
{
|
||||
TerminateProcess(mProcessHandle,0);
|
||||
|
||||
if(isRunning())
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::orphan(void)
|
||||
{
|
||||
// Forget about the process
|
||||
mProcessHandle = 0;
|
||||
}
|
||||
|
||||
// static
|
||||
void LLProcessLauncher::reap(void)
|
||||
{
|
||||
// No actions necessary on Windows.
|
||||
}
|
||||
|
||||
#else // Mac and linux
|
||||
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
static std::list<pid_t> sZombies;
|
||||
|
||||
// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise.
|
||||
static bool reap_pid(pid_t pid)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
pid_t wait_result = ::waitpid(pid, NULL, WNOHANG);
|
||||
if(wait_result == pid)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
else if(wait_result == -1)
|
||||
{
|
||||
if(errno == ECHILD)
|
||||
{
|
||||
// No such process -- this may mean we're ignoring SIGCHILD.
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int LLProcessLauncher::launch(void)
|
||||
{
|
||||
// If there was already a process associated with this object, kill it.
|
||||
kill();
|
||||
orphan();
|
||||
|
||||
int result = 0;
|
||||
int current_wd = -1;
|
||||
|
||||
// create an argv vector for the child process
|
||||
const char ** fake_argv = new const char *[mLaunchArguments.size() + 2]; // 1 for the executable path, 1 for the NULL terminator
|
||||
|
||||
int i = 0;
|
||||
|
||||
// add the executable path
|
||||
fake_argv[i++] = mExecutable.c_str();
|
||||
|
||||
// and any arguments
|
||||
for(int j=0; j < mLaunchArguments.size(); j++)
|
||||
fake_argv[i++] = mLaunchArguments[j].c_str();
|
||||
|
||||
// terminate with a null pointer
|
||||
fake_argv[i] = NULL;
|
||||
|
||||
if(!mWorkingDir.empty())
|
||||
{
|
||||
// save the current working directory
|
||||
current_wd = ::open(".", O_RDONLY);
|
||||
|
||||
// and change to the one the child will be executed in
|
||||
if (::chdir(mWorkingDir.c_str()))
|
||||
{
|
||||
// chdir failed
|
||||
}
|
||||
}
|
||||
|
||||
// flush all buffers before the child inherits them
|
||||
::fflush(NULL);
|
||||
|
||||
pid_t id = vfork();
|
||||
if(id == 0)
|
||||
{
|
||||
// child process
|
||||
|
||||
::execv(mExecutable.c_str(), (char * const *)fake_argv);
|
||||
|
||||
// If we reach this point, the exec failed.
|
||||
// Use _exit() instead of exit() per the vfork man page.
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
// parent process
|
||||
|
||||
if(current_wd >= 0)
|
||||
{
|
||||
// restore the previous working directory
|
||||
if (::fchdir(current_wd))
|
||||
{
|
||||
// chdir failed
|
||||
}
|
||||
::close(current_wd);
|
||||
}
|
||||
|
||||
delete[] fake_argv;
|
||||
|
||||
mProcessID = id;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LLProcessLauncher::isRunning(void)
|
||||
{
|
||||
if(mProcessID != 0)
|
||||
{
|
||||
// Check whether the process has exited, and reap it if it has.
|
||||
if(reap_pid(mProcessID))
|
||||
{
|
||||
// the process has exited.
|
||||
mProcessID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (mProcessID != 0);
|
||||
}
|
||||
|
||||
bool LLProcessLauncher::kill(void)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if(mProcessID != 0)
|
||||
{
|
||||
// Try to kill the process. We'll do approximately the same thing whether the kill returns an error or not, so we ignore the result.
|
||||
(void)::kill(mProcessID, SIGTERM);
|
||||
|
||||
// This will have the side-effect of reaping the zombie if the process has exited.
|
||||
if(isRunning())
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLProcessLauncher::orphan(void)
|
||||
{
|
||||
// Disassociate the process from this object
|
||||
if(mProcessID != 0)
|
||||
{
|
||||
// We may still need to reap the process's zombie eventually
|
||||
sZombies.push_back(mProcessID);
|
||||
|
||||
mProcessID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void LLProcessLauncher::reap(void)
|
||||
{
|
||||
// Attempt to real all saved process ID's.
|
||||
|
||||
std::list<pid_t>::iterator iter = sZombies.begin();
|
||||
while(iter != sZombies.end())
|
||||
{
|
||||
if(reap_pid(*iter))
|
||||
{
|
||||
iter = sZombies.erase(iter);
|
||||
}
|
||||
else
|
||||
{
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
/**
|
||||
* @file llprocesslauncher.h
|
||||
* @brief Utility class for launching, terminating, and tracking the state of processes.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&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_LLPROCESSLAUNCHER_H
|
||||
#define LL_LLPROCESSLAUNCHER_H
|
||||
|
||||
#if LL_WINDOWS
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
LLProcessLauncher handles launching external processes with specified command line arguments.
|
||||
It also keeps track of whether the process is still running, and can kill it if required.
|
||||
*/
|
||||
|
||||
class LL_COMMON_API LLProcessLauncher
|
||||
{
|
||||
LOG_CLASS(LLProcessLauncher);
|
||||
public:
|
||||
LLProcessLauncher();
|
||||
virtual ~LLProcessLauncher();
|
||||
|
||||
void setExecutable(const std::string &executable);
|
||||
void setWorkingDirectory(const std::string &dir);
|
||||
|
||||
const std::string& getExecutable() const;
|
||||
|
||||
void clearArguments();
|
||||
void addArgument(const std::string &arg);
|
||||
void addArgument(const char *arg);
|
||||
|
||||
int launch(void);
|
||||
bool isRunning(void);
|
||||
|
||||
// Attempt to kill the process -- returns true if the process is no longer running when it returns.
|
||||
// Note that even if this returns false, the process may exit some time after it's called.
|
||||
bool kill(void);
|
||||
|
||||
// Use this if you want the external process to continue execution after the LLProcessLauncher instance controlling it is deleted.
|
||||
// Normally, the destructor will attempt to kill the process and wait for termination.
|
||||
// This should only be used if the viewer is about to exit -- otherwise, the child process will become a zombie after it exits.
|
||||
void orphan(void);
|
||||
|
||||
// This needs to be called periodically on Mac/Linux to clean up zombie processes.
|
||||
static void reap(void);
|
||||
|
||||
// Accessors for platform-specific process ID
|
||||
#if LL_WINDOWS
|
||||
HANDLE getProcessHandle() { return mProcessHandle; };
|
||||
#else
|
||||
pid_t getProcessID() { return mProcessID; };
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::string mExecutable;
|
||||
std::string mWorkingDir;
|
||||
std::vector<std::string> mLaunchArguments;
|
||||
|
||||
#if LL_WINDOWS
|
||||
HANDLE mProcessHandle;
|
||||
#else
|
||||
pid_t mProcessID;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // LL_LLPROCESSLAUNCHER_H
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include <boost/type_traits.hpp>
|
||||
#include "llsingleton.h"
|
||||
#include "lltypeinfolookup.h"
|
||||
|
||||
template <typename T>
|
||||
class LLRegistryDefaultComparator
|
||||
|
|
@ -38,6 +39,24 @@ class LLRegistryDefaultComparator
|
|||
bool operator()(const T& lhs, const T& rhs) { return lhs < rhs; }
|
||||
};
|
||||
|
||||
template <typename KEY, typename VALUE>
|
||||
struct LLRegistryMapSelector
|
||||
{
|
||||
typedef std::map<KEY, VALUE> type;
|
||||
};
|
||||
|
||||
template <typename VALUE>
|
||||
struct LLRegistryMapSelector<std::type_info*, VALUE>
|
||||
{
|
||||
typedef LLTypeInfoLookup<VALUE> type;
|
||||
};
|
||||
|
||||
template <typename VALUE>
|
||||
struct LLRegistryMapSelector<const std::type_info*, VALUE>
|
||||
{
|
||||
typedef LLTypeInfoLookup<VALUE> type;
|
||||
};
|
||||
|
||||
template <typename KEY, typename VALUE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> >
|
||||
class LLRegistry
|
||||
{
|
||||
|
|
@ -53,7 +72,7 @@ public:
|
|||
{
|
||||
friend class LLRegistry<KEY, VALUE, COMPARATOR>;
|
||||
public:
|
||||
typedef typename std::map<KEY, VALUE> registry_map_t;
|
||||
typedef typename LLRegistryMapSelector<KEY, VALUE>::type registry_map_t;
|
||||
|
||||
bool add(ref_const_key_t key, ref_const_value_t value)
|
||||
{
|
||||
|
|
@ -269,6 +269,7 @@ namespace
|
|||
virtual LLSD::UUID asUUID() const { return LLUUID(mValue); }
|
||||
virtual LLSD::Date asDate() const { return LLDate(mValue); }
|
||||
virtual LLSD::URI asURI() const { return LLURI(mValue); }
|
||||
virtual int size() const { return mValue.size(); }
|
||||
};
|
||||
|
||||
LLSD::Integer ImplString::asInteger() const
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
#include "llinitparam.h"
|
||||
#include "boost/function.hpp"
|
||||
|
||||
struct LLParamSDParserUtilities
|
||||
struct LL_COMMON_API LLParamSDParserUtilities
|
||||
{
|
||||
static LLSD& getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range);
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ struct LLParamSDParserUtilities
|
|||
static void readSDValues(read_sd_cb_t cb, const LLSD& sd);
|
||||
};
|
||||
|
||||
class LLParamSDParser
|
||||
class LL_COMMON_API LLParamSDParser
|
||||
: public LLInitParam::Parser
|
||||
{
|
||||
LOG_CLASS(LLParamSDParser);
|
||||
|
|
@ -92,7 +92,7 @@ private:
|
|||
};
|
||||
|
||||
|
||||
extern LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR;
|
||||
extern LL_COMMON_API LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR;
|
||||
template<typename T>
|
||||
class LLSDParamAdapter : public T
|
||||
{
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* @file llsortedvector.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-04-08
|
||||
* @brief LLSortedVector class wraps a vector that we maintain in sorted
|
||||
* order so we can perform binary-search lookups.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLSORTEDVECTOR_H)
|
||||
#define LL_LLSORTEDVECTOR_H
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
/**
|
||||
* LLSortedVector contains a std::vector<std::pair> that we keep sorted on the
|
||||
* first of the pair. This makes insertion somewhat more expensive than simple
|
||||
* std::vector::push_back(), but allows us to use binary search for lookups.
|
||||
* It's intended for small aggregates where lookup is far more performance-
|
||||
* critical than insertion; in such cases a binary search on a small, sorted
|
||||
* std::vector can be more performant than a std::map lookup.
|
||||
*/
|
||||
template <typename KEY, typename VALUE>
|
||||
class LLSortedVector
|
||||
{
|
||||
public:
|
||||
typedef LLSortedVector<KEY, VALUE> self;
|
||||
typedef KEY key_type;
|
||||
typedef VALUE mapped_type;
|
||||
typedef std::pair<key_type, mapped_type> value_type;
|
||||
typedef std::vector<value_type> PairVector;
|
||||
typedef typename PairVector::iterator iterator;
|
||||
typedef typename PairVector::const_iterator const_iterator;
|
||||
|
||||
/// Empty
|
||||
LLSortedVector() {}
|
||||
|
||||
/// Fixed initial size
|
||||
LLSortedVector(std::size_t size):
|
||||
mVector(size)
|
||||
{}
|
||||
|
||||
/// Bulk load
|
||||
template <typename ITER>
|
||||
LLSortedVector(ITER begin, ITER end):
|
||||
mVector(begin, end)
|
||||
{
|
||||
// Allow caller to dump in a bunch of (pairs convertible to)
|
||||
// value_type if desired, but make sure we sort afterwards.
|
||||
std::sort(mVector.begin(), mVector.end());
|
||||
}
|
||||
|
||||
/// insert(key, value)
|
||||
std::pair<iterator, bool> insert(const key_type& key, const mapped_type& value)
|
||||
{
|
||||
return insert(value_type(key, value));
|
||||
}
|
||||
|
||||
/// insert(value_type)
|
||||
std::pair<iterator, bool> insert(const value_type& pair)
|
||||
{
|
||||
typedef std::pair<iterator, bool> iterbool;
|
||||
iterator found = std::lower_bound(mVector.begin(), mVector.end(), pair,
|
||||
less<value_type>());
|
||||
// have to check for end() before it's even valid to dereference
|
||||
if (found == mVector.end())
|
||||
{
|
||||
std::size_t index(mVector.size());
|
||||
mVector.push_back(pair);
|
||||
// don't forget that push_back() invalidates 'found'
|
||||
return iterbool(mVector.begin() + index, true);
|
||||
}
|
||||
if (found->first == pair.first)
|
||||
{
|
||||
return iterbool(found, false);
|
||||
}
|
||||
// remember that insert() invalidates 'found' -- save index
|
||||
std::size_t index(found - mVector.begin());
|
||||
mVector.insert(found, pair);
|
||||
// okay, convert from index back to iterator
|
||||
return iterbool(mVector.begin() + index, true);
|
||||
}
|
||||
|
||||
iterator begin() { return mVector.begin(); }
|
||||
iterator end() { return mVector.end(); }
|
||||
const_iterator begin() const { return mVector.begin(); }
|
||||
const_iterator end() const { return mVector.end(); }
|
||||
|
||||
bool empty() const { return mVector.empty(); }
|
||||
std::size_t size() const { return mVector.size(); }
|
||||
|
||||
/// find
|
||||
iterator find(const key_type& key)
|
||||
{
|
||||
iterator found = std::lower_bound(mVector.begin(), mVector.end(),
|
||||
value_type(key, mapped_type()),
|
||||
less<value_type>());
|
||||
if (found == mVector.end() || found->first != key)
|
||||
return mVector.end();
|
||||
return found;
|
||||
}
|
||||
|
||||
const_iterator find(const key_type& key) const
|
||||
{
|
||||
return const_cast<self*>(this)->find(key);
|
||||
}
|
||||
|
||||
private:
|
||||
// Define our own 'less' comparator so we can specialize without messing
|
||||
// with std::less.
|
||||
template <typename T>
|
||||
struct less: public std::less<T> {};
|
||||
|
||||
// Specialize 'less' for an LLSortedVector::value_type involving
|
||||
// std::type_info*. This is one of LLSortedVector's foremost use cases. We
|
||||
// specialize 'less' rather than just defining a specific comparator
|
||||
// because LLSortedVector should be usable for other key_types as well.
|
||||
template <typename T>
|
||||
struct less< std::pair<std::type_info*, T> >:
|
||||
public std::binary_function<std::pair<std::type_info*, T>,
|
||||
std::pair<std::type_info*, T>,
|
||||
bool>
|
||||
{
|
||||
bool operator()(const std::pair<std::type_info*, T>& lhs,
|
||||
const std::pair<std::type_info*, T>& rhs) const
|
||||
{
|
||||
return lhs.first->before(*rhs.first);
|
||||
}
|
||||
};
|
||||
|
||||
// Same as above, but with const std::type_info*.
|
||||
template <typename T>
|
||||
struct less< std::pair<const std::type_info*, T> >:
|
||||
public std::binary_function<std::pair<const std::type_info*, T>,
|
||||
std::pair<const std::type_info*, T>,
|
||||
bool>
|
||||
{
|
||||
bool operator()(const std::pair<const std::type_info*, T>& lhs,
|
||||
const std::pair<const std::type_info*, T>& rhs) const
|
||||
{
|
||||
return lhs.first->before(*rhs.first);
|
||||
}
|
||||
};
|
||||
|
||||
PairVector mVector;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLSORTEDVECTOR_H) */
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* @file llstreamqueue.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-01-05
|
||||
* @brief Implementation for llstreamqueue.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llstreamqueue.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
|
||||
// As of this writing, llstreamqueue.h is entirely template-based, therefore
|
||||
// we don't strictly need a corresponding .cpp file. However, our CMake test
|
||||
// macro assumes one. Here it is.
|
||||
bool llstreamqueue_cpp_ignored = true;
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
/**
|
||||
* @file llstreamqueue.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-01-04
|
||||
* @brief Definition of LLStreamQueue
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLSTREAMQUEUE_H)
|
||||
#define LL_LLSTREAMQUEUE_H
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <iosfwd> // std::streamsize
|
||||
#include <boost/iostreams/categories.hpp>
|
||||
|
||||
/**
|
||||
* This class is a growable buffer between a producer and consumer. It serves
|
||||
* as a queue usable with Boost.Iostreams -- hence, a "stream queue."
|
||||
*
|
||||
* This is especially useful for buffering nonblocking I/O. For instance, we
|
||||
* want application logic to be able to serialize LLSD to a std::ostream. We
|
||||
* may write more data than the destination pipe can handle all at once, but
|
||||
* it's imperative NOT to block the application-level serialization call. So
|
||||
* we buffer it instead. Successive frames can try nonblocking writes to the
|
||||
* destination pipe until all buffered data has been sent.
|
||||
*
|
||||
* Similarly, we want application logic be able to deserialize LLSD from a
|
||||
* std::istream. Again, we must not block that deserialize call waiting for
|
||||
* more data to arrive from the input pipe! Instead we build up a buffer over
|
||||
* a number of frames, using successive nonblocking reads, until we have
|
||||
* "enough" data to be able to present it through a std::istream.
|
||||
*
|
||||
* @note The use cases for this class overlap somewhat with those for the
|
||||
* LLIOPipe/LLPumpIO hierarchies, and indeed we considered using those. This
|
||||
* class has two virtues over the older machinery:
|
||||
*
|
||||
* # It's vastly simpler -- way fewer concepts. It's not clear to me whether
|
||||
* there were ever LLIOPipe/etc. use cases that demanded all the fanciness
|
||||
* rolled in, or whether they were simply overdesigned. In any case, no
|
||||
* remaining Lindens will admit to familiarity with those classes -- and
|
||||
* they're sufficiently obtuse that it would take considerable learning
|
||||
* curve to figure out how to use them properly. The bottom line is that
|
||||
* current management is not keen on any more engineers climbing that curve.
|
||||
* # This class is designed around available components such as std::string,
|
||||
* std::list, Boost.Iostreams. There's less proprietary code.
|
||||
*/
|
||||
template <typename Ch>
|
||||
class LLGenericStreamQueue
|
||||
{
|
||||
public:
|
||||
LLGenericStreamQueue():
|
||||
mSize(0),
|
||||
mClosed(false)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Boost.Iostreams Source Device facade for use with other Boost.Iostreams
|
||||
* functionality. LLGenericStreamQueue doesn't quite fit any of the Boost
|
||||
* 1.48 Iostreams concepts; instead it behaves as both a Sink and a
|
||||
* Source. This is its Source facade.
|
||||
*/
|
||||
struct Source
|
||||
{
|
||||
typedef Ch char_type;
|
||||
typedef boost::iostreams::source_tag category;
|
||||
|
||||
/// Bind the underlying LLGenericStreamQueue
|
||||
Source(LLGenericStreamQueue& sq):
|
||||
mStreamQueue(sq)
|
||||
{}
|
||||
|
||||
// Read up to n characters from the underlying data source into the
|
||||
// buffer s, returning the number of characters read; return -1 to
|
||||
// indicate EOF
|
||||
std::streamsize read(Ch* s, std::streamsize n)
|
||||
{
|
||||
return mStreamQueue.read(s, n);
|
||||
}
|
||||
|
||||
LLGenericStreamQueue& mStreamQueue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Boost.Iostreams Sink Device facade for use with other Boost.Iostreams
|
||||
* functionality. LLGenericStreamQueue doesn't quite fit any of the Boost
|
||||
* 1.48 Iostreams concepts; instead it behaves as both a Sink and a
|
||||
* Source. This is its Sink facade.
|
||||
*/
|
||||
struct Sink
|
||||
{
|
||||
typedef Ch char_type;
|
||||
typedef boost::iostreams::sink_tag category;
|
||||
|
||||
/// Bind the underlying LLGenericStreamQueue
|
||||
Sink(LLGenericStreamQueue& sq):
|
||||
mStreamQueue(sq)
|
||||
{}
|
||||
|
||||
/// Write up to n characters from the buffer s to the output sequence,
|
||||
/// returning the number of characters written
|
||||
std::streamsize write(const Ch* s, std::streamsize n)
|
||||
{
|
||||
return mStreamQueue.write(s, n);
|
||||
}
|
||||
|
||||
/// Send EOF to consumer
|
||||
void close()
|
||||
{
|
||||
mStreamQueue.close();
|
||||
}
|
||||
|
||||
LLGenericStreamQueue& mStreamQueue;
|
||||
};
|
||||
|
||||
/// Present Boost.Iostreams Source facade
|
||||
Source asSource() { return Source(*this); }
|
||||
/// Present Boost.Iostreams Sink facade
|
||||
Sink asSink() { return Sink(*this); }
|
||||
|
||||
/// append data to buffer
|
||||
std::streamsize write(const Ch* s, std::streamsize n)
|
||||
{
|
||||
// Unclear how often we might be asked to write 0 bytes -- perhaps a
|
||||
// naive caller responding to an unready nonblocking read. But if we
|
||||
// do get such a call, don't add a completely empty BufferList entry.
|
||||
if (n == 0)
|
||||
return n;
|
||||
// We could implement this using a single std::string object, a la
|
||||
// ostringstream. But the trouble with appending to a string is that
|
||||
// you might have to recopy all previous contents to grow its size. If
|
||||
// we want this to scale to large data volumes, better to allocate
|
||||
// individual pieces.
|
||||
mBuffer.push_back(string(s, n));
|
||||
mSize += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform this LLGenericStreamQueue that no further data are forthcoming.
|
||||
* For our purposes, close() is strictly a producer-side operation;
|
||||
* there's little point in closing the consumer side.
|
||||
*/
|
||||
void close()
|
||||
{
|
||||
mClosed = true;
|
||||
}
|
||||
|
||||
/// consume data from buffer
|
||||
std::streamsize read(Ch* s, std::streamsize n)
|
||||
{
|
||||
// read() is actually a convenience method for peek() followed by
|
||||
// skip().
|
||||
std::streamsize got(peek(s, n));
|
||||
// We can only skip() as many characters as we can peek(); ignore
|
||||
// skip() return here.
|
||||
skip(n);
|
||||
return got;
|
||||
}
|
||||
|
||||
/// Retrieve data from buffer without consuming. Like read(), return -1 on
|
||||
/// EOF.
|
||||
std::streamsize peek(Ch* s, std::streamsize n) const;
|
||||
|
||||
/// Consume data from buffer without retrieving. Unlike read() and peek(),
|
||||
/// at EOF we simply skip 0 characters.
|
||||
std::streamsize skip(std::streamsize n);
|
||||
|
||||
/// How many characters do we currently have buffered?
|
||||
std::streamsize size() const
|
||||
{
|
||||
return mSize;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef std::basic_string<Ch> string;
|
||||
typedef std::list<string> BufferList;
|
||||
BufferList mBuffer;
|
||||
std::streamsize mSize;
|
||||
bool mClosed;
|
||||
};
|
||||
|
||||
template <typename Ch>
|
||||
std::streamsize LLGenericStreamQueue<Ch>::peek(Ch* s, std::streamsize n) const
|
||||
{
|
||||
// Here we may have to build up 'n' characters from an arbitrary
|
||||
// number of individual BufferList entries.
|
||||
typename BufferList::const_iterator bli(mBuffer.begin()), blend(mBuffer.end());
|
||||
// Indicate EOF if producer has closed the pipe AND we've exhausted
|
||||
// all previously-buffered data.
|
||||
if (mClosed && bli == blend)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
// Here either producer hasn't yet closed, or we haven't yet exhausted
|
||||
// remaining data.
|
||||
std::streamsize needed(n), got(0);
|
||||
// Loop until either we run out of BufferList entries or we've
|
||||
// completely satisfied the request.
|
||||
for ( ; bli != blend && needed; ++bli)
|
||||
{
|
||||
std::streamsize chunk(std::min(needed, std::streamsize(bli->length())));
|
||||
std::copy(bli->begin(), bli->begin() + chunk, s);
|
||||
needed -= chunk;
|
||||
s += chunk;
|
||||
got += chunk;
|
||||
}
|
||||
return got;
|
||||
}
|
||||
|
||||
template <typename Ch>
|
||||
std::streamsize LLGenericStreamQueue<Ch>::skip(std::streamsize n)
|
||||
{
|
||||
typename BufferList::iterator bli(mBuffer.begin()), blend(mBuffer.end());
|
||||
std::streamsize toskip(n), skipped(0);
|
||||
while (bli != blend && toskip >= bli->length())
|
||||
{
|
||||
std::streamsize chunk(bli->length());
|
||||
typename BufferList::iterator zap(bli++);
|
||||
mBuffer.erase(zap);
|
||||
mSize -= chunk;
|
||||
toskip -= chunk;
|
||||
skipped += chunk;
|
||||
}
|
||||
if (bli != blend && toskip)
|
||||
{
|
||||
bli->erase(bli->begin(), bli->begin() + toskip);
|
||||
mSize -= toskip;
|
||||
skipped += toskip;
|
||||
}
|
||||
return skipped;
|
||||
}
|
||||
|
||||
typedef LLGenericStreamQueue<char> LLStreamQueue;
|
||||
typedef LLGenericStreamQueue<wchar_t> LLWStreamQueue;
|
||||
|
||||
#endif /* ! defined(LL_LLSTREAMQUEUE_H) */
|
||||
|
|
@ -44,6 +44,15 @@ public:
|
|||
const LLStrider<Object>& operator = (Object *first) { mObjectp = first; return *this;}
|
||||
void setStride (S32 skipBytes) { mSkip = (skipBytes ? skipBytes : sizeof(Object));}
|
||||
|
||||
LLStrider<Object> operator+(const S32& index)
|
||||
{
|
||||
LLStrider<Object> ret;
|
||||
ret.mBytep = mBytep + mSkip*index;
|
||||
ret.mSkip = mSkip;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void skip(const U32 index) { mBytep += mSkip*index;}
|
||||
U32 getSkip() const { return mSkip; }
|
||||
Object* get() { return mObjectp; }
|
||||
|
|
@ -51,6 +60,7 @@ public:
|
|||
Object& operator *() { return *mObjectp; }
|
||||
Object* operator ++(int) { Object* old = mObjectp; mBytep += mSkip; return old; }
|
||||
Object* operator +=(int i) { mBytep += mSkip*i; return mObjectp; }
|
||||
|
||||
Object& operator[](U32 index) { return *(Object*)(mBytep + (mSkip * index)); }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -912,22 +912,24 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions);
|
|||
template<>
|
||||
void LLStringUtil::getTokens(const std::string& instr, std::vector<std::string >& tokens, const std::string& delims)
|
||||
{
|
||||
std::string currToken;
|
||||
std::string::size_type begIdx, endIdx;
|
||||
|
||||
begIdx = instr.find_first_not_of (delims);
|
||||
while (begIdx != std::string::npos)
|
||||
// Starting at offset 0, scan forward for the next non-delimiter. We're
|
||||
// done when the only characters left in 'instr' are delimiters.
|
||||
for (std::string::size_type begIdx, endIdx = 0;
|
||||
(begIdx = instr.find_first_not_of (delims, endIdx)) != std::string::npos; )
|
||||
{
|
||||
// Found a non-delimiter. After that, find the next delimiter.
|
||||
endIdx = instr.find_first_of (delims, begIdx);
|
||||
if (endIdx == std::string::npos)
|
||||
{
|
||||
// No more delimiters: this token extends to the end of the string.
|
||||
endIdx = instr.length();
|
||||
}
|
||||
|
||||
currToken = instr.substr(begIdx, endIdx - begIdx);
|
||||
// extract the token between begIdx and endIdx; substr() needs length
|
||||
std::string currToken(instr.substr(begIdx, endIdx - begIdx));
|
||||
LLStringUtil::trim (currToken);
|
||||
tokens.push_back(currToken);
|
||||
begIdx = instr.find_first_not_of (delims, endIdx);
|
||||
// next scan past delimiters starts at endIdx
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
#if LL_SOLARIS
|
||||
// stricmp and strnicmp do not exist on Solaris:
|
||||
|
|
@ -182,6 +183,9 @@ public:
|
|||
static bool isPunct(char a) { return ispunct((unsigned char)a) != 0; }
|
||||
static bool isPunct(llwchar a) { return iswpunct(a) != 0; }
|
||||
|
||||
static bool isAlpha(char a) { return isalpha((unsigned char)a) != 0; }
|
||||
static bool isAlpha(llwchar a) { return iswalpha(a) != 0; }
|
||||
|
||||
static bool isAlnum(char a) { return isalnum((unsigned char)a) != 0; }
|
||||
static bool isAlnum(llwchar a) { return iswalnum(a) != 0; }
|
||||
|
||||
|
|
@ -237,40 +241,77 @@ private:
|
|||
static std::string sLocale;
|
||||
|
||||
public:
|
||||
typedef typename std::basic_string<T>::size_type size_type;
|
||||
typedef std::basic_string<T> string_type;
|
||||
typedef typename string_type::size_type size_type;
|
||||
|
||||
public:
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Utility functions that operate on std::strings
|
||||
|
||||
static const std::basic_string<T> null;
|
||||
static const string_type null;
|
||||
|
||||
typedef std::map<LLFormatMapString, LLFormatMapString> format_map_t;
|
||||
LL_COMMON_API static void getTokens(const std::basic_string<T>& instr, std::vector<std::basic_string<T> >& tokens, const std::basic_string<T>& delims);
|
||||
LL_COMMON_API static void formatNumber(std::basic_string<T>& numStr, std::basic_string<T> decimals);
|
||||
LL_COMMON_API static bool formatDatetime(std::basic_string<T>& replacement, std::basic_string<T> token, std::basic_string<T> param, S32 secFromEpoch);
|
||||
LL_COMMON_API static S32 format(std::basic_string<T>& s, const format_map_t& substitutions);
|
||||
LL_COMMON_API static S32 format(std::basic_string<T>& s, const LLSD& substitutions);
|
||||
LL_COMMON_API static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const format_map_t& substitutions);
|
||||
LL_COMMON_API static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const LLSD& substitutions);
|
||||
/// considers any sequence of delims as a single field separator
|
||||
LL_COMMON_API static void getTokens(const string_type& instr,
|
||||
std::vector<string_type >& tokens,
|
||||
const string_type& delims);
|
||||
/// like simple scan overload, but returns scanned vector
|
||||
static std::vector<string_type> getTokens(const string_type& instr,
|
||||
const string_type& delims);
|
||||
/// add support for keep_delims and quotes (either could be empty string)
|
||||
static void getTokens(const string_type& instr,
|
||||
std::vector<string_type>& tokens,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes=string_type());
|
||||
/// like keep_delims-and-quotes overload, but returns scanned vector
|
||||
static std::vector<string_type> getTokens(const string_type& instr,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes=string_type());
|
||||
/// add support for escapes (could be empty string)
|
||||
static void getTokens(const string_type& instr,
|
||||
std::vector<string_type>& tokens,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes,
|
||||
const string_type& escapes);
|
||||
/// like escapes overload, but returns scanned vector
|
||||
static std::vector<string_type> getTokens(const string_type& instr,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes,
|
||||
const string_type& escapes);
|
||||
|
||||
LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals);
|
||||
LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch);
|
||||
LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions);
|
||||
LL_COMMON_API static S32 format(string_type& s, const LLSD& substitutions);
|
||||
LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const format_map_t& substitutions);
|
||||
LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const LLSD& substitutions);
|
||||
LL_COMMON_API static void setLocale (std::string inLocale);
|
||||
LL_COMMON_API static std::string getLocale (void);
|
||||
|
||||
static bool isValidIndex(const std::basic_string<T>& string, size_type i)
|
||||
static bool isValidIndex(const string_type& string, size_type i)
|
||||
{
|
||||
return !string.empty() && (0 <= i) && (i <= string.size());
|
||||
}
|
||||
|
||||
static void trimHead(std::basic_string<T>& string);
|
||||
static void trimTail(std::basic_string<T>& string);
|
||||
static void trim(std::basic_string<T>& string) { trimHead(string); trimTail(string); }
|
||||
static void truncate(std::basic_string<T>& string, size_type count);
|
||||
static bool contains(const string_type& string, T c, size_type i=0)
|
||||
{
|
||||
return string.find(c, i) != string_type::npos;
|
||||
}
|
||||
|
||||
static void toUpper(std::basic_string<T>& string);
|
||||
static void toLower(std::basic_string<T>& string);
|
||||
static void trimHead(string_type& string);
|
||||
static void trimTail(string_type& string);
|
||||
static void trim(string_type& string) { trimHead(string); trimTail(string); }
|
||||
static void truncate(string_type& string, size_type count);
|
||||
|
||||
static void toUpper(string_type& string);
|
||||
static void toLower(string_type& string);
|
||||
|
||||
// True if this is the head of s.
|
||||
static BOOL isHead( const std::basic_string<T>& string, const T* s );
|
||||
static BOOL isHead( const string_type& string, const T* s );
|
||||
|
||||
/**
|
||||
* @brief Returns true if string starts with substr
|
||||
|
|
@ -278,8 +319,8 @@ public:
|
|||
* If etither string or substr are empty, this method returns false.
|
||||
*/
|
||||
static bool startsWith(
|
||||
const std::basic_string<T>& string,
|
||||
const std::basic_string<T>& substr);
|
||||
const string_type& string,
|
||||
const string_type& substr);
|
||||
|
||||
/**
|
||||
* @brief Returns true if string ends in substr
|
||||
|
|
@ -287,19 +328,32 @@ public:
|
|||
* If etither string or substr are empty, this method returns false.
|
||||
*/
|
||||
static bool endsWith(
|
||||
const std::basic_string<T>& string,
|
||||
const std::basic_string<T>& substr);
|
||||
const string_type& string,
|
||||
const string_type& substr);
|
||||
|
||||
static void addCRLF(std::basic_string<T>& string);
|
||||
static void removeCRLF(std::basic_string<T>& string);
|
||||
static void addCRLF(string_type& string);
|
||||
static void removeCRLF(string_type& string);
|
||||
|
||||
static void replaceTabsWithSpaces( std::basic_string<T>& string, size_type spaces_per_tab );
|
||||
static void replaceNonstandardASCII( std::basic_string<T>& string, T replacement );
|
||||
static void replaceChar( std::basic_string<T>& string, T target, T replacement );
|
||||
static void replaceString( std::basic_string<T>& string, std::basic_string<T> target, std::basic_string<T> replacement );
|
||||
static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab );
|
||||
static void replaceNonstandardASCII( string_type& string, T replacement );
|
||||
static void replaceChar( string_type& string, T target, T replacement );
|
||||
static void replaceString( string_type& string, string_type target, string_type replacement );
|
||||
|
||||
static BOOL containsNonprintable(const std::basic_string<T>& string);
|
||||
static void stripNonprintable(std::basic_string<T>& string);
|
||||
static BOOL containsNonprintable(const string_type& string);
|
||||
static void stripNonprintable(string_type& string);
|
||||
|
||||
/**
|
||||
* Double-quote an argument string if needed, unless it's already
|
||||
* double-quoted. Decide whether it's needed based on the presence of any
|
||||
* character in @a triggers (default space or double-quote). If we quote
|
||||
* it, escape any embedded double-quote with the @a escape string (default
|
||||
* backslash).
|
||||
*
|
||||
* Passing triggers="" means always quote, unless it's already double-quoted.
|
||||
*/
|
||||
static string_type quote(const string_type& str,
|
||||
const string_type& triggers=" \"",
|
||||
const string_type& escape="\\");
|
||||
|
||||
/**
|
||||
* @brief Unsafe way to make ascii characters. You should probably
|
||||
|
|
@ -308,18 +362,18 @@ public:
|
|||
* The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII
|
||||
* should work.
|
||||
*/
|
||||
static void _makeASCII(std::basic_string<T>& string);
|
||||
static void _makeASCII(string_type& string);
|
||||
|
||||
// Conversion to other data types
|
||||
static BOOL convertToBOOL(const std::basic_string<T>& string, BOOL& value);
|
||||
static BOOL convertToU8(const std::basic_string<T>& string, U8& value);
|
||||
static BOOL convertToS8(const std::basic_string<T>& string, S8& value);
|
||||
static BOOL convertToS16(const std::basic_string<T>& string, S16& value);
|
||||
static BOOL convertToU16(const std::basic_string<T>& string, U16& value);
|
||||
static BOOL convertToU32(const std::basic_string<T>& string, U32& value);
|
||||
static BOOL convertToS32(const std::basic_string<T>& string, S32& value);
|
||||
static BOOL convertToF32(const std::basic_string<T>& string, F32& value);
|
||||
static BOOL convertToF64(const std::basic_string<T>& string, F64& value);
|
||||
static BOOL convertToBOOL(const string_type& string, BOOL& value);
|
||||
static BOOL convertToU8(const string_type& string, U8& value);
|
||||
static BOOL convertToS8(const string_type& string, S8& value);
|
||||
static BOOL convertToS16(const string_type& string, S16& value);
|
||||
static BOOL convertToU16(const string_type& string, U16& value);
|
||||
static BOOL convertToU32(const string_type& string, U32& value);
|
||||
static BOOL convertToS32(const string_type& string, S32& value);
|
||||
static BOOL convertToF32(const string_type& string, F32& value);
|
||||
static BOOL convertToF64(const string_type& string, F64& value);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utility functions for working with char*'s and strings
|
||||
|
|
@ -327,24 +381,24 @@ public:
|
|||
// Like strcmp but also handles empty strings. Uses
|
||||
// current locale.
|
||||
static S32 compareStrings(const T* lhs, const T* rhs);
|
||||
static S32 compareStrings(const std::basic_string<T>& lhs, const std::basic_string<T>& rhs);
|
||||
static S32 compareStrings(const string_type& lhs, const string_type& rhs);
|
||||
|
||||
// case insensitive version of above. Uses current locale on
|
||||
// Win32, and falls back to a non-locale aware comparison on
|
||||
// Linux.
|
||||
static S32 compareInsensitive(const T* lhs, const T* rhs);
|
||||
static S32 compareInsensitive(const std::basic_string<T>& lhs, const std::basic_string<T>& rhs);
|
||||
static S32 compareInsensitive(const string_type& lhs, const string_type& rhs);
|
||||
|
||||
// Case sensitive comparison with good handling of numbers. Does not use current locale.
|
||||
// a.k.a. strdictcmp()
|
||||
static S32 compareDict(const std::basic_string<T>& a, const std::basic_string<T>& b);
|
||||
static S32 compareDict(const string_type& a, const string_type& b);
|
||||
|
||||
// Case *in*sensitive comparison with good handling of numbers. Does not use current locale.
|
||||
// a.k.a. strdictcmp()
|
||||
static S32 compareDictInsensitive(const std::basic_string<T>& a, const std::basic_string<T>& b);
|
||||
static S32 compareDictInsensitive(const string_type& a, const string_type& b);
|
||||
|
||||
// Puts compareDict() in a form appropriate for LL container classes to use for sorting.
|
||||
static BOOL precedesDict( const std::basic_string<T>& a, const std::basic_string<T>& b );
|
||||
static BOOL precedesDict( const string_type& a, const string_type& b );
|
||||
|
||||
// A replacement for strncpy.
|
||||
// If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds
|
||||
|
|
@ -352,7 +406,7 @@ public:
|
|||
static void copy(T* dst, const T* src, size_type dst_size);
|
||||
|
||||
// Copies src into dst at a given offset.
|
||||
static void copyInto(std::basic_string<T>& dst, const std::basic_string<T>& src, size_type offset);
|
||||
static void copyInto(string_type& dst, const string_type& src, size_type offset);
|
||||
|
||||
static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); }
|
||||
|
||||
|
|
@ -362,7 +416,7 @@ public:
|
|||
#endif
|
||||
|
||||
private:
|
||||
LL_COMMON_API static size_type getSubstitution(const std::basic_string<T>& instr, size_type& start, std::vector<std::basic_string<T> >& tokens);
|
||||
LL_COMMON_API static size_type getSubstitution(const string_type& instr, size_type& start, std::vector<string_type >& tokens);
|
||||
};
|
||||
|
||||
template<class T> const std::basic_string<T> LLStringUtilBase<T>::null;
|
||||
|
|
@ -636,10 +690,325 @@ namespace LLStringFn
|
|||
////////////////////////////////////////////////////////////
|
||||
// NOTE: LLStringUtil::format, getTokens, and support functions moved to llstring.cpp.
|
||||
// There is no LLWStringUtil::format implementation currently.
|
||||
// Calling thse for anything other than LLStringUtil will produce link errors.
|
||||
// Calling these for anything other than LLStringUtil will produce link errors.
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
// static
|
||||
template <class T>
|
||||
std::vector<typename LLStringUtilBase<T>::string_type>
|
||||
LLStringUtilBase<T>::getTokens(const string_type& instr, const string_type& delims)
|
||||
{
|
||||
std::vector<string_type> tokens;
|
||||
getTokens(instr, tokens, delims);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// static
|
||||
template <class T>
|
||||
std::vector<typename LLStringUtilBase<T>::string_type>
|
||||
LLStringUtilBase<T>::getTokens(const string_type& instr,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes)
|
||||
{
|
||||
std::vector<string_type> tokens;
|
||||
getTokens(instr, tokens, drop_delims, keep_delims, quotes);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// static
|
||||
template <class T>
|
||||
std::vector<typename LLStringUtilBase<T>::string_type>
|
||||
LLStringUtilBase<T>::getTokens(const string_type& instr,
|
||||
const string_type& drop_delims,
|
||||
const string_type& keep_delims,
|
||||
const string_type& quotes,
|
||||
const string_type& escapes)
|
||||
{
|
||||
std::vector<string_type> tokens;
|
||||
getTokens(instr, tokens, drop_delims, keep_delims, quotes, escapes);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
namespace LLStringUtilBaseImpl
|
||||
{
|
||||
|
||||
/**
|
||||
* Input string scanner helper for getTokens(), or really any other
|
||||
* character-parsing routine that may have to deal with escape characters.
|
||||
* This implementation defines the concept (also an interface, should you
|
||||
* choose to implement the concept by subclassing) and provides trivial
|
||||
* implementations for a string @em without escape processing.
|
||||
*/
|
||||
template <class T>
|
||||
struct InString
|
||||
{
|
||||
typedef std::basic_string<T> string_type;
|
||||
typedef typename string_type::const_iterator const_iterator;
|
||||
|
||||
InString(const_iterator b, const_iterator e):
|
||||
mIter(b),
|
||||
mEnd(e)
|
||||
{}
|
||||
virtual ~InString() {}
|
||||
|
||||
bool done() const { return mIter == mEnd; }
|
||||
/// Is the current character (*mIter) escaped? This implementation can
|
||||
/// answer trivially because it doesn't support escapes.
|
||||
virtual bool escaped() const { return false; }
|
||||
/// Obtain the current character and advance @c mIter.
|
||||
virtual T next() { return *mIter++; }
|
||||
/// Does the current character match specified character?
|
||||
virtual bool is(T ch) const { return (! done()) && *mIter == ch; }
|
||||
/// Is the current character any one of the specified characters?
|
||||
virtual bool oneof(const string_type& delims) const
|
||||
{
|
||||
return (! done()) && LLStringUtilBase<T>::contains(delims, *mIter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan forward from @from until either @a delim or end. This is primarily
|
||||
* useful for processing quoted substrings.
|
||||
*
|
||||
* If we do see @a delim, append everything from @from until (excluding)
|
||||
* @a delim to @a into, advance @c mIter to skip @a delim, and return @c
|
||||
* true.
|
||||
*
|
||||
* If we do not see @a delim, do not alter @a into or @c mIter and return
|
||||
* @c false. Do not pass GO, do not collect $200.
|
||||
*
|
||||
* @note The @c false case described above implements normal getTokens()
|
||||
* treatment of an unmatched open quote: treat the quote character as if
|
||||
* escaped, that is, simply collect it as part of the current token. Other
|
||||
* plausible behaviors directly affect the way getTokens() deals with an
|
||||
* unmatched quote: e.g. throwing an exception to treat it as an error, or
|
||||
* assuming a close quote beyond end of string (in which case return @c
|
||||
* true).
|
||||
*/
|
||||
virtual bool collect_until(string_type& into, const_iterator from, T delim)
|
||||
{
|
||||
const_iterator found = std::find(from, mEnd, delim);
|
||||
// If we didn't find delim, change nothing, just tell caller.
|
||||
if (found == mEnd)
|
||||
return false;
|
||||
// Found delim! Append everything between from and found.
|
||||
into.append(from, found);
|
||||
// advance past delim in input
|
||||
mIter = found + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
const_iterator mIter, mEnd;
|
||||
};
|
||||
|
||||
/// InString subclass that handles escape characters
|
||||
template <class T>
|
||||
class InEscString: public InString<T>
|
||||
{
|
||||
public:
|
||||
typedef InString<T> super;
|
||||
typedef typename super::string_type string_type;
|
||||
typedef typename super::const_iterator const_iterator;
|
||||
using super::done;
|
||||
using super::mIter;
|
||||
using super::mEnd;
|
||||
|
||||
InEscString(const_iterator b, const_iterator e, const string_type& escapes):
|
||||
super(b, e),
|
||||
mEscapes(escapes)
|
||||
{
|
||||
// Even though we've already initialized 'mIter' via our base-class
|
||||
// constructor, set it again to check for initial escape char.
|
||||
setiter(b);
|
||||
}
|
||||
|
||||
/// This implementation uses the answer cached by setiter().
|
||||
virtual bool escaped() const { return mIsEsc; }
|
||||
virtual T next()
|
||||
{
|
||||
// If we're looking at the escape character of an escape sequence,
|
||||
// skip that character. This is the one time we can modify 'mIter'
|
||||
// without using setiter: for this one case we DO NOT CARE if the
|
||||
// escaped character is itself an escape.
|
||||
if (mIsEsc)
|
||||
++mIter;
|
||||
// If we were looking at an escape character, this is the escaped
|
||||
// character; otherwise it's just the next character.
|
||||
T result(*mIter);
|
||||
// Advance mIter, checking for escape sequence.
|
||||
setiter(mIter + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual bool is(T ch) const
|
||||
{
|
||||
// Like base-class is(), except that an escaped character matches
|
||||
// nothing.
|
||||
return (! done()) && (! mIsEsc) && *mIter == ch;
|
||||
}
|
||||
|
||||
virtual bool oneof(const string_type& delims) const
|
||||
{
|
||||
// Like base-class oneof(), except that an escaped character matches
|
||||
// nothing.
|
||||
return (! done()) && (! mIsEsc) && LLStringUtilBase<T>::contains(delims, *mIter);
|
||||
}
|
||||
|
||||
virtual bool collect_until(string_type& into, const_iterator from, T delim)
|
||||
{
|
||||
// Deal with escapes in the characters we collect; that is, an escaped
|
||||
// character must become just that character without the preceding
|
||||
// escape. Collect characters in a separate string rather than
|
||||
// directly appending to 'into' in case we do not find delim, in which
|
||||
// case we're supposed to leave 'into' unmodified.
|
||||
string_type collected;
|
||||
// For scanning purposes, we're going to work directly with 'mIter'.
|
||||
// Save its current value in case we fail to see delim.
|
||||
const_iterator save_iter(mIter);
|
||||
// Okay, set 'mIter', checking for escape.
|
||||
setiter(from);
|
||||
while (! done())
|
||||
{
|
||||
// If we see an unescaped delim, stop and report success.
|
||||
if ((! mIsEsc) && *mIter == delim)
|
||||
{
|
||||
// Append collected chars to 'into'.
|
||||
into.append(collected);
|
||||
// Don't forget to advance 'mIter' past delim.
|
||||
setiter(mIter + 1);
|
||||
return true;
|
||||
}
|
||||
// We're not at end, and either we're not looking at delim or it's
|
||||
// escaped. Collect this character and keep going.
|
||||
collected.push_back(next());
|
||||
}
|
||||
// Here we hit 'mEnd' without ever seeing delim. Restore mIter and tell
|
||||
// caller.
|
||||
setiter(save_iter);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void setiter(const_iterator i)
|
||||
{
|
||||
mIter = i;
|
||||
|
||||
// Every time we change 'mIter', set 'mIsEsc' to be able to repetitively
|
||||
// answer escaped() without having to rescan 'mEscapes'. mIsEsc caches
|
||||
// contains(mEscapes, *mIter).
|
||||
|
||||
// We're looking at an escaped char if we're not already at end (that
|
||||
// is, *mIter is even meaningful); if *mIter is in fact one of the
|
||||
// specified escape characters; and if there's one more character
|
||||
// following it. That is, if an escape character is the very last
|
||||
// character of the input string, it loses its special meaning.
|
||||
mIsEsc = (! done()) &&
|
||||
LLStringUtilBase<T>::contains(mEscapes, *mIter) &&
|
||||
(mIter+1) != mEnd;
|
||||
}
|
||||
|
||||
const string_type mEscapes;
|
||||
bool mIsEsc;
|
||||
};
|
||||
|
||||
/// getTokens() implementation based on InString concept
|
||||
template <typename INSTRING, typename string_type>
|
||||
void getTokens(INSTRING& instr, std::vector<string_type>& tokens,
|
||||
const string_type& drop_delims, const string_type& keep_delims,
|
||||
const string_type& quotes)
|
||||
{
|
||||
// There are times when we want to match either drop_delims or
|
||||
// keep_delims. Concatenate them up front to speed things up.
|
||||
string_type all_delims(drop_delims + keep_delims);
|
||||
// no tokens yet
|
||||
tokens.clear();
|
||||
|
||||
// try for another token
|
||||
while (! instr.done())
|
||||
{
|
||||
// scan past any drop_delims
|
||||
while (instr.oneof(drop_delims))
|
||||
{
|
||||
// skip this drop_delim
|
||||
instr.next();
|
||||
// but if that was the end of the string, done
|
||||
if (instr.done())
|
||||
return;
|
||||
}
|
||||
// found the start of another token: make a slot for it
|
||||
tokens.push_back(string_type());
|
||||
if (instr.oneof(keep_delims))
|
||||
{
|
||||
// *iter is a keep_delim, a token of exactly 1 character. Append
|
||||
// that character to the new token and proceed.
|
||||
tokens.back().push_back(instr.next());
|
||||
continue;
|
||||
}
|
||||
// Here we have a non-delimiter token, which might consist of a mix of
|
||||
// quoted and unquoted parts. Use bash rules for quoting: you can
|
||||
// embed a quoted substring in the midst of an unquoted token (e.g.
|
||||
// ~/"sub dir"/myfile.txt); you can ram two quoted substrings together
|
||||
// to make a single token (e.g. 'He said, "'"Don't."'"'). We diverge
|
||||
// from bash in that bash considers an unmatched quote an error. Our
|
||||
// param signature doesn't allow for errors, so just pretend it's not
|
||||
// a quote and embed it.
|
||||
// At this level, keep scanning until we hit the next delimiter of
|
||||
// either type (drop_delims or keep_delims).
|
||||
while (! instr.oneof(all_delims))
|
||||
{
|
||||
// If we're looking at an open quote, search forward for
|
||||
// a close quote, collecting characters along the way.
|
||||
if (instr.oneof(quotes) &&
|
||||
instr.collect_until(tokens.back(), instr.mIter+1, *instr.mIter))
|
||||
{
|
||||
// collect_until is cleverly designed to do exactly what we
|
||||
// need here. No further action needed if it returns true.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Either *iter isn't a quote, or there's no matching close
|
||||
// quote: in other words, just an ordinary char. Append it to
|
||||
// current token.
|
||||
tokens.back().push_back(instr.next());
|
||||
}
|
||||
// having scanned that segment of this token, if we've reached the
|
||||
// end of the string, we're done
|
||||
if (instr.done())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LLStringUtilBaseImpl
|
||||
|
||||
// static
|
||||
template <class T>
|
||||
void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens,
|
||||
const string_type& drop_delims, const string_type& keep_delims,
|
||||
const string_type& quotes)
|
||||
{
|
||||
// Because this overload doesn't support escapes, use simple InString to
|
||||
// manage input range.
|
||||
LLStringUtilBaseImpl::InString<T> instring(string.begin(), string.end());
|
||||
LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes);
|
||||
}
|
||||
|
||||
// static
|
||||
template <class T>
|
||||
void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens,
|
||||
const string_type& drop_delims, const string_type& keep_delims,
|
||||
const string_type& quotes, const string_type& escapes)
|
||||
{
|
||||
// This overload must deal with escapes. Delegate that to InEscString
|
||||
// (unless there ARE no escapes).
|
||||
boost::scoped_ptr< LLStringUtilBaseImpl::InString<T> > instrp;
|
||||
if (escapes.empty())
|
||||
instrp.reset(new LLStringUtilBaseImpl::InString<T>(string.begin(), string.end()));
|
||||
else
|
||||
instrp.reset(new LLStringUtilBaseImpl::InEscString<T>(string.begin(), string.end(), escapes));
|
||||
LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes);
|
||||
}
|
||||
|
||||
// static
|
||||
template<class T>
|
||||
|
|
@ -669,7 +1038,7 @@ S32 LLStringUtilBase<T>::compareStrings(const T* lhs, const T* rhs)
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
S32 LLStringUtilBase<T>::compareStrings(const std::basic_string<T>& lhs, const std::basic_string<T>& rhs)
|
||||
S32 LLStringUtilBase<T>::compareStrings(const string_type& lhs, const string_type& rhs)
|
||||
{
|
||||
return LLStringOps::collate(lhs.c_str(), rhs.c_str());
|
||||
}
|
||||
|
|
@ -695,8 +1064,8 @@ S32 LLStringUtilBase<T>::compareInsensitive(const T* lhs, const T* rhs )
|
|||
}
|
||||
else
|
||||
{
|
||||
std::basic_string<T> lhs_string(lhs);
|
||||
std::basic_string<T> rhs_string(rhs);
|
||||
string_type lhs_string(lhs);
|
||||
string_type rhs_string(rhs);
|
||||
LLStringUtilBase<T>::toUpper(lhs_string);
|
||||
LLStringUtilBase<T>::toUpper(rhs_string);
|
||||
result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
|
||||
|
|
@ -706,10 +1075,10 @@ S32 LLStringUtilBase<T>::compareInsensitive(const T* lhs, const T* rhs )
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
S32 LLStringUtilBase<T>::compareInsensitive(const std::basic_string<T>& lhs, const std::basic_string<T>& rhs)
|
||||
S32 LLStringUtilBase<T>::compareInsensitive(const string_type& lhs, const string_type& rhs)
|
||||
{
|
||||
std::basic_string<T> lhs_string(lhs);
|
||||
std::basic_string<T> rhs_string(rhs);
|
||||
string_type lhs_string(lhs);
|
||||
string_type rhs_string(rhs);
|
||||
LLStringUtilBase<T>::toUpper(lhs_string);
|
||||
LLStringUtilBase<T>::toUpper(rhs_string);
|
||||
return LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
|
||||
|
|
@ -720,7 +1089,7 @@ S32 LLStringUtilBase<T>::compareInsensitive(const std::basic_string<T>& lhs, con
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
S32 LLStringUtilBase<T>::compareDict(const std::basic_string<T>& astr, const std::basic_string<T>& bstr)
|
||||
S32 LLStringUtilBase<T>::compareDict(const string_type& astr, const string_type& bstr)
|
||||
{
|
||||
const T* a = astr.c_str();
|
||||
const T* b = bstr.c_str();
|
||||
|
|
@ -761,7 +1130,7 @@ S32 LLStringUtilBase<T>::compareDict(const std::basic_string<T>& astr, const std
|
|||
|
||||
// static
|
||||
template<class T>
|
||||
S32 LLStringUtilBase<T>::compareDictInsensitive(const std::basic_string<T>& astr, const std::basic_string<T>& bstr)
|
||||
S32 LLStringUtilBase<T>::compareDictInsensitive(const string_type& astr, const string_type& bstr)
|
||||
{
|
||||
const T* a = astr.c_str();
|
||||
const T* b = bstr.c_str();
|
||||
|
|
@ -796,7 +1165,7 @@ S32 LLStringUtilBase<T>::compareDictInsensitive(const std::basic_string<T>& astr
|
|||
// Puts compareDict() in a form appropriate for LL container classes to use for sorting.
|
||||
// static
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::precedesDict( const std::basic_string<T>& a, const std::basic_string<T>& b )
|
||||
BOOL LLStringUtilBase<T>::precedesDict( const string_type& a, const string_type& b )
|
||||
{
|
||||
if( a.size() && b.size() )
|
||||
{
|
||||
|
|
@ -810,7 +1179,7 @@ BOOL LLStringUtilBase<T>::precedesDict( const std::basic_string<T>& a, const std
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::toUpper(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::toUpper(string_type& string)
|
||||
{
|
||||
if( !string.empty() )
|
||||
{
|
||||
|
|
@ -824,7 +1193,7 @@ void LLStringUtilBase<T>::toUpper(std::basic_string<T>& string)
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::toLower(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::toLower(string_type& string)
|
||||
{
|
||||
if( !string.empty() )
|
||||
{
|
||||
|
|
@ -838,7 +1207,7 @@ void LLStringUtilBase<T>::toLower(std::basic_string<T>& string)
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::trimHead(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::trimHead(string_type& string)
|
||||
{
|
||||
if( !string.empty() )
|
||||
{
|
||||
|
|
@ -853,7 +1222,7 @@ void LLStringUtilBase<T>::trimHead(std::basic_string<T>& string)
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::trimTail(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::trimTail(string_type& string)
|
||||
{
|
||||
if( string.size() )
|
||||
{
|
||||
|
|
@ -872,7 +1241,7 @@ void LLStringUtilBase<T>::trimTail(std::basic_string<T>& string)
|
|||
// Replace line feeds with carriage return-line feed pairs.
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::addCRLF(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::addCRLF(string_type& string)
|
||||
{
|
||||
const T LF = 10;
|
||||
const T CR = 13;
|
||||
|
|
@ -914,7 +1283,7 @@ void LLStringUtilBase<T>::addCRLF(std::basic_string<T>& string)
|
|||
// Remove all carriage returns
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::removeCRLF(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::removeCRLF(string_type& string)
|
||||
{
|
||||
const T CR = 13;
|
||||
|
||||
|
|
@ -935,10 +1304,10 @@ void LLStringUtilBase<T>::removeCRLF(std::basic_string<T>& string)
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::replaceChar( std::basic_string<T>& string, T target, T replacement )
|
||||
void LLStringUtilBase<T>::replaceChar( string_type& string, T target, T replacement )
|
||||
{
|
||||
size_type found_pos = 0;
|
||||
while( (found_pos = string.find(target, found_pos)) != std::basic_string<T>::npos )
|
||||
while( (found_pos = string.find(target, found_pos)) != string_type::npos )
|
||||
{
|
||||
string[found_pos] = replacement;
|
||||
found_pos++; // avoid infinite defeat if target == replacement
|
||||
|
|
@ -947,10 +1316,10 @@ void LLStringUtilBase<T>::replaceChar( std::basic_string<T>& string, T target, T
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::replaceString( std::basic_string<T>& string, std::basic_string<T> target, std::basic_string<T> replacement )
|
||||
void LLStringUtilBase<T>::replaceString( string_type& string, string_type target, string_type replacement )
|
||||
{
|
||||
size_type found_pos = 0;
|
||||
while( (found_pos = string.find(target, found_pos)) != std::basic_string<T>::npos )
|
||||
while( (found_pos = string.find(target, found_pos)) != string_type::npos )
|
||||
{
|
||||
string.replace( found_pos, target.length(), replacement );
|
||||
found_pos += replacement.length(); // avoid infinite defeat if replacement contains target
|
||||
|
|
@ -959,7 +1328,7 @@ void LLStringUtilBase<T>::replaceString( std::basic_string<T>& string, std::basi
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::replaceNonstandardASCII( std::basic_string<T>& string, T replacement )
|
||||
void LLStringUtilBase<T>::replaceNonstandardASCII( string_type& string, T replacement )
|
||||
{
|
||||
const char LF = 10;
|
||||
const S8 MIN = 32;
|
||||
|
|
@ -979,12 +1348,12 @@ void LLStringUtilBase<T>::replaceNonstandardASCII( std::basic_string<T>& string,
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::replaceTabsWithSpaces( std::basic_string<T>& str, size_type spaces_per_tab )
|
||||
void LLStringUtilBase<T>::replaceTabsWithSpaces( string_type& str, size_type spaces_per_tab )
|
||||
{
|
||||
const T TAB = '\t';
|
||||
const T SPACE = ' ';
|
||||
|
||||
std::basic_string<T> out_str;
|
||||
string_type out_str;
|
||||
// Replace tabs with spaces
|
||||
for (size_type i = 0; i < str.length(); i++)
|
||||
{
|
||||
|
|
@ -1003,7 +1372,7 @@ void LLStringUtilBase<T>::replaceTabsWithSpaces( std::basic_string<T>& str, size
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::containsNonprintable(const std::basic_string<T>& string)
|
||||
BOOL LLStringUtilBase<T>::containsNonprintable(const string_type& string)
|
||||
{
|
||||
const char MIN = 32;
|
||||
BOOL rv = FALSE;
|
||||
|
|
@ -1020,7 +1389,7 @@ BOOL LLStringUtilBase<T>::containsNonprintable(const std::basic_string<T>& strin
|
|||
|
||||
//static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::stripNonprintable(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::stripNonprintable(string_type& string)
|
||||
{
|
||||
const char MIN = 32;
|
||||
size_type j = 0;
|
||||
|
|
@ -1051,8 +1420,43 @@ void LLStringUtilBase<T>::stripNonprintable(std::basic_string<T>& string)
|
|||
delete []c_string;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::basic_string<T> LLStringUtilBase<T>::quote(const string_type& str,
|
||||
const string_type& triggers,
|
||||
const string_type& escape)
|
||||
{
|
||||
size_type len(str.length());
|
||||
// If the string is already quoted, assume user knows what s/he's doing.
|
||||
if (len >= 2 && str[0] == '"' && str[len-1] == '"')
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
// Not already quoted: do we need to? triggers.empty() is a special case
|
||||
// meaning "always quote."
|
||||
if ((! triggers.empty()) && str.find_first_of(triggers) == string_type::npos)
|
||||
{
|
||||
// no trigger characters, don't bother quoting
|
||||
return str;
|
||||
}
|
||||
|
||||
// For whatever reason, we must quote this string.
|
||||
string_type result;
|
||||
result.push_back('"');
|
||||
for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci)
|
||||
{
|
||||
if (*ci == '"')
|
||||
{
|
||||
result.append(escape);
|
||||
}
|
||||
result.push_back(*ci);
|
||||
}
|
||||
result.push_back('"');
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::_makeASCII(std::basic_string<T>& string)
|
||||
void LLStringUtilBase<T>::_makeASCII(string_type& string)
|
||||
{
|
||||
// Replace non-ASCII chars with LL_UNKNOWN_CHAR
|
||||
for (size_type i = 0; i < string.length(); i++)
|
||||
|
|
@ -1082,7 +1486,7 @@ void LLStringUtilBase<T>::copy( T* dst, const T* src, size_type dst_size )
|
|||
|
||||
// static
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::copyInto(std::basic_string<T>& dst, const std::basic_string<T>& src, size_type offset)
|
||||
void LLStringUtilBase<T>::copyInto(string_type& dst, const string_type& src, size_type offset)
|
||||
{
|
||||
if ( offset == dst.length() )
|
||||
{
|
||||
|
|
@ -1092,7 +1496,7 @@ void LLStringUtilBase<T>::copyInto(std::basic_string<T>& dst, const std::basic_s
|
|||
}
|
||||
else
|
||||
{
|
||||
std::basic_string<T> tail = dst.substr(offset);
|
||||
string_type tail = dst.substr(offset);
|
||||
|
||||
dst = dst.substr(0, offset);
|
||||
dst += src;
|
||||
|
|
@ -1103,7 +1507,7 @@ void LLStringUtilBase<T>::copyInto(std::basic_string<T>& dst, const std::basic_s
|
|||
// True if this is the head of s.
|
||||
//static
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::isHead( const std::basic_string<T>& string, const T* s )
|
||||
BOOL LLStringUtilBase<T>::isHead( const string_type& string, const T* s )
|
||||
{
|
||||
if( string.empty() )
|
||||
{
|
||||
|
|
@ -1119,8 +1523,8 @@ BOOL LLStringUtilBase<T>::isHead( const std::basic_string<T>& string, const T* s
|
|||
// static
|
||||
template<class T>
|
||||
bool LLStringUtilBase<T>::startsWith(
|
||||
const std::basic_string<T>& string,
|
||||
const std::basic_string<T>& substr)
|
||||
const string_type& string,
|
||||
const string_type& substr)
|
||||
{
|
||||
if(string.empty() || (substr.empty())) return false;
|
||||
if(0 == string.find(substr)) return true;
|
||||
|
|
@ -1130,8 +1534,8 @@ bool LLStringUtilBase<T>::startsWith(
|
|||
// static
|
||||
template<class T>
|
||||
bool LLStringUtilBase<T>::endsWith(
|
||||
const std::basic_string<T>& string,
|
||||
const std::basic_string<T>& substr)
|
||||
const string_type& string,
|
||||
const string_type& substr)
|
||||
{
|
||||
if(string.empty() || (substr.empty())) return false;
|
||||
std::string::size_type idx = string.rfind(substr);
|
||||
|
|
@ -1141,14 +1545,14 @@ bool LLStringUtilBase<T>::endsWith(
|
|||
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToBOOL(const std::basic_string<T>& string, BOOL& value)
|
||||
BOOL LLStringUtilBase<T>::convertToBOOL(const string_type& string, BOOL& value)
|
||||
{
|
||||
if( string.empty() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::basic_string<T> temp( string );
|
||||
string_type temp( string );
|
||||
trim(temp);
|
||||
if(
|
||||
(temp == "1") ||
|
||||
|
|
@ -1178,7 +1582,7 @@ BOOL LLStringUtilBase<T>::convertToBOOL(const std::basic_string<T>& string, BOOL
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToU8(const std::basic_string<T>& string, U8& value)
|
||||
BOOL LLStringUtilBase<T>::convertToU8(const string_type& string, U8& value)
|
||||
{
|
||||
S32 value32 = 0;
|
||||
BOOL success = convertToS32(string, value32);
|
||||
|
|
@ -1191,7 +1595,7 @@ BOOL LLStringUtilBase<T>::convertToU8(const std::basic_string<T>& string, U8& va
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToS8(const std::basic_string<T>& string, S8& value)
|
||||
BOOL LLStringUtilBase<T>::convertToS8(const string_type& string, S8& value)
|
||||
{
|
||||
S32 value32 = 0;
|
||||
BOOL success = convertToS32(string, value32);
|
||||
|
|
@ -1204,7 +1608,7 @@ BOOL LLStringUtilBase<T>::convertToS8(const std::basic_string<T>& string, S8& va
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToS16(const std::basic_string<T>& string, S16& value)
|
||||
BOOL LLStringUtilBase<T>::convertToS16(const string_type& string, S16& value)
|
||||
{
|
||||
S32 value32 = 0;
|
||||
BOOL success = convertToS32(string, value32);
|
||||
|
|
@ -1217,7 +1621,7 @@ BOOL LLStringUtilBase<T>::convertToS16(const std::basic_string<T>& string, S16&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToU16(const std::basic_string<T>& string, U16& value)
|
||||
BOOL LLStringUtilBase<T>::convertToU16(const string_type& string, U16& value)
|
||||
{
|
||||
S32 value32 = 0;
|
||||
BOOL success = convertToS32(string, value32);
|
||||
|
|
@ -1230,17 +1634,17 @@ BOOL LLStringUtilBase<T>::convertToU16(const std::basic_string<T>& string, U16&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToU32(const std::basic_string<T>& string, U32& value)
|
||||
BOOL LLStringUtilBase<T>::convertToU32(const string_type& string, U32& value)
|
||||
{
|
||||
if( string.empty() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::basic_string<T> temp( string );
|
||||
string_type temp( string );
|
||||
trim(temp);
|
||||
U32 v;
|
||||
std::basic_istringstream<T> i_stream((std::basic_string<T>)temp);
|
||||
std::basic_istringstream<T> i_stream((string_type)temp);
|
||||
if(i_stream >> v)
|
||||
{
|
||||
value = v;
|
||||
|
|
@ -1250,17 +1654,17 @@ BOOL LLStringUtilBase<T>::convertToU32(const std::basic_string<T>& string, U32&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToS32(const std::basic_string<T>& string, S32& value)
|
||||
BOOL LLStringUtilBase<T>::convertToS32(const string_type& string, S32& value)
|
||||
{
|
||||
if( string.empty() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::basic_string<T> temp( string );
|
||||
string_type temp( string );
|
||||
trim(temp);
|
||||
S32 v;
|
||||
std::basic_istringstream<T> i_stream((std::basic_string<T>)temp);
|
||||
std::basic_istringstream<T> i_stream((string_type)temp);
|
||||
if(i_stream >> v)
|
||||
{
|
||||
//TODO: figure out overflow and underflow reporting here
|
||||
|
|
@ -1277,7 +1681,7 @@ BOOL LLStringUtilBase<T>::convertToS32(const std::basic_string<T>& string, S32&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToF32(const std::basic_string<T>& string, F32& value)
|
||||
BOOL LLStringUtilBase<T>::convertToF32(const string_type& string, F32& value)
|
||||
{
|
||||
F64 value64 = 0.0;
|
||||
BOOL success = convertToF64(string, value64);
|
||||
|
|
@ -1290,17 +1694,17 @@ BOOL LLStringUtilBase<T>::convertToF32(const std::basic_string<T>& string, F32&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
BOOL LLStringUtilBase<T>::convertToF64(const std::basic_string<T>& string, F64& value)
|
||||
BOOL LLStringUtilBase<T>::convertToF64(const string_type& string, F64& value)
|
||||
{
|
||||
if( string.empty() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
std::basic_string<T> temp( string );
|
||||
string_type temp( string );
|
||||
trim(temp);
|
||||
F64 v;
|
||||
std::basic_istringstream<T> i_stream((std::basic_string<T>)temp);
|
||||
std::basic_istringstream<T> i_stream((string_type)temp);
|
||||
if(i_stream >> v)
|
||||
{
|
||||
//TODO: figure out overflow and underflow reporting here
|
||||
|
|
@ -1317,7 +1721,7 @@ BOOL LLStringUtilBase<T>::convertToF64(const std::basic_string<T>& string, F64&
|
|||
}
|
||||
|
||||
template<class T>
|
||||
void LLStringUtilBase<T>::truncate(std::basic_string<T>& string, size_type count)
|
||||
void LLStringUtilBase<T>::truncate(string_type& string, size_type count)
|
||||
{
|
||||
size_type cur_size = string.size();
|
||||
string.resize(count < cur_size ? count : cur_size);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* @file lltypeinfolookup.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-04-08
|
||||
* @brief Template data structure like std::map<std::type_info*, T>
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLTYPEINFOLOOKUP_H)
|
||||
#define LL_LLTYPEINFOLOOKUP_H
|
||||
|
||||
#include "llsortedvector.h"
|
||||
#include <typeinfo>
|
||||
|
||||
/**
|
||||
* LLTypeInfoLookup is specifically designed for use cases for which you might
|
||||
* consider std::map<std::type_info*, VALUE>. We have several such data
|
||||
* structures in the viewer. The trouble with them is that at least on Linux,
|
||||
* you can't rely on always getting the same std::type_info* for a given type:
|
||||
* different load modules will produce different std::type_info*.
|
||||
* LLTypeInfoLookup contains a workaround to address this issue.
|
||||
*
|
||||
* Specifically, when we don't find the passed std::type_info*,
|
||||
* LLTypeInfoLookup performs a linear search over registered entries to
|
||||
* compare name() strings. Presuming that this succeeds, we cache the new
|
||||
* (previously unrecognized) std::type_info* to speed future lookups.
|
||||
*
|
||||
* This worst-case fallback search (linear search with string comparison)
|
||||
* should only happen the first time we look up a given type from a particular
|
||||
* load module other than the one from which we initially registered types.
|
||||
* (However, a lookup which wouldn't succeed anyway will always have
|
||||
* worst-case performance.) This class is probably best used with less than a
|
||||
* few dozen different types.
|
||||
*/
|
||||
template <typename VALUE>
|
||||
class LLTypeInfoLookup
|
||||
{
|
||||
public:
|
||||
typedef LLTypeInfoLookup<VALUE> self;
|
||||
typedef LLSortedVector<const std::type_info*, VALUE> vector_type;
|
||||
typedef typename vector_type::key_type key_type;
|
||||
typedef typename vector_type::mapped_type mapped_type;
|
||||
typedef typename vector_type::value_type value_type;
|
||||
typedef typename vector_type::iterator iterator;
|
||||
typedef typename vector_type::const_iterator const_iterator;
|
||||
|
||||
LLTypeInfoLookup() {}
|
||||
|
||||
iterator begin() { return mVector.begin(); }
|
||||
iterator end() { return mVector.end(); }
|
||||
const_iterator begin() const { return mVector.begin(); }
|
||||
const_iterator end() const { return mVector.end(); }
|
||||
bool empty() const { return mVector.empty(); }
|
||||
std::size_t size() const { return mVector.size(); }
|
||||
|
||||
std::pair<iterator, bool> insert(const std::type_info* key, const VALUE& value)
|
||||
{
|
||||
return insert(value_type(key, value));
|
||||
}
|
||||
|
||||
std::pair<iterator, bool> insert(const value_type& pair)
|
||||
{
|
||||
return mVector.insert(pair);
|
||||
}
|
||||
|
||||
// const find() forwards to non-const find(): this can alter mVector!
|
||||
const_iterator find(const std::type_info* key) const
|
||||
{
|
||||
return const_cast<self*>(this)->find(key);
|
||||
}
|
||||
|
||||
// non-const find() caches previously-unknown type_info* to speed future
|
||||
// lookups.
|
||||
iterator find(const std::type_info* key)
|
||||
{
|
||||
iterator found = mVector.find(key);
|
||||
if (found != mVector.end())
|
||||
{
|
||||
// If LLSortedVector::find() found, great, we're done.
|
||||
return found;
|
||||
}
|
||||
// Here we didn't find the passed type_info*. On Linux, though, even
|
||||
// for the same type, typeid(sametype) produces a different type_info*
|
||||
// when used in different load modules. So the fact that we didn't
|
||||
// find the type_info* we seek doesn't mean this type isn't
|
||||
// registered. Scan for matching name() string.
|
||||
for (typename vector_type::iterator ti(mVector.begin()), tend(mVector.end());
|
||||
ti != tend; ++ti)
|
||||
{
|
||||
if (std::string(ti->first->name()) == key->name())
|
||||
{
|
||||
// This unrecognized 'key' is for the same type as ti->first.
|
||||
// To speed future lookups, insert a new entry that lets us
|
||||
// look up ti->second using this same 'key'.
|
||||
return insert(key, ti->second).first;
|
||||
}
|
||||
}
|
||||
// We simply have never seen a type with this type_info* from any load
|
||||
// module.
|
||||
return mVector.end();
|
||||
}
|
||||
|
||||
private:
|
||||
vector_type mVector;
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLTYPEINFOLOOKUP_H) */
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
const S32 LL_VERSION_MAJOR = 3;
|
||||
const S32 LL_VERSION_MINOR = 3;
|
||||
const S32 LL_VERSION_PATCH = 2;
|
||||
const S32 LL_VERSION_PATCH = 4;
|
||||
const S32 LL_VERSION_BUILD = 0;
|
||||
|
||||
const char * const LL_CHANNEL = "Second Life Developer";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @file StringVec.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-02-24
|
||||
* @brief Extend TUT ensure_equals() to handle std::vector<std::string>
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_STRINGVEC_H)
|
||||
#define LL_STRINGVEC_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
typedef std::vector<std::string> StringVec;
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const StringVec& strings)
|
||||
{
|
||||
out << '(';
|
||||
StringVec::const_iterator begin(strings.begin()), end(strings.end());
|
||||
if (begin != end)
|
||||
{
|
||||
out << '"' << *begin << '"';
|
||||
while (++begin != end)
|
||||
{
|
||||
out << ", \"" << *begin << '"';
|
||||
}
|
||||
}
|
||||
out << ')';
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif /* ! defined(LL_STRINGVEC_H) */
|
||||
|
|
@ -30,6 +30,8 @@
|
|||
#define LL_LISTENER_H
|
||||
|
||||
#include "llsd.h"
|
||||
#include "llevents.h"
|
||||
#include "tests/StringVec.h"
|
||||
#include <iostream>
|
||||
|
||||
/*****************************************************************************
|
||||
|
|
@ -133,24 +135,7 @@ struct Collect
|
|||
return false;
|
||||
}
|
||||
void clear() { result.clear(); }
|
||||
typedef std::vector<std::string> StringList;
|
||||
StringList result;
|
||||
StringVec result;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings)
|
||||
{
|
||||
out << '(';
|
||||
Collect::StringList::const_iterator begin(strings.begin()), end(strings.end());
|
||||
if (begin != end)
|
||||
{
|
||||
out << '"' << *begin << '"';
|
||||
while (++begin != end)
|
||||
{
|
||||
out << ", \"" << *begin << '"';
|
||||
}
|
||||
}
|
||||
out << ')';
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif /* ! defined(LL_LISTENER_H) */
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/**
|
||||
* @file llerror_test.cpp
|
||||
* @date December 2006
|
||||
* @brief error unit tests
|
||||
|
|
@ -6,21 +6,21 @@
|
|||
* $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$
|
||||
*/
|
||||
|
|
@ -49,7 +49,7 @@ namespace
|
|||
static bool fatalWasCalled;
|
||||
void fatalCall(const std::string&) { fatalWasCalled = true; }
|
||||
}
|
||||
|
||||
|
||||
namespace tut
|
||||
{
|
||||
class TestRecorder : public LLError::Recorder
|
||||
|
|
@ -57,59 +57,65 @@ namespace tut
|
|||
public:
|
||||
TestRecorder() : mWantsTime(false) { }
|
||||
~TestRecorder() { LLError::removeRecorder(this); }
|
||||
|
||||
|
||||
void recordMessage(LLError::ELevel level,
|
||||
const std::string& message)
|
||||
{
|
||||
mMessages.push_back(message);
|
||||
}
|
||||
|
||||
|
||||
int countMessages() { return (int) mMessages.size(); }
|
||||
void clearMessages() { mMessages.clear(); }
|
||||
|
||||
|
||||
void setWantsTime(bool t) { mWantsTime = t; }
|
||||
bool wantsTime() { return mWantsTime; }
|
||||
|
||||
|
||||
std::string message(int n)
|
||||
{
|
||||
std::ostringstream test_name;
|
||||
test_name << "testing message " << n << ", not enough messages";
|
||||
|
||||
|
||||
tut::ensure(test_name.str(), n < countMessages());
|
||||
return mMessages[n];
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
typedef std::vector<std::string> MessageVector;
|
||||
MessageVector mMessages;
|
||||
|
||||
|
||||
bool mWantsTime;
|
||||
};
|
||||
|
||||
struct ErrorTestData
|
||||
{
|
||||
TestRecorder mRecorder;
|
||||
// addRecorder() expects to be able to later delete the passed
|
||||
// Recorder*. Even though removeRecorder() reclaims ownership, passing
|
||||
// a pointer to a data member rather than a heap Recorder subclass
|
||||
// instance would just be Wrong.
|
||||
TestRecorder* mRecorder;
|
||||
LLError::Settings* mPriorErrorSettings;
|
||||
|
||||
ErrorTestData()
|
||||
|
||||
ErrorTestData():
|
||||
mRecorder(new TestRecorder)
|
||||
{
|
||||
fatalWasCalled = false;
|
||||
|
||||
|
||||
mPriorErrorSettings = LLError::saveAndResetSettings();
|
||||
LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
|
||||
LLError::setFatalFunction(fatalCall);
|
||||
LLError::addRecorder(&mRecorder);
|
||||
LLError::addRecorder(mRecorder);
|
||||
}
|
||||
|
||||
|
||||
~ErrorTestData()
|
||||
{
|
||||
LLError::removeRecorder(&mRecorder);
|
||||
LLError::removeRecorder(mRecorder);
|
||||
delete mRecorder;
|
||||
LLError::restoreSettings(mPriorErrorSettings);
|
||||
}
|
||||
|
||||
|
||||
void ensure_message_count(int expectedCount)
|
||||
{
|
||||
ensure_equals("message count", mRecorder.countMessages(), expectedCount);
|
||||
ensure_equals("message count", mRecorder->countMessages(), expectedCount);
|
||||
}
|
||||
|
||||
void ensure_message_contains(int n, const std::string& expectedText)
|
||||
|
|
@ -117,7 +123,7 @@ namespace tut
|
|||
std::ostringstream test_name;
|
||||
test_name << "testing message " << n;
|
||||
|
||||
ensure_contains(test_name.str(), mRecorder.message(n), expectedText);
|
||||
ensure_contains(test_name.str(), mRecorder->message(n), expectedText);
|
||||
}
|
||||
|
||||
void ensure_message_does_not_contain(int n, const std::string& expectedText)
|
||||
|
|
@ -125,22 +131,22 @@ namespace tut
|
|||
std::ostringstream test_name;
|
||||
test_name << "testing message " << n;
|
||||
|
||||
ensure_does_not_contain(test_name.str(), mRecorder.message(n), expectedText);
|
||||
ensure_does_not_contain(test_name.str(), mRecorder->message(n), expectedText);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
typedef test_group<ErrorTestData> ErrorTestGroup;
|
||||
typedef ErrorTestGroup::object ErrorTestObject;
|
||||
|
||||
|
||||
ErrorTestGroup errorTestGroup("error");
|
||||
|
||||
|
||||
template<> template<>
|
||||
void ErrorTestObject::test<1>()
|
||||
// basic test of output
|
||||
{
|
||||
llinfos << "test" << llendl;
|
||||
llinfos << "bob" << llendl;
|
||||
|
||||
|
||||
ensure_message_contains(0, "test");
|
||||
ensure_message_contains(1, "bob");
|
||||
}
|
||||
|
|
@ -159,7 +165,7 @@ namespace
|
|||
};
|
||||
|
||||
namespace tut
|
||||
{
|
||||
{
|
||||
template<> template<>
|
||||
void ErrorTestObject::test<2>()
|
||||
// messages are filtered based on default level
|
||||
|
|
@ -172,7 +178,7 @@ namespace tut
|
|||
ensure_message_contains(3, "error");
|
||||
ensure_message_contains(4, "four");
|
||||
ensure_message_count(5);
|
||||
|
||||
|
||||
LLError::setDefaultLevel(LLError::LEVEL_INFO);
|
||||
writeSome();
|
||||
ensure_message_contains(5, "two");
|
||||
|
|
@ -180,20 +186,20 @@ namespace tut
|
|||
ensure_message_contains(7, "error");
|
||||
ensure_message_contains(8, "four");
|
||||
ensure_message_count(9);
|
||||
|
||||
|
||||
LLError::setDefaultLevel(LLError::LEVEL_WARN);
|
||||
writeSome();
|
||||
ensure_message_contains(9, "three");
|
||||
ensure_message_contains(10, "error");
|
||||
ensure_message_contains(11, "four");
|
||||
ensure_message_count(12);
|
||||
|
||||
|
||||
LLError::setDefaultLevel(LLError::LEVEL_ERROR);
|
||||
writeSome();
|
||||
ensure_message_contains(12, "error");
|
||||
ensure_message_contains(13, "four");
|
||||
ensure_message_count(14);
|
||||
|
||||
|
||||
LLError::setDefaultLevel(LLError::LEVEL_NONE);
|
||||
writeSome();
|
||||
ensure_message_count(14);
|
||||
|
|
@ -218,14 +224,14 @@ namespace tut
|
|||
{
|
||||
std::string thisFile = __FILE__;
|
||||
std::string abbreviateFile = LLError::abbreviateFile(thisFile);
|
||||
|
||||
|
||||
ensure_ends_with("file name abbreviation",
|
||||
abbreviateFile,
|
||||
"llcommon/tests/llerror_test.cpp"
|
||||
);
|
||||
ensure_does_not_contain("file name abbreviation",
|
||||
abbreviateFile, "indra");
|
||||
|
||||
|
||||
std::string someFile =
|
||||
#if LL_WINDOWS
|
||||
"C:/amy/bob/cam.cpp"
|
||||
|
|
@ -234,12 +240,12 @@ namespace tut
|
|||
#endif
|
||||
;
|
||||
std::string someAbbreviation = LLError::abbreviateFile(someFile);
|
||||
|
||||
|
||||
ensure_equals("non-indra file abbreviation",
|
||||
someAbbreviation, someFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string locationString(int line)
|
||||
|
|
@ -247,22 +253,22 @@ namespace
|
|||
std::ostringstream location;
|
||||
location << LLError::abbreviateFile(__FILE__)
|
||||
<< "(" << line << ") : ";
|
||||
|
||||
|
||||
return location.str();
|
||||
}
|
||||
|
||||
|
||||
std::string writeReturningLocation()
|
||||
{
|
||||
llinfos << "apple" << llendl; int this_line = __LINE__;
|
||||
return locationString(this_line);
|
||||
}
|
||||
|
||||
|
||||
std::string writeReturningLocationAndFunction()
|
||||
{
|
||||
llinfos << "apple" << llendl; int this_line = __LINE__;
|
||||
return locationString(this_line) + __FUNCTION__;
|
||||
}
|
||||
|
||||
|
||||
std::string errorReturningLocation()
|
||||
{
|
||||
llerrs << "die" << llendl; int this_line = __LINE__;
|
||||
|
|
@ -271,20 +277,20 @@ namespace
|
|||
}
|
||||
|
||||
namespace tut
|
||||
{
|
||||
{
|
||||
template<> template<>
|
||||
void ErrorTestObject::test<5>()
|
||||
// file and line information in log messages
|
||||
{
|
||||
std::string location = writeReturningLocation();
|
||||
// expecting default to not print location information
|
||||
|
||||
|
||||
LLError::setPrintLocation(true);
|
||||
writeReturningLocation();
|
||||
|
||||
|
||||
LLError::setPrintLocation(false);
|
||||
writeReturningLocation();
|
||||
|
||||
|
||||
ensure_message_does_not_contain(0, location);
|
||||
ensure_message_contains(1, location);
|
||||
ensure_message_does_not_contain(2, location);
|
||||
|
|
@ -297,7 +303,7 @@ namespace tut
|
|||
existing log messages often do.) The functions all return their C++
|
||||
name so that test can be substantial mechanized.
|
||||
*/
|
||||
|
||||
|
||||
std::string logFromGlobal(bool id)
|
||||
{
|
||||
llinfos << (id ? "logFromGlobal: " : "") << "hi" << llendl;
|
||||
|
|
@ -345,7 +351,7 @@ namespace
|
|||
return "ClassWithNoLogType::logFromStatic";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class ClassWithLogType {
|
||||
LOG_CLASS(ClassWithLogType);
|
||||
public:
|
||||
|
|
@ -360,13 +366,13 @@ namespace
|
|||
return "ClassWithLogType::logFromStatic";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
std::string logFromNamespace(bool id) { return Foo::logFromNamespace(id); }
|
||||
std::string logFromClassWithNoLogTypeMember(bool id) { ClassWithNoLogType c; return c.logFromMember(id); }
|
||||
std::string logFromClassWithNoLogTypeStatic(bool id) { return ClassWithNoLogType::logFromStatic(id); }
|
||||
std::string logFromClassWithLogTypeMember(bool id) { ClassWithLogType c; return c.logFromMember(id); }
|
||||
std::string logFromClassWithLogTypeStatic(bool id) { return ClassWithLogType::logFromStatic(id); }
|
||||
|
||||
|
||||
void ensure_has(const std::string& message,
|
||||
const std::string& actual, const std::string& expected)
|
||||
{
|
||||
|
|
@ -379,18 +385,18 @@ namespace
|
|||
throw tut::failure(ss.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef std::string (*LogFromFunction)(bool);
|
||||
void testLogName(tut::TestRecorder& recorder, LogFromFunction f,
|
||||
void testLogName(tut::TestRecorder* recorder, LogFromFunction f,
|
||||
const std::string& class_name = "")
|
||||
{
|
||||
recorder.clearMessages();
|
||||
recorder->clearMessages();
|
||||
std::string name = f(false);
|
||||
f(true);
|
||||
|
||||
std::string messageWithoutName = recorder.message(0);
|
||||
std::string messageWithName = recorder.message(1);
|
||||
|
||||
|
||||
std::string messageWithoutName = recorder->message(0);
|
||||
std::string messageWithName = recorder->message(1);
|
||||
|
||||
ensure_has(name + " logged without name",
|
||||
messageWithoutName, name);
|
||||
ensure_has(name + " logged with name",
|
||||
|
|
@ -431,18 +437,18 @@ namespace
|
|||
llinfos << "inside" << llendl;
|
||||
return "moo";
|
||||
}
|
||||
|
||||
|
||||
std::string outerLogger()
|
||||
{
|
||||
llinfos << "outside(" << innerLogger() << ")" << llendl;
|
||||
return "bar";
|
||||
}
|
||||
|
||||
|
||||
void uberLogger()
|
||||
{
|
||||
llinfos << "uber(" << outerLogger() << "," << innerLogger() << ")" << llendl;
|
||||
}
|
||||
|
||||
|
||||
class LogWhileLogging
|
||||
{
|
||||
public:
|
||||
|
|
@ -461,11 +467,11 @@ namespace
|
|||
LogWhileLogging l;
|
||||
llinfos << "meta(" << l << ")" << llendl;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace tut
|
||||
{
|
||||
{
|
||||
template<> template<>
|
||||
// handle nested logging
|
||||
void ErrorTestObject::test<7>()
|
||||
|
|
@ -474,31 +480,31 @@ namespace tut
|
|||
ensure_message_contains(0, "inside");
|
||||
ensure_message_contains(1, "outside(moo)");
|
||||
ensure_message_count(2);
|
||||
|
||||
|
||||
uberLogger();
|
||||
ensure_message_contains(2, "inside");
|
||||
ensure_message_contains(3, "inside");
|
||||
ensure_message_contains(4, "outside(moo)");
|
||||
ensure_message_contains(5, "uber(bar,moo)");
|
||||
ensure_message_count(6);
|
||||
|
||||
|
||||
metaLogger();
|
||||
ensure_message_contains(6, "logging");
|
||||
ensure_message_contains(7, "meta(baz)");
|
||||
ensure_message_count(8);
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// special handling of llerrs calls
|
||||
void ErrorTestObject::test<8>()
|
||||
{
|
||||
LLError::setPrintLocation(false);
|
||||
std::string location = errorReturningLocation();
|
||||
|
||||
|
||||
ensure_message_contains(0, location + "error");
|
||||
ensure_message_contains(1, "die");
|
||||
ensure_message_count(2);
|
||||
|
||||
|
||||
ensure("fatal callback called", fatalWasCalled);
|
||||
}
|
||||
}
|
||||
|
|
@ -509,7 +515,7 @@ namespace
|
|||
{
|
||||
return "1947-07-08T03:04:05Z";
|
||||
}
|
||||
|
||||
|
||||
void ufoSighting()
|
||||
{
|
||||
llinfos << "ufo" << llendl;
|
||||
|
|
@ -517,35 +523,35 @@ namespace
|
|||
}
|
||||
|
||||
namespace tut
|
||||
{
|
||||
{
|
||||
template<> template<>
|
||||
// time in output (for recorders that need it)
|
||||
void ErrorTestObject::test<9>()
|
||||
{
|
||||
LLError::setTimeFunction(roswell);
|
||||
|
||||
mRecorder.setWantsTime(false);
|
||||
mRecorder->setWantsTime(false);
|
||||
ufoSighting();
|
||||
ensure_message_contains(0, "ufo");
|
||||
ensure_message_does_not_contain(0, roswell());
|
||||
|
||||
mRecorder.setWantsTime(true);
|
||||
|
||||
mRecorder->setWantsTime(true);
|
||||
ufoSighting();
|
||||
ensure_message_contains(1, "ufo");
|
||||
ensure_message_contains(1, roswell());
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// output order
|
||||
void ErrorTestObject::test<10>()
|
||||
{
|
||||
LLError::setPrintLocation(true);
|
||||
LLError::setTimeFunction(roswell);
|
||||
mRecorder.setWantsTime(true);
|
||||
mRecorder->setWantsTime(true);
|
||||
std::string locationAndFunction = writeReturningLocationAndFunction();
|
||||
|
||||
|
||||
ensure_equals("order is time type location function message",
|
||||
mRecorder.message(0),
|
||||
mRecorder->message(0),
|
||||
roswell() + " INFO: " + locationAndFunction + ": apple");
|
||||
}
|
||||
|
||||
|
|
@ -553,30 +559,30 @@ namespace tut
|
|||
// multiple recorders
|
||||
void ErrorTestObject::test<11>()
|
||||
{
|
||||
TestRecorder altRecorder;
|
||||
LLError::addRecorder(&altRecorder);
|
||||
|
||||
TestRecorder* altRecorder(new TestRecorder);
|
||||
LLError::addRecorder(altRecorder);
|
||||
|
||||
llinfos << "boo" << llendl;
|
||||
|
||||
ensure_message_contains(0, "boo");
|
||||
ensure_equals("alt recorder count", altRecorder.countMessages(), 1);
|
||||
ensure_contains("alt recorder message 0", altRecorder.message(0), "boo");
|
||||
|
||||
ensure_equals("alt recorder count", altRecorder->countMessages(), 1);
|
||||
ensure_contains("alt recorder message 0", altRecorder->message(0), "boo");
|
||||
|
||||
LLError::setTimeFunction(roswell);
|
||||
|
||||
TestRecorder anotherRecorder;
|
||||
anotherRecorder.setWantsTime(true);
|
||||
LLError::addRecorder(&anotherRecorder);
|
||||
|
||||
TestRecorder* anotherRecorder(new TestRecorder);
|
||||
anotherRecorder->setWantsTime(true);
|
||||
LLError::addRecorder(anotherRecorder);
|
||||
|
||||
llinfos << "baz" << llendl;
|
||||
|
||||
std::string when = roswell();
|
||||
|
||||
|
||||
ensure_message_does_not_contain(1, when);
|
||||
ensure_equals("alt recorder count", altRecorder.countMessages(), 2);
|
||||
ensure_does_not_contain("alt recorder message 1", altRecorder.message(1), when);
|
||||
ensure_equals("another recorder count", anotherRecorder.countMessages(), 1);
|
||||
ensure_contains("another recorder message 0", anotherRecorder.message(0), when);
|
||||
ensure_equals("alt recorder count", altRecorder->countMessages(), 2);
|
||||
ensure_does_not_contain("alt recorder message 1", altRecorder->message(1), when);
|
||||
ensure_equals("another recorder count", anotherRecorder->countMessages(), 1);
|
||||
ensure_contains("another recorder message 0", anotherRecorder->message(0), when);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -610,10 +616,10 @@ namespace tut
|
|||
{
|
||||
LLError::setDefaultLevel(LLError::LEVEL_WARN);
|
||||
LLError::setClassLevel("TestBeta", LLError::LEVEL_INFO);
|
||||
|
||||
|
||||
TestAlpha::doAll();
|
||||
TestBeta::doAll();
|
||||
|
||||
|
||||
ensure_message_contains(0, "aim west");
|
||||
ensure_message_contains(1, "error");
|
||||
ensure_message_contains(2, "ate eels");
|
||||
|
|
@ -623,7 +629,7 @@ namespace tut
|
|||
ensure_message_contains(6, "big easy");
|
||||
ensure_message_count(7);
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// filtering by function, and that it will override class filtering
|
||||
void ErrorTestObject::test<13>()
|
||||
|
|
@ -632,13 +638,13 @@ namespace tut
|
|||
LLError::setClassLevel("TestBeta", LLError::LEVEL_WARN);
|
||||
LLError::setFunctionLevel("TestBeta::doInfo", LLError::LEVEL_DEBUG);
|
||||
LLError::setFunctionLevel("TestBeta::doError", LLError::LEVEL_NONE);
|
||||
|
||||
|
||||
TestBeta::doAll();
|
||||
ensure_message_contains(0, "buy iron");
|
||||
ensure_message_contains(1, "bad word");
|
||||
ensure_message_count(2);
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// filtering by file
|
||||
// and that it is overridden by both class and function filtering
|
||||
|
|
@ -652,7 +658,7 @@ namespace tut
|
|||
LLError::LEVEL_NONE);
|
||||
LLError::setFunctionLevel("TestBeta::doError",
|
||||
LLError::LEVEL_NONE);
|
||||
|
||||
|
||||
TestAlpha::doAll();
|
||||
TestBeta::doAll();
|
||||
ensure_message_contains(0, "any idea");
|
||||
|
|
@ -660,7 +666,7 @@ namespace tut
|
|||
ensure_message_contains(2, "bad word");
|
||||
ensure_message_count(3);
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// proper cached, efficient lookup of filtering
|
||||
void ErrorTestObject::test<15>()
|
||||
|
|
@ -690,7 +696,7 @@ namespace tut
|
|||
ensure_message_count(2);
|
||||
ensure_equals("sixth check", LLError::shouldLogCallCount(), 3);
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
// configuration from LLSD
|
||||
void ErrorTestObject::test<16>()
|
||||
|
|
@ -699,26 +705,26 @@ namespace tut
|
|||
LLSD config;
|
||||
config["print-location"] = true;
|
||||
config["default-level"] = "DEBUG";
|
||||
|
||||
|
||||
LLSD set1;
|
||||
set1["level"] = "WARN";
|
||||
set1["files"][0] = this_file;
|
||||
|
||||
|
||||
LLSD set2;
|
||||
set2["level"] = "INFO";
|
||||
set2["classes"][0] = "TestAlpha";
|
||||
|
||||
|
||||
LLSD set3;
|
||||
set3["level"] = "NONE";
|
||||
set3["functions"][0] = "TestAlpha::doError";
|
||||
set3["functions"][1] = "TestBeta::doError";
|
||||
|
||||
|
||||
config["settings"][0] = set1;
|
||||
config["settings"][1] = set2;
|
||||
config["settings"][2] = set3;
|
||||
|
||||
|
||||
LLError::configure(config);
|
||||
|
||||
|
||||
TestAlpha::doAll();
|
||||
TestBeta::doAll();
|
||||
ensure_message_contains(0, "any idea");
|
||||
|
|
@ -726,13 +732,13 @@ namespace tut
|
|||
ensure_message_contains(1, "aim west");
|
||||
ensure_message_contains(2, "bad word");
|
||||
ensure_message_count(3);
|
||||
|
||||
|
||||
// make sure reconfiguring works
|
||||
LLSD config2;
|
||||
config2["default-level"] = "WARN";
|
||||
|
||||
|
||||
LLError::configure(config2);
|
||||
|
||||
|
||||
TestAlpha::doAll();
|
||||
TestBeta::doAll();
|
||||
ensure_message_contains(3, "aim west");
|
||||
|
|
@ -744,13 +750,13 @@ namespace tut
|
|||
ensure_message_contains(8, "big easy");
|
||||
ensure_message_count(9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Tests left:
|
||||
handling of classes without LOG_CLASS
|
||||
|
||||
live update of filtering from file
|
||||
|
||||
live update of filtering from file
|
||||
|
||||
syslog recorder
|
||||
file recorder
|
||||
cerr/stderr recorder
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm> // std::sort()
|
||||
#include <stdexcept>
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
|
@ -42,6 +43,11 @@
|
|||
#include "../test/lltut.h"
|
||||
#include "wrapllerrs.h"
|
||||
|
||||
struct Badness: public std::runtime_error
|
||||
{
|
||||
Badness(const std::string& what): std::runtime_error(what) {}
|
||||
};
|
||||
|
||||
struct Keyed: public LLInstanceTracker<Keyed, std::string>
|
||||
{
|
||||
Keyed(const std::string& name):
|
||||
|
|
@ -53,6 +59,17 @@ struct Keyed: public LLInstanceTracker<Keyed, std::string>
|
|||
|
||||
struct Unkeyed: public LLInstanceTracker<Unkeyed>
|
||||
{
|
||||
Unkeyed(const std::string& thrw="")
|
||||
{
|
||||
// LLInstanceTracker should respond appropriately if a subclass
|
||||
// constructor throws an exception. Specifically, it should run
|
||||
// LLInstanceTracker's destructor and remove itself from the
|
||||
// underlying container.
|
||||
if (! thrw.empty())
|
||||
{
|
||||
throw Badness(thrw);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
|
|
@ -95,6 +112,7 @@ namespace tut
|
|||
void object::test<2>()
|
||||
{
|
||||
ensure_equals(Unkeyed::instanceCount(), 0);
|
||||
Unkeyed* dangling = NULL;
|
||||
{
|
||||
Unkeyed one;
|
||||
ensure_equals(Unkeyed::instanceCount(), 1);
|
||||
|
|
@ -107,7 +125,11 @@ namespace tut
|
|||
ensure_equals(found, two.get());
|
||||
}
|
||||
ensure_equals(Unkeyed::instanceCount(), 1);
|
||||
}
|
||||
// store an unwise pointer to a temp Unkeyed instance
|
||||
dangling = &one;
|
||||
} // make that instance vanish
|
||||
// check the now-invalid pointer to the destroyed instance
|
||||
ensure("getInstance(T*) failed to track destruction", ! Unkeyed::getInstance(dangling));
|
||||
ensure_equals(Unkeyed::instanceCount(), 0);
|
||||
}
|
||||
|
||||
|
|
@ -229,4 +251,49 @@ namespace tut
|
|||
}
|
||||
ensure(! what.empty());
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<8>()
|
||||
{
|
||||
set_test_name("exception in subclass ctor");
|
||||
typedef std::set<Unkeyed*> InstanceSet;
|
||||
InstanceSet existing;
|
||||
// We can't use the iterator-range InstanceSet constructor because
|
||||
// beginInstances() returns an iterator that dereferences to an
|
||||
// Unkeyed&, not an Unkeyed*.
|
||||
for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()),
|
||||
ukend(Unkeyed::endInstances());
|
||||
uki != ukend; ++uki)
|
||||
{
|
||||
existing.insert(&*uki);
|
||||
}
|
||||
Unkeyed* puk = NULL;
|
||||
try
|
||||
{
|
||||
// We don't expect the assignment to take place because we expect
|
||||
// Unkeyed to respond to the non-empty string param by throwing.
|
||||
// We know the LLInstanceTracker base-class constructor will have
|
||||
// run before Unkeyed's constructor, therefore the new instance
|
||||
// will have added itself to the underlying set. The whole
|
||||
// question is, when Unkeyed's constructor throws, will
|
||||
// LLInstanceTracker's destructor remove it from the set? I
|
||||
// realize we're testing the C++ implementation more than
|
||||
// Unkeyed's implementation, but this seems an important point to
|
||||
// nail down.
|
||||
puk = new Unkeyed("throw");
|
||||
}
|
||||
catch (const Badness&)
|
||||
{
|
||||
}
|
||||
// Ensure that every member of the new, updated set of Unkeyed
|
||||
// instances was also present in the original set. If that's not true,
|
||||
// it's because our new Unkeyed ended up in the updated set despite
|
||||
// its constructor exception.
|
||||
for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()),
|
||||
ukend(Unkeyed::endInstances());
|
||||
uki != ukend; ++uki)
|
||||
{
|
||||
ensure("failed to remove instance", existing.find(&*uki) != existing.end());
|
||||
}
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
|
|||
|
|
@ -0,0 +1,694 @@
|
|||
/**
|
||||
* @file llleap_test.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-02-21
|
||||
* @brief Test for llleap.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llleap.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include <boost/lambda/lambda.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "../test/namedtempfile.h"
|
||||
#include "../test/manageapr.h"
|
||||
#include "../test/catch_and_store_what_in.h"
|
||||
#include "wrapllerrs.h"
|
||||
#include "llevents.h"
|
||||
#include "llprocess.h"
|
||||
#include "stringize.h"
|
||||
#include "StringVec.h"
|
||||
#include <functional>
|
||||
|
||||
using boost::assign::list_of;
|
||||
|
||||
static ManageAPR manager;
|
||||
|
||||
StringVec sv(const StringVec& listof) { return listof; }
|
||||
|
||||
#if defined(LL_WINDOWS)
|
||||
#define sleep(secs) _sleep((secs) * 1000)
|
||||
#endif
|
||||
|
||||
#if ! LL_WINDOWS
|
||||
const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte of data
|
||||
#else
|
||||
// "Then there's Windows... sigh." The "very large message" test is flaky in a
|
||||
// way that seems to point to either the OS (nonblocking writes to pipes) or
|
||||
// possibly the apr_file_write() function. Poring over log messages reveals
|
||||
// that at some point along the way apr_file_write() returns 11 (Resource
|
||||
// temporarily unavailable, i.e. EAGAIN) and says it wrote 0 bytes -- even
|
||||
// though it did write the chunk! Our next write attempt retries the same
|
||||
// chunk, resulting in the chunk being duplicated at the child end, corrupting
|
||||
// the data stream. Much as I would love to be able to fix it for real, such a
|
||||
// fix would appear to require distinguishing bogus EAGAIN returns from real
|
||||
// ones -- how?? Empirically this behavior is only observed when writing a
|
||||
// "very large message". To be able to move forward at all, try to bypass this
|
||||
// particular failure by adjusting the size of a "very large message" on
|
||||
// Windows.
|
||||
const size_t BUFFERED_LENGTH = 65336;
|
||||
#endif // LL_WINDOWS
|
||||
|
||||
void waitfor(const std::vector<LLLeap*>& instances, int timeout=60)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < timeout; ++i)
|
||||
{
|
||||
// Every iteration, test whether any of the passed LLLeap instances
|
||||
// still exist (are still running).
|
||||
std::vector<LLLeap*>::const_iterator vli(instances.begin()), vlend(instances.end());
|
||||
for ( ; vli != vlend; ++vli)
|
||||
{
|
||||
// getInstance() returns NULL if it's terminated/gone, non-NULL if
|
||||
// it's still running
|
||||
if (LLLeap::getInstance(*vli))
|
||||
break;
|
||||
}
|
||||
// If we made it through all of 'instances' without finding one that's
|
||||
// still running, we're done.
|
||||
if (vli == vlend)
|
||||
{
|
||||
/*==========================================================================*|
|
||||
std::cout << instances.size() << " LLLeap instances terminated in "
|
||||
<< i << " seconds, proceeding" << std::endl;
|
||||
|*==========================================================================*/
|
||||
return;
|
||||
}
|
||||
// Found an instance that's still running. Wait and pump LLProcess.
|
||||
sleep(1);
|
||||
LLEventPumps::instance().obtain("mainloop").post(LLSD());
|
||||
}
|
||||
tut::ensure(STRINGIZE("at least 1 of " << instances.size()
|
||||
<< " LLLeap instances timed out ("
|
||||
<< timeout << " seconds) without terminating"),
|
||||
i < timeout);
|
||||
}
|
||||
|
||||
void waitfor(LLLeap* instance, int timeout=60)
|
||||
{
|
||||
std::vector<LLLeap*> instances;
|
||||
instances.push_back(instance);
|
||||
waitfor(instances, timeout);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* TUT
|
||||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
struct llleap_data
|
||||
{
|
||||
llleap_data():
|
||||
reader(".py",
|
||||
// This logic is adapted from vita.viewerclient.receiveEvent()
|
||||
boost::lambda::_1 <<
|
||||
"import re\n"
|
||||
"import os\n"
|
||||
"import sys\n"
|
||||
"\n"
|
||||
// Don't forget that this Python script is written to some
|
||||
// temp directory somewhere! Its __file__ is useless in
|
||||
// finding indra/lib/python. Use our __FILE__, with
|
||||
// raw-string syntax to deal with Windows pathnames.
|
||||
"mydir = os.path.dirname(r'" << __FILE__ << "')\n"
|
||||
"try:\n"
|
||||
" from llbase import llsd\n"
|
||||
"except ImportError:\n"
|
||||
// We expect mydir to be .../indra/llcommon/tests.
|
||||
" sys.path.insert(0,\n"
|
||||
" os.path.join(mydir, os.pardir, os.pardir, 'lib', 'python'))\n"
|
||||
" from indra.base import llsd\n"
|
||||
"\n"
|
||||
"class ProtocolError(Exception):\n"
|
||||
" def __init__(self, msg, data):\n"
|
||||
" Exception.__init__(self, msg)\n"
|
||||
" self.data = data\n"
|
||||
"\n"
|
||||
"class ParseError(ProtocolError):\n"
|
||||
" pass\n"
|
||||
"\n"
|
||||
"def get():\n"
|
||||
" hdr = ''\n"
|
||||
" while ':' not in hdr and len(hdr) < 20:\n"
|
||||
" hdr += sys.stdin.read(1)\n"
|
||||
" if not hdr:\n"
|
||||
" sys.exit(0)\n"
|
||||
" if not hdr.endswith(':'):\n"
|
||||
" raise ProtocolError('Expected len:data, got %r' % hdr, hdr)\n"
|
||||
" try:\n"
|
||||
" length = int(hdr[:-1])\n"
|
||||
" except ValueError:\n"
|
||||
" raise ProtocolError('Non-numeric len %r' % hdr[:-1], hdr[:-1])\n"
|
||||
" parts = []\n"
|
||||
" received = 0\n"
|
||||
" while received < length:\n"
|
||||
" parts.append(sys.stdin.read(length - received))\n"
|
||||
" received += len(parts[-1])\n"
|
||||
" data = ''.join(parts)\n"
|
||||
" assert len(data) == length\n"
|
||||
" try:\n"
|
||||
" return llsd.parse(data)\n"
|
||||
// Seems the old indra.base.llsd module didn't properly
|
||||
// convert IndexError (from running off end of string) to
|
||||
// LLSDParseError.
|
||||
" except (IndexError, llsd.LLSDParseError), e:\n"
|
||||
" msg = 'Bad received packet (%s)' % e\n"
|
||||
" print >>sys.stderr, '%s, %s bytes:' % (msg, len(data))\n"
|
||||
" showmax = 40\n"
|
||||
// We've observed failures with very large packets;
|
||||
// dumping the entire packet wastes time and space.
|
||||
// But if the error states a particular byte offset,
|
||||
// truncate to (near) that offset when dumping data.
|
||||
" location = re.search(r' at (byte|index) ([0-9]+)', str(e))\n"
|
||||
" if not location:\n"
|
||||
" # didn't find offset, dump whole thing, no ellipsis\n"
|
||||
" ellipsis = ''\n"
|
||||
" else:\n"
|
||||
" # found offset within error message\n"
|
||||
" trunc = int(location.group(2)) + showmax\n"
|
||||
" data = data[:trunc]\n"
|
||||
" ellipsis = '... (%s more)' % (length - trunc)\n"
|
||||
" offset = -showmax\n"
|
||||
" for offset in xrange(0, len(data)-showmax, showmax):\n"
|
||||
" print >>sys.stderr, '%04d: %r +' % \\\n"
|
||||
" (offset, data[offset:offset+showmax])\n"
|
||||
" offset += showmax\n"
|
||||
" print >>sys.stderr, '%04d: %r%s' % \\\n"
|
||||
" (offset, data[offset:], ellipsis)\n"
|
||||
" raise ParseError(msg, data)\n"
|
||||
"\n"
|
||||
"# deal with initial stdin message\n"
|
||||
// this will throw if the initial write to stdin doesn't
|
||||
// follow len:data protocol, or if we couldn't find 'pump'
|
||||
// in the dict
|
||||
"_reply = get()['pump']\n"
|
||||
"\n"
|
||||
"def replypump():\n"
|
||||
" return _reply\n"
|
||||
"\n"
|
||||
"def put(req):\n"
|
||||
" sys.stdout.write(':'.join((str(len(req)), req)))\n"
|
||||
" sys.stdout.flush()\n"
|
||||
"\n"
|
||||
"def send(pump, data):\n"
|
||||
" put(llsd.format_notation(dict(pump=pump, data=data)))\n"
|
||||
"\n"
|
||||
"def request(pump, data):\n"
|
||||
" # we expect 'data' is a dict\n"
|
||||
" data['reply'] = _reply\n"
|
||||
" send(pump, data)\n"),
|
||||
// Get the actual pathname of the NamedExtTempFile and trim off
|
||||
// the ".py" extension. (We could cache reader.getName() in a
|
||||
// separate member variable, but I happen to know getName() just
|
||||
// returns a NamedExtTempFile member rather than performing any
|
||||
// computation, so I don't mind calling it twice.) Then take the
|
||||
// basename.
|
||||
reader_module(LLProcess::basename(
|
||||
reader.getName().substr(0, reader.getName().length()-3))),
|
||||
pPYTHON(getenv("PYTHON")),
|
||||
PYTHON(pPYTHON? pPYTHON : "")
|
||||
{
|
||||
ensure("Set PYTHON to interpreter pathname", pPYTHON);
|
||||
}
|
||||
NamedExtTempFile reader;
|
||||
const std::string reader_module;
|
||||
const char* pPYTHON;
|
||||
const std::string PYTHON;
|
||||
};
|
||||
typedef test_group<llleap_data> llleap_group;
|
||||
typedef llleap_group::object object;
|
||||
llleap_group llleapgrp("llleap");
|
||||
|
||||
template<> template<>
|
||||
void object::test<1>()
|
||||
{
|
||||
set_test_name("multiple LLLeap instances");
|
||||
NamedTempFile script("py",
|
||||
"import time\n"
|
||||
"time.sleep(1)\n");
|
||||
std::vector<LLLeap*> instances;
|
||||
instances.push_back(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
instances.push_back(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
// In this case we're simply establishing that two LLLeap instances
|
||||
// can coexist without throwing exceptions or bombing in any other
|
||||
// way. Wait for them to terminate.
|
||||
waitfor(instances);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<2>()
|
||||
{
|
||||
set_test_name("stderr to log");
|
||||
NamedTempFile script("py",
|
||||
"import sys\n"
|
||||
"sys.stderr.write('''Hello from Python!\n"
|
||||
"note partial line''')\n");
|
||||
CaptureLog log(LLError::LEVEL_INFO);
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
log.messageWith("Hello from Python!");
|
||||
log.messageWith("note partial line");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<3>()
|
||||
{
|
||||
set_test_name("bad stdout protocol");
|
||||
NamedTempFile script("py",
|
||||
"print 'Hello from Python!'\n");
|
||||
CaptureLog log(LLError::LEVEL_WARN);
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
ensure_contains("error log line",
|
||||
log.messageWith("invalid protocol"), "Hello from Python!");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<4>()
|
||||
{
|
||||
set_test_name("leftover stdout");
|
||||
NamedTempFile script("py",
|
||||
"import sys\n"
|
||||
// note lack of newline
|
||||
"sys.stdout.write('Hello from Python!')\n");
|
||||
CaptureLog log(LLError::LEVEL_WARN);
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
ensure_contains("error log line",
|
||||
log.messageWith("Discarding"), "Hello from Python!");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<5>()
|
||||
{
|
||||
set_test_name("bad stdout len prefix");
|
||||
NamedTempFile script("py",
|
||||
"import sys\n"
|
||||
"sys.stdout.write('5a2:something')\n");
|
||||
CaptureLog log(LLError::LEVEL_WARN);
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
ensure_contains("error log line",
|
||||
log.messageWith("invalid protocol"), "5a2:");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<6>()
|
||||
{
|
||||
set_test_name("empty plugin vector");
|
||||
std::string threw;
|
||||
try
|
||||
{
|
||||
LLLeap::create("empty", StringVec());
|
||||
}
|
||||
CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error)
|
||||
ensure_contains("LLLeap::Error", threw, "no plugin");
|
||||
// try the suppress-exception variant
|
||||
ensure("bad launch returned non-NULL", ! LLLeap::create("empty", StringVec(), false));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<7>()
|
||||
{
|
||||
set_test_name("bad launch");
|
||||
// Synthesize bogus executable name
|
||||
std::string BADPYTHON(PYTHON.substr(0, PYTHON.length()-1) + "x");
|
||||
CaptureLog log;
|
||||
std::string threw;
|
||||
try
|
||||
{
|
||||
LLLeap::create("bad exe", BADPYTHON);
|
||||
}
|
||||
CATCH_AND_STORE_WHAT_IN(threw, LLLeap::Error)
|
||||
ensure_contains("LLLeap::create() didn't throw", threw, "failed");
|
||||
log.messageWith("failed");
|
||||
log.messageWith(BADPYTHON);
|
||||
// try the suppress-exception variant
|
||||
ensure("bad launch returned non-NULL", ! LLLeap::create("bad exe", BADPYTHON, false));
|
||||
}
|
||||
|
||||
// Generic self-contained listener: derive from this and override its
|
||||
// call() method, then tell somebody to post on the pump named getName().
|
||||
// Control will reach your call() override.
|
||||
struct ListenerBase
|
||||
{
|
||||
// Pass the pump name you want; will tweak for uniqueness.
|
||||
ListenerBase(const std::string& name):
|
||||
mPump(name, true)
|
||||
{
|
||||
mPump.listen(name, boost::bind(&ListenerBase::call, this, _1));
|
||||
}
|
||||
|
||||
virtual ~ListenerBase() {} // pacify MSVC
|
||||
|
||||
virtual bool call(const LLSD& request)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LLEventPump& getPump() { return mPump; }
|
||||
const LLEventPump& getPump() const { return mPump; }
|
||||
|
||||
std::string getName() const { return mPump.getName(); }
|
||||
void post(const LLSD& data) { mPump.post(data); }
|
||||
|
||||
LLEventStream mPump;
|
||||
};
|
||||
|
||||
// Mimic a dummy little LLEventAPI that merely sends a reply back to its
|
||||
// requester on the "reply" pump.
|
||||
struct AckAPI: public ListenerBase
|
||||
{
|
||||
AckAPI(): ListenerBase("AckAPI") {}
|
||||
|
||||
virtual bool call(const LLSD& request)
|
||||
{
|
||||
LLEventPumps::instance().obtain(request["reply"]).post("ack");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Give LLLeap script a way to post success/failure.
|
||||
struct Result: public ListenerBase
|
||||
{
|
||||
Result(): ListenerBase("Result") {}
|
||||
|
||||
virtual bool call(const LLSD& request)
|
||||
{
|
||||
mData = request;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ensure() const
|
||||
{
|
||||
tut::ensure(std::string("never posted to ") + getName(), mData.isDefined());
|
||||
// Post an empty string for success, non-empty string is failure message.
|
||||
tut::ensure(mData, mData.asString().empty());
|
||||
}
|
||||
|
||||
LLSD mData;
|
||||
};
|
||||
|
||||
template<> template<>
|
||||
void object::test<8>()
|
||||
{
|
||||
set_test_name("round trip");
|
||||
AckAPI api;
|
||||
Result result;
|
||||
NamedTempFile script("py",
|
||||
boost::lambda::_1 <<
|
||||
"from " << reader_module << " import *\n"
|
||||
// make a request on our little API
|
||||
"request(pump='" << api.getName() << "', data={})\n"
|
||||
// wait for its response
|
||||
"resp = get()\n"
|
||||
"result = '' if resp == dict(pump=replypump(), data='ack')\\\n"
|
||||
" else 'bad: ' + str(resp)\n"
|
||||
"send(pump='" << result.getName() << "', data=result)\n");
|
||||
waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))));
|
||||
result.ensure();
|
||||
}
|
||||
|
||||
struct ReqIDAPI: public ListenerBase
|
||||
{
|
||||
ReqIDAPI(): ListenerBase("ReqIDAPI") {}
|
||||
|
||||
virtual bool call(const LLSD& request)
|
||||
{
|
||||
// free function from llevents.h
|
||||
sendReply(LLSD(), request);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template<> template<>
|
||||
void object::test<9>()
|
||||
{
|
||||
set_test_name("many small messages");
|
||||
// It's not clear to me whether there's value in iterating many times
|
||||
// over a send/receive loop -- I don't think that will exercise any
|
||||
// interesting corner cases. This test first sends a large number of
|
||||
// messages, then receives all the responses. The intent is to ensure
|
||||
// that some of that data stream crosses buffer boundaries, loop
|
||||
// iterations etc. in OS pipes and the LLLeap/LLProcess implementation.
|
||||
ReqIDAPI api;
|
||||
Result result;
|
||||
NamedTempFile script("py",
|
||||
boost::lambda::_1 <<
|
||||
"import sys\n"
|
||||
"from " << reader_module << " import *\n"
|
||||
// Note that since reader imports llsd, this
|
||||
// 'import *' gets us llsd too.
|
||||
"sample = llsd.format_notation(dict(pump='" <<
|
||||
api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n"
|
||||
// The whole packet has length prefix too: "len:data"
|
||||
"samplen = len(str(len(sample))) + 1 + len(sample)\n"
|
||||
// guess how many messages it will take to
|
||||
// accumulate BUFFERED_LENGTH
|
||||
"count = int(" << BUFFERED_LENGTH << "/samplen)\n"
|
||||
"print >>sys.stderr, 'Sending %s requests' % count\n"
|
||||
"for i in xrange(count):\n"
|
||||
" request('" << api.getName() << "', dict(reqid=i))\n"
|
||||
// The assumption in this specific test that
|
||||
// replies will arrive in the same order as
|
||||
// requests is ONLY valid because the API we're
|
||||
// invoking sends replies instantly. If the API
|
||||
// had to wait for some external event before
|
||||
// sending its reply, replies could arrive in
|
||||
// arbitrary order, and we'd have to tick them
|
||||
// off from a set.
|
||||
"result = ''\n"
|
||||
"for i in xrange(count):\n"
|
||||
" resp = get()\n"
|
||||
" if resp['data']['reqid'] != i:\n"
|
||||
" result = 'expected reqid=%s in %s' % (i, resp)\n"
|
||||
" break\n"
|
||||
"send(pump='" << result.getName() << "', data=result)\n");
|
||||
waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))),
|
||||
300); // needs more realtime than most tests
|
||||
result.ensure();
|
||||
}
|
||||
|
||||
// This is the body of test<10>, extracted so we can run it over a number
|
||||
// of large-message sizes.
|
||||
void test_large_message(const std::string& PYTHON, const std::string& reader_module,
|
||||
const std::string& test_name, size_t size)
|
||||
{
|
||||
ReqIDAPI api;
|
||||
Result result;
|
||||
NamedTempFile script("py",
|
||||
boost::lambda::_1 <<
|
||||
"import sys\n"
|
||||
"from " << reader_module << " import *\n"
|
||||
// Generate a very large string value.
|
||||
"desired = int(sys.argv[1])\n"
|
||||
// 7 chars per item: 6 digits, 1 comma
|
||||
"count = int((desired - 50)/7)\n"
|
||||
"large = ''.join('%06d,' % i for i in xrange(count))\n"
|
||||
// Pass 'large' as reqid because we know the API
|
||||
// will echo reqid, and we want to receive it back.
|
||||
"request('" << api.getName() << "', dict(reqid=large))\n"
|
||||
"try:\n"
|
||||
" resp = get()\n"
|
||||
"except ParseError, e:\n"
|
||||
" # try to find where e.data diverges from expectation\n"
|
||||
// Normally we'd expect a 'pump' key in there,
|
||||
// too, with value replypump(). But Python
|
||||
// serializes keys in a different order than C++,
|
||||
// so incoming data start with 'data'.
|
||||
// Truthfully, though, if we get as far as 'pump'
|
||||
// before we find a difference, something's very
|
||||
// strange.
|
||||
" expect = llsd.format_notation(dict(data=dict(reqid=large)))\n"
|
||||
" chunk = 40\n"
|
||||
" for offset in xrange(0, max(len(e.data), len(expect)), chunk):\n"
|
||||
" if e.data[offset:offset+chunk] != \\\n"
|
||||
" expect[offset:offset+chunk]:\n"
|
||||
" print >>sys.stderr, 'Offset %06d: expect %r,\\n'\\\n"
|
||||
" ' get %r' %\\\n"
|
||||
" (offset,\n"
|
||||
" expect[offset:offset+chunk],\n"
|
||||
" e.data[offset:offset+chunk])\n"
|
||||
" break\n"
|
||||
" else:\n"
|
||||
" print >>sys.stderr, 'incoming data matches expect?!'\n"
|
||||
" send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n"
|
||||
" sys.exit(1)\n"
|
||||
"\n"
|
||||
"echoed = resp['data']['reqid']\n"
|
||||
"if echoed == large:\n"
|
||||
" send('" << result.getName() << "', '')\n"
|
||||
" sys.exit(0)\n"
|
||||
// Here we know echoed did NOT match; try to find where
|
||||
"for i in xrange(count):\n"
|
||||
" start = 7*i\n"
|
||||
" end = 7*(i+1)\n"
|
||||
" if end > len(echoed)\\\n"
|
||||
" or echoed[start:end] != large[start:end]:\n"
|
||||
" send('" << result.getName() << "',\n"
|
||||
" 'at offset %s, expected %r but got %r' %\n"
|
||||
" (start, large[start:end], echoed[start:end]))\n"
|
||||
"sys.exit(1)\n");
|
||||
waitfor(LLLeap::create(test_name,
|
||||
sv(list_of
|
||||
(PYTHON)
|
||||
(script.getName())
|
||||
(stringize(size)))),
|
||||
180); // try a longer timeout
|
||||
result.ensure();
|
||||
}
|
||||
|
||||
struct TestLargeMessage: public std::binary_function<size_t, size_t, bool>
|
||||
{
|
||||
TestLargeMessage(const std::string& PYTHON_, const std::string& reader_module_,
|
||||
const std::string& test_name_):
|
||||
PYTHON(PYTHON_),
|
||||
reader_module(reader_module_),
|
||||
test_name(test_name_)
|
||||
{}
|
||||
|
||||
bool operator()(size_t left, size_t right) const
|
||||
{
|
||||
// We don't know whether upper_bound is going to pass the "sought
|
||||
// value" as the left or the right operand. We pass 0 as the
|
||||
// "sought value" so we can distinguish it. Of course that means
|
||||
// the sequence we're searching must not itself contain 0!
|
||||
size_t size;
|
||||
bool success;
|
||||
if (left)
|
||||
{
|
||||
size = left;
|
||||
// Consider our return value carefully. Normal binary_search
|
||||
// (or, in our case, upper_bound) expects a container sorted
|
||||
// in ascending order, and defaults to the std::less
|
||||
// comparator. Our container is in fact in ascending order, so
|
||||
// return consistently with std::less. Here we were called as
|
||||
// compare(item, sought). If std::less were called that way,
|
||||
// 'true' would mean to move right (to higher numbers) within
|
||||
// the sequence: the item being considered is less than the
|
||||
// sought value. For us, that means that test_large_message()
|
||||
// success should return 'true'.
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
size = right;
|
||||
// Here we were called as compare(sought, item). If std::less
|
||||
// were called that way, 'true' would mean to move left (to
|
||||
// lower numbers) within the sequence: the sought value is
|
||||
// less than the item being considered. For us, that means
|
||||
// test_large_message() FAILURE should return 'true', hence
|
||||
// test_large_message() success should return 'false'.
|
||||
success = false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
test_large_message(PYTHON, reader_module, test_name, size);
|
||||
std::cout << "test_large_message(" << size << ") succeeded" << std::endl;
|
||||
return success;
|
||||
}
|
||||
catch (const failure& e)
|
||||
{
|
||||
std::cout << "test_large_message(" << size << ") failed: " << e.what() << std::endl;
|
||||
return ! success;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string PYTHON, reader_module, test_name;
|
||||
};
|
||||
|
||||
// The point of this function is to try to find a size at which
|
||||
// test_large_message() can succeed. We still want the overall test to
|
||||
// fail; otherwise we won't get the coder's attention -- but if
|
||||
// test_large_message() fails, try to find a plausible size at which it
|
||||
// DOES work.
|
||||
void test_or_split(const std::string& PYTHON, const std::string& reader_module,
|
||||
const std::string& test_name, size_t size)
|
||||
{
|
||||
try
|
||||
{
|
||||
test_large_message(PYTHON, reader_module, test_name, size);
|
||||
}
|
||||
catch (const failure& e)
|
||||
{
|
||||
std::cout << "test_large_message(" << size << ") failed: " << e.what() << std::endl;
|
||||
// If it still fails below 4K, give up: subdividing any further is
|
||||
// pointless.
|
||||
if (size >= 4096)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Recur with half the size
|
||||
size_t smaller(size/2);
|
||||
test_or_split(PYTHON, reader_module, test_name, smaller);
|
||||
// Recursive call will throw if test_large_message()
|
||||
// failed, therefore we only reach the line below if it
|
||||
// succeeded.
|
||||
std::cout << "but test_large_message(" << smaller << ") succeeded" << std::endl;
|
||||
|
||||
// Binary search for largest size that works. But since
|
||||
// std::binary_search() only returns bool, actually use
|
||||
// std::upper_bound(), consistent with our desire to find
|
||||
// the LARGEST size that works. First generate a sorted
|
||||
// container of all the sizes we intend to try, from
|
||||
// 'smaller' (known to work) to 'size' (known to fail). We
|
||||
// could whomp up magic iterators to do this dynamically,
|
||||
// without actually instantiating a vector, but for a test
|
||||
// program this will do. At least preallocate the vector.
|
||||
// Per TestLargeMessage comments, it's important that this
|
||||
// vector not contain 0.
|
||||
std::vector<size_t> sizes;
|
||||
sizes.reserve((size - smaller)/4096 + 1);
|
||||
for (size_t sz(smaller), szend(size); sz < szend; sz += 4096)
|
||||
sizes.push_back(sz);
|
||||
// our comparator
|
||||
TestLargeMessage tester(PYTHON, reader_module, test_name);
|
||||
// Per TestLargeMessage comments, pass 0 as the sought value.
|
||||
std::vector<size_t>::const_iterator found =
|
||||
std::upper_bound(sizes.begin(), sizes.end(), 0, tester);
|
||||
if (found != sizes.end() && found != sizes.begin())
|
||||
{
|
||||
std::cout << "test_large_message(" << *(found - 1)
|
||||
<< ") is largest that succeeds" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "cannot determine largest test_large_message(size) "
|
||||
<< "that succeeds" << std::endl;
|
||||
}
|
||||
}
|
||||
catch (const failure&)
|
||||
{
|
||||
// The recursive test_or_split() call above has already
|
||||
// handled the exception. We don't want our caller to see
|
||||
// innermost exception; propagate outermost (below).
|
||||
}
|
||||
}
|
||||
// In any case, because we reached here through failure of
|
||||
// our original test_large_message(size) call, ensure failure
|
||||
// propagates.
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<10>()
|
||||
{
|
||||
set_test_name("very large message");
|
||||
test_or_split(PYTHON, reader_module, get_test_name(), BUFFERED_LENGTH);
|
||||
}
|
||||
} // namespace tut
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -40,41 +40,15 @@ typedef U32 uint32_t;
|
|||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "llprocesslauncher.h"
|
||||
#include "llprocess.h"
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
|
||||
/*==========================================================================*|
|
||||
// Whoops, seems Linden's Boost package and the viewer are built with
|
||||
// different settings of VC's /Zc:wchar_t switch! Using Boost.Filesystem
|
||||
// pathname operations produces Windows link errors:
|
||||
// unresolved external symbol "private: static class std::codecvt<unsigned short,
|
||||
// char,int> const * & __cdecl boost::filesystem3::path::wchar_t_codecvt_facet()"
|
||||
// unresolved external symbol "void __cdecl boost::filesystem3::path_traits::convert()"
|
||||
// See:
|
||||
// http://boost.2283326.n4.nabble.com/filesystem-v3-unicode-and-std-codecvt-linker-error-td3455549.html
|
||||
// which points to:
|
||||
// http://msdn.microsoft.com/en-us/library/dh8che7s%28v=VS.100%29.aspx
|
||||
|
||||
// As we're not trying to preserve compatibility with old Boost.Filesystem
|
||||
// code, but rather writing brand-new code, use the newest available
|
||||
// Filesystem API.
|
||||
#define BOOST_FILESYSTEM_VERSION 3
|
||||
#include "boost/filesystem.hpp"
|
||||
#include "boost/filesystem/v3/fstream.hpp"
|
||||
|*==========================================================================*/
|
||||
#include "boost/range.hpp"
|
||||
#include "boost/foreach.hpp"
|
||||
#include "boost/function.hpp"
|
||||
#include "boost/lambda/lambda.hpp"
|
||||
#include "boost/lambda/bind.hpp"
|
||||
namespace lambda = boost::lambda;
|
||||
/*==========================================================================*|
|
||||
// Aaaarrgh, Linden's Boost package doesn't even include Boost.Iostreams!
|
||||
#include "boost/iostreams/stream.hpp"
|
||||
#include "boost/iostreams/device/file_descriptor.hpp"
|
||||
|*==========================================================================*/
|
||||
|
||||
#include "../llsd.h"
|
||||
#include "../llsdserialize.h"
|
||||
|
|
@ -82,236 +56,17 @@ namespace lambda = boost::lambda;
|
|||
#include "../llformat.h"
|
||||
|
||||
#include "../test/lltut.h"
|
||||
#include "../test/manageapr.h"
|
||||
#include "../test/namedtempfile.h"
|
||||
#include "stringize.h"
|
||||
|
||||
static ManageAPR manager;
|
||||
|
||||
std::vector<U8> string_to_vector(const std::string& str)
|
||||
{
|
||||
return std::vector<U8>(str.begin(), str.end());
|
||||
}
|
||||
|
||||
#if ! LL_WINDOWS
|
||||
// We want to call strerror_r(), but alarmingly, there are two different
|
||||
// variants. The one that returns int always populates the passed buffer
|
||||
// (except in case of error), whereas the other one always returns a valid
|
||||
// char* but might or might not populate the passed buffer. How do we know
|
||||
// which one we're getting? Define adapters for each and let the compiler
|
||||
// select the applicable adapter.
|
||||
|
||||
// strerror_r() returns char*
|
||||
std::string message_from(int /*orig_errno*/, const char* /*buffer*/, const char* strerror_ret)
|
||||
{
|
||||
return strerror_ret;
|
||||
}
|
||||
|
||||
// strerror_r() returns int
|
||||
std::string message_from(int orig_errno, const char* buffer, int strerror_ret)
|
||||
{
|
||||
if (strerror_ret == 0)
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
// Here strerror_r() has set errno. Since strerror_r() has already failed,
|
||||
// seems like a poor bet to call it again to diagnose its own error...
|
||||
int stre_errno = errno;
|
||||
if (stre_errno == ERANGE)
|
||||
{
|
||||
return STRINGIZE("strerror_r() can't explain errno " << orig_errno
|
||||
<< " (buffer too small)");
|
||||
}
|
||||
if (stre_errno == EINVAL)
|
||||
{
|
||||
return STRINGIZE("unknown errno " << orig_errno);
|
||||
}
|
||||
// Here we don't even understand the errno from strerror_r()!
|
||||
return STRINGIZE("strerror_r() can't explain errno " << orig_errno
|
||||
<< " (error " << stre_errno << ')');
|
||||
}
|
||||
#endif // ! LL_WINDOWS
|
||||
|
||||
// boost::filesystem::temp_directory_path() isn't yet in Boost 1.45! :-(
|
||||
std::string temp_directory_path()
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
char buffer[4096];
|
||||
GetTempPathA(sizeof(buffer), buffer);
|
||||
return buffer;
|
||||
|
||||
#else // LL_DARWIN, LL_LINUX
|
||||
static const char* vars[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR" };
|
||||
BOOST_FOREACH(const char* var, vars)
|
||||
{
|
||||
const char* found = getenv(var);
|
||||
if (found)
|
||||
return found;
|
||||
}
|
||||
return "/tmp";
|
||||
#endif // LL_DARWIN, LL_LINUX
|
||||
}
|
||||
|
||||
// Windows presents a kinda sorta compatibility layer. Code to the yucky
|
||||
// Windows names because they're less likely than the Posix names to collide
|
||||
// with any other names in this source.
|
||||
#if LL_WINDOWS
|
||||
#define _remove DeleteFileA
|
||||
#else // ! LL_WINDOWS
|
||||
#define _open open
|
||||
#define _write write
|
||||
#define _close close
|
||||
#define _remove remove
|
||||
#endif // ! LL_WINDOWS
|
||||
|
||||
// Create a text file with specified content "somewhere in the
|
||||
// filesystem," cleaning up when it goes out of scope.
|
||||
class NamedTempFile
|
||||
{
|
||||
public:
|
||||
// Function that accepts an ostream ref and (presumably) writes stuff to
|
||||
// it, e.g.:
|
||||
// (lambda::_1 << "the value is " << 17 << '\n')
|
||||
typedef boost::function<void(std::ostream&)> Streamer;
|
||||
|
||||
NamedTempFile(const std::string& ext, const std::string& content):
|
||||
mPath(temp_directory_path())
|
||||
{
|
||||
createFile(ext, lambda::_1 << content);
|
||||
}
|
||||
|
||||
// Disambiguate when passing string literal
|
||||
NamedTempFile(const std::string& ext, const char* content):
|
||||
mPath(temp_directory_path())
|
||||
{
|
||||
createFile(ext, lambda::_1 << content);
|
||||
}
|
||||
|
||||
NamedTempFile(const std::string& ext, const Streamer& func):
|
||||
mPath(temp_directory_path())
|
||||
{
|
||||
createFile(ext, func);
|
||||
}
|
||||
|
||||
~NamedTempFile()
|
||||
{
|
||||
_remove(mPath.c_str());
|
||||
}
|
||||
|
||||
std::string getName() const { return mPath; }
|
||||
|
||||
private:
|
||||
void createFile(const std::string& ext, const Streamer& func)
|
||||
{
|
||||
// Silly maybe, but use 'ext' as the name prefix. Strip off a leading
|
||||
// '.' if present.
|
||||
int pfx_offset = ((! ext.empty()) && ext[0] == '.')? 1 : 0;
|
||||
|
||||
#if ! LL_WINDOWS
|
||||
// Make sure mPath ends with a directory separator, if it doesn't already.
|
||||
if (mPath.empty() ||
|
||||
! (mPath[mPath.length() - 1] == '\\' || mPath[mPath.length() - 1] == '/'))
|
||||
{
|
||||
mPath.append("/");
|
||||
}
|
||||
|
||||
// mkstemp() accepts and modifies a char* template string. Generate
|
||||
// the template string, then copy to modifiable storage.
|
||||
// mkstemp() requires its template string to end in six X's.
|
||||
mPath += ext.substr(pfx_offset) + "XXXXXX";
|
||||
// Copy to vector<char>
|
||||
std::vector<char> pathtemplate(mPath.begin(), mPath.end());
|
||||
// append a nul byte for classic-C semantics
|
||||
pathtemplate.push_back('\0');
|
||||
// std::vector promises that a pointer to the 0th element is the same
|
||||
// as a pointer to a contiguous classic-C array
|
||||
int fd(mkstemp(&pathtemplate[0]));
|
||||
if (fd == -1)
|
||||
{
|
||||
// The documented errno values (http://linux.die.net/man/3/mkstemp)
|
||||
// are used in a somewhat unusual way, so provide context-specific
|
||||
// errors.
|
||||
if (errno == EEXIST)
|
||||
{
|
||||
LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath
|
||||
<< "\") could not create unique file " << LL_ENDL;
|
||||
}
|
||||
if (errno == EINVAL)
|
||||
{
|
||||
LL_ERRS("NamedTempFile") << "bad mkstemp() file path template '"
|
||||
<< mPath << "'" << LL_ENDL;
|
||||
}
|
||||
// Shrug, something else
|
||||
int mkst_errno = errno;
|
||||
char buffer[256];
|
||||
LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath << "\") failed: "
|
||||
<< message_from(mkst_errno, buffer,
|
||||
strerror_r(mkst_errno, buffer, sizeof(buffer)))
|
||||
<< LL_ENDL;
|
||||
}
|
||||
// mkstemp() seems to have worked! Capture the modified filename.
|
||||
// Avoid the nul byte we appended.
|
||||
mPath.assign(pathtemplate.begin(), (pathtemplate.end()-1));
|
||||
|
||||
/*==========================================================================*|
|
||||
// Define an ostream on the open fd. Tell it to close fd on destruction.
|
||||
boost::iostreams::stream<boost::iostreams::file_descriptor_sink>
|
||||
out(fd, boost::iostreams::close_handle);
|
||||
|*==========================================================================*/
|
||||
|
||||
// Write desired content.
|
||||
std::ostringstream out;
|
||||
// Stream stuff to it.
|
||||
func(out);
|
||||
|
||||
std::string data(out.str());
|
||||
int written(_write(fd, data.c_str(), data.length()));
|
||||
int closed(_close(fd));
|
||||
llassert_always(written == data.length() && closed == 0);
|
||||
|
||||
#else // LL_WINDOWS
|
||||
// GetTempFileName() is documented to require a MAX_PATH buffer.
|
||||
char tempname[MAX_PATH];
|
||||
// Use 'ext' as filename prefix, but skip leading '.' if any.
|
||||
// The 0 param is very important: requests iterating until we get a
|
||||
// unique name.
|
||||
if (0 == GetTempFileNameA(mPath.c_str(), ext.c_str() + pfx_offset, 0, tempname))
|
||||
{
|
||||
// I always have to look up this call... :-P
|
||||
LPSTR msgptr;
|
||||
FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
GetLastError(),
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
LPSTR(&msgptr), // have to cast (char**) to (char*)
|
||||
0, NULL );
|
||||
LL_ERRS("NamedTempFile") << "GetTempFileName(\"" << mPath << "\", \""
|
||||
<< (ext.c_str() + pfx_offset) << "\") failed: "
|
||||
<< msgptr << LL_ENDL;
|
||||
LocalFree(msgptr);
|
||||
}
|
||||
// GetTempFileName() appears to have worked! Capture the actual
|
||||
// filename.
|
||||
mPath = tempname;
|
||||
// Open the file and stream content to it. Destructor will close.
|
||||
std::ofstream out(tempname);
|
||||
func(out);
|
||||
|
||||
#endif // LL_WINDOWS
|
||||
}
|
||||
|
||||
void peep()
|
||||
{
|
||||
std::cout << "File '" << mPath << "' contains:\n";
|
||||
std::ifstream reader(mPath.c_str());
|
||||
std::string line;
|
||||
while (std::getline(reader, line))
|
||||
std::cout << line << '\n';
|
||||
std::cout << "---\n";
|
||||
}
|
||||
|
||||
std::string mPath;
|
||||
};
|
||||
|
||||
namespace tut
|
||||
{
|
||||
struct sd_xml_data
|
||||
|
|
@ -1783,7 +1538,7 @@ namespace tut
|
|||
const char* PYTHON(getenv("PYTHON"));
|
||||
ensure("Set $PYTHON to the Python interpreter", PYTHON);
|
||||
|
||||
NamedTempFile scriptfile(".py", script);
|
||||
NamedTempFile scriptfile("py", script);
|
||||
|
||||
#if LL_WINDOWS
|
||||
std::string q("\"");
|
||||
|
|
@ -1802,14 +1557,15 @@ namespace tut
|
|||
}
|
||||
|
||||
#else // LL_DARWIN, LL_LINUX
|
||||
LLProcessLauncher py;
|
||||
py.setExecutable(PYTHON);
|
||||
py.addArgument(scriptfile.getName());
|
||||
ensure_equals(STRINGIZE("Couldn't launch " << desc << " script"), py.launch(), 0);
|
||||
LLProcess::Params params;
|
||||
params.executable = PYTHON;
|
||||
params.args.add(scriptfile.getName());
|
||||
LLProcessPtr py(LLProcess::create(params));
|
||||
ensure(STRINGIZE("Couldn't launch " << desc << " script"), py);
|
||||
// Implementing timeout would mean messing with alarm() and
|
||||
// catching SIGALRM... later maybe...
|
||||
int status(0);
|
||||
if (waitpid(py.getProcessID(), &status, 0) == -1)
|
||||
if (waitpid(py->getProcessID(), &status, 0) == -1)
|
||||
{
|
||||
int waitpid_errno(errno);
|
||||
ensure_equals(STRINGIZE("Couldn't retrieve rc from " << desc << " script: "
|
||||
|
|
@ -1888,12 +1644,12 @@ namespace tut
|
|||
" else:\n"
|
||||
" assert False, 'Too many data items'\n";
|
||||
|
||||
// Create a something.llsd file containing 'data' serialized to
|
||||
// Create an llsdXXXXXX file containing 'data' serialized to
|
||||
// notation. It's important to separate with newlines because Python's
|
||||
// llsd module doesn't support parsing from a file stream, only from a
|
||||
// string, so we have to know how much of the file to read into a
|
||||
// string.
|
||||
NamedTempFile file(".llsd",
|
||||
NamedTempFile file("llsd",
|
||||
// NamedTempFile's boost::function constructor
|
||||
// takes a callable. To this callable it passes the
|
||||
// std::ostream with which it's writing the
|
||||
|
|
@ -1926,7 +1682,7 @@ namespace tut
|
|||
// Create an empty data file. This is just a placeholder for our
|
||||
// script to write into. Create it to establish a unique name that
|
||||
// we know.
|
||||
NamedTempFile file(".llsd", "");
|
||||
NamedTempFile file("llsd", "");
|
||||
|
||||
python("write Python notation",
|
||||
lambda::_1 <<
|
||||
|
|
|
|||
|
|
@ -0,0 +1,197 @@
|
|||
/**
|
||||
* @file llstreamqueue_test.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2012-01-05
|
||||
* @brief Test for llstreamqueue.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
||||
* Copyright (c) 2012, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llstreamqueue.h"
|
||||
// STL headers
|
||||
#include <vector>
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/foreach.hpp>
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "stringize.h"
|
||||
|
||||
/*****************************************************************************
|
||||
* TUT
|
||||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
struct llstreamqueue_data
|
||||
{
|
||||
llstreamqueue_data():
|
||||
// we want a buffer with actual bytes in it, not an empty vector
|
||||
buffer(10)
|
||||
{}
|
||||
// As LLStreamQueue is merely a typedef for
|
||||
// LLGenericStreamQueue<char>, and no logic in LLGenericStreamQueue is
|
||||
// specific to the <char> instantiation, we're comfortable for now
|
||||
// testing only the narrow-char version.
|
||||
LLStreamQueue strq;
|
||||
// buffer for use in multiple tests
|
||||
std::vector<char> buffer;
|
||||
};
|
||||
typedef test_group<llstreamqueue_data> llstreamqueue_group;
|
||||
typedef llstreamqueue_group::object object;
|
||||
llstreamqueue_group llstreamqueuegrp("llstreamqueue");
|
||||
|
||||
template<> template<>
|
||||
void object::test<1>()
|
||||
{
|
||||
set_test_name("empty LLStreamQueue");
|
||||
ensure_equals("brand-new LLStreamQueue isn't empty",
|
||||
strq.size(), 0);
|
||||
ensure_equals("brand-new LLStreamQueue returns data",
|
||||
strq.asSource().read(&buffer[0], buffer.size()), 0);
|
||||
strq.asSink().close();
|
||||
ensure_equals("closed empty LLStreamQueue not at EOF",
|
||||
strq.asSource().read(&buffer[0], buffer.size()), -1);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<2>()
|
||||
{
|
||||
set_test_name("one internal block, one buffer");
|
||||
LLStreamQueue::Sink sink(strq.asSink());
|
||||
ensure_equals("write(\"\")", sink.write("", 0), 0);
|
||||
ensure_equals("0 write should leave LLStreamQueue empty (size())",
|
||||
strq.size(), 0);
|
||||
ensure_equals("0 write should leave LLStreamQueue empty (peek())",
|
||||
strq.peek(&buffer[0], buffer.size()), 0);
|
||||
// The meaning of "atomic" is that it must be smaller than our buffer.
|
||||
std::string atomic("atomic");
|
||||
ensure("test data exceeds buffer", atomic.length() < buffer.size());
|
||||
ensure_equals(STRINGIZE("write(\"" << atomic << "\")"),
|
||||
sink.write(&atomic[0], atomic.length()), atomic.length());
|
||||
ensure_equals("size() after write()", strq.size(), atomic.length());
|
||||
size_t peeklen(strq.peek(&buffer[0], buffer.size()));
|
||||
ensure_equals(STRINGIZE("peek(\"" << atomic << "\")"),
|
||||
peeklen, atomic.length());
|
||||
ensure_equals(STRINGIZE("peek(\"" << atomic << "\") result"),
|
||||
std::string(buffer.begin(), buffer.begin() + peeklen), atomic);
|
||||
ensure_equals("size() after peek()", strq.size(), atomic.length());
|
||||
// peek() should not consume. Use a different buffer to prove it isn't
|
||||
// just leftover data from the first peek().
|
||||
std::vector<char> again(buffer.size());
|
||||
peeklen = size_t(strq.peek(&again[0], again.size()));
|
||||
ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again"),
|
||||
peeklen, atomic.length());
|
||||
ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again result"),
|
||||
std::string(again.begin(), again.begin() + peeklen), atomic);
|
||||
// now consume.
|
||||
std::vector<char> third(buffer.size());
|
||||
size_t readlen(strq.read(&third[0], third.size()));
|
||||
ensure_equals(STRINGIZE("read(\"" << atomic << "\")"),
|
||||
readlen, atomic.length());
|
||||
ensure_equals(STRINGIZE("read(\"" << atomic << "\") result"),
|
||||
std::string(third.begin(), third.begin() + readlen), atomic);
|
||||
ensure_equals("peek() after read()", strq.peek(&buffer[0], buffer.size()), 0);
|
||||
ensure_equals("size() after read()", strq.size(), 0);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<3>()
|
||||
{
|
||||
set_test_name("basic skip()");
|
||||
std::string lovecraft("lovecraft");
|
||||
ensure("test data exceeds buffer", lovecraft.length() < buffer.size());
|
||||
ensure_equals(STRINGIZE("write(\"" << lovecraft << "\")"),
|
||||
strq.write(&lovecraft[0], lovecraft.length()), lovecraft.length());
|
||||
size_t peeklen(strq.peek(&buffer[0], buffer.size()));
|
||||
ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\")"),
|
||||
peeklen, lovecraft.length());
|
||||
ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\") result"),
|
||||
std::string(buffer.begin(), buffer.begin() + peeklen), lovecraft);
|
||||
std::streamsize skip1(4);
|
||||
ensure_equals(STRINGIZE("skip(" << skip1 << ")"), strq.skip(skip1), skip1);
|
||||
ensure_equals("size() after skip()", strq.size(), lovecraft.length() - skip1);
|
||||
size_t readlen(strq.read(&buffer[0], buffer.size()));
|
||||
ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\")"),
|
||||
readlen, lovecraft.length() - skip1);
|
||||
ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\") result"),
|
||||
std::string(buffer.begin(), buffer.begin() + readlen),
|
||||
lovecraft.substr(skip1));
|
||||
ensure_equals("unconsumed", strq.read(&buffer[0], buffer.size()), 0);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<4>()
|
||||
{
|
||||
set_test_name("skip() multiple blocks");
|
||||
std::string blocks[] = { "books of ", "H.P. ", "Lovecraft" };
|
||||
std::streamsize total(blocks[0].length() + blocks[1].length() + blocks[2].length());
|
||||
std::streamsize leave(5); // len("craft") above
|
||||
std::streamsize skip(total - leave);
|
||||
std::streamsize written(0);
|
||||
BOOST_FOREACH(const std::string& block, blocks)
|
||||
{
|
||||
written += strq.write(&block[0], block.length());
|
||||
ensure_equals("size() after write()", strq.size(), written);
|
||||
}
|
||||
std::streamsize skiplen(strq.skip(skip));
|
||||
ensure_equals(STRINGIZE("skip(" << skip << ")"), skiplen, skip);
|
||||
ensure_equals("size() after skip()", strq.size(), leave);
|
||||
size_t readlen(strq.read(&buffer[0], buffer.size()));
|
||||
ensure_equals("read(\"craft\")", readlen, leave);
|
||||
ensure_equals("read(\"craft\") result",
|
||||
std::string(buffer.begin(), buffer.begin() + readlen), "craft");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<5>()
|
||||
{
|
||||
set_test_name("concatenate blocks");
|
||||
std::string blocks[] = { "abcd", "efghij", "klmnopqrs" };
|
||||
BOOST_FOREACH(const std::string& block, blocks)
|
||||
{
|
||||
strq.write(&block[0], block.length());
|
||||
}
|
||||
std::vector<char> longbuffer(30);
|
||||
std::streamsize readlen(strq.read(&longbuffer[0], longbuffer.size()));
|
||||
ensure_equals("read() multiple blocks",
|
||||
readlen, blocks[0].length() + blocks[1].length() + blocks[2].length());
|
||||
ensure_equals("read() multiple blocks result",
|
||||
std::string(longbuffer.begin(), longbuffer.begin() + readlen),
|
||||
blocks[0] + blocks[1] + blocks[2]);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<6>()
|
||||
{
|
||||
set_test_name("split blocks");
|
||||
std::string blocks[] = { "abcdefghijklm", "nopqrstuvwxyz" };
|
||||
BOOST_FOREACH(const std::string& block, blocks)
|
||||
{
|
||||
strq.write(&block[0], block.length());
|
||||
}
|
||||
strq.close();
|
||||
// We've already verified what strq.size() should be at this point;
|
||||
// see above test named "skip() multiple blocks"
|
||||
std::streamsize chksize(strq.size());
|
||||
std::streamsize readlen(strq.read(&buffer[0], buffer.size()));
|
||||
ensure_equals("read() 0", readlen, buffer.size());
|
||||
ensure_equals("read() 0 result", std::string(buffer.begin(), buffer.end()), "abcdefghij");
|
||||
chksize -= readlen;
|
||||
ensure_equals("size() after read() 0", strq.size(), chksize);
|
||||
readlen = strq.read(&buffer[0], buffer.size());
|
||||
ensure_equals("read() 1", readlen, buffer.size());
|
||||
ensure_equals("read() 1 result", std::string(buffer.begin(), buffer.end()), "klmnopqrst");
|
||||
chksize -= readlen;
|
||||
ensure_equals("size() after read() 1", strq.size(), chksize);
|
||||
readlen = strq.read(&buffer[0], buffer.size());
|
||||
ensure_equals("read() 2", readlen, chksize);
|
||||
ensure_equals("read() 2 result",
|
||||
std::string(buffer.begin(), buffer.begin() + readlen), "uvwxyz");
|
||||
ensure_equals("read() 3", strq.read(&buffer[0], buffer.size()), -1);
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
@ -29,7 +29,11 @@
|
|||
#include "linden_common.h"
|
||||
#include "../test/lltut.h"
|
||||
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include "../llstring.h"
|
||||
#include "StringVec.h"
|
||||
|
||||
using boost::assign::list_of;
|
||||
|
||||
namespace tut
|
||||
{
|
||||
|
|
@ -750,4 +754,118 @@ namespace tut
|
|||
ensure("empty substr.", !LLStringUtil::endsWith(empty, value));
|
||||
ensure("empty everything.", !LLStringUtil::endsWith(empty, empty));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void string_index_object_t::test<41>()
|
||||
{
|
||||
set_test_name("getTokens(\"delims\")");
|
||||
ensure_equals("empty string", LLStringUtil::getTokens("", " "), StringVec());
|
||||
ensure_equals("only delims",
|
||||
LLStringUtil::getTokens(" \r\n ", " \r\n"), StringVec());
|
||||
ensure_equals("sequence of delims",
|
||||
LLStringUtil::getTokens(",,, one ,,,", ","), list_of("one"));
|
||||
// nat considers this a dubious implementation side effect, but I'd
|
||||
// hate to change it now...
|
||||
ensure_equals("noncontiguous tokens",
|
||||
LLStringUtil::getTokens(", ,, , one ,,,", ","), list_of("")("")("one"));
|
||||
ensure_equals("space-padded tokens",
|
||||
LLStringUtil::getTokens(", one , two ,", ","), list_of("one")("two"));
|
||||
ensure_equals("no delims", LLStringUtil::getTokens("one", ","), list_of("one"));
|
||||
}
|
||||
|
||||
// Shorthand for verifying that getTokens() behaves the same when you
|
||||
// don't pass a string of escape characters, when you pass an empty string
|
||||
// (different overloads), and when you pass a string of characters that
|
||||
// aren't actually present.
|
||||
void ensure_getTokens(const std::string& desc,
|
||||
const std::string& string,
|
||||
const std::string& drop_delims,
|
||||
const std::string& keep_delims,
|
||||
const std::string& quotes,
|
||||
const std::vector<std::string>& expect)
|
||||
{
|
||||
ensure_equals(desc + " - no esc",
|
||||
LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes),
|
||||
expect);
|
||||
ensure_equals(desc + " - empty esc",
|
||||
LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, ""),
|
||||
expect);
|
||||
ensure_equals(desc + " - unused esc",
|
||||
LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "!"),
|
||||
expect);
|
||||
}
|
||||
|
||||
void ensure_getTokens(const std::string& desc,
|
||||
const std::string& string,
|
||||
const std::string& drop_delims,
|
||||
const std::string& keep_delims,
|
||||
const std::vector<std::string>& expect)
|
||||
{
|
||||
ensure_getTokens(desc, string, drop_delims, keep_delims, "", expect);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void string_index_object_t::test<42>()
|
||||
{
|
||||
set_test_name("getTokens(\"delims\", etc.)");
|
||||
// Signatures to test in this method:
|
||||
// getTokens(string, drop_delims, keep_delims [, quotes [, escapes]])
|
||||
// If you omit keep_delims, you get the older function (test above).
|
||||
|
||||
// cases like the getTokens(string, delims) tests above
|
||||
ensure_getTokens("empty string", "", " ", "", StringVec());
|
||||
ensure_getTokens("only delims",
|
||||
" \r\n ", " \r\n", "", StringVec());
|
||||
ensure_getTokens("sequence of delims",
|
||||
",,, one ,,,", ", ", "", list_of("one"));
|
||||
// Note contrast with the case in the previous method
|
||||
ensure_getTokens("noncontiguous tokens",
|
||||
", ,, , one ,,,", ", ", "", list_of("one"));
|
||||
ensure_getTokens("space-padded tokens",
|
||||
", one , two ,", ", ", "",
|
||||
list_of("one")("two"));
|
||||
ensure_getTokens("no delims", "one", ",", "", list_of("one"));
|
||||
|
||||
// drop_delims vs. keep_delims
|
||||
ensure_getTokens("arithmetic",
|
||||
" ab+def / xx* yy ", " ", "+-*/",
|
||||
list_of("ab")("+")("def")("/")("xx")("*")("yy"));
|
||||
|
||||
// quotes
|
||||
ensure_getTokens("no quotes",
|
||||
"She said, \"Don't go.\"", " ", ",", "",
|
||||
list_of("She")("said")(",")("\"Don't")("go.\""));
|
||||
ensure_getTokens("quotes",
|
||||
"She said, \"Don't go.\"", " ", ",", "\"",
|
||||
list_of("She")("said")(",")("Don't go."));
|
||||
ensure_getTokens("quotes and delims",
|
||||
"run c:/'Documents and Settings'/someone", " ", "", "'",
|
||||
list_of("run")("c:/Documents and Settings/someone"));
|
||||
ensure_getTokens("unmatched quote",
|
||||
"baby don't leave", " ", "", "'",
|
||||
list_of("baby")("don't")("leave"));
|
||||
ensure_getTokens("adjacent quoted",
|
||||
"abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'",
|
||||
list_of("abcdef \"ghijkl' mnopqr"));
|
||||
ensure_getTokens("quoted empty string",
|
||||
"--set SomeVar ''", " ", "", "'",
|
||||
list_of("--set")("SomeVar")(""));
|
||||
|
||||
// escapes
|
||||
// Don't use backslash as an escape for these tests -- you'll go nuts
|
||||
// between the C++ string scanner and getTokens() escapes. Test with
|
||||
// something else!
|
||||
ensure_equals("escaped delims",
|
||||
LLStringUtil::getTokens("^ a - dog^-gone^ phrase", " ", "-", "", "^"),
|
||||
list_of(" a")("-")("dog-gone phrase"));
|
||||
ensure_equals("escaped quotes",
|
||||
LLStringUtil::getTokens("say: 'this isn^'t w^orking'.", " ", "", "'", "^"),
|
||||
list_of("say:")("this isn't working."));
|
||||
ensure_equals("escaped escape",
|
||||
LLStringUtil::getTokens("want x^^2", " ", "", "", "^"),
|
||||
list_of("want")("x^2"));
|
||||
ensure_equals("escape at end",
|
||||
LLStringUtil::getTokens("it's^ up there^", " ", "", "'", "^"),
|
||||
list_of("it's up")("there^"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
"""\
|
||||
@file setpython.py
|
||||
@author Nat Goodspeed
|
||||
@date 2011-07-13
|
||||
@brief Set PYTHON environment variable for tests that care.
|
||||
|
||||
$LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||
Copyright (c) 2011, Linden Research, Inc.
|
||||
$/LicenseInfo$
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ["PYTHON"] = sys.executable
|
||||
sys.exit(subprocess.call(sys.argv[1:]))
|
||||
|
|
@ -29,7 +29,22 @@
|
|||
#if ! defined(LL_WRAPLLERRS_H)
|
||||
#define LL_WRAPLLERRS_H
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
|
||||
#endif
|
||||
|
||||
#include <tut/tut.hpp>
|
||||
#include "llerrorcontrol.h"
|
||||
#include "stringize.h"
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
// statically reference the function in test.cpp... it's short, we could
|
||||
// replicate, but better to reuse
|
||||
extern void wouldHaveCrashed(const std::string& message);
|
||||
|
||||
struct WrapLL_ERRS
|
||||
{
|
||||
|
|
@ -70,4 +85,118 @@ struct WrapLL_ERRS
|
|||
LLError::FatalFunction mPriorFatal;
|
||||
};
|
||||
|
||||
/**
|
||||
* LLError::addRecorder() accepts ownership of the passed Recorder* -- it
|
||||
* expects to be able to delete it later. CaptureLog isa Recorder whose
|
||||
* pointer we want to be able to pass without any ownership implications.
|
||||
* For such cases, instantiate a new RecorderProxy(yourRecorder) and pass
|
||||
* that. Your heap RecorderProxy might later be deleted, but not yourRecorder.
|
||||
*/
|
||||
class RecorderProxy: public LLError::Recorder
|
||||
{
|
||||
public:
|
||||
RecorderProxy(LLError::Recorder* recorder):
|
||||
mRecorder(recorder)
|
||||
{}
|
||||
|
||||
virtual void recordMessage(LLError::ELevel level, const std::string& message)
|
||||
{
|
||||
mRecorder->recordMessage(level, message);
|
||||
}
|
||||
|
||||
virtual bool wantsTime()
|
||||
{
|
||||
return mRecorder->wantsTime();
|
||||
}
|
||||
|
||||
private:
|
||||
LLError::Recorder* mRecorder;
|
||||
};
|
||||
|
||||
/**
|
||||
* Capture log messages. This is adapted (simplified) from the one in
|
||||
* llerror_test.cpp.
|
||||
*/
|
||||
class CaptureLog : public LLError::Recorder, public boost::noncopyable
|
||||
{
|
||||
public:
|
||||
CaptureLog(LLError::ELevel level=LLError::LEVEL_DEBUG):
|
||||
// Mostly what we're trying to accomplish by saving and resetting
|
||||
// LLError::Settings is to bypass the default RecordToStderr and
|
||||
// RecordToWinDebug Recorders. As these are visible only inside
|
||||
// llerror.cpp, we can't just call LLError::removeRecorder() with
|
||||
// each. For certain tests we need to produce, capture and examine
|
||||
// DEBUG log messages -- but we don't want to spam the user's console
|
||||
// with that output. If it turns out that saveAndResetSettings() has
|
||||
// some bad effect, give up and just let the DEBUG level log messages
|
||||
// display.
|
||||
mOldSettings(LLError::saveAndResetSettings()),
|
||||
mProxy(new RecorderProxy(this))
|
||||
{
|
||||
LLError::setFatalFunction(wouldHaveCrashed);
|
||||
LLError::setDefaultLevel(level);
|
||||
LLError::addRecorder(mProxy);
|
||||
}
|
||||
|
||||
~CaptureLog()
|
||||
{
|
||||
LLError::removeRecorder(mProxy);
|
||||
delete mProxy;
|
||||
LLError::restoreSettings(mOldSettings);
|
||||
}
|
||||
|
||||
void recordMessage(LLError::ELevel level,
|
||||
const std::string& message)
|
||||
{
|
||||
mMessages.push_back(message);
|
||||
}
|
||||
|
||||
/// Don't assume the message we want is necessarily the LAST log message
|
||||
/// emitted by the underlying code; search backwards through all messages
|
||||
/// for the sought string.
|
||||
std::string messageWith(const std::string& search, bool required=true)
|
||||
{
|
||||
for (MessageList::const_reverse_iterator rmi(mMessages.rbegin()), rmend(mMessages.rend());
|
||||
rmi != rmend; ++rmi)
|
||||
{
|
||||
if (rmi->find(search) != std::string::npos)
|
||||
return *rmi;
|
||||
}
|
||||
// failed to find any such message
|
||||
if (! required)
|
||||
return std::string();
|
||||
|
||||
throw tut::failure(STRINGIZE("failed to find '" << search
|
||||
<< "' in captured log messages:\n"
|
||||
<< boost::ref(*this)));
|
||||
}
|
||||
|
||||
std::ostream& streamto(std::ostream& out) const
|
||||
{
|
||||
MessageList::const_iterator mi(mMessages.begin()), mend(mMessages.end());
|
||||
if (mi != mend)
|
||||
{
|
||||
// handle first message separately: it doesn't get a newline
|
||||
out << *mi++;
|
||||
for ( ; mi != mend; ++mi)
|
||||
{
|
||||
// every subsequent message gets a newline
|
||||
out << '\n' << *mi;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
typedef std::list<std::string> MessageList;
|
||||
MessageList mMessages;
|
||||
LLError::Settings* mOldSettings;
|
||||
LLError::Recorder* mProxy;
|
||||
};
|
||||
|
||||
inline
|
||||
std::ostream& operator<<(std::ostream& out, const CaptureLog& log)
|
||||
{
|
||||
return log.streamto(out);
|
||||
}
|
||||
|
||||
#endif /* ! defined(LL_WRAPLLERRS_H) */
|
||||
|
|
|
|||
|
|
@ -48,11 +48,15 @@
|
|||
//static
|
||||
std::string LLImage::sLastErrorMessage;
|
||||
LLMutex* LLImage::sMutex = NULL;
|
||||
bool LLImage::sUseNewByteRange = false;
|
||||
S32 LLImage::sMinimalReverseByteRangePercent = 75;
|
||||
LLPrivateMemoryPool* LLImageBase::sPrivatePoolp = NULL ;
|
||||
|
||||
//static
|
||||
void LLImage::initClass()
|
||||
void LLImage::initClass(bool use_new_byte_range, S32 minimal_reverse_byte_range_percent)
|
||||
{
|
||||
sUseNewByteRange = use_new_byte_range;
|
||||
sMinimalReverseByteRangePercent = minimal_reverse_byte_range_percent;
|
||||
sMutex = new LLMutex(NULL);
|
||||
|
||||
LLImageBase::createPrivatePool() ;
|
||||
|
|
@ -1334,7 +1338,8 @@ LLImageFormatted::LLImageFormatted(S8 codec)
|
|||
mCodec(codec),
|
||||
mDecoding(0),
|
||||
mDecoded(0),
|
||||
mDiscardLevel(-1)
|
||||
mDiscardLevel(-1),
|
||||
mLevels(0)
|
||||
{
|
||||
mMemType = LLMemType::MTYPE_IMAGEFORMATTED;
|
||||
}
|
||||
|
|
@ -1561,7 +1566,7 @@ void LLImageFormatted::appendData(U8 *data, S32 size)
|
|||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
BOOL LLImageFormatted::load(const std::string &filename)
|
||||
BOOL LLImageFormatted::load(const std::string &filename, int load_size)
|
||||
{
|
||||
resetLastError();
|
||||
|
||||
|
|
@ -1580,14 +1585,19 @@ BOOL LLImageFormatted::load(const std::string &filename)
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
// Constrain the load size to acceptable values
|
||||
if ((load_size == 0) || (load_size > file_size))
|
||||
{
|
||||
load_size = file_size;
|
||||
}
|
||||
BOOL res;
|
||||
U8 *data = allocateData(file_size);
|
||||
apr_size_t bytes_read = file_size;
|
||||
U8 *data = allocateData(load_size);
|
||||
apr_size_t bytes_read = load_size;
|
||||
apr_status_t s = apr_file_read(apr_file, data, &bytes_read); // modifies bytes_read
|
||||
if (s != APR_SUCCESS || (S32) bytes_read != file_size)
|
||||
if (s != APR_SUCCESS || (S32) bytes_read != load_size)
|
||||
{
|
||||
deleteData();
|
||||
setLastError("Unable to read entire file",filename);
|
||||
setLastError("Unable to read file",filename);
|
||||
res = FALSE;
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ const S32 MAX_PRECINCT_SIZE = 2048; // No reason to be bigger than MAX_IMAGE_S
|
|||
const S32 MIN_PRECINCT_SIZE = 4; // Can't be smaller than MIN_BLOCK_SIZE
|
||||
const S32 MAX_BLOCK_SIZE = 64; // Max total block size is 4096, hence 64x64 when using square blocks
|
||||
const S32 MIN_BLOCK_SIZE = 4; // Min block dim is 4 according to jpeg2000 spec
|
||||
const S32 MIN_LAYER_SIZE = 2000; // Size of the first quality layer (after header). Must be > to FIRST_PACKET_SIZE!!
|
||||
const S32 MAX_NB_LAYERS = 64; // Max number of layers we'll entertain in SL (practical limit)
|
||||
|
||||
const S32 MIN_IMAGE_SIZE = (1<<MIN_IMAGE_MIP); // 4, only used for expand/contract power of 2
|
||||
const S32 MAX_IMAGE_SIZE = (1<<MAX_IMAGE_MIP); // 2048
|
||||
|
|
@ -60,6 +62,7 @@ const S32 MAX_IMAGE_DATA_SIZE = MAX_IMAGE_AREA * MAX_IMAGE_COMPONENTS; //2048 *
|
|||
// *TODO: change both to 1024 when SIM texture fetching is deprecated
|
||||
const S32 FIRST_PACKET_SIZE = 600;
|
||||
const S32 MAX_IMG_PACKET_SIZE = 1000;
|
||||
const S32 HTTP_PACKET_SIZE = 1496;
|
||||
|
||||
// Base classes for images.
|
||||
// There are two major parts for the image:
|
||||
|
|
@ -89,15 +92,20 @@ typedef enum e_image_codec
|
|||
class LLImage
|
||||
{
|
||||
public:
|
||||
static void initClass();
|
||||
static void initClass(bool use_new_byte_range = false, S32 minimal_reverse_byte_range_percent = 75);
|
||||
static void cleanupClass();
|
||||
|
||||
static const std::string& getLastError();
|
||||
static void setLastError(const std::string& message);
|
||||
|
||||
static bool useNewByteRange() { return sUseNewByteRange; }
|
||||
static S32 getReverseByteRangePercent() { return sMinimalReverseByteRangePercent; }
|
||||
|
||||
protected:
|
||||
static LLMutex* sMutex;
|
||||
static std::string sLastErrorMessage;
|
||||
static bool sUseNewByteRange;
|
||||
static S32 sMinimalReverseByteRangePercent;
|
||||
};
|
||||
|
||||
//============================================================================
|
||||
|
|
@ -294,7 +302,7 @@ public:
|
|||
// getRawDiscardLevel() by default returns mDiscardLevel, but may be overridden (LLImageJ2C)
|
||||
virtual S8 getRawDiscardLevel() { return mDiscardLevel; }
|
||||
|
||||
BOOL load(const std::string& filename);
|
||||
BOOL load(const std::string& filename, int load_size = 0);
|
||||
BOOL save(const std::string& filename);
|
||||
|
||||
virtual BOOL updateData() = 0; // pure virtual
|
||||
|
|
@ -313,6 +321,8 @@ public:
|
|||
BOOL isDecoded() const { return mDecoded ? TRUE : FALSE; }
|
||||
void setDiscardLevel(S8 discard_level) { mDiscardLevel = discard_level; }
|
||||
S8 getDiscardLevel() const { return mDiscardLevel; }
|
||||
S8 getLevels() const { return mLevels; }
|
||||
void setLevels(S8 nlevels) { mLevels = nlevels; }
|
||||
|
||||
// setLastError needs to be deferred for J2C images since it may be called from a DLL
|
||||
virtual void resetLastError();
|
||||
|
|
@ -325,7 +335,8 @@ protected:
|
|||
S8 mCodec;
|
||||
S8 mDecoding;
|
||||
S8 mDecoded; // unused, but changing LLImage layout requires recompiling static Mac/Linux libs. 2009-01-30 JC
|
||||
S8 mDiscardLevel;
|
||||
S8 mDiscardLevel; // Current resolution level worked on. 0 = full res, 1 = half res, 2 = quarter res, etc...
|
||||
S8 mLevels; // Number of resolution levels in that image. Min is 1. 0 means unknown.
|
||||
|
||||
public:
|
||||
static S32 sGlobalFormattedMemory;
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ std::string LLImageJ2C::getEngineInfo()
|
|||
LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C),
|
||||
mMaxBytes(0),
|
||||
mRawDiscardLevel(-1),
|
||||
mRate(0.0f),
|
||||
mRate(DEFAULT_COMPRESSION_RATE),
|
||||
mReversible(FALSE),
|
||||
mAreaUsedForDataSizeCalcs(0)
|
||||
{
|
||||
|
|
@ -142,6 +142,7 @@ BOOL LLImageJ2C::updateData()
|
|||
|
||||
BOOL LLImageJ2C::initDecode(LLImageRaw &raw_image, int discard_level, int* region)
|
||||
{
|
||||
setDiscardLevel(discard_level != -1 ? discard_level : 0);
|
||||
return mImpl->initDecode(*this,raw_image,discard_level,region);
|
||||
}
|
||||
|
||||
|
|
@ -261,19 +262,34 @@ S32 LLImageJ2C::calcHeaderSizeJ2C()
|
|||
//static
|
||||
S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate)
|
||||
{
|
||||
// Note: this only provides an *estimate* of the size in bytes of an image level
|
||||
// *TODO: find a way to read the true size (when available) and convey the fact
|
||||
// that the result is an estimate in the other cases
|
||||
if (rate <= 0.f) rate = .125f;
|
||||
while (discard_level > 0)
|
||||
// Note: This provides an estimation for the first to last quality layer of a given discard level
|
||||
// This is however an efficient approximation, as the true discard level boundary would be
|
||||
// in general too big for fast fetching.
|
||||
// For details about the equation used here, see https://wiki.lindenlab.com/wiki/THX1138_KDU_Improvements#Byte_Range_Study
|
||||
|
||||
// Estimate the number of layers. This is consistent with what's done for j2c encoding in LLImageJ2CKDU::encodeImpl().
|
||||
S32 nb_layers = 1;
|
||||
S32 surface = w*h;
|
||||
S32 s = 64*64;
|
||||
while (surface > s)
|
||||
{
|
||||
if (w < 1 || h < 1)
|
||||
break;
|
||||
w >>= 1;
|
||||
h >>= 1;
|
||||
discard_level--;
|
||||
nb_layers++;
|
||||
s *= 4;
|
||||
}
|
||||
S32 bytes = (S32)((F32)(w*h*comp)*rate);
|
||||
F32 layer_factor = 3.0f * (7 - llclamp(nb_layers,1,6));
|
||||
|
||||
// Compute w/pow(2,discard_level) and h/pow(2,discard_level)
|
||||
w >>= discard_level;
|
||||
h >>= discard_level;
|
||||
w = llmax(w, 1);
|
||||
h = llmax(h, 1);
|
||||
|
||||
// Temporary: compute both new and old range and pick one according to the settings TextureNewByteRange
|
||||
// *TODO: Take the old code out once we have enough tests done
|
||||
S32 bytes;
|
||||
S32 new_bytes = (S32) (sqrt((F32)(w*h))*(F32)(comp)*rate*1000.f/layer_factor);
|
||||
S32 old_bytes = (S32)((F32)(w*h*comp)*rate);
|
||||
bytes = (LLImage::useNewByteRange() && (new_bytes < old_bytes) ? new_bytes : old_bytes);
|
||||
bytes = llmax(bytes, calcHeaderSizeJ2C());
|
||||
return bytes;
|
||||
}
|
||||
|
|
@ -283,15 +299,12 @@ S32 LLImageJ2C::calcHeaderSize()
|
|||
return calcHeaderSizeJ2C();
|
||||
}
|
||||
|
||||
|
||||
// calcDataSize() returns how many bytes to read
|
||||
// to load discard_level (including header and higher discard levels)
|
||||
// calcDataSize() returns how many bytes to read to load discard_level (including header)
|
||||
S32 LLImageJ2C::calcDataSize(S32 discard_level)
|
||||
{
|
||||
discard_level = llclamp(discard_level, 0, MAX_DISCARD_LEVEL);
|
||||
|
||||
if ( mAreaUsedForDataSizeCalcs != (getHeight() * getWidth())
|
||||
|| mDataSizes[0] == 0)
|
||||
|| (mDataSizes[0] == 0))
|
||||
{
|
||||
mAreaUsedForDataSizeCalcs = getHeight() * getWidth();
|
||||
|
||||
|
|
@ -301,25 +314,6 @@ S32 LLImageJ2C::calcDataSize(S32 discard_level)
|
|||
mDataSizes[level] = calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), level, mRate);
|
||||
level--;
|
||||
}
|
||||
|
||||
/* This is technically a more correct way to calculate the size required
|
||||
for each discard level, since they should include the size needed for
|
||||
lower levels. Unfortunately, this doesn't work well and will lead to
|
||||
download stalls. The true correct way is to parse the header. This will
|
||||
all go away with http textures at some point.
|
||||
|
||||
// Calculate the size for each discard level. Lower levels (higher quality)
|
||||
// contain the cumulative size of higher levels
|
||||
S32 total_size = calcHeaderSizeJ2C();
|
||||
|
||||
S32 level = MAX_DISCARD_LEVEL; // Start at the highest discard
|
||||
while ( level >= 0 )
|
||||
{ // Add in this discard level and all before it
|
||||
total_size += calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), level, mRate);
|
||||
mDataSizes[level] = total_size;
|
||||
level--;
|
||||
}
|
||||
*/
|
||||
}
|
||||
return mDataSizes[discard_level];
|
||||
}
|
||||
|
|
@ -334,8 +328,9 @@ S32 LLImageJ2C::calcDiscardLevelBytes(S32 bytes)
|
|||
}
|
||||
while (1)
|
||||
{
|
||||
S32 bytes_needed = calcDataSize(discard_level); // virtual
|
||||
if (bytes >= bytes_needed - (bytes_needed>>2)) // For J2c, up the res at 75% of the optimal number of bytes
|
||||
S32 bytes_needed = calcDataSize(discard_level);
|
||||
// Use TextureReverseByteRange percent (see settings.xml) of the optimal size to qualify as correct rendering for the given discard level
|
||||
if (bytes >= (bytes_needed*LLImage::getReverseByteRangePercent()/100))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
@ -348,11 +343,6 @@ S32 LLImageJ2C::calcDiscardLevelBytes(S32 bytes)
|
|||
return discard_level;
|
||||
}
|
||||
|
||||
void LLImageJ2C::setRate(F32 rate)
|
||||
{
|
||||
mRate = rate;
|
||||
}
|
||||
|
||||
void LLImageJ2C::setMaxBytes(S32 max_bytes)
|
||||
{
|
||||
mMaxBytes = max_bytes;
|
||||
|
|
@ -474,6 +464,7 @@ LLImageCompressionTester::LLImageCompressionTester() : LLMetricPerformanceTester
|
|||
addMetric("Perf Compression (kB/s)");
|
||||
|
||||
mRunBytesInDecompression = 0;
|
||||
mRunBytesOutDecompression = 0;
|
||||
mRunBytesInCompression = 0;
|
||||
|
||||
mTotalBytesInDecompression = 0;
|
||||
|
|
@ -483,6 +474,7 @@ LLImageCompressionTester::LLImageCompressionTester() : LLMetricPerformanceTester
|
|||
|
||||
mTotalTimeDecompression = 0.0f;
|
||||
mTotalTimeCompression = 0.0f;
|
||||
mRunTimeDecompression = 0.0f;
|
||||
}
|
||||
|
||||
LLImageCompressionTester::~LLImageCompressionTester()
|
||||
|
|
@ -565,12 +557,17 @@ void LLImageCompressionTester::updateDecompressionStats(const S32 bytesIn, const
|
|||
mTotalBytesInDecompression += bytesIn;
|
||||
mRunBytesInDecompression += bytesIn;
|
||||
mTotalBytesOutDecompression += bytesOut;
|
||||
if (mRunBytesInDecompression > (1000000))
|
||||
mRunBytesOutDecompression += bytesOut;
|
||||
//if (mRunBytesInDecompression > (1000000))
|
||||
if (mRunBytesOutDecompression > (10000000))
|
||||
//if ((mTotalTimeDecompression - mRunTimeDecompression) >= (5.0f))
|
||||
{
|
||||
// Output everything
|
||||
outputTestResults();
|
||||
// Reset the decompression data of the run
|
||||
mRunBytesInDecompression = 0;
|
||||
mRunBytesOutDecompression = 0;
|
||||
mRunTimeDecompression = mTotalTimeDecompression;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@
|
|||
#include "llassettype.h"
|
||||
#include "llmetricperformancetester.h"
|
||||
|
||||
// JPEG2000 : compression rate used in j2c conversion.
|
||||
const F32 DEFAULT_COMPRESSION_RATE = 1.f/8.f;
|
||||
|
||||
class LLImageJ2CImpl;
|
||||
class LLImageCompressionTester ;
|
||||
|
||||
|
|
@ -67,12 +70,11 @@ public:
|
|||
|
||||
// Encode accessors
|
||||
void setReversible(const BOOL reversible); // Use non-lossy?
|
||||
void setRate(F32 rate);
|
||||
void setMaxBytes(S32 max_bytes);
|
||||
S32 getMaxBytes() const { return mMaxBytes; }
|
||||
|
||||
static S32 calcHeaderSizeJ2C();
|
||||
static S32 calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate = 0.f);
|
||||
static S32 calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate = DEFAULT_COMPRESSION_RATE);
|
||||
|
||||
static std::string getEngineInfo();
|
||||
|
||||
|
|
@ -154,13 +156,15 @@ class LLImageCompressionTester : public LLMetricPerformanceTesterBasic
|
|||
U32 mTotalBytesOutDecompression; // Total bytes produced by decompressor
|
||||
U32 mTotalBytesInCompression; // Total bytes fed to compressor
|
||||
U32 mTotalBytesOutCompression; // Total bytes produced by compressor
|
||||
U32 mRunBytesInDecompression; // Bytes fed to decompressor in this run
|
||||
U32 mRunBytesInDecompression; // Bytes fed to decompressor in this run
|
||||
U32 mRunBytesOutDecompression; // Bytes produced by the decompressor in this run
|
||||
U32 mRunBytesInCompression; // Bytes fed to compressor in this run
|
||||
//
|
||||
// Time
|
||||
//
|
||||
F32 mTotalTimeDecompression; // Total time spent in computing decompression
|
||||
F32 mTotalTimeCompression; // Total time spent in computing compression
|
||||
F32 mRunTimeDecompression; // Time in this run (we output every 5 sec in decompress)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const S32 PARCEL_UNIT_AREA = 16;
|
|||
const F32 PARCEL_HEIGHT = 50.f;
|
||||
|
||||
//Height above ground which parcel boundries exist for explicitly banned avatars
|
||||
const F32 BAN_HEIGHT = 768.f;
|
||||
const F32 BAN_HEIGHT = 5000.f;
|
||||
|
||||
// Maximum number of entries in an access list
|
||||
const S32 PARCEL_MAX_ACCESS_LIST = 300;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "llmath.h"
|
||||
#include "llkdumem.h"
|
||||
|
||||
#include "kdu_block_coding.h"
|
||||
|
||||
class kdc_flow_control {
|
||||
|
||||
|
|
@ -244,7 +245,9 @@ void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, BOOL keep_codestream, ECod
|
|||
mCodeStreamp->create(mInputp);
|
||||
|
||||
// Set the maximum number of bytes to use from the codestream
|
||||
mCodeStreamp->set_max_bytes(max_bytes);
|
||||
// *TODO: This seems to be wrong. The base class should have no idea of how j2c compression works so no
|
||||
// good way of computing what's the byte range to be used.
|
||||
mCodeStreamp->set_max_bytes(max_bytes,true);
|
||||
|
||||
// If you want to flip or rotate the image for some reason, change
|
||||
// the resolution, or identify a restricted region of interest, this is
|
||||
|
|
@ -291,8 +294,13 @@ void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, BOOL keep_codestream, ECod
|
|||
}
|
||||
}
|
||||
|
||||
// Get the number of resolution levels in that image
|
||||
mLevels = mCodeStreamp->get_min_dwt_levels();
|
||||
|
||||
// Set the base dimensions
|
||||
base.setSize(dims.size.x, dims.size.y, components);
|
||||
|
||||
base.setLevels(mLevels);
|
||||
|
||||
if (!keep_codestream)
|
||||
{
|
||||
mCodeStreamp->destroy();
|
||||
|
|
@ -351,7 +359,8 @@ BOOL LLImageJ2CKDU::initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int bloc
|
|||
mLevels = levels;
|
||||
if (mLevels != 0)
|
||||
{
|
||||
mLevels = llclamp(mLevels,MIN_DECOMPOSITION_LEVELS,MIN_DECOMPOSITION_LEVELS);
|
||||
mLevels = llclamp(mLevels,MIN_DECOMPOSITION_LEVELS,MAX_DECOMPOSITION_LEVELS);
|
||||
base.setLevels(mLevels);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -364,6 +373,9 @@ BOOL LLImageJ2CKDU::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 deco
|
|||
// To regain control, we throw an exception, and catch it here.
|
||||
try
|
||||
{
|
||||
// Merov : Test!! DO NOT COMMIT!!
|
||||
//findDiscardLevelsBoundaries(base);
|
||||
|
||||
base.updateRawDiscardLevel();
|
||||
setupCodeStream(base, TRUE, mode);
|
||||
|
||||
|
|
@ -381,7 +393,7 @@ BOOL LLImageJ2CKDU::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 deco
|
|||
region_kdu->size.y = region[3] - region[1];
|
||||
}
|
||||
int discard = (discard_level != -1 ? discard_level : base.getRawDiscardLevel());
|
||||
|
||||
//llinfos << "Merov debug : initDecode, discard used = " << discard << ", asked = " << discard_level << llendl;
|
||||
// Apply loading restrictions
|
||||
mCodeStreamp->apply_input_restrictions( first_channel, max_channel_count, discard, 0, region_kdu);
|
||||
|
||||
|
|
@ -394,12 +406,9 @@ BOOL LLImageJ2CKDU::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 deco
|
|||
|
||||
// Resize raw_image according to the image to be decoded
|
||||
kdu_dims dims; mCodeStreamp->get_dims(0,dims);
|
||||
// *TODO: Use the real number of levels read from the file throughout the code instead of relying on an infered value from dimensions
|
||||
//S32 levels = mCodeStreamp->get_min_dwt_levels();
|
||||
S32 channels = base.getComponents() - first_channel;
|
||||
channels = llmin(channels,max_channel_count);
|
||||
raw_image.resize(dims.size.x, dims.size.y, channels);
|
||||
//llinfos << "j2c image dimension: width = " << dims.size.x << ", height = " << dims.size.y << ", channels = " << channels << ", levels = " << levels << llendl;
|
||||
|
||||
if (!mTileIndicesp)
|
||||
{
|
||||
|
|
@ -583,12 +592,6 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co
|
|||
comment.put_text(comment_text);
|
||||
}
|
||||
|
||||
// Set codestream options
|
||||
int num_layer_specs = 0;
|
||||
|
||||
kdu_long layer_bytes[64];
|
||||
U32 max_bytes = 0;
|
||||
|
||||
if (num_components >= 3)
|
||||
{
|
||||
// Note that we always use YCC and not YUV
|
||||
|
|
@ -596,66 +599,51 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co
|
|||
set_default_colour_weights(codestream.access_siz());
|
||||
}
|
||||
|
||||
// Set codestream options
|
||||
int nb_layers = 0;
|
||||
kdu_long layer_bytes[MAX_NB_LAYERS];
|
||||
U32 max_bytes = (U32)(base.getWidth() * base.getHeight() * base.getComponents());
|
||||
|
||||
// Rate is the argument passed into the LLImageJ2C which specifies the target compression rate. The default is 8:1.
|
||||
// *TODO: mRate is actually always 8:1 in the viewer. Test different values.
|
||||
llassert (base.mRate > 0.f);
|
||||
max_bytes = (U32)((F32)(max_bytes) * base.mRate);
|
||||
|
||||
// This code is where we specify the target number of bytes for each quality layer.
|
||||
// We're using a logarithmic spacing rule that fits with our way of fetching texture data.
|
||||
// Note: For more info on this layers business, read kdu_codestream::flush() doc in kdu_compressed.h
|
||||
layer_bytes[nb_layers++] = FIRST_PACKET_SIZE;
|
||||
U32 i = MIN_LAYER_SIZE;
|
||||
while ((i < max_bytes) && (nb_layers < (MAX_NB_LAYERS-1)))
|
||||
{
|
||||
layer_bytes[nb_layers++] = i;
|
||||
i *= 4;
|
||||
}
|
||||
// Note: for small images, we can have (max_bytes < FIRST_PACKET_SIZE), hence the test
|
||||
if (layer_bytes[nb_layers-1] < max_bytes)
|
||||
{
|
||||
// Set the last quality layer so to fit the preset compression ratio
|
||||
layer_bytes[nb_layers++] = max_bytes;
|
||||
}
|
||||
|
||||
if (reversible)
|
||||
{
|
||||
// Use 0 for a last quality layer for reversible images so all remaining code blocks will be flushed
|
||||
// Hack: KDU encoding for reversible images has a bug for small images that leads to j2c images that
|
||||
// cannot be open or are very blurry. Avoiding that last layer prevents the problem to happen.
|
||||
if ((base.getWidth() >= 32) || (base.getHeight() >= 32))
|
||||
{
|
||||
layer_bytes[nb_layers++] = 0;
|
||||
}
|
||||
codestream.access_siz()->parse_string("Creversible=yes");
|
||||
// *TODO: we should use yuv in reversible mode and one level since those images are small.
|
||||
// Don't turn this on now though as both create problems on decoding for the moment
|
||||
//codestream.access_siz()->parse_string("Clevels=1");
|
||||
// *TODO: we should use yuv in reversible mode
|
||||
// Don't turn this on now though as it creates problems on decoding for the moment
|
||||
//codestream.access_siz()->parse_string("Cycc=no");
|
||||
// If we're doing reversible (i.e. lossless compression), assumes we're not using quality layers.
|
||||
// *TODO: this is incorrect and unecessary. Try using the regular layer setting.
|
||||
codestream.access_siz()->parse_string("Clayers=1");
|
||||
num_layer_specs = 1;
|
||||
layer_bytes[0] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rate is the argument passed into the LLImageJ2C which
|
||||
// specifies the target compression rate. The default is 8:1.
|
||||
// Possibly if max_bytes < 500, we should just use the default setting?
|
||||
// *TODO: mRate is actually always 8:1 in the viewer. Test different values. Also force to reversible for small (< 500 bytes) textures.
|
||||
if (base.mRate != 0.f)
|
||||
{
|
||||
max_bytes = (U32)(base.mRate*base.getWidth()*base.getHeight()*base.getComponents());
|
||||
}
|
||||
else
|
||||
{
|
||||
max_bytes = (U32)(base.getWidth()*base.getHeight()*base.getComponents()*0.125);
|
||||
}
|
||||
|
||||
const U32 min_bytes = FIRST_PACKET_SIZE;
|
||||
if (max_bytes > min_bytes)
|
||||
{
|
||||
U32 i;
|
||||
// This code is where we specify the target number of bytes for
|
||||
// each layer. Not sure if we should do this for small images
|
||||
// or not. The goal is to have this roughly align with
|
||||
// different quality levels that we decode at.
|
||||
for (i = min_bytes; i < max_bytes; i*=4)
|
||||
{
|
||||
if (i == min_bytes * 4)
|
||||
{
|
||||
i = 2000;
|
||||
}
|
||||
layer_bytes[num_layer_specs] = i;
|
||||
num_layer_specs++;
|
||||
}
|
||||
layer_bytes[num_layer_specs] = max_bytes;
|
||||
num_layer_specs++;
|
||||
|
||||
std::string layer_string = llformat("Clayers=%d",num_layer_specs);
|
||||
codestream.access_siz()->parse_string(layer_string.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
layer_bytes[0] = min_bytes;
|
||||
num_layer_specs = 1;
|
||||
std::string layer_string = llformat("Clayers=%d",num_layer_specs);
|
||||
codestream.access_siz()->parse_string(layer_string.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string layer_string = llformat("Clayers=%d",nb_layers);
|
||||
codestream.access_siz()->parse_string(layer_string.c_str());
|
||||
|
||||
// Set up data ordering, markers, etc... if precincts or blocks specified
|
||||
if ((mBlocksSize != -1) || (mPrecinctsSize != -1))
|
||||
{
|
||||
|
|
@ -669,23 +657,26 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co
|
|||
std::string blocks_string = llformat("Cblk={%d,%d}",mBlocksSize,mBlocksSize);
|
||||
codestream.access_siz()->parse_string(blocks_string.c_str());
|
||||
}
|
||||
std::string ordering_string = llformat("Corder=RPCL");
|
||||
std::string ordering_string = llformat("Corder=LRCP");
|
||||
codestream.access_siz()->parse_string(ordering_string.c_str());
|
||||
std::string PLT_string = llformat("ORGgen_plt=yes");
|
||||
codestream.access_siz()->parse_string(PLT_string.c_str());
|
||||
std::string Parts_string = llformat("ORGtparts=R");
|
||||
codestream.access_siz()->parse_string(Parts_string.c_str());
|
||||
}
|
||||
|
||||
// Set the number of wavelets subresolutions (aka levels)
|
||||
if (mLevels != 0)
|
||||
{
|
||||
std::string levels_string = llformat("Clevels=%d",mLevels);
|
||||
codestream.access_siz()->parse_string(levels_string.c_str());
|
||||
}
|
||||
|
||||
// Complete the encode settings
|
||||
codestream.access_siz()->finalize_all();
|
||||
codestream.change_appearance(transpose,vflip,hflip);
|
||||
|
||||
// Now we are ready for sample data processing.
|
||||
// Now we are ready for sample data processing
|
||||
kdc_flow_control *tile = new kdc_flow_control(&mem_in,codestream);
|
||||
bool done = false;
|
||||
while (!done)
|
||||
|
|
@ -702,7 +693,7 @@ BOOL LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co
|
|||
}
|
||||
|
||||
// Produce the compressed output
|
||||
codestream.flush(layer_bytes,num_layer_specs);
|
||||
codestream.flush(layer_bytes,nb_layers);
|
||||
|
||||
// Cleanup
|
||||
delete tile;
|
||||
|
|
@ -750,6 +741,207 @@ BOOL LLImageJ2CKDU::getMetadata(LLImageJ2C &base)
|
|||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* STATIC copy_block */
|
||||
/*****************************************************************************/
|
||||
|
||||
static void copy_block(kdu_block *in, kdu_block *out)
|
||||
{
|
||||
if (in->K_max_prime != out->K_max_prime)
|
||||
{
|
||||
std::cout << "Cannot copy blocks belonging to subbands with different quantization parameters." << std::endl;
|
||||
return;
|
||||
}
|
||||
if ((in->size.x != out->size.x) || (in->size.y != out->size.y))
|
||||
{
|
||||
std::cout << "Cannot copy code-blocks with different dimensions." << std::endl;
|
||||
return;
|
||||
}
|
||||
out->missing_msbs = in->missing_msbs;
|
||||
if (out->max_passes < (in->num_passes+2)) // Gives us enough to round up
|
||||
out->set_max_passes(in->num_passes+2,false); // to the next whole bit-plane
|
||||
out->num_passes = in->num_passes;
|
||||
int num_bytes = 0;
|
||||
for (int z=0; z < in->num_passes; z++)
|
||||
{
|
||||
num_bytes += (out->pass_lengths[z] = in->pass_lengths[z]);
|
||||
out->pass_slopes[z] = in->pass_slopes[z];
|
||||
}
|
||||
|
||||
// Just copy compressed code-bytes. Block transcoding not supported.
|
||||
if (out->max_bytes < num_bytes)
|
||||
out->set_max_bytes(num_bytes,false);
|
||||
memcpy(out->byte_buffer,in->byte_buffer,(size_t) num_bytes);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* STATIC copy_tile */
|
||||
/*****************************************************************************/
|
||||
|
||||
static void
|
||||
copy_tile(kdu_tile tile_in, kdu_tile tile_out, int tnum_in, int tnum_out,
|
||||
kdu_params *siz_in, kdu_params *siz_out, int skip_components,
|
||||
int &num_blocks)
|
||||
{
|
||||
int num_components = tile_out.get_num_components();
|
||||
int new_tpart=0, next_tpart = 1;
|
||||
|
||||
for (int c=0; c < num_components; c++)
|
||||
{
|
||||
kdu_tile_comp comp_in, comp_out;
|
||||
comp_in = tile_in.access_component(c);
|
||||
comp_out = tile_out.access_component(c);
|
||||
int num_resolutions = comp_out.get_num_resolutions();
|
||||
//std::cout << " Copying tile : num_resolutions = " << num_resolutions << std::endl;
|
||||
for (int r=0; r < num_resolutions; r++)
|
||||
{
|
||||
kdu_resolution res_in; res_in = comp_in.access_resolution(r);
|
||||
kdu_resolution res_out; res_out = comp_out.access_resolution(r);
|
||||
int b, min_band;
|
||||
int num_bands = res_in.get_valid_band_indices(min_band);
|
||||
std::cout << " Copying tile : num_bands = " << num_bands << std::endl;
|
||||
for (b=min_band; num_bands > 0; num_bands--, b++)
|
||||
{
|
||||
kdu_subband band_in; band_in = res_in.access_subband(b);
|
||||
kdu_subband band_out; band_out = res_out.access_subband(b);
|
||||
kdu_dims blocks_in; band_in.get_valid_blocks(blocks_in);
|
||||
kdu_dims blocks_out; band_out.get_valid_blocks(blocks_out);
|
||||
if ((blocks_in.size.x != blocks_out.size.x) ||
|
||||
(blocks_in.size.y != blocks_out.size.y))
|
||||
{
|
||||
std::cout << "Transcoding operation cannot proceed: Code-block partitions for the input and output code-streams do not agree." << std::endl;
|
||||
return;
|
||||
}
|
||||
kdu_coords idx;
|
||||
//std::cout << " Copying tile : block indices, x = " << blocks_out.size.x << " and y = " << blocks_out.size.y << std::endl;
|
||||
for (idx.y=0; idx.y < blocks_out.size.y; idx.y++)
|
||||
{
|
||||
for (idx.x=0; idx.x < blocks_out.size.x; idx.x++)
|
||||
{
|
||||
kdu_block *in =
|
||||
band_in.open_block(idx+blocks_in.pos,&new_tpart);
|
||||
for (; next_tpart <= new_tpart; next_tpart++)
|
||||
siz_out->copy_from(siz_in,tnum_in,tnum_out,next_tpart,
|
||||
skip_components);
|
||||
kdu_block *out = band_out.open_block(idx+blocks_out.pos);
|
||||
copy_block(in,out);
|
||||
band_in.close_block(in);
|
||||
band_out.close_block(out);
|
||||
num_blocks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the block boundary for each discard level in the input image.
|
||||
// We parse the input blocks and copy them in a temporary output stream.
|
||||
// For the moment, we do nothing more that parsing the raw list of blocks and outputing result.
|
||||
void LLImageJ2CKDU::findDiscardLevelsBoundaries(LLImageJ2C &base)
|
||||
{
|
||||
// We need the number of levels in that image before starting.
|
||||
getMetadata(base);
|
||||
|
||||
for (int discard_level = 0; discard_level < mLevels; discard_level++)
|
||||
{
|
||||
//std::cout << "Parsing discard level = " << discard_level << std::endl;
|
||||
// Create the input codestream object.
|
||||
setupCodeStream(base, TRUE, MODE_FAST);
|
||||
mCodeStreamp->apply_input_restrictions(0, 4, discard_level, 0, NULL);
|
||||
mCodeStreamp->set_max_bytes(KDU_LONG_MAX,true);
|
||||
siz_params *siz_in = mCodeStreamp->access_siz();
|
||||
|
||||
// Create the output codestream object.
|
||||
siz_params siz;
|
||||
siz.copy_from(siz_in,-1,-1,-1,0,discard_level,false,false,false);
|
||||
siz.set(Scomponents,0,0,mCodeStreamp->get_num_components());
|
||||
|
||||
U32 max_output_size = base.getWidth()*base.getHeight()*base.getComponents();
|
||||
max_output_size = (max_output_size < 1000 ? 1000 : max_output_size);
|
||||
U8 *output_buffer = new U8[max_output_size];
|
||||
U32 output_size = 0; // Address updated by LLKDUMemTarget to give the final compressed buffer size
|
||||
LLKDUMemTarget output(output_buffer, output_size, max_output_size);
|
||||
kdu_codestream codestream_out;
|
||||
codestream_out.create(&siz,&output);
|
||||
//codestream_out.share_buffering(*mCodeStreamp);
|
||||
siz_params *siz_out = codestream_out.access_siz();
|
||||
siz_out->copy_from(siz_in,-1,-1,-1,0,discard_level,false,false,false);
|
||||
codestream_out.access_siz()->finalize_all(-1);
|
||||
|
||||
// Set up rate control variables
|
||||
kdu_long max_bytes = KDU_LONG_MAX;
|
||||
kdu_params *cod = siz_out->access_cluster(COD_params);
|
||||
int total_layers; cod->get(Clayers,0,0,total_layers);
|
||||
kdu_long *layer_bytes = new kdu_long[total_layers];
|
||||
int nel, non_empty_layers = 0;
|
||||
|
||||
// Now ready to perform the transfer of compressed data between streams
|
||||
int flush_counter = INT_MAX;
|
||||
kdu_dims tile_indices_in;
|
||||
mCodeStreamp->get_valid_tiles(tile_indices_in);
|
||||
kdu_dims tile_indices_out;
|
||||
codestream_out.get_valid_tiles(tile_indices_out);
|
||||
assert((tile_indices_in.size.x == tile_indices_out.size.x) &&
|
||||
(tile_indices_in.size.y == tile_indices_out.size.y));
|
||||
int num_blocks=0;
|
||||
|
||||
kdu_coords idx;
|
||||
//std::cout << "Parsing tiles : x = " << tile_indices_out.size.x << " to y = " << tile_indices_out.size.y << std::endl;
|
||||
for (idx.y=0; idx.y < tile_indices_out.size.y; idx.y++)
|
||||
{
|
||||
for (idx.x=0; idx.x < tile_indices_out.size.x; idx.x++)
|
||||
{
|
||||
kdu_tile tile_in = mCodeStreamp->open_tile(idx+tile_indices_in.pos);
|
||||
int tnum_in = tile_in.get_tnum();
|
||||
int tnum_out = idx.x + idx.y*tile_indices_out.size.x;
|
||||
siz_out->copy_from(siz_in,tnum_in,tnum_out,0,0,discard_level,false,false,false);
|
||||
siz_out->finalize_all(tnum_out);
|
||||
// Note: do not open the output tile without first copying any tile-specific code-stream parameters
|
||||
kdu_tile tile_out = codestream_out.open_tile(idx+tile_indices_out.pos);
|
||||
assert(tnum_out == tile_out.get_tnum());
|
||||
copy_tile(tile_in,tile_out,tnum_in,tnum_out,siz_in,siz_out,0,num_blocks);
|
||||
tile_in.close();
|
||||
tile_out.close();
|
||||
flush_counter--;
|
||||
if ((flush_counter <= 0) && codestream_out.ready_for_flush())
|
||||
{
|
||||
flush_counter = INT_MAX;
|
||||
nel = codestream_out.trans_out(max_bytes,layer_bytes,total_layers);
|
||||
non_empty_layers = (nel > non_empty_layers)?nel:non_empty_layers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the output code-stream
|
||||
if (codestream_out.ready_for_flush())
|
||||
{
|
||||
nel = codestream_out.trans_out(max_bytes,layer_bytes,total_layers);
|
||||
non_empty_layers = (nel > non_empty_layers)?nel:non_empty_layers;
|
||||
}
|
||||
if (non_empty_layers > total_layers)
|
||||
non_empty_layers = total_layers; // Can happen if a tile has more layers
|
||||
|
||||
// Print out stats
|
||||
std::cout << "Code stream parsing for discard level = " << discard_level << std::endl;
|
||||
std::cout << " Total compressed memory in = " << mCodeStreamp->get_compressed_data_memory() << " bytes" << std::endl;
|
||||
std::cout << " Total compressed memory out = " << codestream_out.get_compressed_data_memory() << " bytes" << std::endl;
|
||||
//std::cout << " Output contains " << total_layers << " quality layers" << std::endl;
|
||||
std::cout << " Transferred " << num_blocks << " code-blocks from in to out" << std::endl;
|
||||
//std::cout << " Read " << mCodeStreamp->get_num_tparts() << " tile-part(s) from a total of " << (int) tile_indices_in.area() << " tile(s)" << std::endl;
|
||||
std::cout << " Total bytes read = " << mCodeStreamp->get_total_bytes() << std::endl;
|
||||
//std::cout << " Wrote " << codestream_out.get_num_tparts() << " tile-part(s) in a total of " << (int) tile_indices_out.area() << " tile(s)" << std::endl;
|
||||
std::cout << " Total bytes written = " << codestream_out.get_total_bytes() << std::endl;
|
||||
std::cout << "-------------" << std::endl;
|
||||
|
||||
// Clean-up
|
||||
cleanupCodeStream();
|
||||
codestream_out.destroy();
|
||||
delete[] output_buffer;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void set_default_colour_weights(kdu_params *siz)
|
||||
{
|
||||
kdu_params *cod = siz->access_cluster(COD_params);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ protected:
|
|||
BOOL reversible=FALSE);
|
||||
/*virtual*/ BOOL initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level = -1, int* region = NULL);
|
||||
/*virtual*/ BOOL initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size = -1, int precincts_size = -1, int levels = 0);
|
||||
void findDiscardLevelsBoundaries(LLImageJ2C &base);
|
||||
|
||||
private:
|
||||
BOOL initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, ECodeStreamMode mode, S32 first_channel, S32 max_channel_count, int discard_level = -1, int* region = NULL);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
// Class to test
|
||||
#include "llimagej2ckdu.h"
|
||||
#include "llkdumem.h"
|
||||
#include "kdu_block_coding.h"
|
||||
// Tut header
|
||||
#include "lltut.h"
|
||||
|
||||
|
|
@ -86,7 +87,7 @@ void LLImageFormatted::resetLastError() { }
|
|||
void LLImageFormatted::sanityCheck() { }
|
||||
void LLImageFormatted::setLastError(const std::string& , const std::string& ) { }
|
||||
|
||||
LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C) { }
|
||||
LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C), mRate(DEFAULT_COMPRESSION_RATE) { }
|
||||
LLImageJ2C::~LLImageJ2C() { }
|
||||
S32 LLImageJ2C::calcDataSize(S32 ) { return 0; }
|
||||
S32 LLImageJ2C::calcDiscardLevelBytes(S32 ) { return 0; }
|
||||
|
|
@ -107,16 +108,25 @@ bool LLKDUMemIn::get(int, kdu_line_buf&, int) { return false; }
|
|||
|
||||
// Stub Kakadu Library calls
|
||||
kdu_tile_comp kdu_tile::access_component(int ) { kdu_tile_comp a; return a; }
|
||||
kdu_block_encoder::kdu_block_encoder() { }
|
||||
kdu_block_decoder::kdu_block_decoder() { }
|
||||
void kdu_block::set_max_passes(int , bool ) { }
|
||||
void kdu_block::set_max_bytes(int , bool ) { }
|
||||
void kdu_block::set_max_samples(int ) { }
|
||||
void kdu_tile::close(kdu_thread_env* ) { }
|
||||
int kdu_tile::get_num_components() { return 0; }
|
||||
bool kdu_tile::get_ycc() { return false; }
|
||||
void kdu_tile::set_components_of_interest(int , const int* ) { }
|
||||
int kdu_tile::get_tnum() { return 0; }
|
||||
kdu_resolution kdu_tile_comp::access_resolution() { kdu_resolution a; return a; }
|
||||
kdu_resolution kdu_tile_comp::access_resolution(int ) { kdu_resolution a; return a; }
|
||||
int kdu_tile_comp::get_bit_depth(bool ) { return 8; }
|
||||
bool kdu_tile_comp::get_reversible() { return false; }
|
||||
int kdu_tile_comp::get_num_resolutions() { return 1; }
|
||||
kdu_subband kdu_resolution::access_subband(int ) { kdu_subband a; return a; }
|
||||
void kdu_resolution::get_dims(kdu_dims& ) { }
|
||||
int kdu_resolution::which() { return 0; }
|
||||
int kdu_resolution::get_valid_band_indices(int &) { return 1; }
|
||||
kdu_decoder::kdu_decoder(kdu_subband , kdu_sample_allocator*, bool , float, int, kdu_thread_env*, kdu_thread_queue*) { }
|
||||
kdu_synthesis::kdu_synthesis(kdu_resolution, kdu_sample_allocator*, bool, float, kdu_thread_env*, kdu_thread_queue*) { }
|
||||
kdu_params::kdu_params(const char*, bool, bool, bool, bool, bool) { }
|
||||
|
|
@ -124,6 +134,7 @@ kdu_params::~kdu_params() { }
|
|||
void kdu_params::set(const char* , int , int , bool ) { }
|
||||
void kdu_params::set(const char* , int , int , int ) { }
|
||||
void kdu_params::finalize_all(bool ) { }
|
||||
void kdu_params::finalize_all(int, bool ) { }
|
||||
void kdu_params::copy_from(kdu_params*, int, int, int, int, int, bool, bool, bool) { }
|
||||
bool kdu_params::parse_string(const char*) { return false; }
|
||||
bool kdu_params::get(const char*, int, int, bool&, bool, bool, bool) { return false; }
|
||||
|
|
@ -135,6 +146,7 @@ void kdu_codestream::set_fast() { }
|
|||
void kdu_codestream::set_fussy() { }
|
||||
void kdu_codestream::get_dims(int, kdu_dims&, bool ) { }
|
||||
int kdu_codestream::get_min_dwt_levels() { return 5; }
|
||||
int kdu_codestream::get_max_tile_layers() { return 1; }
|
||||
void kdu_codestream::change_appearance(bool, bool, bool) { }
|
||||
void kdu_codestream::get_tile_dims(kdu_coords, int, kdu_dims&, bool ) { }
|
||||
void kdu_codestream::destroy() { }
|
||||
|
|
@ -148,9 +160,18 @@ void kdu_codestream::get_subsampling(int , kdu_coords&, bool ) { }
|
|||
void kdu_codestream::flush(kdu_long *, int , kdu_uint16 *, bool, bool, double, kdu_thread_env*) { }
|
||||
void kdu_codestream::set_resilient(bool ) { }
|
||||
int kdu_codestream::get_num_components(bool ) { return 0; }
|
||||
kdu_long kdu_codestream::get_total_bytes(bool ) { return 0; }
|
||||
kdu_long kdu_codestream::get_compressed_data_memory(bool ) {return 0; }
|
||||
void kdu_codestream::share_buffering(kdu_codestream ) { }
|
||||
int kdu_codestream::get_num_tparts() { return 0; }
|
||||
int kdu_codestream::trans_out(kdu_long, kdu_long*, int, bool, kdu_thread_env* ) { return 0; }
|
||||
bool kdu_codestream::ready_for_flush(kdu_thread_env*) { return false; }
|
||||
siz_params* kdu_codestream::access_siz() { return NULL; }
|
||||
kdu_tile kdu_codestream::open_tile(kdu_coords , kdu_thread_env* ) { kdu_tile a; return a; }
|
||||
kdu_codestream_comment kdu_codestream::add_comment() { kdu_codestream_comment a; return a; }
|
||||
void kdu_subband::close_block(kdu_block*, kdu_thread_env*) { }
|
||||
void kdu_subband::get_valid_blocks(kdu_dims &indices) { }
|
||||
kdu_block* kdu_subband::open_block(kdu_coords, int*, kdu_thread_env*) { return NULL; }
|
||||
bool kdu_codestream_comment::put_text(const char*) { return false; }
|
||||
void kdu_customize_warnings(kdu_message*) { }
|
||||
void kdu_customize_errors(kdu_message*) { }
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ const F32 F_ALMOST_ONE = 1.0f - F_ALMOST_ZERO;
|
|||
const F32 FP_MAG_THRESHOLD = 0.0000001f;
|
||||
|
||||
// TODO: Replace with logic like is_approx_equal
|
||||
inline BOOL is_approx_zero( F32 f ) { return (-F_APPROXIMATELY_ZERO < f) && (f < F_APPROXIMATELY_ZERO); }
|
||||
inline bool is_approx_zero( F32 f ) { return (-F_APPROXIMATELY_ZERO < f) && (f < F_APPROXIMATELY_ZERO); }
|
||||
|
||||
// These functions work by interpreting sign+exp+mantissa as an unsigned
|
||||
// integer.
|
||||
|
|
@ -111,13 +111,13 @@ inline BOOL is_approx_zero( F32 f ) { return (-F_APPROXIMATELY_ZERO < f) && (f <
|
|||
// WARNING: Infinity is comparable with F32_MAX and negative
|
||||
// infinity is comparable with F32_MIN
|
||||
|
||||
inline BOOL is_approx_equal(F32 x, F32 y)
|
||||
inline bool is_approx_equal(F32 x, F32 y)
|
||||
{
|
||||
const S32 COMPARE_MANTISSA_UP_TO_BIT = 0x02;
|
||||
return (std::abs((S32) ((U32&)x - (U32&)y) ) < COMPARE_MANTISSA_UP_TO_BIT);
|
||||
}
|
||||
|
||||
inline BOOL is_approx_equal(F64 x, F64 y)
|
||||
inline bool is_approx_equal(F64 x, F64 y)
|
||||
{
|
||||
const S64 COMPARE_MANTISSA_UP_TO_BIT = 0x02;
|
||||
return (std::abs((S32) ((U64&)x - (U64&)y) ) < COMPARE_MANTISSA_UP_TO_BIT);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class LLVolumeTriangle;
|
|||
#include "llstrider.h"
|
||||
#include "v4coloru.h"
|
||||
#include "llrefcount.h"
|
||||
#include "llpointer.h"
|
||||
#include "llfile.h"
|
||||
|
||||
//============================================================================
|
||||
|
|
@ -919,6 +920,10 @@ public:
|
|||
LLVector2* mTexCoords;
|
||||
U16* mIndices;
|
||||
|
||||
//vertex buffer filled in by LLFace to cache this volume face geometry in vram
|
||||
// (declared as a LLPointer to LLRefCount to avoid dependency on LLVertexBuffer)
|
||||
mutable LLPointer<LLRefCount> mVertexBuffer;
|
||||
|
||||
std::vector<S32> mEdge;
|
||||
|
||||
//list of skin weights for rigged volumes
|
||||
|
|
|
|||
|
|
@ -548,6 +548,7 @@ LLCurl::Multi::Multi(F32 idle_time_out)
|
|||
mErrorCount(0),
|
||||
mState(STATE_READY),
|
||||
mDead(FALSE),
|
||||
mValid(TRUE),
|
||||
mMutexp(NULL),
|
||||
mDeletionMutexp(NULL),
|
||||
mEasyMutexp(NULL)
|
||||
|
|
@ -583,22 +584,33 @@ LLCurl::Multi::Multi(F32 idle_time_out)
|
|||
|
||||
LLCurl::Multi::~Multi()
|
||||
{
|
||||
cleanup() ;
|
||||
cleanup(true) ;
|
||||
|
||||
delete mDeletionMutexp ;
|
||||
mDeletionMutexp = NULL ;
|
||||
}
|
||||
|
||||
void LLCurl::Multi::cleanup()
|
||||
void LLCurl::Multi::cleanup(bool deleted)
|
||||
{
|
||||
if(!mCurlMultiHandle)
|
||||
{
|
||||
return ; //nothing to clean.
|
||||
}
|
||||
llassert_always(deleted || !mValid) ;
|
||||
|
||||
LLMutexLock lock(mDeletionMutexp);
|
||||
|
||||
// Clean up active
|
||||
for(easy_active_list_t::iterator iter = mEasyActiveList.begin();
|
||||
iter != mEasyActiveList.end(); ++iter)
|
||||
{
|
||||
Easy* easy = *iter;
|
||||
check_curl_multi_code(curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle()));
|
||||
|
||||
if(deleted)
|
||||
{
|
||||
easy->mResponder = NULL ; //avoid triggering mResponder.
|
||||
}
|
||||
delete easy;
|
||||
}
|
||||
mEasyActiveList.clear();
|
||||
|
|
@ -610,11 +622,9 @@ void LLCurl::Multi::cleanup()
|
|||
|
||||
check_curl_multi_code(LLCurl::deleteMultiHandle(mCurlMultiHandle));
|
||||
mCurlMultiHandle = NULL ;
|
||||
|
||||
|
||||
delete mMutexp ;
|
||||
mMutexp = NULL ;
|
||||
delete mDeletionMutexp ;
|
||||
mDeletionMutexp = NULL ;
|
||||
delete mEasyMutexp ;
|
||||
mEasyMutexp = NULL ;
|
||||
|
||||
|
|
@ -644,10 +654,20 @@ void LLCurl::Multi::unlock()
|
|||
|
||||
void LLCurl::Multi::markDead()
|
||||
{
|
||||
LLMutexLock lock(mDeletionMutexp) ;
|
||||
{
|
||||
LLMutexLock lock(mDeletionMutexp) ;
|
||||
|
||||
mDead = TRUE ;
|
||||
LLCurl::getCurlThread()->setPriority(mHandle, LLQueuedThread::PRIORITY_URGENT) ;
|
||||
if(mCurlMultiHandle != NULL)
|
||||
{
|
||||
mDead = TRUE ;
|
||||
LLCurl::getCurlThread()->setPriority(mHandle, LLQueuedThread::PRIORITY_URGENT) ;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//not valid, delete it.
|
||||
delete this;
|
||||
}
|
||||
|
||||
void LLCurl::Multi::setState(LLCurl::Multi::ePerformState state)
|
||||
|
|
@ -741,10 +761,14 @@ bool LLCurl::Multi::doPerform()
|
|||
setState(STATE_COMPLETED) ;
|
||||
mIdleTimer.reset() ;
|
||||
}
|
||||
else if(mIdleTimer.getElapsedTimeF32() > mIdleTimeOut) //idle for too long, remove it.
|
||||
else if(!mValid && mIdleTimer.getElapsedTimeF32() > mIdleTimeOut) //idle for too long, remove it.
|
||||
{
|
||||
dead = true ;
|
||||
}
|
||||
else if(mValid && mIdleTimer.getElapsedTimeF32() > mIdleTimeOut - 1.f) //idle for too long, mark it invalid.
|
||||
{
|
||||
mValid = FALSE ;
|
||||
}
|
||||
|
||||
return dead ;
|
||||
}
|
||||
|
|
@ -966,15 +990,8 @@ void LLCurlThread::killMulti(LLCurl::Multi* multi)
|
|||
return ;
|
||||
}
|
||||
|
||||
if(multi->isValid())
|
||||
{
|
||||
multi->markDead() ;
|
||||
}
|
||||
else
|
||||
{
|
||||
deleteMulti(multi) ;
|
||||
}
|
||||
}
|
||||
|
||||
//private
|
||||
bool LLCurlThread::doMultiPerform(LLCurl::Multi* multi)
|
||||
|
|
@ -992,6 +1009,10 @@ void LLCurlThread::deleteMulti(LLCurl::Multi* multi)
|
|||
void LLCurlThread::cleanupMulti(LLCurl::Multi* multi)
|
||||
{
|
||||
multi->cleanup() ;
|
||||
if(multi->isDead()) //check if marked dead during cleaning up.
|
||||
{
|
||||
deleteMulti(multi) ;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
|
@ -1506,7 +1527,8 @@ void LLCurl::cleanupClass()
|
|||
delete sHandleMutexp ;
|
||||
sHandleMutexp = NULL ;
|
||||
|
||||
llassert(Easy::sActiveHandles.empty());
|
||||
// removed as per https://jira.secondlife.com/browse/SH-3115
|
||||
//llassert(Easy::sActiveHandles.empty());
|
||||
}
|
||||
|
||||
//static
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ public:
|
|||
ePerformState getState() ;
|
||||
|
||||
bool isCompleted() ;
|
||||
bool isValid() {return mCurlMultiHandle != NULL ;}
|
||||
bool isValid() {return mCurlMultiHandle != NULL && mValid;}
|
||||
bool isDead() {return mDead;}
|
||||
|
||||
bool waitToComplete() ;
|
||||
|
|
@ -318,7 +318,7 @@ public:
|
|||
|
||||
private:
|
||||
void easyFree(LLCurl::Easy*);
|
||||
void cleanup() ;
|
||||
void cleanup(bool deleted = false) ;
|
||||
|
||||
CURLM* mCurlMultiHandle;
|
||||
|
||||
|
|
@ -333,6 +333,7 @@ private:
|
|||
ePerformState mState;
|
||||
|
||||
BOOL mDead ;
|
||||
BOOL mValid ;
|
||||
LLMutex* mMutexp ;
|
||||
LLMutex* mDeletionMutexp ;
|
||||
LLMutex* mEasyMutexp ;
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ S32 getElementSize(const LLSD& llsd)
|
|||
case LLSD::TypeReal:
|
||||
return sizeof(F64);
|
||||
case LLSD::TypeString:
|
||||
return llsd.asString().size();
|
||||
return llsd.size();
|
||||
case LLSD::TypeUUID:
|
||||
return sizeof(LLUUID);
|
||||
case LLSD::TypeDate:
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
// external library headers
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "../test/catch_and_store_what_in.h"
|
||||
#include "llsdserialize.h"
|
||||
#include "llevents.h"
|
||||
#include "stringize.h"
|
||||
|
|
@ -72,43 +73,14 @@ namespace tut
|
|||
template<> template<>
|
||||
void llsdmessage_object::test<1>()
|
||||
{
|
||||
bool threw = false;
|
||||
std::string threw;
|
||||
// This should fail...
|
||||
try
|
||||
{
|
||||
LLSDMessage localListener;
|
||||
}
|
||||
catch (const LLEventPump::DupPumpName&)
|
||||
{
|
||||
threw = true;
|
||||
}
|
||||
catch (const std::runtime_error& ex)
|
||||
{
|
||||
// This clause is because on Linux, on the viewer side, for this
|
||||
// one test program (though not others!), the
|
||||
// LLEventPump::DupPumpName exception isn't caught by the clause
|
||||
// above. Warn the user...
|
||||
std::cerr << "Failed to catch " << typeid(ex).name() << std::endl;
|
||||
// But if the expected exception was thrown, allow the test to
|
||||
// succeed anyway. Not sure how else to handle this odd case.
|
||||
if (std::string(typeid(ex).name()) == typeid(LLEventPump::DupPumpName).name())
|
||||
{
|
||||
threw = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't even recognize this exception. Let it propagate
|
||||
// out to TUT to fail the test.
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "Utterly failed to catch expected exception!" << std::endl;
|
||||
// This case is full of fail. We HAVE to address it.
|
||||
throw;
|
||||
}
|
||||
ensure("second LLSDMessage should throw", threw);
|
||||
CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupPumpName)
|
||||
ensure("second LLSDMessage should throw", ! threw.empty());
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include "llpluginprocessparent.h"
|
||||
#include "llpluginmessagepipe.h"
|
||||
#include "llpluginmessageclasses.h"
|
||||
#include "stringize.h"
|
||||
|
||||
#include "llapr.h"
|
||||
|
||||
|
|
@ -133,8 +134,8 @@ LLPluginProcessParent::~LLPluginProcessParent()
|
|||
// and remove it from our map
|
||||
mSharedMemoryRegions.erase(iter);
|
||||
}
|
||||
|
||||
mProcess.kill();
|
||||
|
||||
LLProcess::kill(mProcess);
|
||||
killSockets();
|
||||
}
|
||||
|
||||
|
|
@ -159,8 +160,8 @@ void LLPluginProcessParent::errorState(void)
|
|||
|
||||
void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_dir, const std::string &plugin_filename, bool debug)
|
||||
{
|
||||
mProcess.setExecutable(launcher_filename);
|
||||
mProcess.setWorkingDirectory(plugin_dir);
|
||||
mProcessParams.executable = launcher_filename;
|
||||
mProcessParams.cwd = plugin_dir;
|
||||
mPluginFile = plugin_filename;
|
||||
mPluginDir = plugin_dir;
|
||||
mCPUUsage = 0.0f;
|
||||
|
|
@ -371,10 +372,8 @@ void LLPluginProcessParent::idle(void)
|
|||
// Launch the plugin process.
|
||||
|
||||
// Only argument to the launcher is the port number we're listening on
|
||||
std::stringstream stream;
|
||||
stream << mBoundPort;
|
||||
mProcess.addArgument(stream.str());
|
||||
if(mProcess.launch() != 0)
|
||||
mProcessParams.args.add(stringize(mBoundPort));
|
||||
if (! (mProcess = LLProcess::create(mProcessParams)))
|
||||
{
|
||||
errorState();
|
||||
}
|
||||
|
|
@ -388,19 +387,18 @@ void LLPluginProcessParent::idle(void)
|
|||
// The command we're constructing would look like this on the command line:
|
||||
// osascript -e 'tell application "Terminal"' -e 'set win to do script "gdb -pid 12345"' -e 'do script "continue" in win' -e 'end tell'
|
||||
|
||||
std::stringstream cmd;
|
||||
|
||||
mDebugger.setExecutable("/usr/bin/osascript");
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("tell application \"Terminal\"");
|
||||
mDebugger.addArgument("-e");
|
||||
cmd << "set win to do script \"gdb -pid " << mProcess.getProcessID() << "\"";
|
||||
mDebugger.addArgument(cmd.str());
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("do script \"continue\" in win");
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("end tell");
|
||||
mDebugger.launch();
|
||||
LLProcess::Params params;
|
||||
params.executable = "/usr/bin/osascript";
|
||||
params.args.add("-e");
|
||||
params.args.add("tell application \"Terminal\"");
|
||||
params.args.add("-e");
|
||||
params.args.add(STRINGIZE("set win to do script \"gdb -pid "
|
||||
<< mProcess->getProcessID() << "\""));
|
||||
params.args.add("-e");
|
||||
params.args.add("do script \"continue\" in win");
|
||||
params.args.add("-e");
|
||||
params.args.add("end tell");
|
||||
mDebugger = LLProcess::create(params);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
|
@ -470,7 +468,7 @@ void LLPluginProcessParent::idle(void)
|
|||
break;
|
||||
|
||||
case STATE_EXITING:
|
||||
if(!mProcess.isRunning())
|
||||
if (! LLProcess::isRunning(mProcess))
|
||||
{
|
||||
setState(STATE_CLEANUP);
|
||||
}
|
||||
|
|
@ -498,7 +496,7 @@ void LLPluginProcessParent::idle(void)
|
|||
break;
|
||||
|
||||
case STATE_CLEANUP:
|
||||
mProcess.kill();
|
||||
LLProcess::kill(mProcess);
|
||||
killSockets();
|
||||
setState(STATE_DONE);
|
||||
break;
|
||||
|
|
@ -1077,7 +1075,7 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit()
|
|||
{
|
||||
bool result = false;
|
||||
|
||||
if(!mProcess.isRunning())
|
||||
if (! LLProcess::isRunning(mProcess))
|
||||
{
|
||||
LL_WARNS("Plugin") << "child exited" << LL_ENDL;
|
||||
result = true;
|
||||
|
|
|
|||
|
|
@ -30,13 +30,14 @@
|
|||
#define LL_LLPLUGINPROCESSPARENT_H
|
||||
|
||||
#include "llapr.h"
|
||||
#include "llprocesslauncher.h"
|
||||
#include "llprocess.h"
|
||||
#include "llpluginmessage.h"
|
||||
#include "llpluginmessagepipe.h"
|
||||
#include "llpluginsharedmemory.h"
|
||||
|
||||
#include "lliosocket.h"
|
||||
#include "llthread.h"
|
||||
#include "llsd.h"
|
||||
|
||||
class LLPluginProcessParentOwner
|
||||
{
|
||||
|
|
@ -139,26 +140,27 @@ private:
|
|||
};
|
||||
EState mState;
|
||||
void setState(EState state);
|
||||
|
||||
|
||||
bool pluginLockedUp();
|
||||
bool pluginLockedUpOrQuit();
|
||||
|
||||
bool accept();
|
||||
|
||||
|
||||
LLSocket::ptr_t mListenSocket;
|
||||
LLSocket::ptr_t mSocket;
|
||||
U32 mBoundPort;
|
||||
|
||||
LLProcessLauncher mProcess;
|
||||
|
||||
|
||||
LLProcess::Params mProcessParams;
|
||||
LLProcessPtr mProcess;
|
||||
|
||||
std::string mPluginFile;
|
||||
std::string mPluginDir;
|
||||
|
||||
LLPluginProcessParentOwner *mOwner;
|
||||
|
||||
|
||||
typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType;
|
||||
sharedMemoryRegionsType mSharedMemoryRegions;
|
||||
|
||||
|
||||
LLSD mMessageClassVersions;
|
||||
std::string mPluginVersionString;
|
||||
|
||||
|
|
@ -171,7 +173,7 @@ private:
|
|||
bool mBlocked;
|
||||
bool mPolledInput;
|
||||
|
||||
LLProcessLauncher mDebugger;
|
||||
LLProcessPtr mDebugger;
|
||||
|
||||
F32 mPluginLaunchTimeout; // Somewhat longer timeout for initial launch.
|
||||
F32 mPluginLockupTimeout; // If we don't receive a heartbeat in this many seconds, we declare the plugin locked up.
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ void LLCubeMap::initGL()
|
|||
{
|
||||
U32 texname = 0;
|
||||
|
||||
LLImageGL::generateTextures(1, &texname);
|
||||
LLImageGL::generateTextures(LLTexUnit::TT_CUBE_MAP, GL_RGB8, 1, &texname);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -422,6 +422,16 @@ S32 LLFontGL::renderUTF8(const std::string &text, S32 begin_offset, S32 x, S32 y
|
|||
}
|
||||
|
||||
// font metrics - override for LLFontFreetype that returns units of virtual pixels
|
||||
F32 LLFontGL::getAscenderHeight() const
|
||||
{
|
||||
return mFontFreetype->getAscenderHeight() / sScaleY;
|
||||
}
|
||||
|
||||
F32 LLFontGL::getDescenderHeight() const
|
||||
{
|
||||
return mFontFreetype->getDescenderHeight() / sScaleY;
|
||||
}
|
||||
|
||||
S32 LLFontGL::getLineHeight() const
|
||||
{
|
||||
return llceil(mFontFreetype->getAscenderHeight() / sScaleY) + llceil(mFontFreetype->getDescenderHeight() / sScaleY);
|
||||
|
|
|
|||
|
|
@ -115,6 +115,8 @@ public:
|
|||
S32 renderUTF8(const std::string &text, S32 begin_offset, S32 x, S32 y, const LLColor4 &color, HAlign halign, VAlign valign, U8 style = NORMAL, ShadowType shadow = NO_SHADOW) const;
|
||||
|
||||
// font metrics - override for LLFontFreetype that returns units of virtual pixels
|
||||
F32 getAscenderHeight() const;
|
||||
F32 getDescenderHeight() const;
|
||||
S32 getLineHeight() const;
|
||||
|
||||
S32 getWidth(const std::string& utf8text) const;
|
||||
|
|
|
|||
|
|
@ -94,6 +94,10 @@ void APIENTRY gl_debug_callback(GLenum source,
|
|||
llwarns << "Severity: " << std::hex << severity << llendl;
|
||||
llwarns << "Message: " << message << llendl;
|
||||
llwarns << "-----------------------" << llendl;
|
||||
if (severity == GL_DEBUG_SEVERITY_HIGH_ARB)
|
||||
{
|
||||
llerrs << "Halting on GL Error" << llendl;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -245,6 +249,12 @@ PFNGLTEXIMAGE3DMULTISAMPLEPROC glTexImage3DMultisample = NULL;
|
|||
PFNGLGETMULTISAMPLEFVPROC glGetMultisamplefv = NULL;
|
||||
PFNGLSAMPLEMASKIPROC glSampleMaski = NULL;
|
||||
|
||||
//transform feedback (4.0 core)
|
||||
PFNGLBEGINTRANSFORMFEEDBACKPROC glBeginTransformFeedback = NULL;
|
||||
PFNGLENDTRANSFORMFEEDBACKPROC glEndTransformFeedback = NULL;
|
||||
PFNGLTRANSFORMFEEDBACKVARYINGSPROC glTransformFeedbackVaryings = NULL;
|
||||
PFNGLBINDBUFFERRANGEPROC glBindBufferRange = NULL;
|
||||
|
||||
//GL_ARB_debug_output
|
||||
PFNGLDEBUGMESSAGECONTROLARBPROC glDebugMessageControlARB = NULL;
|
||||
PFNGLDEBUGMESSAGEINSERTARBPROC glDebugMessageInsertARB = NULL;
|
||||
|
|
@ -417,6 +427,7 @@ LLGLManager::LLGLManager() :
|
|||
mHasDrawBuffers(FALSE),
|
||||
mHasTextureRectangle(FALSE),
|
||||
mHasTextureMultisample(FALSE),
|
||||
mHasTransformFeedback(FALSE),
|
||||
mMaxSampleMaskWords(0),
|
||||
mMaxColorTextureSamples(0),
|
||||
mMaxDepthTextureSamples(0),
|
||||
|
|
@ -554,7 +565,8 @@ bool LLGLManager::initGL()
|
|||
parse_gl_version( &mDriverVersionMajor,
|
||||
&mDriverVersionMinor,
|
||||
&mDriverVersionRelease,
|
||||
&mDriverVersionVendorString );
|
||||
&mDriverVersionVendorString,
|
||||
&mGLVersionString);
|
||||
|
||||
mGLVersion = mDriverVersionMajor + mDriverVersionMinor * .1f;
|
||||
|
||||
|
|
@ -572,6 +584,15 @@ bool LLGLManager::initGL()
|
|||
#endif
|
||||
}
|
||||
|
||||
if (mGLVersion >= 2.1f && LLImageGL::sCompressTextures)
|
||||
{ //use texture compression
|
||||
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
|
||||
}
|
||||
else
|
||||
{ //GL version is < 3.0, always disable texture compression
|
||||
LLImageGL::sCompressTextures = false;
|
||||
}
|
||||
|
||||
// Trailing space necessary to keep "nVidia Corpor_ati_on" cards
|
||||
// from being recognized as ATI.
|
||||
if (mGLVendor.substr(0,4) == "ATI ")
|
||||
|
|
@ -592,11 +613,8 @@ bool LLGLManager::initGL()
|
|||
#endif // LL_WINDOWS
|
||||
|
||||
#if (LL_WINDOWS || LL_LINUX) && !LL_MESA_HEADLESS
|
||||
// release 7277 is a point at which we verify that ATI OpenGL
|
||||
// drivers get pretty stable with SL, ~Catalyst 8.2,
|
||||
// for both Win32 and Linux.
|
||||
if (mDriverVersionRelease < 7277 &&
|
||||
mDriverVersionRelease != 0) // 0 == Undetectable driver version - these get to pretend to be new ATI drivers, though that decision may be revisited.
|
||||
// count any pre OpenGL 3.0 implementation as an old driver
|
||||
if (mGLVersion < 3.f)
|
||||
{
|
||||
mATIOldDriver = TRUE;
|
||||
}
|
||||
|
|
@ -735,6 +753,11 @@ bool LLGLManager::initGL()
|
|||
}
|
||||
#endif
|
||||
|
||||
if (mIsIntel && mGLVersion <= 3.f)
|
||||
{ //never try to use framebuffer objects on older intel drivers (crashy)
|
||||
mHasFramebufferObject = FALSE;
|
||||
}
|
||||
|
||||
if (mHasFramebufferObject)
|
||||
{
|
||||
glGetIntegerv(GL_MAX_SAMPLES, &mMaxSamples);
|
||||
|
|
@ -923,7 +946,6 @@ void LLGLManager::initExtensions()
|
|||
mHasMultitexture = glh_init_extensions("GL_ARB_multitexture");
|
||||
mHasATIMemInfo = ExtensionExists("GL_ATI_meminfo", gGLHExts.mSysExts);
|
||||
mHasNVXMemInfo = ExtensionExists("GL_NVX_gpu_memory_info", gGLHExts.mSysExts);
|
||||
mHasMipMapGeneration = glh_init_extensions("GL_SGIS_generate_mipmap");
|
||||
mHasSeparateSpecularColor = glh_init_extensions("GL_EXT_separate_specular_color");
|
||||
mHasAnisotropic = glh_init_extensions("GL_EXT_texture_filter_anisotropic");
|
||||
glh_init_extensions("GL_ARB_texture_cube_map");
|
||||
|
|
@ -948,11 +970,14 @@ void LLGLManager::initExtensions()
|
|||
ExtensionExists("GL_EXT_packed_depth_stencil", gGLHExts.mSysExts);
|
||||
#endif
|
||||
|
||||
mHasMipMapGeneration = mHasFramebufferObject || mGLVersion >= 1.4f;
|
||||
|
||||
mHasDrawBuffers = ExtensionExists("GL_ARB_draw_buffers", gGLHExts.mSysExts);
|
||||
mHasBlendFuncSeparate = ExtensionExists("GL_EXT_blend_func_separate", gGLHExts.mSysExts);
|
||||
mHasTextureRectangle = ExtensionExists("GL_ARB_texture_rectangle", gGLHExts.mSysExts);
|
||||
mHasTextureMultisample = ExtensionExists("GL_ARB_texture_multisample", gGLHExts.mSysExts);
|
||||
mHasDebugOutput = ExtensionExists("GL_ARB_debug_output", gGLHExts.mSysExts);
|
||||
mHasTransformFeedback = mGLVersion >= 4.f ? TRUE : FALSE;
|
||||
#if !LL_DARWIN
|
||||
mHasPointParameters = !mIsATI && ExtensionExists("GL_ARB_point_parameters", gGLHExts.mSysExts);
|
||||
#endif
|
||||
|
|
@ -1192,7 +1217,14 @@ void LLGLManager::initExtensions()
|
|||
glTexImage3DMultisample = (PFNGLTEXIMAGE3DMULTISAMPLEPROC) GLH_EXT_GET_PROC_ADDRESS("glTexImage3DMultisample");
|
||||
glGetMultisamplefv = (PFNGLGETMULTISAMPLEFVPROC) GLH_EXT_GET_PROC_ADDRESS("glGetMultisamplefv");
|
||||
glSampleMaski = (PFNGLSAMPLEMASKIPROC) GLH_EXT_GET_PROC_ADDRESS("glSampleMaski");
|
||||
}
|
||||
}
|
||||
if (mHasTransformFeedback)
|
||||
{
|
||||
glBeginTransformFeedback = (PFNGLBEGINTRANSFORMFEEDBACKPROC) GLH_EXT_GET_PROC_ADDRESS("glBeginTransformFeedback");
|
||||
glEndTransformFeedback = (PFNGLENDTRANSFORMFEEDBACKPROC) GLH_EXT_GET_PROC_ADDRESS("glEndTransformFeedback");
|
||||
glTransformFeedbackVaryings = (PFNGLTRANSFORMFEEDBACKVARYINGSPROC) GLH_EXT_GET_PROC_ADDRESS("glTransformFeedbackVaryings");
|
||||
glBindBufferRange = (PFNGLBINDBUFFERRANGEPROC) GLH_EXT_GET_PROC_ADDRESS("glBindBufferRange");
|
||||
}
|
||||
if (mHasDebugOutput)
|
||||
{
|
||||
glDebugMessageControlARB = (PFNGLDEBUGMESSAGECONTROLARBPROC) GLH_EXT_GET_PROC_ADDRESS("glDebugMessageControlARB");
|
||||
|
|
@ -1897,7 +1929,7 @@ void LLGLState::checkClientArrays(const std::string& msg, U32 data_mask)
|
|||
glClientActiveTextureARB(GL_TEXTURE0_ARB);
|
||||
gGL.getTexUnit(0)->activate();
|
||||
|
||||
if (gGLManager.mHasVertexShader)
|
||||
if (gGLManager.mHasVertexShader && LLGLSLShader::sNoFixedFunction)
|
||||
{ //make sure vertex attribs are all disabled
|
||||
GLint count;
|
||||
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS_ARB, &count);
|
||||
|
|
@ -1949,6 +1981,7 @@ LLGLState::LLGLState(LLGLenum state, S32 enabled) :
|
|||
case GL_COLOR_MATERIAL:
|
||||
case GL_FOG:
|
||||
case GL_LINE_STIPPLE:
|
||||
case GL_POLYGON_STIPPLE:
|
||||
mState = 0;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2037,7 +2070,7 @@ void LLGLManager::initGLStates()
|
|||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void parse_gl_version( S32* major, S32* minor, S32* release, std::string* vendor_specific )
|
||||
void parse_gl_version( S32* major, S32* minor, S32* release, std::string* vendor_specific, std::string* version_string )
|
||||
{
|
||||
// GL_VERSION returns a null-terminated string with the format:
|
||||
// <major>.<minor>[.<release>] [<vendor specific>]
|
||||
|
|
@ -2053,6 +2086,8 @@ void parse_gl_version( S32* major, S32* minor, S32* release, std::string* vendor
|
|||
return;
|
||||
}
|
||||
|
||||
version_string->assign(version);
|
||||
|
||||
std::string ver_copy( version );
|
||||
S32 len = (S32)strlen( version ); /* Flawfinder: ignore */
|
||||
S32 i = 0;
|
||||
|
|
@ -2414,3 +2449,65 @@ LLGLSquashToFarClip::~LLGLSquashToFarClip()
|
|||
gGL.matrixMode(LLRender::MM_MODELVIEW);
|
||||
}
|
||||
|
||||
|
||||
|
||||
LLGLSyncFence::LLGLSyncFence()
|
||||
{
|
||||
#ifdef GL_ARB_sync
|
||||
mSync = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
LLGLSyncFence::~LLGLSyncFence()
|
||||
{
|
||||
#ifdef GL_ARB_sync
|
||||
if (mSync)
|
||||
{
|
||||
glDeleteSync(mSync);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LLGLSyncFence::placeFence()
|
||||
{
|
||||
#ifdef GL_ARB_sync
|
||||
if (mSync)
|
||||
{
|
||||
glDeleteSync(mSync);
|
||||
}
|
||||
mSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LLGLSyncFence::isCompleted()
|
||||
{
|
||||
bool ret = true;
|
||||
#ifdef GL_ARB_sync
|
||||
if (mSync)
|
||||
{
|
||||
GLenum status = glClientWaitSync(mSync, 0, 1);
|
||||
if (status == GL_TIMEOUT_EXPIRED)
|
||||
{
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LLGLSyncFence::wait()
|
||||
{
|
||||
#ifdef GL_ARB_sync
|
||||
if (mSync)
|
||||
{
|
||||
while (glClientWaitSync(mSync, 0, FENCE_WAIT_TIME_NANOSECONDS) == GL_TIMEOUT_EXPIRED)
|
||||
{ //track the number of times we've waited here
|
||||
static S32 waits = 0;
|
||||
waits++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ public:
|
|||
BOOL mHasDepthClamp;
|
||||
BOOL mHasTextureRectangle;
|
||||
BOOL mHasTextureMultisample;
|
||||
BOOL mHasTransformFeedback;
|
||||
S32 mMaxSampleMaskWords;
|
||||
S32 mMaxColorTextureSamples;
|
||||
S32 mMaxDepthTextureSamples;
|
||||
|
|
@ -141,6 +142,7 @@ public:
|
|||
S32 mGLSLVersionMajor;
|
||||
S32 mGLSLVersionMinor;
|
||||
std::string mDriverVersionVendorString;
|
||||
std::string mGLVersionString;
|
||||
|
||||
S32 mVRAM; // VRAM in MB
|
||||
S32 mGLMaxVertexRange;
|
||||
|
|
@ -417,13 +419,38 @@ public:
|
|||
virtual void updateGL() = 0;
|
||||
};
|
||||
|
||||
const U32 FENCE_WAIT_TIME_NANOSECONDS = 1000; //1 ms
|
||||
|
||||
class LLGLFence
|
||||
{
|
||||
public:
|
||||
virtual void placeFence() = 0;
|
||||
virtual bool isCompleted() = 0;
|
||||
virtual void wait() = 0;
|
||||
};
|
||||
|
||||
class LLGLSyncFence : public LLGLFence
|
||||
{
|
||||
public:
|
||||
#ifdef GL_ARB_sync
|
||||
GLsync mSync;
|
||||
#endif
|
||||
|
||||
LLGLSyncFence();
|
||||
virtual ~LLGLSyncFence();
|
||||
|
||||
void placeFence();
|
||||
bool isCompleted();
|
||||
void wait();
|
||||
};
|
||||
|
||||
extern LLMatrix4 gGLObliqueProjectionInverse;
|
||||
|
||||
#include "llglstates.h"
|
||||
|
||||
void init_glstates();
|
||||
|
||||
void parse_gl_version( S32* major, S32* minor, S32* release, std::string* vendor_specific );
|
||||
void parse_gl_version( S32* major, S32* minor, S32* release, std::string* vendor_specific, std::string* version_string );
|
||||
|
||||
extern BOOL gClothRipple;
|
||||
extern BOOL gHeadlessClient;
|
||||
|
|
|
|||
|
|
@ -528,6 +528,13 @@ extern PFNGLTEXIMAGE3DMULTISAMPLEPROC glTexImage3DMultisample;
|
|||
extern PFNGLGETMULTISAMPLEFVPROC glGetMultisamplefv;
|
||||
extern PFNGLSAMPLEMASKIPROC glSampleMaski;
|
||||
|
||||
//transform feedback (4.0 core)
|
||||
extern PFNGLBEGINTRANSFORMFEEDBACKPROC glBeginTransformFeedback;
|
||||
extern PFNGLENDTRANSFORMFEEDBACKPROC glEndTransformFeedback;
|
||||
extern PFNGLTRANSFORMFEEDBACKVARYINGSPROC glTransformFeedbackVaryings;
|
||||
extern PFNGLBINDBUFFERRANGEPROC glBindBufferRange;
|
||||
|
||||
|
||||
#elif LL_WINDOWS
|
||||
//----------------------------------------------------------------------------
|
||||
// LL_WINDOWS
|
||||
|
|
@ -759,6 +766,12 @@ extern PFNGLTEXIMAGE3DMULTISAMPLEPROC glTexImage3DMultisample;
|
|||
extern PFNGLGETMULTISAMPLEFVPROC glGetMultisamplefv;
|
||||
extern PFNGLSAMPLEMASKIPROC glSampleMaski;
|
||||
|
||||
//transform feedback (4.0 core)
|
||||
extern PFNGLBEGINTRANSFORMFEEDBACKPROC glBeginTransformFeedback;
|
||||
extern PFNGLENDTRANSFORMFEEDBACKPROC glEndTransformFeedback;
|
||||
extern PFNGLTRANSFORMFEEDBACKVARYINGSPROC glTransformFeedbackVaryings;
|
||||
extern PFNGLBINDBUFFERRANGEPROC glBindBufferRange;
|
||||
|
||||
//GL_ARB_debug_output
|
||||
extern PFNGLDEBUGMESSAGECONTROLARBPROC glDebugMessageControlARB;
|
||||
extern PFNGLDEBUGMESSAGEINSERTARBPROC glDebugMessageInsertARB;
|
||||
|
|
|
|||
|
|
@ -129,7 +129,9 @@ void LLGLSLShader::unload()
|
|||
}
|
||||
|
||||
BOOL LLGLSLShader::createShader(vector<string> * attributes,
|
||||
vector<string> * uniforms)
|
||||
vector<string> * uniforms,
|
||||
U32 varying_count,
|
||||
const char** varyings)
|
||||
{
|
||||
//reloading, reset matrix hash values
|
||||
for (U32 i = 0; i < LLRender::NUM_MATRIX_MODES; ++i)
|
||||
|
|
@ -172,6 +174,13 @@ BOOL LLGLSLShader::createShader(vector<string> * attributes,
|
|||
mFeatures.mIndexedTextureChannels = llmin(mFeatures.mIndexedTextureChannels, 1);
|
||||
}
|
||||
|
||||
#ifdef GL_INTERLEAVED_ATTRIBS
|
||||
if (varying_count > 0 && varyings)
|
||||
{
|
||||
glTransformFeedbackVaryings(mProgramObject, varying_count, varyings, GL_INTERLEAVED_ATTRIBS);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Map attributes and uniforms
|
||||
if (success)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -76,7 +76,9 @@ public:
|
|||
|
||||
void unload();
|
||||
BOOL createShader(std::vector<std::string> * attributes,
|
||||
std::vector<std::string> * uniforms);
|
||||
std::vector<std::string> * uniforms,
|
||||
U32 varying_count = 0,
|
||||
const char** varyings = NULL);
|
||||
BOOL attachObject(std::string object);
|
||||
void attachObject(GLhandleARB object);
|
||||
void attachObjects(GLhandleARB* objects = NULL, S32 count = 0);
|
||||
|
|
|
|||
|
|
@ -42,8 +42,11 @@
|
|||
//----------------------------------------------------------------------------
|
||||
const F32 MIN_TEXTURE_LIFETIME = 10.f;
|
||||
|
||||
//which power of 2 is i?
|
||||
//assumes i is a power of 2 > 0
|
||||
U32 wpo2(U32 i);
|
||||
|
||||
//statics
|
||||
LLGLuint LLImageGL::sCurrentBoundTextures[MAX_GL_TEXTURE_UNITS] = { 0 };
|
||||
|
||||
U32 LLImageGL::sUniqueCount = 0;
|
||||
U32 LLImageGL::sBindCount = 0;
|
||||
|
|
@ -51,12 +54,14 @@ S32 LLImageGL::sGlobalTextureMemoryInBytes = 0;
|
|||
S32 LLImageGL::sBoundTextureMemoryInBytes = 0;
|
||||
S32 LLImageGL::sCurBoundTextureMemory = 0;
|
||||
S32 LLImageGL::sCount = 0;
|
||||
std::list<U32> LLImageGL::sDeadTextureList;
|
||||
LLImageGL::dead_texturelist_t LLImageGL::sDeadTextureList[LLTexUnit::TT_NONE];
|
||||
U32 LLImageGL::sCurTexName = 1;
|
||||
|
||||
BOOL LLImageGL::sGlobalUseAnisotropic = FALSE;
|
||||
F32 LLImageGL::sLastFrameTime = 0.f;
|
||||
BOOL LLImageGL::sAllowReadBackRaw = FALSE ;
|
||||
LLImageGL* LLImageGL::sDefaultGLTexture = NULL ;
|
||||
bool LLImageGL::sCompressTextures = false;
|
||||
|
||||
std::set<LLImageGL*> LLImageGL::sImageList;
|
||||
|
||||
|
|
@ -65,19 +70,10 @@ std::set<LLImageGL*> LLImageGL::sImageList;
|
|||
//****************************************************************************************************
|
||||
//-----------------------
|
||||
//debug use
|
||||
BOOL gAuditTexture = FALSE ;
|
||||
#define MAX_TEXTURE_LOG_SIZE 22 //2048 * 2048
|
||||
std::vector<S32> LLImageGL::sTextureLoadedCounter(MAX_TEXTURE_LOG_SIZE + 1) ;
|
||||
std::vector<S32> LLImageGL::sTextureBoundCounter(MAX_TEXTURE_LOG_SIZE + 1) ;
|
||||
std::vector<S32> LLImageGL::sTextureCurBoundCounter(MAX_TEXTURE_LOG_SIZE + 1) ;
|
||||
S32 LLImageGL::sCurTexSizeBar = -1 ;
|
||||
S32 LLImageGL::sCurTexPickSize = -1 ;
|
||||
LLPointer<LLImageGL> LLImageGL::sHighlightTexturep = NULL;
|
||||
S32 LLImageGL::sMaxCatagories = 1 ;
|
||||
S32 LLImageGL::sMaxCategories = 1 ;
|
||||
|
||||
std::vector<S32> LLImageGL::sTextureMemByCategory;
|
||||
std::vector<S32> LLImageGL::sTextureMemByCategoryBound ;
|
||||
std::vector<S32> LLImageGL::sTextureCurMemByCategoryBound ;
|
||||
//------------------------
|
||||
//****************************************************************************************************
|
||||
//End for texture auditing use only
|
||||
|
|
@ -175,49 +171,11 @@ BOOL is_little_endian()
|
|||
//static
|
||||
void LLImageGL::initClass(S32 num_catagories)
|
||||
{
|
||||
sMaxCatagories = num_catagories ;
|
||||
|
||||
sTextureMemByCategory.resize(sMaxCatagories);
|
||||
sTextureMemByCategoryBound.resize(sMaxCatagories) ;
|
||||
sTextureCurMemByCategoryBound.resize(sMaxCatagories) ;
|
||||
}
|
||||
|
||||
//static
|
||||
void LLImageGL::cleanupClass()
|
||||
{
|
||||
sTextureMemByCategory.clear() ;
|
||||
sTextureMemByCategoryBound.clear() ;
|
||||
sTextureCurMemByCategoryBound.clear() ;
|
||||
}
|
||||
|
||||
//static
|
||||
void LLImageGL::setHighlightTexture(S32 category)
|
||||
{
|
||||
const S32 dim = 128;
|
||||
sHighlightTexturep = new LLImageGL() ;
|
||||
LLPointer<LLImageRaw> image_raw = new LLImageRaw(dim,dim,3);
|
||||
U8* data = image_raw->getData();
|
||||
for (S32 i = 0; i<dim; i++)
|
||||
{
|
||||
for (S32 j = 0; j<dim; j++)
|
||||
{
|
||||
const S32 border = 2;
|
||||
if (i<border || j<border || i>=(dim-border) || j>=(dim-border))
|
||||
{
|
||||
*data++ = 0xff;
|
||||
*data++ = 0xff;
|
||||
*data++ = 0xff;
|
||||
}
|
||||
else
|
||||
{
|
||||
*data++ = 0xff;
|
||||
*data++ = 0xff;
|
||||
*data++ = 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
sHighlightTexturep->createGLTexture(0, image_raw, 0, TRUE, category);
|
||||
image_raw = NULL;
|
||||
}
|
||||
|
||||
//static
|
||||
|
|
@ -285,31 +243,11 @@ void LLImageGL::updateStats(F32 current_time)
|
|||
sLastFrameTime = current_time;
|
||||
sBoundTextureMemoryInBytes = sCurBoundTextureMemory;
|
||||
sCurBoundTextureMemory = 0;
|
||||
|
||||
if(gAuditTexture)
|
||||
{
|
||||
for(U32 i = 0 ; i < sTextureCurBoundCounter.size() ; i++)
|
||||
{
|
||||
sTextureBoundCounter[i] = sTextureCurBoundCounter[i] ;
|
||||
sTextureCurBoundCounter[i] = 0 ;
|
||||
}
|
||||
for(U32 i = 0 ; i < sTextureCurMemByCategoryBound.size() ; i++)
|
||||
{
|
||||
sTextureMemByCategoryBound[i] = sTextureCurMemByCategoryBound[i] ;
|
||||
sTextureCurMemByCategoryBound[i] = 0 ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//static
|
||||
S32 LLImageGL::updateBoundTexMem(const S32 mem, const S32 ncomponents, S32 category)
|
||||
{
|
||||
if(gAuditTexture && ncomponents > 0 && category > -1)
|
||||
{
|
||||
sTextureCurBoundCounter[getTextureCounterIndex(mem / ncomponents)]++ ;
|
||||
sTextureCurMemByCategoryBound[category] += mem ;
|
||||
}
|
||||
|
||||
LLImageGL::sCurBoundTextureMemory += mem ;
|
||||
return LLImageGL::sCurBoundTextureMemory;
|
||||
}
|
||||
|
|
@ -477,10 +415,13 @@ void LLImageGL::init(BOOL usemipmaps)
|
|||
mDiscardLevelInAtlas = -1 ;
|
||||
mTexelsInAtlas = 0 ;
|
||||
mTexelsInGLTexture = 0 ;
|
||||
|
||||
mAllowCompression = true;
|
||||
|
||||
mTarget = GL_TEXTURE_2D;
|
||||
mBindTarget = LLTexUnit::TT_TEXTURE;
|
||||
mHasMipMaps = false;
|
||||
mMipLevels = -1;
|
||||
|
||||
mIsResident = 0;
|
||||
|
||||
|
|
@ -671,8 +612,24 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
is_compressed = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (mUseMipMaps)
|
||||
{
|
||||
//set has mip maps to true before binding image so tex parameters get set properly
|
||||
gGL.getTexUnit(0)->unbind(mBindTarget);
|
||||
mHasMipMaps = true;
|
||||
mTexOptionsDirty = true;
|
||||
setFilteringOption(LLTexUnit::TFO_ANISOTROPIC);
|
||||
}
|
||||
else
|
||||
{
|
||||
mHasMipMaps = false;
|
||||
}
|
||||
|
||||
llverify(gGL.getTexUnit(0)->bind(this));
|
||||
|
||||
|
||||
if (mUseMipMaps)
|
||||
{
|
||||
if (data_hasmips)
|
||||
|
|
@ -685,6 +642,9 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
S32 w = getWidth(d);
|
||||
S32 h = getHeight(d);
|
||||
S32 gl_level = d-mCurrentDiscardLevel;
|
||||
|
||||
mMipLevels = llmax(mMipLevels, gl_level);
|
||||
|
||||
if (d > mCurrentDiscardLevel)
|
||||
{
|
||||
data_in -= dataFormatBytes(mFormatPrimary, w, h); // see above comment
|
||||
|
|
@ -705,7 +665,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
stop_glerror();
|
||||
}
|
||||
|
||||
LLImageGL::setManualImage(mTarget, gl_level, mFormatInternal, w, h, mFormatPrimary, GL_UNSIGNED_BYTE, (GLvoid*)data_in);
|
||||
LLImageGL::setManualImage(mTarget, gl_level, mFormatInternal, w, h, mFormatPrimary, GL_UNSIGNED_BYTE, (GLvoid*)data_in, mAllowCompression);
|
||||
if (gl_level == 0)
|
||||
{
|
||||
analyzeAlpha(data_in, w, h);
|
||||
|
|
@ -727,10 +687,6 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
{
|
||||
if (mAutoGenMips)
|
||||
{
|
||||
if (!gGLManager.mHasFramebufferObject)
|
||||
{
|
||||
glTexParameteri(LLTexUnit::getInternalType(mBindTarget), GL_GENERATE_MIPMAP_SGIS, TRUE);
|
||||
}
|
||||
stop_glerror();
|
||||
{
|
||||
// LLFastTimer t2(FTM_TEMP4);
|
||||
|
|
@ -744,10 +700,15 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
S32 w = getWidth(mCurrentDiscardLevel);
|
||||
S32 h = getHeight(mCurrentDiscardLevel);
|
||||
|
||||
mMipLevels = wpo2(llmax(w, h));
|
||||
|
||||
//use legacy mipmap generation mode
|
||||
glTexParameteri(mTarget, GL_GENERATE_MIPMAP, GL_TRUE);
|
||||
|
||||
LLImageGL::setManualImage(mTarget, 0, mFormatInternal,
|
||||
w, h,
|
||||
mFormatPrimary, mFormatType,
|
||||
data_in);
|
||||
data_in, mAllowCompression);
|
||||
analyzeAlpha(data_in, w, h);
|
||||
stop_glerror();
|
||||
|
||||
|
|
@ -759,16 +720,10 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
stop_glerror();
|
||||
}
|
||||
}
|
||||
|
||||
if (gGLManager.mHasFramebufferObject)
|
||||
{
|
||||
glGenerateMipmap(LLTexUnit::getInternalType(mBindTarget));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create mips by hand
|
||||
// about 30% faster than autogen on ATI 9800, 50% slower on nVidia 4800
|
||||
// ~4x faster than gluBuild2DMipmaps
|
||||
S32 width = getWidth(mCurrentDiscardLevel);
|
||||
S32 height = getHeight(mCurrentDiscardLevel);
|
||||
|
|
@ -778,6 +733,9 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
const U8* cur_mip_data = 0;
|
||||
S32 prev_mip_size = 0;
|
||||
S32 cur_mip_size = 0;
|
||||
|
||||
mMipLevels = nummips;
|
||||
|
||||
for (int m=0; m<nummips; m++)
|
||||
{
|
||||
if (m==0)
|
||||
|
|
@ -805,7 +763,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
stop_glerror();
|
||||
}
|
||||
|
||||
LLImageGL::setManualImage(mTarget, m, mFormatInternal, w, h, mFormatPrimary, mFormatType, cur_mip_data);
|
||||
LLImageGL::setManualImage(mTarget, m, mFormatInternal, w, h, mFormatPrimary, mFormatType, cur_mip_data, mAllowCompression);
|
||||
if (m == 0)
|
||||
{
|
||||
analyzeAlpha(data_in, w, h);
|
||||
|
|
@ -842,10 +800,10 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
{
|
||||
llerrs << "Compressed Image has mipmaps but data does not (can not auto generate compressed mips)" << llendl;
|
||||
}
|
||||
mHasMipMaps = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
mMipLevels = 0;
|
||||
S32 w = getWidth();
|
||||
S32 h = getHeight();
|
||||
if (is_compressed)
|
||||
|
|
@ -863,7 +821,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
}
|
||||
|
||||
LLImageGL::setManualImage(mTarget, 0, mFormatInternal, w, h,
|
||||
mFormatPrimary, mFormatType, (GLvoid *)data_in);
|
||||
mFormatPrimary, mFormatType, (GLvoid *)data_in, mAllowCompression);
|
||||
analyzeAlpha(data_in, w, h);
|
||||
|
||||
updatePickMask(w, h, data_in);
|
||||
|
|
@ -877,7 +835,6 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips)
|
|||
}
|
||||
|
||||
}
|
||||
mHasMipMaps = false;
|
||||
}
|
||||
stop_glerror();
|
||||
mGLTextureCreated = true;
|
||||
|
|
@ -1090,27 +1047,69 @@ BOOL LLImageGL::setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_
|
|||
}
|
||||
|
||||
// static
|
||||
void LLImageGL::generateTextures(S32 numTextures, U32 *textures)
|
||||
void LLImageGL::generateTextures(LLTexUnit::eTextureType type, U32 format, S32 numTextures, U32 *textures)
|
||||
{
|
||||
glGenTextures(numTextures, (GLuint*)textures);
|
||||
bool empty = true;
|
||||
|
||||
dead_texturelist_t::iterator iter = sDeadTextureList[type].find(format);
|
||||
|
||||
if (iter != sDeadTextureList[type].end())
|
||||
{
|
||||
empty = iter->second.empty();
|
||||
}
|
||||
|
||||
for (S32 i = 0; i < numTextures; ++i)
|
||||
{
|
||||
if (!empty)
|
||||
{
|
||||
textures[i] = iter->second.front();
|
||||
iter->second.pop_front();
|
||||
empty = iter->second.empty();
|
||||
}
|
||||
else
|
||||
{
|
||||
textures[i] = sCurTexName++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void LLImageGL::deleteTextures(S32 numTextures, U32 *textures, bool immediate)
|
||||
void LLImageGL::deleteTextures(LLTexUnit::eTextureType type, U32 format, S32 mip_levels, S32 numTextures, U32 *textures, bool immediate)
|
||||
{
|
||||
for (S32 i = 0; i < numTextures; i++)
|
||||
if (gGLManager.mInited)
|
||||
{
|
||||
sDeadTextureList.push_back(textures[i]);
|
||||
}
|
||||
if (format == 0 || type == LLTexUnit::TT_CUBE_MAP || mip_levels == -1)
|
||||
{ //unknown internal format or unknown number of mip levels, not safe to reuse
|
||||
glDeleteTextures(numTextures, textures);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (S32 i = 0; i < numTextures; ++i)
|
||||
{ //remove texture from VRAM by setting its size to zero
|
||||
for (S32 j = 0; j <= mip_levels; j++)
|
||||
{
|
||||
gGL.getTexUnit(0)->bindManual(type, textures[i]);
|
||||
|
||||
if (immediate)
|
||||
glTexImage2D(LLTexUnit::getInternalType(type), j, format, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
}
|
||||
|
||||
llassert(std::find(sDeadTextureList[type][format].begin(),
|
||||
sDeadTextureList[type][format].end(), textures[i]) ==
|
||||
sDeadTextureList[type][format].end());
|
||||
|
||||
sDeadTextureList[type][format].push_back(textures[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*if (immediate)
|
||||
{
|
||||
LLImageGL::deleteDeadTextures();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
// static
|
||||
void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels)
|
||||
void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels, bool allow_compression)
|
||||
{
|
||||
bool use_scratch = false;
|
||||
U32* scratch = NULL;
|
||||
|
|
@ -1173,6 +1172,36 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt
|
|||
}
|
||||
}
|
||||
|
||||
if (LLImageGL::sCompressTextures && allow_compression)
|
||||
{
|
||||
switch (intformat)
|
||||
{
|
||||
case GL_RGB:
|
||||
case GL_RGB8:
|
||||
intformat = GL_COMPRESSED_RGB;
|
||||
break;
|
||||
case GL_RGBA:
|
||||
case GL_RGBA8:
|
||||
intformat = GL_COMPRESSED_RGBA;
|
||||
break;
|
||||
case GL_LUMINANCE:
|
||||
case GL_LUMINANCE8:
|
||||
intformat = GL_COMPRESSED_LUMINANCE;
|
||||
break;
|
||||
case GL_LUMINANCE_ALPHA:
|
||||
case GL_LUMINANCE8_ALPHA8:
|
||||
intformat = GL_COMPRESSED_LUMINANCE_ALPHA;
|
||||
break;
|
||||
case GL_ALPHA:
|
||||
case GL_ALPHA8:
|
||||
intformat = GL_COMPRESSED_ALPHA;
|
||||
break;
|
||||
default:
|
||||
llwarns << "Could not compress format: " << std::hex << intformat << llendl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
stop_glerror();
|
||||
glTexImage2D(target, miplevel, intformat, width, height, 0, pixformat, pixtype, use_scratch ? scratch : pixels);
|
||||
stop_glerror();
|
||||
|
|
@ -1201,10 +1230,11 @@ BOOL LLImageGL::createGLTexture()
|
|||
|
||||
if(mTexName)
|
||||
{
|
||||
glDeleteTextures(1, (reinterpret_cast<GLuint*>(&mTexName))) ;
|
||||
LLImageGL::deleteTextures(mBindTarget, mFormatInternal, mMipLevels, 1, (reinterpret_cast<GLuint*>(&mTexName))) ;
|
||||
}
|
||||
|
||||
glGenTextures(1, (GLuint*)&mTexName);
|
||||
|
||||
LLImageGL::generateTextures(mBindTarget, mFormatInternal, 1, &mTexName);
|
||||
stop_glerror();
|
||||
if (!mTexName)
|
||||
{
|
||||
|
|
@ -1284,7 +1314,7 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S
|
|||
return TRUE ;
|
||||
}
|
||||
|
||||
setCategory(category) ;
|
||||
setCategory(category);
|
||||
const U8* rawdata = imageraw->getData();
|
||||
return createGLTexture(discard_level, rawdata, FALSE, usename);
|
||||
}
|
||||
|
|
@ -1317,7 +1347,7 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
|
|||
}
|
||||
else
|
||||
{
|
||||
LLImageGL::generateTextures(1, &mTexName);
|
||||
LLImageGL::generateTextures(mBindTarget, mFormatInternal, 1, &mTexName);
|
||||
stop_glerror();
|
||||
{
|
||||
llverify(gGL.getTexUnit(0)->bind(this));
|
||||
|
|
@ -1362,12 +1392,7 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
|
|||
{
|
||||
sGlobalTextureMemoryInBytes -= mTextureMemory;
|
||||
|
||||
if(gAuditTexture)
|
||||
{
|
||||
decTextureCounter(mTextureMemory, mComponents, mCategory) ;
|
||||
}
|
||||
|
||||
LLImageGL::deleteTextures(1, &old_name);
|
||||
LLImageGL::deleteTextures(mBindTarget, mFormatInternal, mMipLevels, 1, &old_name);
|
||||
|
||||
stop_glerror();
|
||||
}
|
||||
|
|
@ -1376,10 +1401,6 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
|
|||
sGlobalTextureMemoryInBytes += mTextureMemory;
|
||||
mTexelsInGLTexture = getWidth() * getHeight() ;
|
||||
|
||||
if(gAuditTexture)
|
||||
{
|
||||
incTextureCounter(mTextureMemory, mComponents, mCategory) ;
|
||||
}
|
||||
// mark this as bound at this point, so we don't throw it out immediately
|
||||
mLastBindTime = sLastFrameTime;
|
||||
return TRUE;
|
||||
|
|
@ -1500,7 +1521,7 @@ void LLImageGL::deleteDeadTextures()
|
|||
{
|
||||
bool reset = false;
|
||||
|
||||
while (!sDeadTextureList.empty())
|
||||
/*while (!sDeadTextureList.empty())
|
||||
{
|
||||
GLuint tex = sDeadTextureList.front();
|
||||
sDeadTextureList.pop_front();
|
||||
|
|
@ -1522,7 +1543,7 @@ void LLImageGL::deleteDeadTextures()
|
|||
|
||||
glDeleteTextures(1, &tex);
|
||||
stop_glerror();
|
||||
}
|
||||
}*/
|
||||
|
||||
if (reset)
|
||||
{
|
||||
|
|
@ -1536,22 +1557,29 @@ void LLImageGL::destroyGLTexture()
|
|||
{
|
||||
if(mTextureMemory)
|
||||
{
|
||||
if(gAuditTexture)
|
||||
{
|
||||
decTextureCounter(mTextureMemory, mComponents, mCategory) ;
|
||||
}
|
||||
sGlobalTextureMemoryInBytes -= mTextureMemory;
|
||||
mTextureMemory = 0;
|
||||
}
|
||||
|
||||
LLImageGL::deleteTextures(1, &mTexName);
|
||||
mTexName = 0;
|
||||
LLImageGL::deleteTextures(mBindTarget, mFormatInternal, mMipLevels, 1, &mTexName);
|
||||
mCurrentDiscardLevel = -1 ; //invalidate mCurrentDiscardLevel.
|
||||
mTexName = 0;
|
||||
mGLTextureCreated = FALSE ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//force to invalidate the gl texture, most likely a sculpty texture
|
||||
void LLImageGL::forceToInvalidateGLTexture()
|
||||
{
|
||||
if (mTexName != 0)
|
||||
{
|
||||
destroyGLTexture();
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentDiscardLevel = -1 ; //invalidate mCurrentDiscardLevel.
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -1969,70 +1997,6 @@ BOOL LLImageGL::getMask(const LLVector2 &tc)
|
|||
return res;
|
||||
}
|
||||
|
||||
void LLImageGL::setCategory(S32 category)
|
||||
{
|
||||
#if 0 //turn this off temporarily because it is not in use now.
|
||||
if(!gAuditTexture)
|
||||
{
|
||||
return ;
|
||||
}
|
||||
if(mCategory != category)
|
||||
{
|
||||
if(mCategory > -1)
|
||||
{
|
||||
sTextureMemByCategory[mCategory] -= mTextureMemory ;
|
||||
}
|
||||
if(category > -1 && category < sMaxCatagories)
|
||||
{
|
||||
sTextureMemByCategory[category] += mTextureMemory ;
|
||||
mCategory = category;
|
||||
}
|
||||
else
|
||||
{
|
||||
mCategory = -1 ;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//for debug use
|
||||
//val is a "power of two" number
|
||||
S32 LLImageGL::getTextureCounterIndex(U32 val)
|
||||
{
|
||||
//index range is [0, MAX_TEXTURE_LOG_SIZE].
|
||||
if(val < 2)
|
||||
{
|
||||
return 0 ;
|
||||
}
|
||||
else if(val >= (1 << MAX_TEXTURE_LOG_SIZE))
|
||||
{
|
||||
return MAX_TEXTURE_LOG_SIZE ;
|
||||
}
|
||||
else
|
||||
{
|
||||
S32 ret = 0 ;
|
||||
while(val >>= 1)
|
||||
{
|
||||
++ret;
|
||||
}
|
||||
return ret ;
|
||||
}
|
||||
}
|
||||
|
||||
//static
|
||||
void LLImageGL::incTextureCounter(U32 val, S32 ncomponents, S32 category)
|
||||
{
|
||||
sTextureLoadedCounter[getTextureCounterIndex(val)]++ ;
|
||||
sTextureMemByCategory[category] += (S32)val * ncomponents ;
|
||||
}
|
||||
|
||||
//static
|
||||
void LLImageGL::decTextureCounter(U32 val, S32 ncomponents, S32 category)
|
||||
{
|
||||
sTextureLoadedCounter[getTextureCounterIndex(val)]-- ;
|
||||
sTextureMemByCategory[category] += (S32)val * ncomponents ;
|
||||
}
|
||||
|
||||
void LLImageGL::setCurTexSizebar(S32 index, BOOL set_pick_size)
|
||||
{
|
||||
sCurTexSizeBar = index ;
|
||||
|
|
|
|||
|
|
@ -45,8 +45,16 @@ class LLImageGL : public LLRefCount
|
|||
{
|
||||
friend class LLTexUnit;
|
||||
public:
|
||||
static std::list<U32> sDeadTextureList;
|
||||
static U32 sCurTexName;
|
||||
|
||||
//previously used but now available texture names
|
||||
// sDeadTextureList[<usage>][<internal format>]
|
||||
typedef std::map<U32, std::list<U32> > dead_texturelist_t;
|
||||
static dead_texturelist_t sDeadTextureList[LLTexUnit::TT_NONE];
|
||||
|
||||
// These 2 functions replace glGenTextures() and glDeleteTextures()
|
||||
static void generateTextures(LLTexUnit::eTextureType type, U32 format, S32 numTextures, U32 *textures);
|
||||
static void deleteTextures(LLTexUnit::eTextureType type, U32 format, S32 mip_levels, S32 numTextures, U32 *textures, bool immediate = false);
|
||||
static void deleteDeadTextures();
|
||||
|
||||
// Size calculation
|
||||
|
|
@ -94,16 +102,13 @@ public:
|
|||
|
||||
void setSize(S32 width, S32 height, S32 ncomponents);
|
||||
void setComponents(S32 ncomponents) { mComponents = (S8)ncomponents ;}
|
||||
void setAllowCompression(bool allow) { mAllowCompression = allow; }
|
||||
|
||||
// These 3 functions currently wrap glGenTextures(), glDeleteTextures(), and glTexImage2D()
|
||||
// for tracking purposes and will be deprecated in the future
|
||||
static void generateTextures(S32 numTextures, U32 *textures);
|
||||
static void deleteTextures(S32 numTextures, U32 *textures, bool immediate = false);
|
||||
static void setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels);
|
||||
static void setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void *pixels, bool allow_compression = true);
|
||||
|
||||
BOOL createGLTexture() ;
|
||||
BOOL createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0, BOOL to_create = TRUE,
|
||||
S32 category = sMaxCatagories - 1);
|
||||
BOOL createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0, BOOL to_create = TRUE,
|
||||
S32 category = sMaxCategories-1);
|
||||
BOOL createGLTexture(S32 discard_level, const U8* data, BOOL data_hasmips = FALSE, S32 usename = 0);
|
||||
void setImage(const LLImageRaw* imageraw);
|
||||
void setImage(const U8* data_in, BOOL data_hasmips = FALSE);
|
||||
|
|
@ -114,6 +119,7 @@ public:
|
|||
// Read back a raw image for this discard level, if it exists
|
||||
BOOL readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compressed_ok) const;
|
||||
void destroyGLTexture();
|
||||
void forceToInvalidateGLTexture();
|
||||
|
||||
void setExplicitFormat(LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format = 0, BOOL swap_bytes = FALSE);
|
||||
void setComponents(S8 ncomponents) { mComponents = ncomponents; }
|
||||
|
|
@ -209,11 +215,14 @@ private:
|
|||
U32 mTexelsInAtlas ;
|
||||
U32 mTexelsInGLTexture;
|
||||
|
||||
bool mAllowCompression;
|
||||
|
||||
protected:
|
||||
LLGLenum mTarget; // Normally GL_TEXTURE2D, sometimes something else (ex. cube maps)
|
||||
LLTexUnit::eTextureType mBindTarget; // Normally TT_TEXTURE, sometimes something else (ex. cube maps)
|
||||
bool mHasMipMaps;
|
||||
|
||||
S32 mMipLevels;
|
||||
|
||||
LLGLboolean mIsResident;
|
||||
|
||||
S8 mComponents;
|
||||
|
|
@ -234,8 +243,6 @@ public:
|
|||
static S32 sCount;
|
||||
|
||||
static F32 sLastFrameTime;
|
||||
|
||||
static LLGLuint sCurrentBoundTextures[MAX_GL_TEXTURE_UNITS]; // Currently bound texture ID
|
||||
|
||||
// Global memory statistics
|
||||
static S32 sGlobalTextureMemoryInBytes; // Tracks main memory texmem
|
||||
|
|
@ -246,7 +253,7 @@ public:
|
|||
static BOOL sGlobalUseAnisotropic;
|
||||
static LLImageGL* sDefaultGLTexture ;
|
||||
static BOOL sAutomatedTest;
|
||||
|
||||
static bool sCompressTextures; //use GL texture compression
|
||||
#if DEBUG_MISS
|
||||
BOOL mMissed; // Missed on last bind?
|
||||
BOOL getMissed() const { return mMissed; };
|
||||
|
|
@ -257,9 +264,10 @@ public:
|
|||
public:
|
||||
static void initClass(S32 num_catagories) ;
|
||||
static void cleanupClass() ;
|
||||
private:
|
||||
static S32 sMaxCatagories ;
|
||||
|
||||
private:
|
||||
static S32 sMaxCategories;
|
||||
|
||||
//the flag to allow to call readBackRaw(...).
|
||||
//can be removed if we do not use that function at all.
|
||||
static BOOL sAllowReadBackRaw ;
|
||||
|
|
@ -269,39 +277,22 @@ private:
|
|||
//****************************************************************************************************
|
||||
private:
|
||||
S32 mCategory ;
|
||||
public:
|
||||
void setCategory(S32 category) ;
|
||||
S32 getCategory()const {return mCategory ;}
|
||||
|
||||
public:
|
||||
void setCategory(S32 category) {mCategory = category;}
|
||||
S32 getCategory()const {return mCategory;}
|
||||
|
||||
//for debug use: show texture size distribution
|
||||
//----------------------------------------
|
||||
static LLPointer<LLImageGL> sHighlightTexturep; //default texture to replace normal textures
|
||||
static std::vector<S32> sTextureLoadedCounter ;
|
||||
static std::vector<S32> sTextureBoundCounter ;
|
||||
static std::vector<S32> sTextureCurBoundCounter ;
|
||||
static S32 sCurTexSizeBar ;
|
||||
static S32 sCurTexPickSize ;
|
||||
|
||||
static void setHighlightTexture(S32 category) ;
|
||||
static S32 getTextureCounterIndex(U32 val) ;
|
||||
static void incTextureCounter(U32 val, S32 ncomponents, S32 category) ;
|
||||
static void decTextureCounter(U32 val, S32 ncomponents, S32 category) ;
|
||||
static void setCurTexSizebar(S32 index, BOOL set_pick_size = TRUE) ;
|
||||
static void resetCurTexSizebar();
|
||||
//----------------------------------------
|
||||
|
||||
//for debug use: show texture category distribution
|
||||
//----------------------------------------
|
||||
|
||||
static std::vector<S32> sTextureMemByCategory;
|
||||
static std::vector<S32> sTextureMemByCategoryBound ;
|
||||
static std::vector<S32> sTextureCurMemByCategoryBound ;
|
||||
//----------------------------------------
|
||||
//****************************************************************************************************
|
||||
//End of definitions for texture auditing use only
|
||||
//****************************************************************************************************
|
||||
|
||||
};
|
||||
|
||||
extern BOOL gAuditTexture;
|
||||
#endif // LL_LLIMAGEGL_H
|
||||
|
|
|
|||
|
|
@ -246,14 +246,6 @@ bool LLTexUnit::bind(LLTexture* texture, bool for_rendering, bool forceBind)
|
|||
}
|
||||
|
||||
//in audit, replace the selected texture by the default one.
|
||||
if(gAuditTexture && for_rendering && LLImageGL::sCurTexPickSize > 0)
|
||||
{
|
||||
if(texture->getWidth() * texture->getHeight() == LLImageGL::sCurTexPickSize)
|
||||
{
|
||||
gl_tex->updateBindStats(gl_tex->mTextureMemory);
|
||||
return bind(LLImageGL::sHighlightTexturep.get());
|
||||
}
|
||||
}
|
||||
if ((mCurrTexture != gl_tex->getTexName()) || forceBind)
|
||||
{
|
||||
activate();
|
||||
|
|
@ -416,12 +408,14 @@ void LLTexUnit::unbind(eTextureType type)
|
|||
|
||||
if (mIndex < 0) return;
|
||||
|
||||
//always flush and activate for consistency
|
||||
// some code paths assume unbind always flushes and sets the active texture
|
||||
gGL.flush();
|
||||
activate();
|
||||
|
||||
// Disabled caching of binding state.
|
||||
if (mCurrTexType == type)
|
||||
{
|
||||
gGL.flush();
|
||||
|
||||
activate();
|
||||
mCurrTexture = 0;
|
||||
if (LLGLSLShader::sNoFixedFunction && type == LLTexUnit::TT_TEXTURE)
|
||||
{
|
||||
|
|
@ -472,11 +466,25 @@ void LLTexUnit::setTextureFilteringOption(LLTexUnit::eTextureFilterOptions optio
|
|||
}
|
||||
else if (option >= TFO_BILINEAR)
|
||||
{
|
||||
glTexParameteri(sGLTextureType[mCurrTexType], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
if (mHasMipMaps)
|
||||
{
|
||||
glTexParameteri(sGLTextureType[mCurrTexType], GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexParameteri(sGLTextureType[mCurrTexType], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexParameteri(sGLTextureType[mCurrTexType], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
if (mHasMipMaps)
|
||||
{
|
||||
glTexParameteri(sGLTextureType[mCurrTexType], GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
|
||||
}
|
||||
else
|
||||
{
|
||||
glTexParameteri(sGLTextureType[mCurrTexType], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
}
|
||||
}
|
||||
|
||||
if (gGLManager.mHasAnisotropic)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ bool LLRenderTarget::sUseFBO = false;
|
|||
LLRenderTarget::LLRenderTarget() :
|
||||
mResX(0),
|
||||
mResY(0),
|
||||
mTex(0),
|
||||
mFBO(0),
|
||||
mDepth(0),
|
||||
mStencil(0),
|
||||
|
|
@ -135,7 +134,7 @@ bool LLRenderTarget::addColorAttachment(U32 color_fmt)
|
|||
}
|
||||
|
||||
U32 tex;
|
||||
LLImageGL::generateTextures(1, &tex);
|
||||
LLImageGL::generateTextures(mUsage, color_fmt, 1, &tex);
|
||||
gGL.getTexUnit(0)->bindManual(mUsage, tex);
|
||||
|
||||
stop_glerror();
|
||||
|
|
@ -143,7 +142,7 @@ bool LLRenderTarget::addColorAttachment(U32 color_fmt)
|
|||
|
||||
{
|
||||
clear_glerror();
|
||||
LLImageGL::setManualImage(LLTexUnit::getInternalType(mUsage), 0, color_fmt, mResX, mResY, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
LLImageGL::setManualImage(LLTexUnit::getInternalType(mUsage), 0, color_fmt, mResX, mResY, GL_RGBA, GL_UNSIGNED_BYTE, NULL, false);
|
||||
if (glGetError() != GL_NO_ERROR)
|
||||
{
|
||||
llwarns << "Could not allocate color buffer for render target." << llendl;
|
||||
|
|
@ -193,6 +192,7 @@ bool LLRenderTarget::addColorAttachment(U32 color_fmt)
|
|||
}
|
||||
|
||||
mTex.push_back(tex);
|
||||
mInternalFormat.push_back(color_fmt);
|
||||
|
||||
if (gDebugGL)
|
||||
{ //bind and unbind to validate target
|
||||
|
|
@ -217,13 +217,13 @@ bool LLRenderTarget::allocateDepth()
|
|||
}
|
||||
else
|
||||
{
|
||||
LLImageGL::generateTextures(1, &mDepth);
|
||||
LLImageGL::generateTextures(mUsage, GL_DEPTH_COMPONENT24, 1, &mDepth);
|
||||
gGL.getTexUnit(0)->bindManual(mUsage, mDepth);
|
||||
|
||||
U32 internal_type = LLTexUnit::getInternalType(mUsage);
|
||||
stop_glerror();
|
||||
clear_glerror();
|
||||
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL);
|
||||
LLImageGL::setManualImage(internal_type, 0, GL_DEPTH_COMPONENT24, mResX, mResY, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL, false);
|
||||
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
|
||||
}
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ void LLRenderTarget::release()
|
|||
}
|
||||
else
|
||||
{
|
||||
LLImageGL::deleteTextures(1, &mDepth, true);
|
||||
LLImageGL::deleteTextures(mUsage, 0, 0, 1, &mDepth, true);
|
||||
stop_glerror();
|
||||
}
|
||||
mDepth = 0;
|
||||
|
|
@ -326,8 +326,9 @@ void LLRenderTarget::release()
|
|||
if (mTex.size() > 0)
|
||||
{
|
||||
sBytesAllocated -= mResX*mResY*4*mTex.size();
|
||||
LLImageGL::deleteTextures(mTex.size(), &mTex[0], true);
|
||||
LLImageGL::deleteTextures(mUsage, mInternalFormat[0], 0, mTex.size(), &mTex[0], true);
|
||||
mTex.clear();
|
||||
mInternalFormat.clear();
|
||||
}
|
||||
|
||||
mResX = mResY = 0;
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ protected:
|
|||
U32 mResX;
|
||||
U32 mResY;
|
||||
std::vector<U32> mTex;
|
||||
std::vector<U32> mInternalFormat;
|
||||
U32 mFBO;
|
||||
U32 mDepth;
|
||||
bool mStencil;
|
||||
|
|
|
|||
|
|
@ -702,7 +702,7 @@ GLhandleARB LLShaderMgr::loadShaderFile(const std::string& filename, S32 & shade
|
|||
|
||||
if (texture_index_channels > 1)
|
||||
{
|
||||
text[count++] = strdup("VARYING_FLAT ivec4 vary_texture_index;\n");
|
||||
text[count++] = strdup("VARYING_FLAT int vary_texture_index;\n");
|
||||
}
|
||||
|
||||
text[count++] = strdup("vec4 diffuseLookup(vec2 texcoord)\n");
|
||||
|
|
@ -716,20 +716,33 @@ GLhandleARB LLShaderMgr::loadShaderFile(const std::string& filename, S32 & shade
|
|||
}
|
||||
else if (major_version > 1 || minor_version >= 30)
|
||||
{ //switches are supported in GLSL 1.30 and later
|
||||
text[count++] = strdup("\tvec4 ret = vec4(1,0,1,1);\n");
|
||||
text[count++] = strdup("\tswitch (vary_texture_index.r)\n");
|
||||
text[count++] = strdup("\t{\n");
|
||||
|
||||
//switch body
|
||||
for (S32 i = 0; i < texture_index_channels; ++i)
|
||||
{
|
||||
std::string case_str = llformat("\t\tcase %d: ret = texture2D(tex%d, texcoord); break;\n", i, i);
|
||||
text[count++] = strdup(case_str.c_str());
|
||||
if (gGLManager.mIsNVIDIA)
|
||||
{ //switches are unreliable on some NVIDIA drivers
|
||||
for (U32 i = 0; i < texture_index_channels; ++i)
|
||||
{
|
||||
std::string if_string = llformat("\t%sif (vary_texture_index == %d) { return texture2D(tex%d, texcoord); }\n", i > 0 ? "else " : "", i, i);
|
||||
text[count++] = strdup(if_string.c_str());
|
||||
}
|
||||
text[count++] = strdup("\treturn vec4(1,0,1,1);\n");
|
||||
text[count++] = strdup("}\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
text[count++] = strdup("\tvec4 ret = vec4(1,0,1,1);\n");
|
||||
text[count++] = strdup("\tswitch (vary_texture_index)\n");
|
||||
text[count++] = strdup("\t{\n");
|
||||
|
||||
//switch body
|
||||
for (S32 i = 0; i < texture_index_channels; ++i)
|
||||
{
|
||||
std::string case_str = llformat("\t\tcase %d: return texture2D(tex%d, texcoord);\n", i, i);
|
||||
text[count++] = strdup(case_str.c_str());
|
||||
}
|
||||
|
||||
text[count++] = strdup("\t}\n");
|
||||
text[count++] = strdup("\treturn ret;\n");
|
||||
text[count++] = strdup("}\n");
|
||||
text[count++] = strdup("\t}\n");
|
||||
text[count++] = strdup("\treturn ret;\n");
|
||||
text[count++] = strdup("}\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{ //should never get here. Indexed texture rendering requires GLSL 1.30 or later
|
||||
|
|
@ -1026,6 +1039,9 @@ void LLShaderMgr::initAttribsAndUniforms()
|
|||
mReservedUniforms.push_back("size");
|
||||
mReservedUniforms.push_back("falloff");
|
||||
|
||||
mReservedUniforms.push_back("box_center");
|
||||
mReservedUniforms.push_back("box_size");
|
||||
|
||||
|
||||
mReservedUniforms.push_back("minLuminance");
|
||||
mReservedUniforms.push_back("maxExtractAlpha");
|
||||
|
|
@ -1062,8 +1078,9 @@ void LLShaderMgr::initAttribsAndUniforms()
|
|||
mReservedUniforms.push_back("proj_shadow_res");
|
||||
mReservedUniforms.push_back("depth_cutoff");
|
||||
mReservedUniforms.push_back("norm_cutoff");
|
||||
mReservedUniforms.push_back("shadow_target_width");
|
||||
|
||||
llassert(mReservedUniforms.size() == LLShaderMgr::DEFERRED_NORM_CUTOFF+1);
|
||||
llassert(mReservedUniforms.size() == LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH+1);
|
||||
|
||||
mReservedUniforms.push_back("tc_scale");
|
||||
mReservedUniforms.push_back("rcp_screen_res");
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ public:
|
|||
LIGHT_CENTER,
|
||||
LIGHT_SIZE,
|
||||
LIGHT_FALLOFF,
|
||||
BOX_CENTER,
|
||||
BOX_SIZE,
|
||||
|
||||
GLOW_MIN_LUMINANCE,
|
||||
GLOW_MAX_EXTRACT_ALPHA,
|
||||
|
|
@ -130,6 +132,7 @@ public:
|
|||
DEFERRED_PROJ_SHADOW_RES,
|
||||
DEFERRED_DEPTH_CUTOFF,
|
||||
DEFERRED_NORM_CUTOFF,
|
||||
DEFERRED_SHADOW_TARGET_WIDTH,
|
||||
|
||||
FXAA_TC_SCALE,
|
||||
FXAA_RCP_SCREEN_RES,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@
|
|||
#include "llglslshader.h"
|
||||
#include "llmemory.h"
|
||||
|
||||
#if LL_DARWIN
|
||||
#define LL_VBO_POOLING 1
|
||||
#else
|
||||
#endif
|
||||
//Next Highest Power Of Two
|
||||
//helper function, returns first number > v that is a power of 2, or v if v is already a power of 2
|
||||
U32 nhpo2(U32 v)
|
||||
|
|
@ -49,6 +53,37 @@ U32 nhpo2(U32 v)
|
|||
return r;
|
||||
}
|
||||
|
||||
//which power of 2 is i?
|
||||
//assumes i is a power of 2 > 0
|
||||
U32 wpo2(U32 i)
|
||||
{
|
||||
llassert(i > 0);
|
||||
llassert(nhpo2(i) == i);
|
||||
|
||||
U32 r = 0;
|
||||
|
||||
while (i >>= 1) ++r;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
const U32 LL_VBO_BLOCK_SIZE = 2048;
|
||||
const U32 LL_VBO_POOL_MAX_SEED_SIZE = 256*1024;
|
||||
|
||||
U32 vbo_block_size(U32 size)
|
||||
{ //what block size will fit size?
|
||||
U32 mod = size % LL_VBO_BLOCK_SIZE;
|
||||
return mod == 0 ? size : size + (LL_VBO_BLOCK_SIZE-mod);
|
||||
}
|
||||
|
||||
U32 vbo_block_index(U32 size)
|
||||
{
|
||||
return vbo_block_size(size)/LL_VBO_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
const U32 LL_VBO_POOL_SEED_COUNT = vbo_block_index(LL_VBO_POOL_MAX_SEED_SIZE);
|
||||
|
||||
|
||||
//============================================================================
|
||||
|
||||
|
|
@ -57,7 +92,16 @@ LLVBOPool LLVertexBuffer::sStreamVBOPool(GL_STREAM_DRAW_ARB, GL_ARRAY_BUFFER_ARB
|
|||
LLVBOPool LLVertexBuffer::sDynamicVBOPool(GL_DYNAMIC_DRAW_ARB, GL_ARRAY_BUFFER_ARB);
|
||||
LLVBOPool LLVertexBuffer::sStreamIBOPool(GL_STREAM_DRAW_ARB, GL_ELEMENT_ARRAY_BUFFER_ARB);
|
||||
LLVBOPool LLVertexBuffer::sDynamicIBOPool(GL_DYNAMIC_DRAW_ARB, GL_ELEMENT_ARRAY_BUFFER_ARB);
|
||||
|
||||
U32 LLVBOPool::sBytesPooled = 0;
|
||||
U32 LLVBOPool::sIndexBytesPooled = 0;
|
||||
U32 LLVBOPool::sCurGLName = 1;
|
||||
|
||||
std::list<U32> LLVertexBuffer::sAvailableVAOName;
|
||||
U32 LLVertexBuffer::sCurVAOName = 1;
|
||||
|
||||
U32 LLVertexBuffer::sAllocatedIndexBytes = 0;
|
||||
U32 LLVertexBuffer::sIndexCount = 0;
|
||||
|
||||
LLPrivateMemoryPool* LLVertexBuffer::sPrivatePoolp = NULL;
|
||||
U32 LLVertexBuffer::sBindCount = 0;
|
||||
|
|
@ -74,99 +118,88 @@ U32 LLVertexBuffer::sLastMask = 0;
|
|||
bool LLVertexBuffer::sVBOActive = false;
|
||||
bool LLVertexBuffer::sIBOActive = false;
|
||||
U32 LLVertexBuffer::sAllocatedBytes = 0;
|
||||
U32 LLVertexBuffer::sVertexCount = 0;
|
||||
bool LLVertexBuffer::sMapped = false;
|
||||
bool LLVertexBuffer::sUseStreamDraw = true;
|
||||
bool LLVertexBuffer::sUseVAO = false;
|
||||
bool LLVertexBuffer::sPreferStreamDraw = false;
|
||||
|
||||
const U32 FENCE_WAIT_TIME_NANOSECONDS = 10000; //1 ms
|
||||
|
||||
class LLGLSyncFence : public LLGLFence
|
||||
U32 LLVBOPool::genBuffer()
|
||||
{
|
||||
public:
|
||||
#ifdef GL_ARB_sync
|
||||
GLsync mSync;
|
||||
#endif
|
||||
|
||||
LLGLSyncFence()
|
||||
U32 ret = 0;
|
||||
|
||||
if (mGLNamePool.empty())
|
||||
{
|
||||
#ifdef GL_ARB_sync
|
||||
mSync = 0;
|
||||
#endif
|
||||
ret = sCurGLName++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = mGLNamePool.front();
|
||||
mGLNamePool.pop_front();
|
||||
}
|
||||
|
||||
virtual ~LLGLSyncFence()
|
||||
{
|
||||
#ifdef GL_ARB_sync
|
||||
if (mSync)
|
||||
{
|
||||
glDeleteSync(mSync);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void placeFence()
|
||||
{
|
||||
#ifdef GL_ARB_sync
|
||||
if (mSync)
|
||||
{
|
||||
glDeleteSync(mSync);
|
||||
}
|
||||
mSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void wait()
|
||||
{
|
||||
#ifdef GL_ARB_sync
|
||||
if (mSync)
|
||||
{
|
||||
while (glClientWaitSync(mSync, 0, FENCE_WAIT_TIME_NANOSECONDS) == GL_TIMEOUT_EXPIRED)
|
||||
{ //track the number of times we've waited here
|
||||
static S32 waits = 0;
|
||||
waits++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
//which power of 2 is i?
|
||||
//assumes i is a power of 2 > 0
|
||||
U32 wpo2(U32 i)
|
||||
{
|
||||
llassert(i > 0);
|
||||
llassert(nhpo2(i) == i);
|
||||
|
||||
U32 r = 0;
|
||||
|
||||
while (i >>= 1) ++r;
|
||||
|
||||
return r;
|
||||
return ret;
|
||||
}
|
||||
|
||||
volatile U8* LLVBOPool::allocate(U32& name, U32 size)
|
||||
void LLVBOPool::deleteBuffer(U32 name)
|
||||
{
|
||||
llassert(nhpo2(size) == size);
|
||||
if (gGLManager.mInited)
|
||||
{
|
||||
LLVertexBuffer::unbind();
|
||||
|
||||
U32 i = wpo2(size);
|
||||
glBindBufferARB(mType, name);
|
||||
glBufferDataARB(mType, 0, NULL, mUsage);
|
||||
|
||||
llassert(std::find(mGLNamePool.begin(), mGLNamePool.end(), name) == mGLNamePool.end());
|
||||
|
||||
mGLNamePool.push_back(name);
|
||||
|
||||
glBindBufferARB(mType, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LLVBOPool::LLVBOPool(U32 vboUsage, U32 vboType)
|
||||
: mUsage(vboUsage), mType(vboType)
|
||||
{
|
||||
mMissCount.resize(LL_VBO_POOL_SEED_COUNT);
|
||||
std::fill(mMissCount.begin(), mMissCount.end(), 0);
|
||||
}
|
||||
|
||||
volatile U8* LLVBOPool::allocate(U32& name, U32 size, bool for_seed)
|
||||
{
|
||||
llassert(vbo_block_size(size) == size);
|
||||
|
||||
volatile U8* ret = NULL;
|
||||
|
||||
U32 i = vbo_block_index(size);
|
||||
|
||||
if (mFreeList.size() <= i)
|
||||
{
|
||||
mFreeList.resize(i+1);
|
||||
}
|
||||
|
||||
volatile U8* ret = NULL;
|
||||
|
||||
if (mFreeList[i].empty())
|
||||
if (mFreeList[i].empty() || for_seed)
|
||||
{
|
||||
//make a new buffer
|
||||
glGenBuffersARB(1, &name);
|
||||
name = genBuffer();
|
||||
|
||||
glBindBufferARB(mType, name);
|
||||
LLVertexBuffer::sAllocatedBytes += size;
|
||||
|
||||
if (!for_seed && i < LL_VBO_POOL_SEED_COUNT)
|
||||
{ //record this miss
|
||||
mMissCount[i]++;
|
||||
}
|
||||
|
||||
if (mType == GL_ARRAY_BUFFER_ARB)
|
||||
{
|
||||
LLVertexBuffer::sAllocatedBytes += size;
|
||||
}
|
||||
else
|
||||
{
|
||||
LLVertexBuffer::sAllocatedIndexBytes += size;
|
||||
}
|
||||
|
||||
if (LLVertexBuffer::sDisableVBOMapping || mUsage != GL_DYNAMIC_DRAW_ARB)
|
||||
{
|
||||
|
|
@ -179,13 +212,39 @@ volatile U8* LLVBOPool::allocate(U32& name, U32 size)
|
|||
}
|
||||
|
||||
glBindBufferARB(mType, 0);
|
||||
|
||||
if (for_seed)
|
||||
{ //put into pool for future use
|
||||
llassert(mFreeList.size() > i);
|
||||
|
||||
Record rec;
|
||||
rec.mGLName = name;
|
||||
rec.mClientData = ret;
|
||||
|
||||
if (mType == GL_ARRAY_BUFFER_ARB)
|
||||
{
|
||||
sBytesPooled += size;
|
||||
}
|
||||
else
|
||||
{
|
||||
sIndexBytesPooled += size;
|
||||
}
|
||||
mFreeList[i].push_back(rec);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
name = mFreeList[i].front().mGLName;
|
||||
ret = mFreeList[i].front().mClientData;
|
||||
|
||||
sBytesPooled -= size;
|
||||
if (mType == GL_ARRAY_BUFFER_ARB)
|
||||
{
|
||||
sBytesPooled -= size;
|
||||
}
|
||||
else
|
||||
{
|
||||
sIndexBytesPooled -= size;
|
||||
}
|
||||
|
||||
mFreeList[i].pop_front();
|
||||
}
|
||||
|
|
@ -195,30 +254,49 @@ volatile U8* LLVBOPool::allocate(U32& name, U32 size)
|
|||
|
||||
void LLVBOPool::release(U32 name, volatile U8* buffer, U32 size)
|
||||
{
|
||||
llassert(nhpo2(size) == size);
|
||||
llassert(vbo_block_size(size) == size);
|
||||
|
||||
U32 i = wpo2(size);
|
||||
deleteBuffer(name);
|
||||
ll_aligned_free_16((U8*) buffer);
|
||||
|
||||
llassert(mFreeList.size() > i);
|
||||
|
||||
Record rec;
|
||||
rec.mGLName = name;
|
||||
rec.mClientData = buffer;
|
||||
|
||||
if (buffer == NULL)
|
||||
if (mType == GL_ARRAY_BUFFER_ARB)
|
||||
{
|
||||
glDeleteBuffersARB(1, &rec.mGLName);
|
||||
LLVertexBuffer::sAllocatedBytes -= size;
|
||||
}
|
||||
else
|
||||
{
|
||||
sBytesPooled += size;
|
||||
mFreeList[i].push_back(rec);
|
||||
LLVertexBuffer::sAllocatedIndexBytes -= size;
|
||||
}
|
||||
}
|
||||
|
||||
void LLVBOPool::seedPool()
|
||||
{
|
||||
U32 dummy_name = 0;
|
||||
|
||||
if (mFreeList.size() < LL_VBO_POOL_SEED_COUNT)
|
||||
{
|
||||
mFreeList.resize(LL_VBO_POOL_SEED_COUNT);
|
||||
}
|
||||
|
||||
for (U32 i = 0; i < LL_VBO_POOL_SEED_COUNT; i++)
|
||||
{
|
||||
if (mMissCount[i] > mFreeList[i].size())
|
||||
{
|
||||
U32 size = i*LL_VBO_BLOCK_SIZE;
|
||||
|
||||
S32 count = mMissCount[i] - mFreeList[i].size();
|
||||
for (U32 j = 0; j < count; ++j)
|
||||
{
|
||||
allocate(dummy_name, size, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LLVBOPool::cleanup()
|
||||
{
|
||||
U32 size = 1;
|
||||
U32 size = LL_VBO_BLOCK_SIZE;
|
||||
|
||||
for (U32 i = 0; i < mFreeList.size(); ++i)
|
||||
{
|
||||
|
|
@ -228,8 +306,8 @@ void LLVBOPool::cleanup()
|
|||
{
|
||||
Record& r = l.front();
|
||||
|
||||
glDeleteBuffersARB(1, &r.mGLName);
|
||||
|
||||
deleteBuffer(r.mGLName);
|
||||
|
||||
if (r.mClientData)
|
||||
{
|
||||
ll_aligned_free_16((void*) r.mClientData);
|
||||
|
|
@ -237,12 +315,23 @@ void LLVBOPool::cleanup()
|
|||
|
||||
l.pop_front();
|
||||
|
||||
LLVertexBuffer::sAllocatedBytes -= size;
|
||||
sBytesPooled -= size;
|
||||
if (mType == GL_ARRAY_BUFFER_ARB)
|
||||
{
|
||||
sBytesPooled -= size;
|
||||
LLVertexBuffer::sAllocatedBytes -= size;
|
||||
}
|
||||
else
|
||||
{
|
||||
sIndexBytesPooled -= size;
|
||||
LLVertexBuffer::sAllocatedIndexBytes -= size;
|
||||
}
|
||||
}
|
||||
|
||||
size *= 2;
|
||||
size += LL_VBO_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
//reset miss counts
|
||||
std::fill(mMissCount.begin(), mMissCount.end(), 0);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -276,6 +365,41 @@ U32 LLVertexBuffer::sGLMode[LLRender::NUM_MODES] =
|
|||
GL_LINE_LOOP,
|
||||
};
|
||||
|
||||
//static
|
||||
U32 LLVertexBuffer::getVAOName()
|
||||
{
|
||||
U32 ret = 0;
|
||||
|
||||
if (!sAvailableVAOName.empty())
|
||||
{
|
||||
ret = sAvailableVAOName.front();
|
||||
sAvailableVAOName.pop_front();
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef GL_ARB_vertex_array_object
|
||||
glGenVertexArrays(1, &ret);
|
||||
#endif
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
//static
|
||||
void LLVertexBuffer::releaseVAOName(U32 name)
|
||||
{
|
||||
sAvailableVAOName.push_back(name);
|
||||
}
|
||||
|
||||
|
||||
//static
|
||||
void LLVertexBuffer::seedPools()
|
||||
{
|
||||
sStreamVBOPool.seedPool();
|
||||
sDynamicVBOPool.seedPool();
|
||||
sStreamIBOPool.seedPool();
|
||||
sDynamicIBOPool.seedPool();
|
||||
}
|
||||
|
||||
//static
|
||||
void LLVertexBuffer::setupClientArrays(U32 data_mask)
|
||||
|
|
@ -885,7 +1009,7 @@ LLVertexBuffer::~LLVertexBuffer()
|
|||
if (mGLArray)
|
||||
{
|
||||
#if GL_ARB_vertex_array_object
|
||||
glDeleteVertexArrays(1, &mGLArray);
|
||||
releaseVAOName(mGLArray);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -898,6 +1022,9 @@ LLVertexBuffer::~LLVertexBuffer()
|
|||
|
||||
mFence = NULL;
|
||||
|
||||
sVertexCount -= mNumVerts;
|
||||
sIndexCount -= mNumIndices;
|
||||
|
||||
llassert_always(!mMappedData && !mMappedIndexData);
|
||||
};
|
||||
|
||||
|
|
@ -929,7 +1056,7 @@ void LLVertexBuffer::waitFence() const
|
|||
|
||||
void LLVertexBuffer::genBuffer(U32 size)
|
||||
{
|
||||
mSize = nhpo2(size);
|
||||
mSize = vbo_block_size(size);
|
||||
|
||||
if (mUsage == GL_STREAM_DRAW_ARB)
|
||||
{
|
||||
|
|
@ -945,7 +1072,7 @@ void LLVertexBuffer::genBuffer(U32 size)
|
|||
|
||||
void LLVertexBuffer::genIndices(U32 size)
|
||||
{
|
||||
mIndicesSize = nhpo2(size);
|
||||
mIndicesSize = vbo_block_size(size);
|
||||
|
||||
if (mUsage == GL_STREAM_DRAW_ARB)
|
||||
{
|
||||
|
|
@ -1108,10 +1235,10 @@ void LLVertexBuffer::updateNumVerts(S32 nverts)
|
|||
|
||||
llassert(nverts >= 0);
|
||||
|
||||
if (nverts >= 65535)
|
||||
if (nverts > 65536)
|
||||
{
|
||||
llwarns << "Vertex buffer overflow!" << llendl;
|
||||
nverts = 65535;
|
||||
nverts = 65536;
|
||||
}
|
||||
|
||||
U32 needed_size = calcOffsets(mTypeMask, mOffsets, nverts);
|
||||
|
|
@ -1121,7 +1248,9 @@ void LLVertexBuffer::updateNumVerts(S32 nverts)
|
|||
createGLBuffer(needed_size);
|
||||
}
|
||||
|
||||
sVertexCount -= mNumVerts;
|
||||
mNumVerts = nverts;
|
||||
sVertexCount += mNumVerts;
|
||||
}
|
||||
|
||||
void LLVertexBuffer::updateNumIndices(S32 nindices)
|
||||
|
|
@ -1137,7 +1266,9 @@ void LLVertexBuffer::updateNumIndices(S32 nindices)
|
|||
createGLIndices(needed_size);
|
||||
}
|
||||
|
||||
sIndexCount -= mNumIndices;
|
||||
mNumIndices = nindices;
|
||||
sIndexCount += mNumIndices;
|
||||
}
|
||||
|
||||
void LLVertexBuffer::allocateBuffer(S32 nverts, S32 nindices, bool create)
|
||||
|
|
@ -1163,7 +1294,7 @@ void LLVertexBuffer::allocateBuffer(S32 nverts, S32 nindices, bool create)
|
|||
if (gGLManager.mHasVertexArrayObject && useVBOs() && (LLRender::sGLCoreProfile || sUseVAO))
|
||||
{
|
||||
#if GL_ARB_vertex_array_object
|
||||
glGenVertexArrays(1, &mGLArray);
|
||||
mGLArray = getVAOName();
|
||||
#endif
|
||||
setupVertexArray();
|
||||
}
|
||||
|
|
@ -1199,7 +1330,7 @@ void LLVertexBuffer::setupVertexArray()
|
|||
1, //TYPE_WEIGHT,
|
||||
4, //TYPE_WEIGHT4,
|
||||
4, //TYPE_CLOTHWEIGHT,
|
||||
4, //TYPE_TEXTURE_INDEX
|
||||
1, //TYPE_TEXTURE_INDEX
|
||||
};
|
||||
|
||||
U32 attrib_type[] =
|
||||
|
|
@ -1216,7 +1347,7 @@ void LLVertexBuffer::setupVertexArray()
|
|||
GL_FLOAT, //TYPE_WEIGHT,
|
||||
GL_FLOAT, //TYPE_WEIGHT4,
|
||||
GL_FLOAT, //TYPE_CLOTHWEIGHT,
|
||||
GL_UNSIGNED_BYTE, //TYPE_TEXTURE_INDEX
|
||||
GL_UNSIGNED_INT, //TYPE_TEXTURE_INDEX
|
||||
};
|
||||
|
||||
bool attrib_integer[] =
|
||||
|
|
@ -2033,6 +2164,16 @@ void LLVertexBuffer::flush()
|
|||
}
|
||||
}
|
||||
|
||||
// bind for transform feedback (quick 'n dirty)
|
||||
void LLVertexBuffer::bindForFeedback(U32 channel, U32 type, U32 index, U32 count)
|
||||
{
|
||||
#ifdef GL_TRANSFORM_FEEDBACK_BUFFER
|
||||
U32 offset = mOffsets[type] + sTypeSize[type]*index;
|
||||
U32 size= (sTypeSize[type]*count);
|
||||
glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, channel, mGLBuffer, offset, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Set for rendering
|
||||
void LLVertexBuffer::setBuffer(U32 data_mask)
|
||||
{
|
||||
|
|
@ -2184,10 +2325,10 @@ void LLVertexBuffer::setupVertexBuffer(U32 data_mask)
|
|||
stop_glerror();
|
||||
volatile U8* base = useVBOs() ? (U8*) mAlignedOffset : mMappedData;
|
||||
|
||||
/*if ((data_mask & mTypeMask) != data_mask)
|
||||
if (gDebugGL && ((data_mask & mTypeMask) != data_mask))
|
||||
{
|
||||
llerrs << "LLVertexBuffer::setupVertexBuffer missing required components for supplied data mask." << llendl;
|
||||
}*/
|
||||
}
|
||||
|
||||
if (LLGLSLShader::sNoFixedFunction)
|
||||
{
|
||||
|
|
@ -2263,7 +2404,7 @@ void LLVertexBuffer::setupVertexBuffer(U32 data_mask)
|
|||
#if !LL_DARWIN
|
||||
S32 loc = TYPE_TEXTURE_INDEX;
|
||||
void *ptr = (void*) (base + mOffsets[TYPE_VERTEX] + 12);
|
||||
glVertexAttribIPointer(loc, 4, GL_UNSIGNED_BYTE, LLVertexBuffer::sTypeSize[TYPE_VERTEX], ptr);
|
||||
glVertexAttribIPointer(loc, 1, GL_UNSIGNED_INT, LLVertexBuffer::sTypeSize[TYPE_VERTEX], ptr);
|
||||
#endif
|
||||
}
|
||||
if (data_mask & MAP_VERTEX)
|
||||
|
|
|
|||
|
|
@ -55,24 +55,30 @@ class LLVBOPool
|
|||
{
|
||||
public:
|
||||
static U32 sBytesPooled;
|
||||
static U32 sIndexBytesPooled;
|
||||
|
||||
LLVBOPool(U32 vboUsage, U32 vboType)
|
||||
: mUsage(vboUsage)
|
||||
, mType(vboType)
|
||||
{}
|
||||
static U32 sCurGLName;
|
||||
|
||||
LLVBOPool(U32 vboUsage, U32 vboType);
|
||||
|
||||
const U32 mUsage;
|
||||
const U32 mType;
|
||||
|
||||
//size MUST be a power of 2
|
||||
volatile U8* allocate(U32& name, U32 size);
|
||||
volatile U8* allocate(U32& name, U32 size, bool for_seed = false);
|
||||
|
||||
//size MUST be the size provided to allocate that returned the given name
|
||||
void release(U32 name, volatile U8* buffer, U32 size);
|
||||
|
||||
//batch allocate buffers to be provided to the application on demand
|
||||
void seedPool();
|
||||
|
||||
//destroy all records in mFreeList
|
||||
void cleanup();
|
||||
|
||||
U32 genBuffer();
|
||||
void deleteBuffer(U32 name);
|
||||
|
||||
class Record
|
||||
{
|
||||
public:
|
||||
|
|
@ -80,16 +86,14 @@ public:
|
|||
volatile U8* mClientData;
|
||||
};
|
||||
|
||||
std::list<U32> mGLNamePool;
|
||||
|
||||
typedef std::list<Record> record_list_t;
|
||||
std::vector<record_list_t> mFreeList;
|
||||
std::vector<U32> mMissCount;
|
||||
|
||||
};
|
||||
|
||||
class LLGLFence
|
||||
{
|
||||
public:
|
||||
virtual void placeFence() = 0;
|
||||
virtual void wait() = 0;
|
||||
};
|
||||
|
||||
//============================================================================
|
||||
// base class
|
||||
|
|
@ -124,13 +128,22 @@ public:
|
|||
static LLVBOPool sStreamIBOPool;
|
||||
static LLVBOPool sDynamicIBOPool;
|
||||
|
||||
static std::list<U32> sAvailableVAOName;
|
||||
static U32 sCurVAOName;
|
||||
|
||||
static bool sUseStreamDraw;
|
||||
static bool sUseVAO;
|
||||
static bool sPreferStreamDraw;
|
||||
|
||||
static void seedPools();
|
||||
|
||||
static U32 getVAOName();
|
||||
static void releaseVAOName(U32 name);
|
||||
|
||||
static void initClass(bool use_vbo, bool no_vbo_mapping);
|
||||
static void cleanupClass();
|
||||
static void setupClientArrays(U32 data_mask);
|
||||
static void pushPositions(U32 mode, const LLVector4a* pos, U32 count);
|
||||
static void drawArrays(U32 mode, const std::vector<LLVector3>& pos, const std::vector<LLVector3>& norm);
|
||||
static void drawElements(U32 mode, const LLVector4a* pos, const LLVector2* tc, S32 num_indices, const U16* indicesp);
|
||||
|
||||
|
|
@ -207,7 +220,6 @@ protected:
|
|||
void destroyGLIndices();
|
||||
void updateNumVerts(S32 nverts);
|
||||
void updateNumIndices(S32 nindices);
|
||||
bool useVBOs() const;
|
||||
void unmapBuffer();
|
||||
|
||||
public:
|
||||
|
|
@ -217,6 +229,8 @@ public:
|
|||
volatile U8* mapVertexBuffer(S32 type, S32 index, S32 count, bool map_range);
|
||||
volatile U8* mapIndexBuffer(S32 index, S32 count, bool map_range);
|
||||
|
||||
void bindForFeedback(U32 channel, U32 type, U32 index, U32 count);
|
||||
|
||||
// set for rendering
|
||||
virtual void setBuffer(U32 data_mask); // calls setupVertexBuffer() if data_mask is not 0
|
||||
void flush(); //flush pending data to GL memory
|
||||
|
|
@ -239,12 +253,14 @@ public:
|
|||
bool getNormalStrider(LLStrider<LLVector3>& strider, S32 index=0, S32 count = -1, bool map_range = false);
|
||||
bool getBinormalStrider(LLStrider<LLVector3>& strider, S32 index=0, S32 count = -1, bool map_range = false);
|
||||
bool getColorStrider(LLStrider<LLColor4U>& strider, S32 index=0, S32 count = -1, bool map_range = false);
|
||||
bool getTextureIndexStrider(LLStrider<LLColor4U>& strider, S32 index=0, S32 count = -1, bool map_range = false);
|
||||
bool getEmissiveStrider(LLStrider<LLColor4U>& strider, S32 index=0, S32 count = -1, bool map_range = false);
|
||||
bool getWeightStrider(LLStrider<F32>& strider, S32 index=0, S32 count = -1, bool map_range = false);
|
||||
bool getWeight4Strider(LLStrider<LLVector4>& strider, S32 index=0, S32 count = -1, bool map_range = false);
|
||||
bool getClothWeightStrider(LLStrider<LLVector4>& strider, S32 index=0, S32 count = -1, bool map_range = false);
|
||||
|
||||
|
||||
bool useVBOs() const;
|
||||
bool isEmpty() const { return mEmpty; }
|
||||
bool isLocked() const { return mVertexLocked || mIndexLocked; }
|
||||
S32 getNumVerts() const { return mNumVerts; }
|
||||
|
|
@ -332,6 +348,9 @@ public:
|
|||
static bool sIBOActive;
|
||||
static U32 sLastMask;
|
||||
static U32 sAllocatedBytes;
|
||||
static U32 sAllocatedIndexBytes;
|
||||
static U32 sVertexCount;
|
||||
static U32 sIndexCount;
|
||||
static U32 sBindCount;
|
||||
static U32 sSetCount;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ include(LLRender)
|
|||
include(LLWindow)
|
||||
include(LLVFS)
|
||||
include(LLXML)
|
||||
include(LLXUIXML)
|
||||
|
||||
include_directories(
|
||||
${LLCOMMON_INCLUDE_DIRS}
|
||||
|
|
@ -24,7 +23,7 @@ include_directories(
|
|||
${LLWINDOW_INCLUDE_DIRS}
|
||||
${LLVFS_INCLUDE_DIRS}
|
||||
${LLXML_INCLUDE_DIRS}
|
||||
${LLXUIXML_INCLUDE_DIRS}
|
||||
${LIBS_PREBUILD_DIR}/include/hunspell
|
||||
)
|
||||
|
||||
set(llui_SOURCE_FILES
|
||||
|
|
@ -87,10 +86,10 @@ set(llui_SOURCE_FILES
|
|||
llscrolllistcolumn.cpp
|
||||
llscrolllistctrl.cpp
|
||||
llscrolllistitem.cpp
|
||||
llsdparam.cpp
|
||||
llsearcheditor.cpp
|
||||
llslider.cpp
|
||||
llsliderctrl.cpp
|
||||
llspellcheck.cpp
|
||||
llspinctrl.cpp
|
||||
llstatbar.cpp
|
||||
llstatgraph.cpp
|
||||
|
|
@ -104,11 +103,13 @@ set(llui_SOURCE_FILES
|
|||
lltextutil.cpp
|
||||
lltextvalidate.cpp
|
||||
lltimectrl.cpp
|
||||
lltrans.cpp
|
||||
lltransutil.cpp
|
||||
lltoggleablemenu.cpp
|
||||
lltoolbar.cpp
|
||||
lltooltip.cpp
|
||||
llui.cpp
|
||||
lluicolor.cpp
|
||||
lluicolortable.cpp
|
||||
lluictrl.cpp
|
||||
lluictrlfactory.cpp
|
||||
|
|
@ -125,6 +126,7 @@ set(llui_SOURCE_FILES
|
|||
llview.cpp
|
||||
llviewquery.cpp
|
||||
llwindowshade.cpp
|
||||
llxuiparser.cpp
|
||||
)
|
||||
|
||||
set(llui_HEADER_FILES
|
||||
|
|
@ -197,9 +199,10 @@ set(llui_HEADER_FILES
|
|||
llscrolllistcolumn.h
|
||||
llscrolllistctrl.h
|
||||
llscrolllistitem.h
|
||||
llsdparam.h
|
||||
llsliderctrl.h
|
||||
llslider.h
|
||||
llspellcheck.h
|
||||
llspellcheckmenuhandler.h
|
||||
llspinctrl.h
|
||||
llstatbar.h
|
||||
llstatgraph.h
|
||||
|
|
@ -216,6 +219,7 @@ set(llui_HEADER_FILES
|
|||
lltoggleablemenu.h
|
||||
lltoolbar.h
|
||||
lltooltip.h
|
||||
lltrans.h
|
||||
lltransutil.h
|
||||
lluicolortable.h
|
||||
lluiconstants.h
|
||||
|
|
@ -223,6 +227,7 @@ set(llui_HEADER_FILES
|
|||
lluictrl.h
|
||||
lluifwd.h
|
||||
llui.h
|
||||
lluicolor.h
|
||||
lluiimage.h
|
||||
lluistring.h
|
||||
llundo.h
|
||||
|
|
@ -236,6 +241,7 @@ set(llui_HEADER_FILES
|
|||
llview.h
|
||||
llviewquery.h
|
||||
llwindowshade.h
|
||||
llxuiparser.h
|
||||
)
|
||||
|
||||
set_source_files_properties(${llui_HEADER_FILES}
|
||||
|
|
@ -266,6 +272,7 @@ target_link_libraries(llui
|
|||
${LLXUIXML_LIBRARIES}
|
||||
${LLXML_LIBRARIES}
|
||||
${LLMATH_LIBRARIES}
|
||||
${HUNSPELL_LIBRARY}
|
||||
${LLCOMMON_LIBRARIES} # must be after llimage, llwindow, llrender
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -196,24 +196,24 @@ void LLContainerView::arrange(S32 width, S32 height, BOOL called_from_parent)
|
|||
if (total_height < height)
|
||||
total_height = height;
|
||||
|
||||
LLRect my_rect = getRect();
|
||||
if (followsTop())
|
||||
{
|
||||
// HACK: casting away const. Should use setRect or some helper function instead.
|
||||
const_cast<LLRect&>(getRect()).mBottom = getRect().mTop - total_height;
|
||||
my_rect.mBottom = my_rect.mTop - total_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
// HACK: casting away const. Should use setRect or some helper function instead.
|
||||
const_cast<LLRect&>(getRect()).mTop = getRect().mBottom + total_height;
|
||||
my_rect.mTop = my_rect.mBottom + total_height;
|
||||
}
|
||||
// HACK: casting away const. Should use setRect or some helper function instead.
|
||||
const_cast<LLRect&>(getRect()).mRight = getRect().mLeft + width;
|
||||
|
||||
my_rect.mRight = my_rect.mLeft + width;
|
||||
setRect(my_rect);
|
||||
|
||||
top = total_height;
|
||||
if (mShowLabel)
|
||||
{
|
||||
top -= 20;
|
||||
}
|
||||
{
|
||||
top -= 20;
|
||||
}
|
||||
|
||||
bottom = top;
|
||||
|
||||
|
|
|
|||
|
|
@ -360,7 +360,6 @@ void LLFolderView::reshape(S32 width, S32 height, BOOL called_from_parent)
|
|||
{
|
||||
width = scroll_rect.getWidth();
|
||||
}
|
||||
|
||||
LLView::reshape(width, height, called_from_parent);
|
||||
mReshapeSignal(mSelectedItems, FALSE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ BOOL LLFolderViewItem::passedFilter(S32 filter_generation)
|
|||
}
|
||||
|
||||
void LLFolderViewItem::refresh()
|
||||
{
|
||||
{
|
||||
LLFolderViewModelItem& vmi = *getViewModelItem();
|
||||
|
||||
mLabel = vmi.getDisplayName();
|
||||
|
|
@ -219,11 +219,11 @@ void LLFolderViewItem::refresh()
|
|||
mIconOpen = vmi.getIconOpen();
|
||||
mIconOverlay = vmi.getIconOverlay();
|
||||
|
||||
if (mRoot->useLabelSuffix())
|
||||
{
|
||||
if (mRoot->useLabelSuffix())
|
||||
{
|
||||
mLabelStyle = vmi.getLabelStyle();
|
||||
mLabelSuffix = vmi.getLabelSuffix();
|
||||
}
|
||||
}
|
||||
|
||||
//TODO RN: make sure this logic still fires
|
||||
//std::string searchable_label(mLabel);
|
||||
|
|
@ -253,7 +253,7 @@ void LLFolderViewItem::arrangeAndSet(BOOL set_selection,
|
|||
LLFolderView* root = getRoot();
|
||||
if (getParentFolder())
|
||||
{
|
||||
getParentFolder()->requestArrange();
|
||||
getParentFolder()->requestArrange();
|
||||
}
|
||||
if(set_selection)
|
||||
{
|
||||
|
|
@ -370,7 +370,7 @@ BOOL LLFolderViewItem::isMovable()
|
|||
BOOL LLFolderViewItem::isRemovable()
|
||||
{
|
||||
return getViewModelItem()->isItemRemovable();
|
||||
}
|
||||
}
|
||||
|
||||
void LLFolderViewItem::destroyView()
|
||||
{
|
||||
|
|
@ -394,7 +394,7 @@ BOOL LLFolderViewItem::remove()
|
|||
return FALSE;
|
||||
}
|
||||
return getViewModelItem()->removeItem();
|
||||
}
|
||||
}
|
||||
|
||||
// Build an appropriate context menu for the item.
|
||||
void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags)
|
||||
|
|
@ -481,24 +481,24 @@ BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask )
|
|||
|
||||
if( hasMouseCapture() && isMovable() )
|
||||
{
|
||||
LLFolderView* root = getRoot();
|
||||
LLFolderView* root = getRoot();
|
||||
|
||||
if( (x - mDragStartX) * (x - mDragStartX) + (y - mDragStartY) * (y - mDragStartY) > drag_and_drop_threshold() * drag_and_drop_threshold()
|
||||
&& root->getCurSelectedItem()
|
||||
&& root->startDrag())
|
||||
{
|
||||
// RN: when starting drag and drop, clear out last auto-open
|
||||
root->autoOpenTest(NULL);
|
||||
root->setShowSelectionContext(TRUE);
|
||||
{
|
||||
// RN: when starting drag and drop, clear out last auto-open
|
||||
root->autoOpenTest(NULL);
|
||||
root->setShowSelectionContext(TRUE);
|
||||
|
||||
// Release keyboard focus, so that if stuff is dropped into the
|
||||
// world, pressing the delete key won't blow away the inventory
|
||||
// item.
|
||||
gFocusMgr.setKeyboardFocus(NULL);
|
||||
// Release keyboard focus, so that if stuff is dropped into the
|
||||
// world, pressing the delete key won't blow away the inventory
|
||||
// item.
|
||||
gFocusMgr.setKeyboardFocus(NULL);
|
||||
|
||||
getWindow()->setCursor(UI_CURSOR_ARROW);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
getWindow()->setCursor(UI_CURSOR_NOLOCKED);
|
||||
|
|
@ -599,17 +599,17 @@ BOOL LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
|
|||
|
||||
void LLFolderViewItem::draw()
|
||||
{
|
||||
static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE);
|
||||
static LLUIColor sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE);
|
||||
static LLUIColor sHighlightFgColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE);
|
||||
static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE);
|
||||
static LLUIColor sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE);
|
||||
static LLUIColor sHighlightFgColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE);
|
||||
static LLUIColor sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE);
|
||||
static LLUIColor sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE);
|
||||
static LLUIColor sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE);
|
||||
static LLUIColor sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemColor", DEFAULT_WHITE);
|
||||
static LLUIColor sLibraryColor = LLUIColorTable::instance().getColor("InventoryItemLibraryColor", DEFAULT_WHITE);
|
||||
static LLUIColor sLinkColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE);
|
||||
static LLUIColor sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE);
|
||||
static LLUIColor sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE);
|
||||
static LLUIColor sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemColor", DEFAULT_WHITE);
|
||||
static LLUIColor sLibraryColor = LLUIColorTable::instance().getColor("InventoryItemLibraryColor", DEFAULT_WHITE);
|
||||
static LLUIColor sLinkColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE);
|
||||
static LLUIColor sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE);
|
||||
static LLUIColor sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE);
|
||||
static LLUIColor sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE);
|
||||
|
||||
const Params& default_params = LLUICtrlFactory::getDefaultParams<LLFolderViewItem>();
|
||||
const S32 TOP_PAD = default_params.item_top_pad;
|
||||
|
|
@ -806,7 +806,7 @@ const LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void ) c
|
|||
LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void )
|
||||
{
|
||||
return getRoot()->getFolderViewModel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///----------------------------------------------------------------------------
|
||||
|
|
@ -1495,11 +1495,27 @@ BOOL LLFolderViewFolder::addItem(LLFolderViewItem* item)
|
|||
|
||||
item->getViewModelItem()->dirtyFilter();
|
||||
|
||||
// XXX stinson TODO : handle the creation date
|
||||
#if 0
|
||||
// Update the folder creation date if the child is newer than our current date
|
||||
setCreationDate(llmax<time_t>(mCreationDate, item->getCreationDate()));
|
||||
#endif
|
||||
|
||||
// Handle sorting
|
||||
requestArrange();
|
||||
requestSort();
|
||||
|
||||
getViewModelItem()->addChild(item->getViewModelItem());
|
||||
// XXX stinson TODO : handle the creation date
|
||||
#if 0
|
||||
// Traverse parent folders and update creation date and resort, if necessary
|
||||
LLFolderViewFolder* parentp = getParentFolder();
|
||||
while (parentp)
|
||||
{
|
||||
// Update the folder creation date if the child is newer than our current date
|
||||
parentp->setCreationDate(llmax<time_t>(parentp->mCreationDate, item->getCreationDate()));
|
||||
}
|
||||
#endif
|
||||
|
||||
//TODO RN - make sort bubble up as long as parent Folder doesn't have anything matching sort criteria
|
||||
//// Traverse parent folders and update creation date and resort, if necessary
|
||||
|
|
@ -1545,14 +1561,14 @@ void LLFolderViewFolder::requestArrange()
|
|||
{
|
||||
//if ( mLastArrangeGeneration != -1)
|
||||
{
|
||||
mLastArrangeGeneration = -1;
|
||||
// flag all items up to root
|
||||
if (mParentFolder)
|
||||
{
|
||||
mParentFolder->requestArrange();
|
||||
mLastArrangeGeneration = -1;
|
||||
// flag all items up to root
|
||||
if (mParentFolder)
|
||||
{
|
||||
mParentFolder->requestArrange();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLFolderViewFolder::toggleOpen()
|
||||
{
|
||||
|
|
@ -1569,12 +1585,12 @@ void LLFolderViewFolder::setOpenArrangeRecursively(BOOL openitem, ERecurseType r
|
|||
{
|
||||
BOOL was_open = isOpen();
|
||||
mIsOpen = openitem;
|
||||
if(!was_open && openitem)
|
||||
{
|
||||
if(!was_open && openitem)
|
||||
{
|
||||
getViewModelItem()->openItem();
|
||||
}
|
||||
else if(was_open && !openitem)
|
||||
{
|
||||
}
|
||||
else if(was_open && !openitem)
|
||||
{
|
||||
getViewModelItem()->closeItem();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
#include "llkeyboard.h"
|
||||
#include "llrect.h"
|
||||
#include "llresmgr.h"
|
||||
#include "llspellcheck.h"
|
||||
#include "llstring.h"
|
||||
#include "llwindow.h"
|
||||
#include "llui.h"
|
||||
|
|
@ -65,6 +66,7 @@ const S32 SCROLL_INCREMENT_ADD = 0; // make space for typing
|
|||
const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing
|
||||
const F32 AUTO_SCROLL_TIME = 0.05f;
|
||||
const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click. *TODO: make this equal to the double click interval?
|
||||
const F32 SPELLCHECK_DELAY = 0.5f; // delay between the last keypress and spell checking the word the cursor is on
|
||||
|
||||
const std::string PASSWORD_ASTERISK( "\xE2\x80\xA2" ); // U+2022 BULLET
|
||||
|
||||
|
|
@ -88,6 +90,7 @@ LLLineEditor::Params::Params()
|
|||
background_image_focused("background_image_focused"),
|
||||
select_on_focus("select_on_focus", false),
|
||||
revert_on_esc("revert_on_esc", true),
|
||||
spellcheck("spellcheck", false),
|
||||
commit_on_focus_lost("commit_on_focus_lost", true),
|
||||
ignore_tab("ignore_tab", true),
|
||||
is_password("is_password", false),
|
||||
|
|
@ -134,6 +137,9 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
|
|||
mIgnoreArrowKeys( FALSE ),
|
||||
mIgnoreTab( p.ignore_tab ),
|
||||
mDrawAsterixes( p.is_password ),
|
||||
mSpellCheck( p.spellcheck ),
|
||||
mSpellCheckStart(-1),
|
||||
mSpellCheckEnd(-1),
|
||||
mSelectAllonFocusReceived( p.select_on_focus ),
|
||||
mSelectAllonCommit( TRUE ),
|
||||
mPassDelete(FALSE),
|
||||
|
|
@ -151,7 +157,8 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
|
|||
mHighlightColor(p.highlight_color()),
|
||||
mPreeditBgColor(p.preedit_bg_color()),
|
||||
mGLFont(p.font),
|
||||
mContextMenuHandle()
|
||||
mContextMenuHandle(),
|
||||
mAutoreplaceCallback()
|
||||
{
|
||||
llassert( mMaxLengthBytes > 0 );
|
||||
|
||||
|
|
@ -177,6 +184,12 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
|
|||
updateTextPadding();
|
||||
setCursor(mText.length());
|
||||
|
||||
if (mSpellCheck)
|
||||
{
|
||||
LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLLineEditor::onSpellCheckSettingsChange, this));
|
||||
}
|
||||
mSpellCheckTimer.reset();
|
||||
|
||||
setPrevalidateInput(p.prevalidate_input_callback());
|
||||
setPrevalidate(p.prevalidate_callback());
|
||||
|
||||
|
|
@ -195,7 +208,6 @@ LLLineEditor::~LLLineEditor()
|
|||
gFocusMgr.releaseFocusIfNeeded( this );
|
||||
}
|
||||
|
||||
|
||||
void LLLineEditor::onFocusReceived()
|
||||
{
|
||||
gEditMenuHandler = this;
|
||||
|
|
@ -519,6 +531,99 @@ void LLLineEditor::selectAll()
|
|||
updatePrimary();
|
||||
}
|
||||
|
||||
bool LLLineEditor::getSpellCheck() const
|
||||
{
|
||||
return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck);
|
||||
}
|
||||
|
||||
const std::string& LLLineEditor::getSuggestion(U32 index) const
|
||||
{
|
||||
return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null;
|
||||
}
|
||||
|
||||
U32 LLLineEditor::getSuggestionCount() const
|
||||
{
|
||||
return mSuggestionList.size();
|
||||
}
|
||||
|
||||
void LLLineEditor::replaceWithSuggestion(U32 index)
|
||||
{
|
||||
for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
|
||||
{
|
||||
if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) )
|
||||
{
|
||||
deselect();
|
||||
|
||||
// Delete the misspelled word
|
||||
mText.erase(it->first, it->second - it->first);
|
||||
|
||||
// Insert the suggestion in its place
|
||||
LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]);
|
||||
mText.insert(it->first, suggestion);
|
||||
setCursor(it->first + (S32)suggestion.length());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
mSpellCheckStart = mSpellCheckEnd = -1;
|
||||
}
|
||||
|
||||
void LLLineEditor::addToDictionary()
|
||||
{
|
||||
if (canAddToDictionary())
|
||||
{
|
||||
LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos));
|
||||
}
|
||||
}
|
||||
|
||||
bool LLLineEditor::canAddToDictionary() const
|
||||
{
|
||||
return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
|
||||
}
|
||||
|
||||
void LLLineEditor::addToIgnore()
|
||||
{
|
||||
if (canAddToIgnore())
|
||||
{
|
||||
LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos));
|
||||
}
|
||||
}
|
||||
|
||||
bool LLLineEditor::canAddToIgnore() const
|
||||
{
|
||||
return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
|
||||
}
|
||||
|
||||
std::string LLLineEditor::getMisspelledWord(U32 pos) const
|
||||
{
|
||||
for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
|
||||
{
|
||||
if ( (it->first <= pos) && (it->second >= pos) )
|
||||
{
|
||||
return wstring_to_utf8str(mText.getWString().substr(it->first, it->second - it->first));
|
||||
}
|
||||
}
|
||||
return LLStringUtil::null;
|
||||
}
|
||||
|
||||
bool LLLineEditor::isMisspelledWord(U32 pos) const
|
||||
{
|
||||
for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
|
||||
{
|
||||
if ( (it->first <= pos) && (it->second >= pos) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LLLineEditor::onSpellCheckSettingsChange()
|
||||
{
|
||||
// Recheck the spelling on every change
|
||||
mMisspellRanges.clear();
|
||||
mSpellCheckStart = mSpellCheckEnd = -1;
|
||||
}
|
||||
|
||||
BOOL LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
|
||||
{
|
||||
|
|
@ -866,6 +971,12 @@ void LLLineEditor::addChar(const llwchar uni_char)
|
|||
LLUI::reportBadKeystroke();
|
||||
}
|
||||
|
||||
if (!mReadOnly && mAutoreplaceCallback != NULL)
|
||||
{
|
||||
// call callback
|
||||
mAutoreplaceCallback(mText, mCursorPos);
|
||||
}
|
||||
|
||||
getWindow()->hideCursorUntilMouseMove();
|
||||
}
|
||||
|
||||
|
|
@ -1058,9 +1169,8 @@ void LLLineEditor::cut()
|
|||
LLUI::reportBadKeystroke();
|
||||
}
|
||||
else
|
||||
if( mKeystrokeCallback )
|
||||
{
|
||||
mKeystrokeCallback( this );
|
||||
onKeystroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1187,9 +1297,8 @@ void LLLineEditor::pasteHelper(bool is_primary)
|
|||
LLUI::reportBadKeystroke();
|
||||
}
|
||||
else
|
||||
if( mKeystrokeCallback )
|
||||
{
|
||||
mKeystrokeCallback( this );
|
||||
onKeystroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1442,9 +1551,10 @@ BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask )
|
|||
// Notify owner if requested
|
||||
if (!need_to_rollback && handled)
|
||||
{
|
||||
if (mKeystrokeCallback)
|
||||
onKeystroke();
|
||||
if ( (!selection_modified) && (KEY_BACKSPACE == key) )
|
||||
{
|
||||
mKeystrokeCallback(this);
|
||||
mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1497,12 +1607,11 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char)
|
|||
// Notify owner if requested
|
||||
if( !need_to_rollback && handled )
|
||||
{
|
||||
if( mKeystrokeCallback )
|
||||
{
|
||||
// HACK! The only usage of this callback doesn't do anything with the character.
|
||||
// We'll have to do something about this if something ever changes! - Doug
|
||||
mKeystrokeCallback( this );
|
||||
}
|
||||
// HACK! The only usage of this callback doesn't do anything with the character.
|
||||
// We'll have to do something about this if something ever changes! - Doug
|
||||
onKeystroke();
|
||||
|
||||
mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
|
|
@ -1531,9 +1640,7 @@ void LLLineEditor::doDelete()
|
|||
|
||||
if (!prevalidateInput(text_to_delete))
|
||||
{
|
||||
if( mKeystrokeCallback )
|
||||
mKeystrokeCallback( this );
|
||||
|
||||
onKeystroke();
|
||||
return;
|
||||
}
|
||||
setCursor(getCursor() + 1);
|
||||
|
|
@ -1549,10 +1656,9 @@ void LLLineEditor::doDelete()
|
|||
}
|
||||
else
|
||||
{
|
||||
if( mKeystrokeCallback )
|
||||
{
|
||||
mKeystrokeCallback( this );
|
||||
}
|
||||
onKeystroke();
|
||||
|
||||
mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1624,6 +1730,10 @@ void LLLineEditor::draw()
|
|||
background.stretch( -mBorderThickness );
|
||||
|
||||
S32 lineeditor_v_pad = (background.getHeight() - mGLFont->getLineHeight()) / 2;
|
||||
if (mSpellCheck)
|
||||
{
|
||||
lineeditor_v_pad += 1;
|
||||
}
|
||||
|
||||
drawBackground();
|
||||
|
||||
|
|
@ -1698,14 +1808,14 @@ void LLLineEditor::draw()
|
|||
{
|
||||
S32 select_left;
|
||||
S32 select_right;
|
||||
if( mSelectionStart < getCursor() )
|
||||
if (mSelectionStart < mSelectionEnd)
|
||||
{
|
||||
select_left = mSelectionStart;
|
||||
select_right = getCursor();
|
||||
select_right = mSelectionEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
select_left = getCursor();
|
||||
select_left = mSelectionEnd;
|
||||
select_right = mSelectionStart;
|
||||
}
|
||||
|
||||
|
|
@ -1749,7 +1859,7 @@ void LLLineEditor::draw()
|
|||
if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) )
|
||||
{
|
||||
// unselected, right side
|
||||
mGLFont->render(
|
||||
rendered_text += mGLFont->render(
|
||||
mText, mScrollHPos + rendered_text,
|
||||
rendered_pixels_right, text_bottom,
|
||||
text_color,
|
||||
|
|
@ -1763,7 +1873,7 @@ void LLLineEditor::draw()
|
|||
}
|
||||
else
|
||||
{
|
||||
mGLFont->render(
|
||||
rendered_text = mGLFont->render(
|
||||
mText, mScrollHPos,
|
||||
rendered_pixels_right, text_bottom,
|
||||
text_color,
|
||||
|
|
@ -1778,6 +1888,101 @@ void LLLineEditor::draw()
|
|||
mBorder->setVisible(FALSE); // no more programmatic art.
|
||||
#endif
|
||||
|
||||
if ( (getSpellCheck()) && (mText.length() > 2) )
|
||||
{
|
||||
// Calculate start and end indices for the first and last visible word
|
||||
U32 start = prevWordPos(mScrollHPos), end = nextWordPos(mScrollHPos + rendered_text);
|
||||
|
||||
if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) )
|
||||
{
|
||||
const LLWString& text = mText.getWString().substr(start, end);
|
||||
|
||||
// Find the start of the first word
|
||||
U32 word_start = 0, word_end = 0;
|
||||
while ( (word_start < text.length()) && (!LLStringOps::isAlpha(text[word_start])) )
|
||||
{
|
||||
word_start++;
|
||||
}
|
||||
|
||||
// Iterate over all words in the text block and check them one by one
|
||||
mMisspellRanges.clear();
|
||||
while (word_start < text.length())
|
||||
{
|
||||
// Find the end of the current word (special case handling for "'" when it's used as a contraction)
|
||||
word_end = word_start + 1;
|
||||
while ( (word_end < text.length()) &&
|
||||
((LLWStringUtil::isPartOfWord(text[word_end])) ||
|
||||
((L'\'' == text[word_end]) && (word_end + 1 < text.length()) &&
|
||||
(LLStringOps::isAlnum(text[word_end - 1])) && (LLStringOps::isAlnum(text[word_end + 1])))) )
|
||||
{
|
||||
word_end++;
|
||||
}
|
||||
if (word_end > text.length())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Don't process words shorter than 3 characters
|
||||
std::string word = wstring_to_utf8str(text.substr(word_start, word_end - word_start));
|
||||
if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) )
|
||||
{
|
||||
mMisspellRanges.push_back(std::pair<U32, U32>(start + word_start, start + word_end));
|
||||
}
|
||||
|
||||
// Find the start of the next word
|
||||
word_start = word_end + 1;
|
||||
while ( (word_start < text.length()) && (!LLWStringUtil::isPartOfWord(text[word_start])) )
|
||||
{
|
||||
word_start++;
|
||||
}
|
||||
}
|
||||
|
||||
mSpellCheckStart = start;
|
||||
mSpellCheckEnd = end;
|
||||
}
|
||||
|
||||
// Draw squiggly lines under any (visible) misspelled words
|
||||
for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
|
||||
{
|
||||
// Skip over words that aren't (partially) visible
|
||||
if ( ((it->first < start) && (it->second < start)) || (it->first > end) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip the current word if the user is still busy editing it
|
||||
if ( (!mSpellCheckTimer.hasExpired()) && (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
S32 pxWidth = getRect().getWidth();
|
||||
S32 pxStart = findPixelNearestPos(it->first - getCursor());
|
||||
if (pxStart > pxWidth)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
S32 pxEnd = findPixelNearestPos(it->second - getCursor());
|
||||
if (pxEnd > pxWidth)
|
||||
{
|
||||
pxEnd = pxWidth;
|
||||
}
|
||||
|
||||
S32 pxBottom = (S32)(text_bottom + mGLFont->getDescenderHeight());
|
||||
|
||||
gGL.color4ub(255, 0, 0, 200);
|
||||
while (pxStart + 1 < pxEnd)
|
||||
{
|
||||
gl_line_2d(pxStart, pxBottom, pxStart + 2, pxBottom - 2);
|
||||
if (pxStart + 3 < pxEnd)
|
||||
{
|
||||
gl_line_2d(pxStart + 2, pxBottom - 3, pxStart + 4, pxBottom - 1);
|
||||
}
|
||||
pxStart += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're editing...
|
||||
if( hasFocus())
|
||||
{
|
||||
|
|
@ -2109,6 +2314,15 @@ void LLLineEditor::setSelectAllonFocusReceived(BOOL b)
|
|||
mSelectAllonFocusReceived = b;
|
||||
}
|
||||
|
||||
void LLLineEditor::onKeystroke()
|
||||
{
|
||||
if (mKeystrokeCallback)
|
||||
{
|
||||
mKeystrokeCallback(this);
|
||||
}
|
||||
|
||||
mSpellCheckStart = mSpellCheckEnd = -1;
|
||||
}
|
||||
|
||||
void LLLineEditor::setKeystrokeCallback(callback_t callback, void* user_data)
|
||||
{
|
||||
|
|
@ -2231,10 +2445,9 @@ void LLLineEditor::updatePreedit(const LLWString &preedit_string,
|
|||
|
||||
// Update of the preedit should be caused by some key strokes.
|
||||
mKeystrokeTimer.reset();
|
||||
if( mKeystrokeCallback )
|
||||
{
|
||||
mKeystrokeCallback( this );
|
||||
}
|
||||
onKeystroke();
|
||||
|
||||
mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
|
||||
}
|
||||
|
||||
BOOL LLLineEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const
|
||||
|
|
@ -2386,7 +2599,38 @@ void LLLineEditor::showContextMenu(S32 x, S32 y)
|
|||
|
||||
S32 screen_x, screen_y;
|
||||
localPointToScreen(x, y, &screen_x, &screen_y);
|
||||
menu->show(screen_x, screen_y);
|
||||
|
||||
setCursorAtLocalPos(x);
|
||||
if (hasSelection())
|
||||
{
|
||||
if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) )
|
||||
{
|
||||
deselect();
|
||||
}
|
||||
else
|
||||
{
|
||||
setCursor(llmax(mSelectionStart, mSelectionEnd));
|
||||
}
|
||||
}
|
||||
|
||||
bool use_spellcheck = getSpellCheck(), is_misspelled = false;
|
||||
if (use_spellcheck)
|
||||
{
|
||||
mSuggestionList.clear();
|
||||
|
||||
// If the cursor is on a misspelled word, retrieve suggestions for it
|
||||
std::string misspelled_word = getMisspelledWord(mCursorPos);
|
||||
if ((is_misspelled = !misspelled_word.empty()) == true)
|
||||
{
|
||||
LLSpellChecker::instance().getSuggestions(misspelled_word, mSuggestionList);
|
||||
}
|
||||
}
|
||||
|
||||
menu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty()));
|
||||
menu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled));
|
||||
menu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled));
|
||||
menu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled));
|
||||
menu->show(screen_x, screen_y, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include "llframetimer.h"
|
||||
|
||||
#include "lleditmenuhandler.h"
|
||||
#include "llspellcheckmenuhandler.h"
|
||||
#include "lluictrl.h"
|
||||
#include "lluiimage.h"
|
||||
#include "lluistring.h"
|
||||
|
|
@ -54,7 +55,7 @@ class LLButton;
|
|||
class LLContextMenu;
|
||||
|
||||
class LLLineEditor
|
||||
: public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor
|
||||
: public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor, public LLSpellCheckMenuHandler
|
||||
{
|
||||
public:
|
||||
|
||||
|
|
@ -86,6 +87,7 @@ public:
|
|||
|
||||
Optional<bool> select_on_focus,
|
||||
revert_on_esc,
|
||||
spellcheck,
|
||||
commit_on_focus_lost,
|
||||
ignore_tab,
|
||||
is_password;
|
||||
|
|
@ -146,6 +148,24 @@ public:
|
|||
virtual void deselect();
|
||||
virtual BOOL canDeselect() const;
|
||||
|
||||
// LLSpellCheckMenuHandler overrides
|
||||
/*virtual*/ bool getSpellCheck() const;
|
||||
|
||||
/*virtual*/ const std::string& getSuggestion(U32 index) const;
|
||||
/*virtual*/ U32 getSuggestionCount() const;
|
||||
/*virtual*/ void replaceWithSuggestion(U32 index);
|
||||
|
||||
/*virtual*/ void addToDictionary();
|
||||
/*virtual*/ bool canAddToDictionary() const;
|
||||
|
||||
/*virtual*/ void addToIgnore();
|
||||
/*virtual*/ bool canAddToIgnore() const;
|
||||
|
||||
// Spell checking helper functions
|
||||
std::string getMisspelledWord(U32 pos) const;
|
||||
bool isMisspelledWord(U32 pos) const;
|
||||
void onSpellCheckSettingsChange();
|
||||
|
||||
// view overrides
|
||||
virtual void draw();
|
||||
virtual void reshape(S32 width,S32 height,BOOL called_from_parent=TRUE);
|
||||
|
|
@ -169,6 +189,9 @@ public:
|
|||
virtual BOOL setTextArg( const std::string& key, const LLStringExplicit& text );
|
||||
virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text );
|
||||
|
||||
typedef boost::function<void(LLUIString&, S32&)> autoreplace_callback_t;
|
||||
autoreplace_callback_t mAutoreplaceCallback;
|
||||
void setAutoreplaceCallback(autoreplace_callback_t cb) { mAutoreplaceCallback = cb; }
|
||||
void setLabel(const LLStringExplicit &new_label) { mLabel = new_label; }
|
||||
const std::string& getLabel() { return mLabel.getString(); }
|
||||
|
||||
|
|
@ -223,6 +246,7 @@ public:
|
|||
void setSelectAllonFocusReceived(BOOL b);
|
||||
void setSelectAllonCommit(BOOL b) { mSelectAllonCommit = b; }
|
||||
|
||||
void onKeystroke();
|
||||
typedef boost::function<void (LLLineEditor* caller, void* user_data)> callback_t;
|
||||
void setKeystrokeCallback(callback_t callback, void* user_data);
|
||||
|
||||
|
|
@ -322,6 +346,13 @@ protected:
|
|||
S32 mLastSelectionStart;
|
||||
S32 mLastSelectionEnd;
|
||||
|
||||
bool mSpellCheck;
|
||||
S32 mSpellCheckStart;
|
||||
S32 mSpellCheckEnd;
|
||||
LLTimer mSpellCheckTimer;
|
||||
std::list<std::pair<U32, U32> > mMisspellRanges;
|
||||
std::vector<std::string> mSuggestionList;
|
||||
|
||||
LLTextValidate::validate_func_t mPrevalidateFunc;
|
||||
LLTextValidate::validate_func_t mPrevalidateInputFunc;
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue