SL-20546: Merge branch 'DRTVWR-588-maint-W' into sl-20546.
commit
fea1d9f4d2
|
|
@ -1,18 +1,18 @@
|
|||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- ignore-for-release
|
||||
authors:
|
||||
- dependabot
|
||||
categories:
|
||||
- title: Breaking Changes 🛠
|
||||
labels:
|
||||
- semver-major
|
||||
- breaking-change
|
||||
- title: New Features 🎉
|
||||
labels:
|
||||
- semver-minor
|
||||
- enhancement
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- '*'
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- ignore-for-release
|
||||
authors:
|
||||
- dependabot
|
||||
categories:
|
||||
- title: Breaking Changes 🛠
|
||||
labels:
|
||||
- semver-major
|
||||
- breaking-change
|
||||
- title: New Features 🎉
|
||||
labels:
|
||||
- semver-minor
|
||||
- enhancement
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- '*'
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ jobs:
|
|||
runs-on: windows
|
||||
steps:
|
||||
- name: Sign and package Windows viewer
|
||||
uses: secondlife/viewer-build-util/sign-pkg-windows@main
|
||||
uses: secondlife/viewer-build-util/sign-pkg-windows@v1
|
||||
with:
|
||||
vault_uri: "${{ secrets.AZURE_KEY_VAULT_URI }}"
|
||||
cert_name: "${{ secrets.AZURE_CERT_NAME }}"
|
||||
|
|
@ -293,7 +293,7 @@ jobs:
|
|||
[[ -n "$USERNAME" && -n "$PASSWORD" && -n "$TEAM_ID" ]]
|
||||
|
||||
- name: Sign and package Mac viewer
|
||||
uses: secondlife/viewer-build-util/sign-pkg-mac@main
|
||||
uses: secondlife/viewer-build-util/sign-pkg-mac@v1
|
||||
with:
|
||||
channel: ${{ needs.build.outputs.viewer_channel }}
|
||||
imagename: ${{ needs.build.outputs.imagename }}
|
||||
|
|
@ -309,7 +309,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Post Windows symbols
|
||||
uses: secondlife/viewer-build-util/post-bugsplat-windows@main
|
||||
uses: secondlife/viewer-build-util/post-bugsplat-windows@v1
|
||||
with:
|
||||
username: ${{ secrets.BUGSPLAT_USER }}
|
||||
password: ${{ secrets.BUGSPLAT_PASS }}
|
||||
|
|
@ -322,7 +322,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Post Mac symbols
|
||||
uses: secondlife/viewer-build-util/post-bugsplat-mac@main
|
||||
uses: secondlife/viewer-build-util/post-bugsplat-mac@v1
|
||||
with:
|
||||
username: ${{ secrets.BUGSPLAT_USER }}
|
||||
password: ${{ secrets.BUGSPLAT_PASS }}
|
||||
|
|
@ -337,29 +337,29 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: artifacts
|
||||
name: Windows-installer
|
||||
|
||||
- name: Reshuffle artifact files
|
||||
uses: secondlife/viewer-build-util/release-artifacts@main
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
input-path: artifacts
|
||||
output-path: assets
|
||||
# The *-app artifacts are for use only by the signing and
|
||||
# packaging steps. Once we've generated signed installers, we no
|
||||
# longer need them, and we CERTAINLY don't want to publish
|
||||
# thousands of individual files as separate URLs.
|
||||
exclude: |-
|
||||
Windows-app
|
||||
macOS-app
|
||||
# Use just "Windows" or "macOS" prefix because these are the only
|
||||
# artifacts in which we expect files from both platforms with
|
||||
# colliding names (e.g. autobuild-package.xml). release-artifacts
|
||||
# normally resolves collisions by prepending the artifact name, so
|
||||
# when we anticipate collisions, it's good to keep the prefix
|
||||
# short and sweet.
|
||||
prefix: |-
|
||||
Windows-metadata=Windows
|
||||
macOS-metadata=macOS
|
||||
name: macOS-installer
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: Windows-metadata
|
||||
|
||||
- name: Rename windows metadata
|
||||
run: |
|
||||
mv autobuild-package.xml Windows-autobuild-package.xml
|
||||
mv newview/viewer_version.txt Windows-viewer_version.txt
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: macOS-metadata
|
||||
|
||||
- name: Rename macOS metadata
|
||||
run: |
|
||||
mv autobuild-package.xml macOS-autobuild-package.xml
|
||||
mv newview/viewer_version.txt macOS-viewer_version.txt
|
||||
|
||||
# forked from softprops/action-gh-release
|
||||
- uses: secondlife-3p/action-gh-release@v1
|
||||
|
|
@ -378,4 +378,8 @@ jobs:
|
|||
append_body: true
|
||||
# the only reason we generate a GH release is to post build products
|
||||
fail_on_unmatched_files: true
|
||||
files: "assets/*"
|
||||
files: |
|
||||
*.dmg
|
||||
*.exe
|
||||
*-autobuild-package.xml
|
||||
*-viewer_version.txt
|
||||
|
|
|
|||
|
|
@ -7,9 +7,18 @@
|
|||
*.pyc
|
||||
*.rej
|
||||
*.swp
|
||||
*.vcxproj
|
||||
*.filters
|
||||
*.sln
|
||||
*.depend
|
||||
*.stamp
|
||||
*.rc
|
||||
|
||||
*~
|
||||
|
||||
# Specific paths and/or names
|
||||
CMakeCache.txt
|
||||
cmake_install.cmake
|
||||
LICENSES
|
||||
build-darwin-*
|
||||
build-linux-*
|
||||
|
|
@ -17,6 +26,10 @@ debian/files
|
|||
debian/secondlife-appearance-utility*
|
||||
debian/secondlife-viewer*
|
||||
indra/.distcc
|
||||
indra/cmake/*
|
||||
indra/out/*
|
||||
|
||||
indra/packages/*
|
||||
build-vc80/
|
||||
build-vc100/
|
||||
build-vc120/
|
||||
|
|
@ -76,3 +89,4 @@ web/locale.*
|
|||
web/secondlife.com.*
|
||||
|
||||
.env
|
||||
.vscode
|
||||
|
|
|
|||
262
autobuild.xml
262
autobuild.xml
|
|
@ -702,11 +702,11 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>34af0a90a3015b7e7ec2486090bc4ce6ee5be758</string>
|
||||
<string>7cc58b3acb230a7e65ea5f0ff800be393eb4aa1b</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<key>url</key>
|
||||
<string>https://github.com/secondlife/3p-glext/releases/download/v68-af397ee/glext-68-common-af397ee.tar.zst</string>
|
||||
<string>https://github.com/secondlife/3p-glext/releases/download/v69/glext-68-common-685b36e.tar.zst</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>common</string>
|
||||
|
|
@ -1468,11 +1468,11 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>e50ea94bbaa4ff41bf53b84b7192df1a694c5337</string>
|
||||
<string>3a3e14563cd5fc019c3f139b82aa46ec79847709</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<key>url</key>
|
||||
<string>https://github.com/secondlife/llca/releases/download/v202310121525.0-d22bd98/llca-202310121530.0-common-d22bd98.tar.zst</string>
|
||||
<string>https://github.com/secondlife/llca/releases/download/v202312051403.17-0f5d9c3/llca-202312051404.0-common-0f5d9c3.tar.zst</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>common</string>
|
||||
|
|
@ -1486,7 +1486,7 @@
|
|||
<string>Copyright (c) 2016, Linden Research, Inc.; data provided by the Mozilla NSS Project.
|
||||
</string>
|
||||
<key>version</key>
|
||||
<string>202310121530.0</string>
|
||||
<string>202312051404.0</string>
|
||||
<key>name</key>
|
||||
<string>llca</string>
|
||||
</map>
|
||||
|
|
@ -1732,6 +1732,62 @@
|
|||
<key>description</key>
|
||||
<string>Meshoptimizer. Mesh optimization library.</string>
|
||||
</map>
|
||||
<key>mikktspace</key>
|
||||
<map>
|
||||
<key>canonical_repo</key>
|
||||
<string>https://bitbucket.org/lindenlab/3p-mikktspace</string>
|
||||
<key>copyright</key>
|
||||
<string>Copyright (C) 2011 by Morten S. Mikkelsen</string>
|
||||
<key>description</key>
|
||||
<string>Mikktspace Tangent Generator</string>
|
||||
<key>license</key>
|
||||
<string>Copyright (C) 2011 by Morten S. Mikkelsen</string>
|
||||
<key>license_file</key>
|
||||
<string>mikktspace.txt</string>
|
||||
<key>name</key>
|
||||
<string>mikktspace</string>
|
||||
<key>platforms</key>
|
||||
<map>
|
||||
<key>darwin64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>b48b7ac0792d3ea8f087d99d9e4a29d8</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104415/914944/mikktspace-1-darwin64-574859.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
</map>
|
||||
<key>windows</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>0a016b9c0c1e2c0b557e0124094da6c5</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104407/914918/mikktspace-1-windows-574859.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
</map>
|
||||
<key>windows64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>02e9e5b6fe6788f4d2babb83ec544843</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/104406/914909/mikktspace-1-windows64-574859.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
</map>
|
||||
</map>
|
||||
<key>version</key>
|
||||
<string>1</string>
|
||||
</map>
|
||||
<key>minizip-ng</key>
|
||||
<map>
|
||||
<key>platforms</key>
|
||||
|
|
@ -2318,6 +2374,42 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>name</key>
|
||||
<string>threejs</string>
|
||||
</map>
|
||||
<key>tinygltf</key>
|
||||
<map>
|
||||
<key>canonical_repo</key>
|
||||
<string>https://bitbucket.org/lindenlab/3p-tinygltf</string>
|
||||
<key>copyright</key>
|
||||
<string>// Copyright (c) 2015 - Present Syoyo Fujita, Aurélien Chatelain and many contributors.</string>
|
||||
<key>description</key>
|
||||
<string>tinygltf import library</string>
|
||||
<key>license</key>
|
||||
<string>MIT</string>
|
||||
<key>license_file</key>
|
||||
<string>LICENSES/tinygltf_license.txt</string>
|
||||
<key>name</key>
|
||||
<string>tinygltf</string>
|
||||
<key>platforms</key>
|
||||
<map>
|
||||
<key>common</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>4dad1c0948141e1667c01a3ee755e4dc</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/105849/926137/tinygltf-v2.5.0-common-575729.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>common</string>
|
||||
</map>
|
||||
</map>
|
||||
<key>source</key>
|
||||
<string>https://bitbucket.org/lindenlab/3p-tinygltf</string>
|
||||
<key>source_type</key>
|
||||
<string>git</string>
|
||||
<key>version</key>
|
||||
<string>v2.5.0</string>
|
||||
</map>
|
||||
<key>tracy</key>
|
||||
<map>
|
||||
<key>platforms</key>
|
||||
|
|
@ -2327,11 +2419,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>b84ccb1606b3fc5b216d0123a23a4922e02b6bd8</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<string>9b6e1a1f4b0969d38a1ca8ee00aeb548</string>
|
||||
<key>url</key>
|
||||
<string>https://github.com/secondlife/3p-tracy/releases/download/v0.8.1-235e98f/tracy-v0.8.1.235e98f-darwin64-235e98f.tar.zst</string>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/110584/960613/tracy-v0.8.1.578241-darwin64-578241.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
|
|
@ -2341,11 +2431,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>54f126b85f179362cf0b6024e3cd621b53d68703</string>
|
||||
<string>05b72ae5d733aed7d3bf142287601cc6</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<string>md5</string>
|
||||
<key>url</key>
|
||||
<string>https://github.com/secondlife/3p-tracy/releases/download/v0.8.1-235e98f/tracy-v0.8.1.235e98f-windows64-235e98f.tar.zst</string>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/110586/960637/tracy-v0.8.1.578241-windows64-578241.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
|
|
@ -2369,6 +2459,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>https://bitbucket.org/lindenlab/3p-tracy</string>
|
||||
<key>source_type</key>
|
||||
<string>git</string>
|
||||
<key>version</key>
|
||||
<string>v0.8.1.578241</string>
|
||||
</map>
|
||||
<key>tut</key>
|
||||
<map>
|
||||
|
|
@ -2471,11 +2563,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>d8bc8720846cfa31e23e7e1008e32ba6ad4a8322</string>
|
||||
<string>eb1316584188dafb591f80b46b357c737f90d1a7</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<key>url</key>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.cc7ea1e/viewer_manager-3.0.cc7ea1e-darwin64-cc7ea1e.tar.zst</string>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0-08bf5ee/viewer_manager-3.0-08bf5ee-darwin64-08bf5ee.tar.zst</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
|
|
@ -2485,11 +2577,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>228fae4ee0ce12b9d1d1b0a8ebb0bdf58ee521eb</string>
|
||||
<string>f4677b0ebd9880f29c118af51ada50883dd0a1e4</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<key>url</key>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.cc7ea1e/viewer_manager-3.0.cc7ea1e-linux64-cc7ea1e.tar.zst</string>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0-08bf5ee/viewer_manager-3.0-08bf5ee-linux64-08bf5ee.tar.zst</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>linux64</string>
|
||||
|
|
@ -2499,11 +2591,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>ca6999b64d96d45952fe872b381db9b2abc0248c</string>
|
||||
<string>7426c5a1d7eb231b476625637a1f2daba0a6bc55</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<key>url</key>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0.cc7ea1e/viewer_manager-3.0.cc7ea1e-windows64-cc7ea1e.tar.zst</string>
|
||||
<string>https://github.com/secondlife/viewer-manager/releases/download/v3.0-08bf5ee/viewer_manager-3.0-08bf5ee-windows64-08bf5ee.tar.zst</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
|
|
@ -2516,7 +2608,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>copyright</key>
|
||||
<string>Copyright (c) 2000-2012, Linden Research, Inc.</string>
|
||||
<key>version</key>
|
||||
<string>3.0.cc7ea1e</string>
|
||||
<string>3.0-08bf5ee</string>
|
||||
<key>name</key>
|
||||
<string>viewer-manager</string>
|
||||
<key>description</key>
|
||||
|
|
@ -2630,6 +2722,62 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>description</key>
|
||||
<string>XMLRPC Library</string>
|
||||
</map>
|
||||
<key>vulkan_gltf</key>
|
||||
<map>
|
||||
<key>canonical_repo</key>
|
||||
<string>https://bitbucket.org/lindenlab/3p-vulkan-gltf-pbr</string>
|
||||
<key>copyright</key>
|
||||
<string>Copyright (c) 2018 Sascha Willems</string>
|
||||
<key>description</key>
|
||||
<string>Vulkan GLTF Sample Implementation</string>
|
||||
<key>license</key>
|
||||
<string>Copyright (c) 2018 Sascha Willems</string>
|
||||
<key>license_file</key>
|
||||
<string>LICENSES/vulkan_gltf.txt</string>
|
||||
<key>name</key>
|
||||
<string>vulkan_gltf</string>
|
||||
<key>platforms</key>
|
||||
<map>
|
||||
<key>darwin64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>8cff2060843db3db788511ee34a8e8cc</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/101316/891509/vulkan_gltf-1-darwin64-572743.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
</map>
|
||||
<key>windows</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>58eea384be49ba756ce9c5e66669540b</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/101318/891520/vulkan_gltf-1-windows-572743.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
</map>
|
||||
<key>windows64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>79b6a11622c2f83cfc2b7cd1fafb867b</string>
|
||||
<key>url</key>
|
||||
<string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/101319/891521/vulkan_gltf-1-windows64-572743.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
</map>
|
||||
</map>
|
||||
<key>version</key>
|
||||
<string>1</string>
|
||||
</map>
|
||||
<key>xxhash</key>
|
||||
<map>
|
||||
<key>platforms</key>
|
||||
|
|
@ -2785,7 +2933,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string>
|
||||
<string>-DROOT_PROJECT_NAME:STRING=SecondLife</string>
|
||||
<string>-DINSTALL_PROPRIETARY=TRUE</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -2805,11 +2953,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string>
|
||||
<string>-DROOT_PROJECT_NAME:STRING=SecondLife</string>
|
||||
<string>-DINSTALL_PROPRIETARY=FALSE</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>../indra</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>RelWithDebInfoOS</string>
|
||||
|
|
@ -2826,7 +2974,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string>
|
||||
<string>-DROOT_PROJECT_NAME:STRING=SecondLife</string>
|
||||
<string>-DINSTALL_PROPRIETARY=TRUE</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -2846,11 +2994,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>-DADDRESS_SIZE:STRING=$AUTOBUILD_ADDRSIZE</string>
|
||||
<string>-DROOT_PROJECT_NAME:STRING=SecondLife</string>
|
||||
<string>-DINSTALL_PROPRIETARY=FALSE</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>../indra</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>ReleaseOS</string>
|
||||
|
|
@ -2871,11 +3019,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<array>
|
||||
<string>-G</string>
|
||||
<string>Xcode</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>../indra</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -2887,7 +3035,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>RelWithDebInfo</string>
|
||||
<string>-project</string>
|
||||
<string>SecondLife.xcodeproj</string>
|
||||
</array>
|
||||
<string>-parallelizeTargets</string>
|
||||
</array>
|
||||
</map>
|
||||
<key>default</key>
|
||||
<string>True</string>
|
||||
|
|
@ -2902,7 +3051,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<array>
|
||||
<string>-G</string>
|
||||
<string>Xcode</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -2914,7 +3063,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>RelWithDebInfo</string>
|
||||
<string>-project</string>
|
||||
<string>SecondLife.xcodeproj</string>
|
||||
</array>
|
||||
<string>-parallelizeTargets</string>
|
||||
</array>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>RelWithDebInfoOS</string>
|
||||
|
|
@ -2927,11 +3077,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<array>
|
||||
<string>-G</string>
|
||||
<string>Xcode</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>../indra</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -2943,7 +3093,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>Release</string>
|
||||
<string>-project</string>
|
||||
<string>SecondLife.xcodeproj</string>
|
||||
</array>
|
||||
<string>-parallelizeTargets</string>
|
||||
</array>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>Release</string>
|
||||
|
|
@ -2956,7 +3107,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<array>
|
||||
<string>-G</string>
|
||||
<string>Xcode</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -2968,7 +3119,8 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>Release</string>
|
||||
<string>-project</string>
|
||||
<string>SecondLife.xcodeproj</string>
|
||||
</array>
|
||||
<string>-parallelizeTargets</string>
|
||||
</array>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>ReleaseOS</string>
|
||||
|
|
@ -2992,11 +3144,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>-G</string>
|
||||
<string>Ninja</string>
|
||||
<string>-DLL_TESTS=Off</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>../indra</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -3017,7 +3169,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>-G</string>
|
||||
<string>Ninja</string>
|
||||
<string>-DLL_TESTS=Off</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -3055,11 +3207,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>${AUTOBUILD_WIN_CMAKE_GEN|NOTWIN}</string>
|
||||
<string>-A</string>
|
||||
<string>${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>..\indra</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -3069,11 +3221,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<array>
|
||||
<string>/build</string>
|
||||
<string>RelWithDebInfo|${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>SecondLife.sln</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>default</key>
|
||||
<string>True</string>
|
||||
|
|
@ -3093,11 +3245,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>-DINSTALL_PROPRIETARY=FALSE</string>
|
||||
<string>-DUSE_KDU=FALSE</string>
|
||||
<string>-DUSE_OPENAL:BOOL=ON</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>..\indra</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -3111,11 +3263,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>/p:useenv=true</string>
|
||||
<string>/verbosity:minimal</string>
|
||||
<string>/p:VCBuildAdditionalOptions= /incremental</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>SecondLife.sln</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>RelWithDebInfoOS</string>
|
||||
|
|
@ -3130,11 +3282,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>${AUTOBUILD_WIN_CMAKE_GEN|NOTWIN}</string>
|
||||
<string>-A</string>
|
||||
<string>${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>..\indra</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -3144,11 +3296,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<array>
|
||||
<string>/build</string>
|
||||
<string>Release|${AUTOBUILD_WIN_VSPLATFORM|NOTWIN}</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>SecondLife.sln</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>Release</string>
|
||||
|
|
@ -3167,11 +3319,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>-DINSTALL_PROPRIETARY=FALSE</string>
|
||||
<string>-DUSE_KDU=FALSE</string>
|
||||
<string>-DUSE_OPENAL:BOOL=ON</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>..\indra</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>build</key>
|
||||
<map>
|
||||
|
|
@ -3185,11 +3337,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>/p:useenv=true</string>
|
||||
<string>/verbosity:minimal</string>
|
||||
<string>/p:VCBuildAdditionalOptions= /incremental</string>
|
||||
</array>
|
||||
</array>
|
||||
<key>arguments</key>
|
||||
<array>
|
||||
<string>SecondLife.sln</string>
|
||||
</array>
|
||||
</array>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>ReleaseOS</string>
|
||||
|
|
@ -3217,4 +3369,4 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<string>Second Life Viewer</string>
|
||||
</map>
|
||||
</map>
|
||||
</llsd>
|
||||
</llsd>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ Agathos Frascati
|
|||
CT-317
|
||||
CT-352
|
||||
Ai Austin
|
||||
SL-19399
|
||||
Aiko Ying
|
||||
Aimee Trescothick
|
||||
SNOW-227
|
||||
|
|
@ -239,6 +240,7 @@ Ansariel Hiller
|
|||
SL-18432
|
||||
SL-19140
|
||||
SL-4126
|
||||
SL-20224
|
||||
Aralara Rajal
|
||||
Arare Chantilly
|
||||
CHUIBUG-191
|
||||
|
|
@ -290,6 +292,7 @@ Beq Janus
|
|||
SL-18637
|
||||
SL-19317
|
||||
SL-19660
|
||||
SL-20610
|
||||
Beth Walcher
|
||||
Bezilon Kasei
|
||||
Biancaluce Robbiani
|
||||
|
|
@ -595,6 +598,7 @@ Henri Beauchamp
|
|||
SL-15175
|
||||
SL-19110
|
||||
SL-19159
|
||||
[NO JIRA] (fullbright HUD alpha fix)
|
||||
herina Bode
|
||||
Hikkoshi Sakai
|
||||
VWR-429
|
||||
|
|
@ -927,6 +931,8 @@ LSL Scientist
|
|||
Lamorna Proctor
|
||||
Lares Carter
|
||||
Larry Pixel
|
||||
Lars Næsbye Christensen
|
||||
SL-20054
|
||||
Laurent Bechir
|
||||
Leal Choche
|
||||
Lenae Munz
|
||||
|
|
@ -1418,6 +1424,7 @@ Sovereign Engineer
|
|||
SL-18497
|
||||
SL-18525
|
||||
SL-18534
|
||||
SL-19690
|
||||
SL-19336
|
||||
SpacedOut Frye
|
||||
VWR-34
|
||||
|
|
|
|||
|
|
@ -29,15 +29,6 @@ else()
|
|||
set( USE_AUTOBUILD_3P ON )
|
||||
endif()
|
||||
|
||||
# The viewer code base can now be successfully compiled with -std=c++14. But
|
||||
# turning that on in the generic viewer-build-variables/variables file would
|
||||
# potentially require tweaking each of our ~50 third-party library builds.
|
||||
# Until we decide to set -std=c++14 in viewer-build-variables/variables, set
|
||||
# it locally here: we want to at least prevent inadvertently reintroducing
|
||||
# viewer code that would fail with C++14.
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
include(Variables)
|
||||
include(BuildVersion)
|
||||
|
||||
|
|
|
|||
|
|
@ -187,3 +187,4 @@ if (LINUX OR DARWIN)
|
|||
endif (LINUX OR DARWIN)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ set(cmake_SOURCE_FILES
|
|||
VisualLeakDetector.cmake
|
||||
LibVLCPlugin.cmake
|
||||
XmlRpcEpi.cmake
|
||||
xxHash.cmake
|
||||
ZLIBNG.cmake
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,8 @@ if(WINDOWS)
|
|||
endif (USE_BUGSPLAT)
|
||||
|
||||
if (TARGET ll::fmodstudio)
|
||||
set(debug_files ${debug_files} fmodL.dll)
|
||||
# fmodL is included for logging, only one should be picked by manifest
|
||||
set(release_files ${release_files} fmodL.dll)
|
||||
set(release_files ${release_files} fmod.dll)
|
||||
endif ()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
include_guard()
|
||||
|
||||
# FMODSTUDIO can be set when launching the make using the argument -DFMODSTUDIO:BOOL=ON
|
||||
# FMODSTUDIO can be set when launching the make using the argument -DUSE_FMODSTUDIO:BOOL=ON
|
||||
# When building using proprietary binaries though (i.e. having access to LL private servers),
|
||||
# we always build with FMODSTUDIO.
|
||||
if (INSTALL_PROPRIETARY)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ include(Prebuilt)
|
|||
include(GLH)
|
||||
|
||||
add_library( ll::glext INTERFACE IMPORTED )
|
||||
if (WINDOWS OR LINUX)
|
||||
use_system_binary(glext)
|
||||
use_prebuilt_binary(glext)
|
||||
endif (WINDOWS OR LINUX)
|
||||
use_system_binary(glext)
|
||||
use_prebuilt_binary(glext)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,13 @@ MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources)
|
|||
message("LL_ADD_PROJECT_UNIT_TESTS ${name}_test_additional_CFLAGS ${${name}_test_additional_CFLAGS}")
|
||||
endif()
|
||||
|
||||
if (DARWIN)
|
||||
# test binaries always need to be signed for local development
|
||||
set_target_properties(PROJECT_${project}_TEST_${name}
|
||||
PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-")
|
||||
endif ()
|
||||
|
||||
#
|
||||
# Setup test targets
|
||||
#
|
||||
|
|
@ -221,6 +228,13 @@ FUNCTION(LL_ADD_INTEGRATION_TEST
|
|||
)
|
||||
endif ()
|
||||
|
||||
if (DARWIN)
|
||||
# test binaries always need to be signed for local development
|
||||
set_target_properties(INTEGRATION_TEST_${testname}
|
||||
PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-")
|
||||
endif ()
|
||||
|
||||
# Add link deps to the executable
|
||||
if(TEST_DEBUG)
|
||||
message(STATUS "TARGET_LINK_LIBRARIES(INTEGRATION_TEST_${testname} ${libraries})")
|
||||
|
|
|
|||
|
|
@ -1,2 +1,5 @@
|
|||
# -*- cmake -*-
|
||||
|
||||
include(Variables)
|
||||
include(Mikktspace)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
# -*- cmake -*-
|
||||
include(Prebuilt)
|
||||
|
||||
if (NOT USESYSTEMLIBS)
|
||||
use_prebuilt_binary(mikktspace)
|
||||
endif (NOT USESYSTEMLIBS)
|
||||
|
|
@ -13,7 +13,7 @@ elseif (WINDOWS)
|
|||
foreach(hive HKEY_CURRENT_USER HKEY_LOCAL_MACHINE)
|
||||
# prefer more recent Python versions to older ones, if multiple versions
|
||||
# are installed
|
||||
foreach(pyver 3.11 3.10 3.9 3.8 3.7)
|
||||
foreach(pyver 3.12 3.11 3.10 3.9 3.8 3.7)
|
||||
list(APPEND regpaths "[${hive}\\SOFTWARE\\Python\\PythonCore\\${pyver}\\InstallPath]")
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
|
@ -40,7 +40,7 @@ elseif (WINDOWS)
|
|||
${regpaths}
|
||||
${pymaybe}
|
||||
)
|
||||
include(FindPythonInterp)
|
||||
find_package(Python3 COMPONENTS Interpreter)
|
||||
else()
|
||||
find_program(python python3)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# -*- cmake -*-
|
||||
include(Prebuilt)
|
||||
|
||||
use_prebuilt_binary(tinygltf)
|
||||
|
||||
set(TINYGLTF_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/include/tinygltf)
|
||||
|
||||
|
|
@ -11,8 +11,9 @@ if (USE_TRACY)
|
|||
use_prebuilt_binary(tracy)
|
||||
|
||||
target_include_directories( ll::tracy SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include/tracy)
|
||||
target_link_libraries( ll::tracy INTERFACE TracyClient )
|
||||
|
||||
# See: indra/llcommon/llprofiler.h
|
||||
target_compile_definitions(ll::tracy INTERFACE LL_PROFILER_CONFIGURATION=3 )
|
||||
# See: indra/llcommon/llprofiler.h
|
||||
add_compile_definitions(LL_PROFILER_CONFIGURATION=3)
|
||||
endif (USE_TRACY)
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ set(LIBS_OPEN_PREFIX)
|
|||
set(SCRIPTS_PREFIX ../scripts)
|
||||
set(VIEWER_PREFIX)
|
||||
set(INTEGRATION_TESTS_PREFIX)
|
||||
set(LL_TESTS ON CACHE BOOL "Build and run unit and integration tests (disable for build timing runs to reduce variation")
|
||||
set(LL_TESTS OFF CACHE BOOL "Build and run unit and integration tests (disable for build timing runs to reduce variation")
|
||||
set(INCREMENTAL_LINK OFF CACHE BOOL "Use incremental linking on win32 builds (enable for faster links on some machines)")
|
||||
set(ENABLE_MEDIA_PLUGINS ON CACHE BOOL "Turn off building media plugins if they are imported by third-party library mechanism")
|
||||
set(VIEWER_SYMBOL_FILE "" CACHE STRING "Name of tarball into which to place symbol files")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
# -*- cmake -*-
|
||||
include(Prebuilt)
|
||||
|
||||
use_prebuilt_binary(vulkan_gltf)
|
||||
|
||||
|
|
@ -1050,7 +1050,6 @@ BOOL LLAvatarAppearance::loadSkeletonNode ()
|
|||
mRoot->addChild(mMeshLOD[MESH_ID_UPPER_BODY]);
|
||||
mRoot->addChild(mMeshLOD[MESH_ID_LOWER_BODY]);
|
||||
mRoot->addChild(mMeshLOD[MESH_ID_SKIRT]);
|
||||
mRoot->addChild(mMeshLOD[MESH_ID_HEAD]);
|
||||
|
||||
LLAvatarJoint *skull = (LLAvatarJoint*)mRoot->findJoint("mSkull");
|
||||
if (skull)
|
||||
|
|
|
|||
|
|
@ -377,7 +377,6 @@ BOOL LLTexLayerSet::render( S32 x, S32 y, S32 width, S32 height, LLRenderTarget*
|
|||
// clear buffer area to ensure we don't pick up UI elements
|
||||
{
|
||||
gGL.flush();
|
||||
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
||||
gAlphaMaskProgram.setMinimumAlpha(0.0f);
|
||||
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
||||
gGL.color4f( 0.f, 0.f, 0.f, 1.f );
|
||||
|
|
@ -410,7 +409,6 @@ BOOL LLTexLayerSet::render( S32 x, S32 y, S32 width, S32 height, LLRenderTarget*
|
|||
gGL.flush();
|
||||
|
||||
gGL.setSceneBlendType(LLRender::BT_REPLACE);
|
||||
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
||||
gAlphaMaskProgram.setMinimumAlpha(0.f);
|
||||
|
||||
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
||||
|
|
@ -500,7 +498,6 @@ void LLTexLayerSet::renderAlphaMaskTextures(S32 x, S32 y, S32 width, S32 height,
|
|||
{
|
||||
// Set the alpha channel to one (clean up after previous blending)
|
||||
gGL.flush();
|
||||
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
||||
gAlphaMaskProgram.setMinimumAlpha(0.f);
|
||||
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
||||
gGL.color4f( 0.f, 0.f, 0.f, 1.f );
|
||||
|
|
@ -1025,7 +1022,6 @@ void LLTexLayer::calculateTexLayerColor(const param_color_list_t ¶m_list, LL
|
|||
|
||||
BOOL LLTexLayer::render(S32 x, S32 y, S32 width, S32 height, LLRenderTarget* bound_target)
|
||||
{
|
||||
LLGLEnable color_mat(GL_COLOR_MATERIAL);
|
||||
// *TODO: Is this correct?
|
||||
//gPipeline.disableLights();
|
||||
stop_glerror();
|
||||
|
|
@ -1112,7 +1108,6 @@ BOOL LLTexLayer::render(S32 x, S32 y, S32 width, S32 height, LLRenderTarget* bou
|
|||
if( tex )
|
||||
{
|
||||
bool no_alpha_test = getInfo()->mWriteAllChannels;
|
||||
LLGLDisable alpha_test(no_alpha_test ? GL_ALPHA_TEST : 0);
|
||||
if (no_alpha_test)
|
||||
{
|
||||
gAlphaMaskProgram.setMinimumAlpha(0.f);
|
||||
|
|
@ -1162,7 +1157,6 @@ BOOL LLTexLayer::render(S32 x, S32 y, S32 width, S32 height, LLRenderTarget* bou
|
|||
getInfo()->mStaticImageFileName.empty() &&
|
||||
color_specified )
|
||||
{
|
||||
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
||||
gAlphaMaskProgram.setMinimumAlpha(0.000f);
|
||||
|
||||
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
||||
|
|
@ -1260,7 +1254,6 @@ BOOL LLTexLayer::blendAlphaTexture(S32 x, S32 y, S32 width, S32 height)
|
|||
LLGLTexture* tex = LLTexLayerStaticImageList::getInstance()->getTexture( getInfo()->mStaticImageFileName, getInfo()->mStaticImageIsMask );
|
||||
if( tex )
|
||||
{
|
||||
LLGLSNoAlphaTest gls_no_alpha_test;
|
||||
gAlphaMaskProgram.setMinimumAlpha(0.f);
|
||||
gGL.getTexUnit(0)->bind(tex, TRUE);
|
||||
gl_rect_2d_simple_tex( width, height );
|
||||
|
|
@ -1279,7 +1272,6 @@ BOOL LLTexLayer::blendAlphaTexture(S32 x, S32 y, S32 width, S32 height)
|
|||
LLGLTexture* tex = mLocalTextureObject->getImage();
|
||||
if (tex)
|
||||
{
|
||||
LLGLSNoAlphaTest gls_no_alpha_test;
|
||||
gAlphaMaskProgram.setMinimumAlpha(0.f);
|
||||
gGL.getTexUnit(0)->bind(tex);
|
||||
gl_rect_2d_simple_tex( width, height );
|
||||
|
|
@ -1316,7 +1308,6 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
|||
// Note: if the first param is a mulitply, multiply against the current buffer's alpha
|
||||
if( !first_param || !first_param->getMultiplyBlend() )
|
||||
{
|
||||
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
||||
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
||||
|
||||
// Clear the alpha
|
||||
|
|
@ -1328,7 +1319,6 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
|||
}
|
||||
|
||||
// Accumulate alphas
|
||||
LLGLSNoAlphaTest gls_no_alpha_test;
|
||||
gGL.color4f( 1.f, 1.f, 1.f, 1.f );
|
||||
for (LLTexLayerParamAlpha* param : mParamAlphaList)
|
||||
{
|
||||
|
|
@ -1350,7 +1340,6 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
|||
LLGLTexture* tex = mLocalTextureObject->getImage();
|
||||
if( tex && (tex->getComponents() == 4) )
|
||||
{
|
||||
LLGLSNoAlphaTest gls_no_alpha_test;
|
||||
LLTexUnit::eTextureAddressMode old_mode = tex->getAddressMode();
|
||||
|
||||
gGL.getTexUnit(0)->bind(tex, TRUE);
|
||||
|
|
@ -1370,7 +1359,6 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
|||
{
|
||||
if( (tex->getComponents() == 4) || (tex->getComponents() == 1) )
|
||||
{
|
||||
LLGLSNoAlphaTest gls_no_alpha_test;
|
||||
gGL.getTexUnit(0)->bind(tex, TRUE);
|
||||
gl_rect_2d_simple_tex( width, height );
|
||||
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
||||
|
|
@ -1387,7 +1375,6 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
|||
// Note: we're still using gGL.blendFunc( GL_DST_ALPHA, GL_ZERO );
|
||||
if ( !is_approx_equal(layer_color.mV[VW], 1.f) )
|
||||
{
|
||||
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
||||
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
||||
gGL.color4fv(layer_color.mV);
|
||||
gl_rect_2d_simple( width, height );
|
||||
|
|
@ -1472,7 +1459,14 @@ void LLTexLayer::renderMorphMasks(S32 x, S32 y, S32 width, S32 height, const LLC
|
|||
}
|
||||
else
|
||||
{ // platforms with working drivers...
|
||||
glReadPixels(x, y, width, height, GL_ALPHA, GL_UNSIGNED_BYTE, alpha_data);
|
||||
// We just want GL_ALPHA, but that isn't supported in OGL core profile 4.
|
||||
static const size_t TEMP_BYTES_PER_PIXEL = 4;
|
||||
U8* temp_data = (U8*)ll_aligned_malloc_32(mem_size * TEMP_BYTES_PER_PIXEL);
|
||||
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, temp_data);
|
||||
for (size_t pixel = 0; pixel < pixels; pixel++) {
|
||||
alpha_data[pixel] = temp_data[(pixel * TEMP_BYTES_PER_PIXEL) + 3];
|
||||
}
|
||||
ll_aligned_free_32(temp_data);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ LLTexLayerParamAlpha::LLTexLayerParamAlpha(const LLTexLayerParamAlpha& pOther)
|
|||
mCachedProcessedTexture(pOther.mCachedProcessedTexture),
|
||||
mStaticImageTGA(pOther.mStaticImageTGA),
|
||||
mStaticImageRaw(pOther.mStaticImageRaw),
|
||||
mNeedsCreateTexture(pOther.mNeedsCreateTexture),
|
||||
mNeedsCreateTexture(pOther.mNeedsCreateTexture.load()),
|
||||
mStaticImageInvalid(pOther.mStaticImageInvalid),
|
||||
mAvgDistortionVec(pOther.mAvgDistortionVec),
|
||||
mCachedEffectiveWeight(pOther.mCachedEffectiveWeight)
|
||||
|
|
@ -344,7 +344,6 @@ BOOL LLTexLayerParamAlpha::render(S32 x, S32 y, S32 width, S32 height)
|
|||
mCachedProcessedTexture->setAddressMode(LLTexUnit::TAM_CLAMP);
|
||||
}
|
||||
|
||||
LLGLSNoAlphaTest gls_no_alpha_test;
|
||||
gGL.getTexUnit(0)->bind(mCachedProcessedTexture);
|
||||
gl_rect_2d_simple_tex(width, height);
|
||||
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
||||
|
|
@ -361,7 +360,6 @@ BOOL LLTexLayerParamAlpha::render(S32 x, S32 y, S32 width, S32 height)
|
|||
}
|
||||
else
|
||||
{
|
||||
LLGLDisable no_alpha(GL_ALPHA_TEST);
|
||||
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
|
||||
gGL.color4f(0.f, 0.f, 0.f, effective_weight);
|
||||
gl_rect_2d_simple(width, height);
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ private:
|
|||
LLPointer<LLGLTexture> mCachedProcessedTexture;
|
||||
LLPointer<LLImageTGA> mStaticImageTGA;
|
||||
LLPointer<LLImageRaw> mStaticImageRaw;
|
||||
BOOL mNeedsCreateTexture;
|
||||
std::atomic<BOOL> mNeedsCreateTexture;
|
||||
BOOL mStaticImageInvalid;
|
||||
LL_ALIGN_16(LLVector4a mAvgDistortionVec);
|
||||
F32 mCachedEffectiveWeight;
|
||||
|
|
|
|||
|
|
@ -607,40 +607,37 @@ void LLAudioDecodeMgr::Impl::startMoreDecodes()
|
|||
|
||||
// Kick off a decode
|
||||
mDecodes[decode_id] = LLPointer<LLVorbisDecodeState>(NULL);
|
||||
try
|
||||
{
|
||||
main_queue->postTo(
|
||||
general_queue,
|
||||
[decode_id]() // Work done on general queue
|
||||
bool posted = main_queue->postTo(
|
||||
general_queue,
|
||||
[decode_id]() // Work done on general queue
|
||||
{
|
||||
LLPointer<LLVorbisDecodeState> decode_state = beginDecodingAndWritingAudio(decode_id);
|
||||
|
||||
if (!decode_state)
|
||||
{
|
||||
LLPointer<LLVorbisDecodeState> decode_state = beginDecodingAndWritingAudio(decode_id);
|
||||
|
||||
if (!decode_state)
|
||||
{
|
||||
// Audio decode has errored
|
||||
return decode_state;
|
||||
}
|
||||
|
||||
// Disk write of decoded audio is now in progress off-thread
|
||||
// Audio decode has errored
|
||||
return decode_state;
|
||||
},
|
||||
[decode_id, this](LLPointer<LLVorbisDecodeState> decode_state) // Callback to main thread
|
||||
mutable {
|
||||
if (!gAudiop)
|
||||
{
|
||||
// There is no LLAudioEngine anymore. This might happen if
|
||||
// an audio decode is enqueued just before shutdown.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, we can be certain that the pointer to "this"
|
||||
// is valid because the lifetime of "this" is dependent upon
|
||||
// the lifetime of gAudiop.
|
||||
// Disk write of decoded audio is now in progress off-thread
|
||||
return decode_state;
|
||||
},
|
||||
[decode_id, this](LLPointer<LLVorbisDecodeState> decode_state) // Callback to main thread
|
||||
mutable {
|
||||
if (!gAudiop)
|
||||
{
|
||||
// There is no LLAudioEngine anymore. This might happen if
|
||||
// an audio decode is enqueued just before shutdown.
|
||||
return;
|
||||
}
|
||||
|
||||
enqueueFinishAudio(decode_id, decode_state);
|
||||
});
|
||||
}
|
||||
catch (const LLThreadSafeQueueInterrupt&)
|
||||
// At this point, we can be certain that the pointer to "this"
|
||||
// is valid because the lifetime of "this" is dependent upon
|
||||
// the lifetime of gAudiop.
|
||||
|
||||
enqueueFinishAudio(decode_id, decode_state);
|
||||
});
|
||||
if (! posted)
|
||||
{
|
||||
// Shutdown
|
||||
// Consider making processQueue() do a cleanup instead
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ include(Tracy)
|
|||
|
||||
|
||||
set(llcommon_SOURCE_FILES
|
||||
apply.cpp
|
||||
commoncontrol.cpp
|
||||
indra_constants.cpp
|
||||
lazyeventapi.cpp
|
||||
llallocator.cpp
|
||||
llallocator_heap_profile.cpp
|
||||
llapp.cpp
|
||||
|
|
@ -114,11 +117,16 @@ set(llcommon_SOURCE_FILES
|
|||
set(llcommon_HEADER_FILES
|
||||
CMakeLists.txt
|
||||
|
||||
always_return.h
|
||||
apply.h
|
||||
chrono.h
|
||||
classic_callback.h
|
||||
commoncontrol.h
|
||||
ctype_workaround.h
|
||||
fix_macros.h
|
||||
function_types.h
|
||||
indra_constants.h
|
||||
lazyeventapi.h
|
||||
linden_common.h
|
||||
llalignedarray.h
|
||||
llallocator.h
|
||||
|
|
@ -172,6 +180,7 @@ set(llcommon_HEADER_FILES
|
|||
llinitdestroyclass.h
|
||||
llinitparam.h
|
||||
llinstancetracker.h
|
||||
llinstancetrackersubclass.h
|
||||
llkeybind.h
|
||||
llkeythrottle.h
|
||||
llleap.h
|
||||
|
|
@ -245,6 +254,7 @@ set(llcommon_HEADER_FILES
|
|||
stdtypes.h
|
||||
stringize.h
|
||||
threadpool.h
|
||||
threadpool_fwd.h
|
||||
threadsafeschedule.h
|
||||
timer.h
|
||||
tuple.h
|
||||
|
|
@ -288,9 +298,11 @@ if (LL_TESTS)
|
|||
|
||||
#set(TEST_DEBUG on)
|
||||
set(test_libs llcommon)
|
||||
LL_ADD_INTEGRATION_TEST(apply "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* @file always_return.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2023-01-20
|
||||
* @brief Call specified callable with arbitrary arguments, but always return
|
||||
* specified type.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
|
||||
* Copyright (c) 2023, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_ALWAYS_RETURN_H)
|
||||
#define LL_ALWAYS_RETURN_H
|
||||
|
||||
#include <type_traits> // std::enable_if, std::is_convertible
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
||||
#if __cpp_lib_is_invocable >= 201703L // C++17
|
||||
template <typename CALLABLE, typename... ARGS>
|
||||
using invoke_result = std::invoke_result<CALLABLE, ARGS...>;
|
||||
#else // C++14
|
||||
template <typename CALLABLE, typename... ARGS>
|
||||
using invoke_result = std::result_of<CALLABLE(ARGS...)>;
|
||||
#endif // C++14
|
||||
|
||||
/**
|
||||
* AlwaysReturn<T>()(some_function, some_args...) calls
|
||||
* some_function(some_args...). It is guaranteed to return a value of type
|
||||
* T, regardless of the return type of some_function(). If some_function()
|
||||
* returns a type convertible to T, it will convert and return that value.
|
||||
* Otherwise (notably if some_function() is void), AlwaysReturn returns
|
||||
* T().
|
||||
*
|
||||
* When some_function() returns a type not convertible to T, if
|
||||
* you want AlwaysReturn to return some T value other than
|
||||
* default-constructed T(), pass that value to AlwaysReturn's constructor.
|
||||
*/
|
||||
template <typename DESIRED>
|
||||
class AlwaysReturn
|
||||
{
|
||||
public:
|
||||
/// pass explicit default value if other than default-constructed type
|
||||
AlwaysReturn(const DESIRED& dft=DESIRED()): mDefault(dft) {}
|
||||
|
||||
// callable returns a type not convertible to DESIRED, return default
|
||||
template <typename CALLABLE, typename... ARGS,
|
||||
typename std::enable_if<
|
||||
! std::is_convertible<
|
||||
typename invoke_result<CALLABLE, ARGS...>::type,
|
||||
DESIRED
|
||||
>::value,
|
||||
bool
|
||||
>::type=true>
|
||||
DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
|
||||
{
|
||||
// discard whatever callable(args) returns
|
||||
std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...);
|
||||
return mDefault;
|
||||
}
|
||||
|
||||
// callable returns a type convertible to DESIRED
|
||||
template <typename CALLABLE, typename... ARGS,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<
|
||||
typename invoke_result<CALLABLE, ARGS...>::type,
|
||||
DESIRED
|
||||
>::value,
|
||||
bool
|
||||
>::type=true>
|
||||
DESIRED operator()(CALLABLE&& callable, ARGS&&... args)
|
||||
{
|
||||
return { std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...) };
|
||||
}
|
||||
|
||||
private:
|
||||
DESIRED mDefault;
|
||||
};
|
||||
|
||||
/**
|
||||
* always_return<T>(some_function, some_args...) calls
|
||||
* some_function(some_args...). It is guaranteed to return a value of type
|
||||
* T, regardless of the return type of some_function(). If some_function()
|
||||
* returns a type convertible to T, it will convert and return that value.
|
||||
* Otherwise (notably if some_function() is void), always_return() returns
|
||||
* T().
|
||||
*/
|
||||
template <typename DESIRED, typename CALLABLE, typename... ARGS>
|
||||
DESIRED always_return(CALLABLE&& callable, ARGS&&... args)
|
||||
{
|
||||
return AlwaysReturn<DESIRED>()(std::forward<CALLABLE>(callable),
|
||||
std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* make_always_return<T>(some_function) returns a callable which, when
|
||||
* called with appropriate some_function() arguments, always returns a
|
||||
* value of type T, regardless of the return type of some_function(). If
|
||||
* some_function() returns a type convertible to T, the returned callable
|
||||
* will convert and return that value. Otherwise (notably if
|
||||
* some_function() is void), the returned callable returns T().
|
||||
*
|
||||
* When some_function() returns a type not convertible to T, if
|
||||
* you want the returned callable to return some T value other than
|
||||
* default-constructed T(), pass that value to make_always_return() as its
|
||||
* optional second argument.
|
||||
*/
|
||||
template <typename DESIRED, typename CALLABLE>
|
||||
auto make_always_return(CALLABLE&& callable, const DESIRED& dft=DESIRED())
|
||||
{
|
||||
return
|
||||
[dft, callable = std::forward<CALLABLE>(callable)]
|
||||
(auto&&... args)
|
||||
{
|
||||
return AlwaysReturn<DESIRED>(dft)(callable,
|
||||
std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_ALWAYS_RETURN_H) */
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* @file apply.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-12-21
|
||||
* @brief Implementation for apply.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "apply.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "stringize.h"
|
||||
|
||||
void LL::apply_validate_size(size_t size, size_t arity)
|
||||
{
|
||||
if (size != arity)
|
||||
{
|
||||
LLTHROW(apply_error(stringize("LL::apply(func(", arity, " args), "
|
||||
"std::vector(", size, " elements))")));
|
||||
}
|
||||
}
|
||||
|
|
@ -12,8 +12,11 @@
|
|||
#if ! defined(LL_APPLY_H)
|
||||
#define LL_APPLY_H
|
||||
|
||||
#include "llexception.h"
|
||||
#include <boost/type_traits/function_traits.hpp>
|
||||
#include <functional> // std::mem_fn()
|
||||
#include <tuple>
|
||||
#include <type_traits> // std::is_member_pointer
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
|
@ -54,20 +57,67 @@ namespace LL
|
|||
}, \
|
||||
(ARGS))
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
/*****************************************************************************
|
||||
* invoke()
|
||||
*****************************************************************************/
|
||||
#if __cpp_lib_invoke >= 201411L
|
||||
|
||||
// C++17 implementation
|
||||
using std::apply;
|
||||
using std::invoke;
|
||||
|
||||
#else // no std::invoke
|
||||
|
||||
// Use invoke() to handle pointer-to-method:
|
||||
// derived from https://stackoverflow.com/a/38288251
|
||||
template<typename Fn, typename... Args,
|
||||
typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
|
||||
int>::type = 0 >
|
||||
auto invoke(Fn&& f, Args&&... args)
|
||||
{
|
||||
return std::mem_fn(std::forward<Fn>(f))(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename Fn, typename... Args,
|
||||
typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
|
||||
int>::type = 0 >
|
||||
auto invoke(Fn&& f, Args&&... args)
|
||||
{
|
||||
return std::forward<Fn>(f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
#endif // no std::invoke
|
||||
|
||||
/*****************************************************************************
|
||||
* apply(function, tuple); apply(function, array)
|
||||
*****************************************************************************/
|
||||
#if __cpp_lib_apply >= 201603L
|
||||
|
||||
// C++17 implementation
|
||||
// We don't just say 'using std::apply;' because that template is too general:
|
||||
// it also picks up the apply(function, vector) case, which we want to handle
|
||||
// below.
|
||||
template <typename CALLABLE, typename... ARGS>
|
||||
auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
|
||||
{
|
||||
return std::apply(std::forward<CALLABLE>(func), args);
|
||||
}
|
||||
|
||||
#else // C++14
|
||||
|
||||
// Derived from https://stackoverflow.com/a/20441189
|
||||
// and https://en.cppreference.com/w/cpp/utility/apply
|
||||
template <typename CALLABLE, typename TUPLE, std::size_t... I>
|
||||
auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence<I...>)
|
||||
template <typename CALLABLE, typename... ARGS, std::size_t... I>
|
||||
auto apply_impl(CALLABLE&& func, const std::tuple<ARGS...>& args, std::index_sequence<I...>)
|
||||
{
|
||||
// We accept const std::tuple& so a caller can construct an tuple on the
|
||||
// fly. But std::get<I>(const tuple) adds a const qualifier to everything
|
||||
// it extracts. Get a non-const ref to this tuple so we can extract
|
||||
// without the extraneous const.
|
||||
auto& non_const_args{ const_cast<std::tuple<ARGS...>&>(args) };
|
||||
|
||||
// call func(unpacked args)
|
||||
return std::forward<CALLABLE>(func)(std::move(std::get<I>(args))...);
|
||||
return invoke(std::forward<CALLABLE>(func),
|
||||
std::forward<ARGS>(std::get<I>(non_const_args))...);
|
||||
}
|
||||
|
||||
template <typename CALLABLE, typename... ARGS>
|
||||
|
|
@ -81,6 +131,8 @@ auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
|
|||
std::index_sequence_for<ARGS...>{});
|
||||
}
|
||||
|
||||
#endif // C++14
|
||||
|
||||
// per https://stackoverflow.com/a/57510428/5533635
|
||||
template <typename CALLABLE, typename T, size_t SIZE>
|
||||
auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
|
||||
|
|
@ -88,28 +140,92 @@ auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
|
|||
return apply(std::forward<CALLABLE>(func), std::tuple_cat(args));
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* bind_front()
|
||||
*****************************************************************************/
|
||||
// To invoke a non-static member function with a tuple, you need a callable
|
||||
// that binds your member function with an instance pointer or reference.
|
||||
// std::bind_front() is perfect: std::bind_front(&cls::method, instance).
|
||||
// Unfortunately bind_front() only enters the standard library in C++20.
|
||||
#if __cpp_lib_bind_front >= 201907L
|
||||
|
||||
// C++20 implementation
|
||||
using std::bind_front;
|
||||
|
||||
#else // no std::bind_front()
|
||||
|
||||
template<typename Fn, typename... Args,
|
||||
typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
|
||||
int>::type = 0 >
|
||||
auto bind_front(Fn&& f, Args&&... args)
|
||||
{
|
||||
// Don't use perfect forwarding for f or args: we must bind them for later.
|
||||
return [f, pfx_args=std::make_tuple(args...)]
|
||||
(auto&&... sfx_args)
|
||||
{
|
||||
// Use perfect forwarding for sfx_args because we use them as soon as
|
||||
// we receive them.
|
||||
return apply(
|
||||
f,
|
||||
std::tuple_cat(pfx_args,
|
||||
std::make_tuple(std::forward<decltype(sfx_args)>(sfx_args)...)));
|
||||
};
|
||||
}
|
||||
|
||||
template<typename Fn, typename... Args,
|
||||
typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
|
||||
int>::type = 0 >
|
||||
auto bind_front(Fn&& f, Args&&... args)
|
||||
{
|
||||
return bind_front(std::mem_fn(std::forward<Fn>(f)), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
#endif // C++20 with std::bind_front()
|
||||
|
||||
/*****************************************************************************
|
||||
* apply(function, std::vector)
|
||||
*****************************************************************************/
|
||||
// per https://stackoverflow.com/a/28411055/5533635
|
||||
template <typename CALLABLE, typename T, std::size_t... I>
|
||||
auto apply_impl(CALLABLE&& func, const std::vector<T>& args, std::index_sequence<I...>)
|
||||
{
|
||||
return apply_impl(std::forward<CALLABLE>(func),
|
||||
std::make_tuple(std::forward<T>(args[I])...),
|
||||
I...);
|
||||
return apply(std::forward<CALLABLE>(func),
|
||||
std::make_tuple(args[I]...));
|
||||
}
|
||||
|
||||
// this goes beyond C++17 std::apply()
|
||||
// produce suitable error if apply(func, vector) is the wrong size for func()
|
||||
void apply_validate_size(size_t size, size_t arity);
|
||||
|
||||
/// possible exception from apply() validation
|
||||
struct apply_error: public LLException
|
||||
{
|
||||
apply_error(const std::string& what): LLException(what) {}
|
||||
};
|
||||
|
||||
template <size_t ARITY, typename CALLABLE, typename T>
|
||||
auto apply_n(CALLABLE&& func, const std::vector<T>& args)
|
||||
{
|
||||
apply_validate_size(args.size(), ARITY);
|
||||
return apply_impl(std::forward<CALLABLE>(func),
|
||||
args,
|
||||
std::make_index_sequence<ARITY>());
|
||||
}
|
||||
|
||||
/**
|
||||
* apply(function, std::vector) goes beyond C++17 std::apply(). For this case
|
||||
* @a function @emph cannot be variadic: the compiler must know at compile
|
||||
* time how many arguments to pass. This isn't Python. (But see apply_n() to
|
||||
* pass a specific number of args to a variadic function.)
|
||||
*/
|
||||
template <typename CALLABLE, typename T>
|
||||
auto apply(CALLABLE&& func, const std::vector<T>& args)
|
||||
{
|
||||
// infer arity from the definition of func
|
||||
constexpr auto arity = boost::function_traits<CALLABLE>::arity;
|
||||
assert(args.size() == arity);
|
||||
return apply_impl(std::forward<CALLABLE>(func),
|
||||
args,
|
||||
std::make_index_sequence<arity>());
|
||||
// now that we have a compile-time arity, apply_n() works
|
||||
return apply_n<arity>(std::forward<CALLABLE>(func), args);
|
||||
}
|
||||
|
||||
#endif // C++14
|
||||
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_APPLY_H) */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
/**
|
||||
* @file commoncontrol.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-06-08
|
||||
* @brief Implementation for commoncontrol.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "commoncontrol.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llevents.h"
|
||||
#include "llsdutil.h"
|
||||
|
||||
LLSD LL::CommonControl::access(const LLSD& params)
|
||||
{
|
||||
// We can't actually introduce a link-time dependency on llxml, or on any
|
||||
// global LLControlGroup (*koff* gSavedSettings *koff*) but we can issue a
|
||||
// runtime query. If we're running as part of a viewer with
|
||||
// LLViewerControlListener, we can use that to interact with any
|
||||
// instantiated LLControGroup.
|
||||
LLSD response;
|
||||
{
|
||||
LLEventStream reply("reply");
|
||||
LLTempBoundListener connection = reply.listen("listener",
|
||||
[&response] (const LLSD& event)
|
||||
{
|
||||
response = event;
|
||||
return false;
|
||||
});
|
||||
LLSD rparams{ params };
|
||||
rparams["reply"] = reply.getName();
|
||||
LLEventPumps::instance().obtain("LLViewerControl").post(rparams);
|
||||
}
|
||||
// LLViewerControlListener responds immediately. If it's listening at all,
|
||||
// it will already have set response.
|
||||
if (! response.isDefined())
|
||||
{
|
||||
LLTHROW(NoListener("No LLViewerControl listener instantiated"));
|
||||
}
|
||||
LLSD error{ response["error"] };
|
||||
if (error.isDefined())
|
||||
{
|
||||
LLTHROW(ParamError(error));
|
||||
}
|
||||
response.erase("error");
|
||||
response.erase("reqid");
|
||||
return response;
|
||||
}
|
||||
|
||||
/// set control group.key to defined default value
|
||||
LLSD LL::CommonControl::set_default(const std::string& group, const std::string& key)
|
||||
{
|
||||
return access(llsd::map("op", "set",
|
||||
"group", group, "key", key))["value"];
|
||||
}
|
||||
|
||||
/// set control group.key to specified value
|
||||
LLSD LL::CommonControl::set(const std::string& group, const std::string& key, const LLSD& value)
|
||||
{
|
||||
return access(llsd::map("op", "set",
|
||||
"group", group, "key", key, "value", value))["value"];
|
||||
}
|
||||
|
||||
/// toggle boolean control group.key
|
||||
LLSD LL::CommonControl::toggle(const std::string& group, const std::string& key)
|
||||
{
|
||||
return access(llsd::map("op", "toggle",
|
||||
"group", group, "key", key))["value"];
|
||||
}
|
||||
|
||||
/// get the definition for control group.key, (! isDefined()) if bad
|
||||
/// ["name"], ["type"], ["value"], ["comment"]
|
||||
LLSD LL::CommonControl::get_def(const std::string& group, const std::string& key)
|
||||
{
|
||||
return access(llsd::map("op", "get",
|
||||
"group", group, "key", key));
|
||||
}
|
||||
|
||||
/// get the value of control group.key
|
||||
LLSD LL::CommonControl::get(const std::string& group, const std::string& key)
|
||||
{
|
||||
return access(llsd::map("op", "get",
|
||||
"group", group, "key", key))["value"];
|
||||
}
|
||||
|
||||
/// get defined groups
|
||||
std::vector<std::string> LL::CommonControl::get_groups()
|
||||
{
|
||||
auto groups{ access(llsd::map("op", "groups"))["groups"] };
|
||||
return { groups.beginArray(), groups.endArray() };
|
||||
}
|
||||
|
||||
/// get definitions for all variables in group
|
||||
LLSD LL::CommonControl::get_vars(const std::string& group)
|
||||
{
|
||||
return access(llsd::map("op", "vars", "group", group))["vars"];
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* @file commoncontrol.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-06-08
|
||||
* @brief Access LLViewerControl LLEventAPI, if process has one.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_COMMONCONTROL_H)
|
||||
#define LL_COMMONCONTROL_H
|
||||
|
||||
#include <vector>
|
||||
#include "llexception.h"
|
||||
#include "llsd.h"
|
||||
|
||||
namespace LL
|
||||
{
|
||||
class CommonControl
|
||||
{
|
||||
public:
|
||||
struct Error: public LLException
|
||||
{
|
||||
Error(const std::string& what): LLException(what) {}
|
||||
};
|
||||
|
||||
/// Exception thrown if there's no LLViewerControl LLEventAPI
|
||||
struct NoListener: public Error
|
||||
{
|
||||
NoListener(const std::string& what): Error(what) {}
|
||||
};
|
||||
|
||||
struct ParamError: public Error
|
||||
{
|
||||
ParamError(const std::string& what): Error(what) {}
|
||||
};
|
||||
|
||||
/// set control group.key to defined default value
|
||||
static
|
||||
LLSD set_default(const std::string& group, const std::string& key);
|
||||
|
||||
/// set control group.key to specified value
|
||||
static
|
||||
LLSD set(const std::string& group, const std::string& key, const LLSD& value);
|
||||
|
||||
/// toggle boolean control group.key
|
||||
static
|
||||
LLSD toggle(const std::string& group, const std::string& key);
|
||||
|
||||
/// get the definition for control group.key, (! isDefined()) if bad
|
||||
/// ["name"], ["type"], ["value"], ["comment"]
|
||||
static
|
||||
LLSD get_def(const std::string& group, const std::string& key);
|
||||
|
||||
/// get the value of control group.key
|
||||
static
|
||||
LLSD get(const std::string& group, const std::string& key);
|
||||
|
||||
/// get defined groups
|
||||
static
|
||||
std::vector<std::string> get_groups();
|
||||
|
||||
/// get definitions for all variables in group
|
||||
static
|
||||
LLSD get_vars(const std::string& group);
|
||||
|
||||
private:
|
||||
static
|
||||
LLSD access(const LLSD& params);
|
||||
};
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_COMMONCONTROL_H) */
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @file function_types.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2023-01-20
|
||||
* @brief Extend boost::function_types to examine boost::function and
|
||||
* std::function
|
||||
*
|
||||
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
|
||||
* Copyright (c) 2023, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_FUNCTION_TYPES_H)
|
||||
#define LL_FUNCTION_TYPES_H
|
||||
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/function_types/function_arity.hpp>
|
||||
#include <functional>
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
||||
template <typename F>
|
||||
struct function_arity_impl
|
||||
{
|
||||
static constexpr auto value = boost::function_types::function_arity<F>::value;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct function_arity_impl<std::function<F>>
|
||||
{
|
||||
static constexpr auto value = function_arity_impl<F>::value;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct function_arity_impl<boost::function<F>>
|
||||
{
|
||||
static constexpr auto value = function_arity_impl<F>::value;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct function_arity
|
||||
{
|
||||
static constexpr auto value = function_arity_impl<typename std::decay<F>::type>::value;
|
||||
};
|
||||
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_FUNCTION_TYPES_H) */
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* @file lazyeventapi.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-06-17
|
||||
* @brief Implementation for lazyeventapi.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "lazyeventapi.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
#include <algorithm> // std::find_if
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llevents.h"
|
||||
#include "llsdutil.h"
|
||||
|
||||
LL::LazyEventAPIBase::LazyEventAPIBase(
|
||||
const std::string& name, const std::string& desc, const std::string& field)
|
||||
{
|
||||
// populate embedded LazyEventAPIParams instance
|
||||
mParams.name = name;
|
||||
mParams.desc = desc;
|
||||
mParams.field = field;
|
||||
// mParams.init and mOperations are populated by subsequent add() calls.
|
||||
|
||||
// Our raison d'etre: register as an LLEventPumps::PumpFactory
|
||||
// so obtain() will notice any request for this name and call us.
|
||||
// Of course, our subclass constructor must finish running (making add()
|
||||
// calls) before mParams will be fully populated, but we expect that to
|
||||
// happen well before the first LLEventPumps::obtain(name) call.
|
||||
mRegistered = LLEventPumps::instance().registerPumpFactory(
|
||||
name,
|
||||
[this](const std::string& name){ return construct(name); });
|
||||
}
|
||||
|
||||
LL::LazyEventAPIBase::~LazyEventAPIBase()
|
||||
{
|
||||
// If our constructor's registerPumpFactory() call was unsuccessful, that
|
||||
// probably means somebody else claimed the name first. If that's the
|
||||
// case, do NOT unregister their name out from under them!
|
||||
// If this is a static instance being destroyed at process shutdown,
|
||||
// LLEventPumps will probably have been cleaned up already.
|
||||
if (mRegistered && ! LLEventPumps::wasDeleted())
|
||||
{
|
||||
// unregister the callback to this doomed instance
|
||||
LLEventPumps::instance().unregisterPumpFactory(mParams.name);
|
||||
}
|
||||
}
|
||||
|
||||
LLSD LL::LazyEventAPIBase::getMetadata(const std::string& name) const
|
||||
{
|
||||
// Since mOperations is a vector rather than a map, just search.
|
||||
auto found = std::find_if(mOperations.begin(), mOperations.end(),
|
||||
[&name](const auto& namedesc)
|
||||
{ return (namedesc.first == name); });
|
||||
if (found == mOperations.end())
|
||||
return {};
|
||||
|
||||
// LLEventDispatcher() supplements the returned metadata in different
|
||||
// ways, depending on metadata provided to the specific add() method.
|
||||
// Don't try to emulate all that. At some point we might consider more
|
||||
// closely unifying LLEventDispatcher machinery with LazyEventAPI, but for
|
||||
// now this will have to do.
|
||||
return llsd::map("name", found->first, "desc", found->second);
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
/**
|
||||
* @file lazyeventapi.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-06-16
|
||||
* @brief Declaring a static module-scope LazyEventAPI registers a specific
|
||||
* LLEventAPI for future on-demand instantiation.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LAZYEVENTAPI_H)
|
||||
#define LL_LAZYEVENTAPI_H
|
||||
|
||||
#include "apply.h"
|
||||
#include "lleventapi.h"
|
||||
#include "llinstancetracker.h"
|
||||
#include <boost/signals2/signal.hpp>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility> // std::pair
|
||||
#include <vector>
|
||||
|
||||
namespace LL
|
||||
{
|
||||
/**
|
||||
* Bundle params we want to pass to LLEventAPI's protected constructor. We
|
||||
* package them this way so a subclass constructor can simply forward an
|
||||
* opaque reference to the LLEventAPI constructor.
|
||||
*/
|
||||
// This is a class instead of a plain struct mostly so when we forward-
|
||||
// declare it we don't have to remember the distinction.
|
||||
class LazyEventAPIParams
|
||||
{
|
||||
public:
|
||||
// package the parameters used by the normal LLEventAPI constructor
|
||||
std::string name, desc, field;
|
||||
// bundle LLEventAPI::add() calls collected by LazyEventAPI::add(), so
|
||||
// the special LLEventAPI constructor we engage can "play back" those
|
||||
// add() calls
|
||||
boost::signals2::signal<void(LLEventAPI*)> init;
|
||||
};
|
||||
|
||||
/**
|
||||
* LazyEventAPIBase implements most of the functionality of LazyEventAPI
|
||||
* (q.v.), but we need the LazyEventAPI template subclass so we can accept
|
||||
* the specific LLEventAPI subclass type.
|
||||
*/
|
||||
// No LLInstanceTracker key: we don't need to find a specific instance,
|
||||
// LLLeapListener just needs to be able to enumerate all instances.
|
||||
class LazyEventAPIBase: public LLInstanceTracker<LazyEventAPIBase>
|
||||
{
|
||||
public:
|
||||
LazyEventAPIBase(const std::string& name, const std::string& desc,
|
||||
const std::string& field);
|
||||
virtual ~LazyEventAPIBase();
|
||||
|
||||
// Do not copy or move: once constructed, LazyEventAPIBase must stay
|
||||
// put: we bind its instance pointer into a callback.
|
||||
LazyEventAPIBase(const LazyEventAPIBase&) = delete;
|
||||
LazyEventAPIBase(LazyEventAPIBase&&) = delete;
|
||||
LazyEventAPIBase& operator=(const LazyEventAPIBase&) = delete;
|
||||
LazyEventAPIBase& operator=(LazyEventAPIBase&&) = delete;
|
||||
|
||||
// capture add() calls we want to play back on LLEventAPI construction
|
||||
template <typename... ARGS>
|
||||
void add(const std::string& name, const std::string& desc, ARGS&&... rest)
|
||||
{
|
||||
// capture the metadata separately
|
||||
mOperations.push_back(std::make_pair(name, desc));
|
||||
// Use connect_extended() so the lambda is passed its own
|
||||
// connection.
|
||||
|
||||
// apply() can't accept a template per se; it needs a particular
|
||||
// specialization. Specialize out here to work around a clang bug:
|
||||
// https://github.com/llvm/llvm-project/issues/41999
|
||||
auto func{ &LazyEventAPIBase::add_trampoline
|
||||
<const std::string&, const std::string&, ARGS...> };
|
||||
// We can't bind an unexpanded parameter pack into a lambda --
|
||||
// shame really. Instead, capture all our args as a std::tuple and
|
||||
// then, in the lambda, use apply() to pass to add_trampoline().
|
||||
auto args{ std::make_tuple(name, desc, std::forward<ARGS>(rest)...) };
|
||||
|
||||
mParams.init.connect_extended(
|
||||
[func, args]
|
||||
(const boost::signals2::connection& conn, LLEventAPI* instance)
|
||||
{
|
||||
// we only need this connection once
|
||||
conn.disconnect();
|
||||
// apply() expects a tuple specifying ALL the arguments,
|
||||
// so prepend instance.
|
||||
apply(func, std::tuple_cat(std::make_tuple(instance), args));
|
||||
});
|
||||
}
|
||||
|
||||
// The following queries mimic the LLEventAPI / LLEventDispatcher
|
||||
// query API.
|
||||
|
||||
// Get the string name of the subject LLEventAPI
|
||||
std::string getName() const { return mParams.name; }
|
||||
// Get the documentation string
|
||||
std::string getDesc() const { return mParams.desc; }
|
||||
// Retrieve the LLSD key we use for dispatching
|
||||
std::string getDispatchKey() const { return mParams.field; }
|
||||
|
||||
// operations
|
||||
using NameDesc = std::pair<std::string, std::string>;
|
||||
|
||||
private:
|
||||
// metadata that might be queried by LLLeapListener
|
||||
std::vector<NameDesc> mOperations;
|
||||
|
||||
public:
|
||||
using const_iterator = decltype(mOperations)::const_iterator;
|
||||
const_iterator begin() const { return mOperations.begin(); }
|
||||
const_iterator end() const { return mOperations.end(); }
|
||||
LLSD getMetadata(const std::string& name) const;
|
||||
|
||||
protected:
|
||||
// Params with which to instantiate the companion LLEventAPI subclass
|
||||
LazyEventAPIParams mParams;
|
||||
|
||||
private:
|
||||
// true if we successfully registered our LLEventAPI on construction
|
||||
bool mRegistered;
|
||||
|
||||
// actually instantiate the companion LLEventAPI subclass
|
||||
virtual LLEventPump* construct(const std::string& name) = 0;
|
||||
|
||||
// Passing an overloaded function to any function that accepts an
|
||||
// arbitrary callable is a PITB because you have to specify the
|
||||
// correct overload. What we want is for the compiler to select the
|
||||
// correct overload, based on the carefully-wrought enable_ifs in
|
||||
// LLEventDispatcher. This (one and only) add_trampoline() method
|
||||
// exists solely to pass to LL::apply(). Once add_trampoline() is
|
||||
// called with the expanded arguments, we hope the compiler will Do
|
||||
// The Right Thing in selecting the correct LLEventAPI::add()
|
||||
// overload.
|
||||
template <typename... ARGS>
|
||||
static
|
||||
void add_trampoline(LLEventAPI* instance, ARGS&&... args)
|
||||
{
|
||||
instance->add(std::forward<ARGS>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* LazyEventAPI provides a way to register a particular LLEventAPI to be
|
||||
* instantiated on demand, that is, when its name is passed to
|
||||
* LLEventPumps::obtain().
|
||||
*
|
||||
* Derive your listener from LLEventAPI as usual, with its various
|
||||
* operation methods, but code your constructor to accept
|
||||
* <tt>(const LL::LazyEventAPIParams& params)</tt>
|
||||
* and forward that reference to (the protected)
|
||||
* <tt>LLEventAPI(const LL::LazyEventAPIParams&)</tt> constructor.
|
||||
*
|
||||
* Then derive your listener registrar from
|
||||
* <tt>LazyEventAPI<your LLEventAPI subclass></tt>. The constructor should
|
||||
* look very like a traditional LLEventAPI constructor:
|
||||
*
|
||||
* * pass (name, desc [, field]) to LazyEventAPI's constructor
|
||||
* * in the body, make a series of add() calls referencing your LLEventAPI
|
||||
* subclass methods.
|
||||
*
|
||||
* You may use any LLEventAPI::add() methods, that is, any
|
||||
* LLEventDispatcher::add() methods. But the target methods you pass to
|
||||
* add() must belong to your LLEventAPI subclass, not the LazyEventAPI
|
||||
* subclass.
|
||||
*
|
||||
* Declare a static instance of your LazyEventAPI listener registrar
|
||||
* class. When it's constructed at static initialization time, it will
|
||||
* register your LLEventAPI subclass with LLEventPumps. It will also
|
||||
* collect metadata for the LLEventAPI and its operations to provide to
|
||||
* LLLeapListener's introspection queries.
|
||||
*
|
||||
* When someone later calls LLEventPumps::obtain() to post an event to
|
||||
* your LLEventAPI subclass, obtain() will instantiate it using
|
||||
* LazyEventAPI's name, desc, field and add() calls.
|
||||
*/
|
||||
template <class EVENTAPI>
|
||||
class LazyEventAPI: public LazyEventAPIBase
|
||||
{
|
||||
public:
|
||||
// for subclass constructor to reference handler methods
|
||||
using listener = EVENTAPI;
|
||||
|
||||
LazyEventAPI(const std::string& name, const std::string& desc,
|
||||
const std::string& field="op"):
|
||||
// Forward ctor params to LazyEventAPIBase
|
||||
LazyEventAPIBase(name, desc, field)
|
||||
{}
|
||||
|
||||
private:
|
||||
LLEventPump* construct(const std::string& /*name*/) override
|
||||
{
|
||||
// base class has carefully assembled LazyEventAPIParams embedded
|
||||
// in this instance, just pass to LLEventAPI subclass constructor
|
||||
return new EVENTAPI(mParams);
|
||||
}
|
||||
};
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_LAZYEVENTAPI_H) */
|
||||
|
|
@ -528,6 +528,7 @@ S32 LLAPRFile::seek(apr_file_t* file_handle, apr_seek_where_t where, S32 offset)
|
|||
//static
|
||||
S32 LLAPRFile::readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
//*****************************************
|
||||
LLAPRFilePoolScope scope(pool);
|
||||
apr_file_t* file_handle = open(filename, scope.getVolatileAPRPool(), APR_READ|APR_BINARY);
|
||||
|
|
@ -572,6 +573,7 @@ S32 LLAPRFile::readEx(const std::string& filename, void *buf, S32 offset, S32 nb
|
|||
//static
|
||||
S32 LLAPRFile::writeEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
apr_int32_t flags = APR_CREATE|APR_WRITE|APR_BINARY;
|
||||
if (offset < 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ LLAssetDictionary::LLAssetDictionary()
|
|||
addEntry(LLAssetType::AT_WIDGET, new AssetEntry("WIDGET", "widget", "widget", false, false, false));
|
||||
addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false));
|
||||
addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true));
|
||||
addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true));
|
||||
addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false));
|
||||
addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, FALSE, FALSE, FALSE));
|
||||
|
||||
|
|
|
|||
|
|
@ -127,8 +127,9 @@ public:
|
|||
AT_RESERVED_6 = 55,
|
||||
|
||||
AT_SETTINGS = 56, // Collection of settings
|
||||
|
||||
AT_COUNT = 57,
|
||||
AT_MATERIAL = 57, // Render Material
|
||||
|
||||
AT_COUNT = 58,
|
||||
|
||||
// +*********************************************************+
|
||||
// | TO ADD AN ELEMENT TO THIS ENUM: |
|
||||
|
|
|
|||
|
|
@ -79,9 +79,9 @@ struct LLContextStatus
|
|||
|
||||
LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLContextStatus& context_status);
|
||||
|
||||
#define dumpStack(tag) \
|
||||
if (debugLoggingEnabled(tag)) \
|
||||
{ \
|
||||
LLCallStack cs; \
|
||||
LL_DEBUGS(tag) << "STACK:\n" << "====================\n" << cs << "====================" << LL_ENDL; \
|
||||
}
|
||||
#define dumpStack(tag) \
|
||||
LL_DEBUGS(tag) << "STACK:\n" \
|
||||
<< "====================\n" \
|
||||
<< LLCallStack() \
|
||||
<< "====================" \
|
||||
<< LL_ENDL;
|
||||
|
|
|
|||
|
|
@ -37,12 +37,13 @@ thread_local bool gProfilerEnabled = false;
|
|||
|
||||
#if (TRACY_ENABLE)
|
||||
// Override new/delete for tracy memory profiling
|
||||
void *operator new(size_t size)
|
||||
|
||||
void* ll_tracy_new(size_t size)
|
||||
{
|
||||
void* ptr;
|
||||
if (gProfilerEnabled)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
|
||||
//LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
|
||||
ptr = (malloc)(size);
|
||||
}
|
||||
else
|
||||
|
|
@ -57,12 +58,22 @@ void *operator new(size_t size)
|
|||
return ptr;
|
||||
}
|
||||
|
||||
void operator delete(void *ptr) noexcept
|
||||
void* operator new(size_t size)
|
||||
{
|
||||
return ll_tracy_new(size);
|
||||
}
|
||||
|
||||
void* operator new[](std::size_t count)
|
||||
{
|
||||
return ll_tracy_new(count);
|
||||
}
|
||||
|
||||
void ll_tracy_delete(void* ptr)
|
||||
{
|
||||
TracyFree(ptr);
|
||||
if (gProfilerEnabled)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
|
||||
//LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
|
||||
(free)(ptr);
|
||||
}
|
||||
else
|
||||
|
|
@ -71,6 +82,16 @@ void operator delete(void *ptr) noexcept
|
|||
}
|
||||
}
|
||||
|
||||
void operator delete(void *ptr) noexcept
|
||||
{
|
||||
ll_tracy_delete(ptr);
|
||||
}
|
||||
|
||||
void operator delete[](void* ptr) noexcept
|
||||
{
|
||||
ll_tracy_delete(ptr);
|
||||
}
|
||||
|
||||
// C-style malloc/free can't be so easily overridden, so we define tracy versions and use
|
||||
// a pre-processor #define in linden_common.h to redirect to them. The parens around the native
|
||||
// functions below prevents recursive substitution by the preprocessor.
|
||||
|
|
|
|||
|
|
@ -278,6 +278,7 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl
|
|||
catch (std::bad_alloc&)
|
||||
{
|
||||
// Out of memory on stack allocation?
|
||||
printActiveCoroutines();
|
||||
LL_ERRS("LLCoros") << "Bad memory allocation in LLCoros::launch(" << prefix << ")!" << LL_ENDL;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1603,20 +1603,6 @@ namespace LLError
|
|||
}
|
||||
}
|
||||
|
||||
bool debugLoggingEnabled(const std::string& tag)
|
||||
{
|
||||
LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
|
||||
if (!lock.isLocked())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
|
||||
LLError::ELevel level = LLError::LEVEL_DEBUG;
|
||||
bool res = checkLevelMap(s->mTagLevelMap, tag, level);
|
||||
return res;
|
||||
}
|
||||
|
||||
void crashdriver(void (*callback)(int*))
|
||||
{
|
||||
// The LLERROR_CRASH macro used to have inline code of the form:
|
||||
|
|
|
|||
|
|
@ -82,9 +82,11 @@ const int LL_ERR_NOERR = 0;
|
|||
|
||||
#ifdef SHOW_ASSERT
|
||||
#define llassert(func) llassert_always_msg(func, #func)
|
||||
#define llassert_msg(func, msg) llassert_always_msg(func, msg)
|
||||
#define llverify(func) llassert_always_msg(func, #func)
|
||||
#else
|
||||
#define llassert(func)
|
||||
#define llassert_msg(func, msg)
|
||||
#define llverify(func) do {if (func) {}} while(0)
|
||||
#endif
|
||||
|
||||
|
|
@ -462,8 +464,31 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
|
|||
LLError::CallSite& _site(_sites[which]); \
|
||||
lllog_test_()
|
||||
|
||||
// Check at run-time whether logging is enabled, without generating output
|
||||
/*
|
||||
// Check at run-time whether logging is enabled, without generating output.
|
||||
Resist the temptation to add a function like this because it incurs the
|
||||
expense of locking and map-searching every time control reaches it.
|
||||
bool debugLoggingEnabled(const std::string& tag);
|
||||
|
||||
Instead of:
|
||||
|
||||
if debugLoggingEnabled("SomeTag")
|
||||
{
|
||||
// ... presumably expensive operation ...
|
||||
LL_DEBUGS("SomeTag") << ... << LL_ENDL;
|
||||
}
|
||||
|
||||
Use this:
|
||||
|
||||
LL_DEBUGS("SomeTag");
|
||||
// ... presumably expensive operation ...
|
||||
LL_CONT << ...;
|
||||
LL_ENDL;
|
||||
|
||||
LL_DEBUGS("SomeTag") performs the locking and map-searching ONCE, then caches
|
||||
the result in a static variable.
|
||||
*/
|
||||
|
||||
// used by LLERROR_CRASH
|
||||
void crashdriver(void (*)(int*));
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llerror.h"
|
||||
#include "lazyeventapi.h"
|
||||
|
||||
LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const std::string& field):
|
||||
lbase(name, field),
|
||||
|
|
@ -43,6 +44,13 @@ LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const s
|
|||
{
|
||||
}
|
||||
|
||||
LLEventAPI::LLEventAPI(const LL::LazyEventAPIParams& params):
|
||||
LLEventAPI(params.name, params.desc, params.field)
|
||||
{
|
||||
// call initialization functions with our brand-new instance pointer
|
||||
params.init(this);
|
||||
}
|
||||
|
||||
LLEventAPI::~LLEventAPI()
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@
|
|||
#include "llinstancetracker.h"
|
||||
#include <string>
|
||||
|
||||
namespace LL
|
||||
{
|
||||
class LazyEventAPIParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* LLEventAPI not only provides operation dispatch functionality, inherited
|
||||
* from LLDispatchListener -- it also gives us event API introspection.
|
||||
|
|
@ -64,19 +69,6 @@ public:
|
|||
/// Get the documentation string
|
||||
std::string getDesc() const { return mDesc; }
|
||||
|
||||
/**
|
||||
* Publish only selected add() methods from LLEventDispatcher.
|
||||
* Every LLEventAPI add() @em must have a description string.
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
void add(const std::string& name,
|
||||
const std::string& desc,
|
||||
CALLABLE callable,
|
||||
const LLSD& required=LLSD())
|
||||
{
|
||||
LLEventDispatcher::add(name, desc, callable, required);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a Response object in any LLEventAPI subclass method that
|
||||
* wants to guarantee a reply (if requested) will be sent on exit from the
|
||||
|
|
@ -150,16 +142,20 @@ public:
|
|||
* @endcode
|
||||
*/
|
||||
LLSD& operator[](const LLSD::String& key) { return mResp[key]; }
|
||||
|
||||
/**
|
||||
* set the response to the given data
|
||||
*/
|
||||
void setResponse(LLSD const & response){ mResp = response; }
|
||||
|
||||
/**
|
||||
* set the response to the given data
|
||||
*/
|
||||
void setResponse(LLSD const & response){ mResp = response; }
|
||||
|
||||
LLSD mResp, mReq;
|
||||
LLSD::String mKey;
|
||||
};
|
||||
|
||||
protected:
|
||||
// constructor used only by subclasses registered by LazyEventAPI
|
||||
LLEventAPI(const LL::LazyEventAPIParams&);
|
||||
|
||||
private:
|
||||
std::string mDesc;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -40,70 +40,12 @@
|
|||
// other Linden headers
|
||||
#include "llevents.h"
|
||||
#include "llerror.h"
|
||||
#include "llexception.h"
|
||||
#include "llsdutil.h"
|
||||
#include "stringize.h"
|
||||
#include <iomanip> // std::quoted()
|
||||
#include <memory> // std::auto_ptr
|
||||
|
||||
/*****************************************************************************
|
||||
* LLSDArgsSource
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* Store an LLSD array, producing its elements one at a time. Die with LL_ERRS
|
||||
* if the consumer requests more elements than the array contains.
|
||||
*/
|
||||
class LL_COMMON_API LLSDArgsSource
|
||||
{
|
||||
public:
|
||||
LLSDArgsSource(const std::string function, const LLSD& args);
|
||||
~LLSDArgsSource();
|
||||
|
||||
LLSD next();
|
||||
|
||||
void done() const;
|
||||
|
||||
private:
|
||||
std::string _function;
|
||||
LLSD _args;
|
||||
LLSD::Integer _index;
|
||||
};
|
||||
|
||||
LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args):
|
||||
_function(function),
|
||||
_args(args),
|
||||
_index(0)
|
||||
{
|
||||
if (! (_args.isUndefined() || _args.isArray()))
|
||||
{
|
||||
LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of "
|
||||
<< _args << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
LLSDArgsSource::~LLSDArgsSource()
|
||||
{
|
||||
done();
|
||||
}
|
||||
|
||||
LLSD LLSDArgsSource::next()
|
||||
{
|
||||
if (_index >= _args.size())
|
||||
{
|
||||
LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the "
|
||||
<< _args.size() << " provided: " << _args << LL_ENDL;
|
||||
}
|
||||
return _args[_index++];
|
||||
}
|
||||
|
||||
void LLSDArgsSource::done() const
|
||||
{
|
||||
if (_index < _args.size())
|
||||
{
|
||||
LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index
|
||||
<< " of the " << _args.size() << " arguments provided: "
|
||||
<< _args << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* LLSDArgsMapper
|
||||
*****************************************************************************/
|
||||
|
|
@ -156,19 +98,26 @@ void LLSDArgsSource::done() const
|
|||
* - Holes are filled with the default values.
|
||||
* - Any remaining holes constitute an error.
|
||||
*/
|
||||
class LL_COMMON_API LLSDArgsMapper
|
||||
class LL_COMMON_API LLEventDispatcher::LLSDArgsMapper
|
||||
{
|
||||
public:
|
||||
/// Accept description of function: function name, param names, param
|
||||
/// default values
|
||||
LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults);
|
||||
LLSDArgsMapper(LLEventDispatcher* parent, const std::string& function,
|
||||
const LLSD& names, const LLSD& defaults);
|
||||
|
||||
/// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS.
|
||||
/// Given arguments map, return LLSD::Array of parameter values, or
|
||||
/// trigger error.
|
||||
LLSD map(const LLSD& argsmap) const;
|
||||
|
||||
private:
|
||||
static std::string formatlist(const LLSD&);
|
||||
template <typename... ARGS>
|
||||
[[noreturn]] void callFail(ARGS&&... args) const;
|
||||
|
||||
// store a plain dumb back-pointer because we don't have to manage the
|
||||
// parent LLEventDispatcher's lifespan
|
||||
LLEventDispatcher* _parent;
|
||||
// The function-name string is purely descriptive. We want error messages
|
||||
// to be able to indicate which function's LLSDArgsMapper has the problem.
|
||||
std::string _function;
|
||||
|
|
@ -187,15 +136,18 @@ private:
|
|||
FilledVector _has_dft;
|
||||
};
|
||||
|
||||
LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
|
||||
const LLSD& names, const LLSD& defaults):
|
||||
LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(LLEventDispatcher* parent,
|
||||
const std::string& function,
|
||||
const LLSD& names,
|
||||
const LLSD& defaults):
|
||||
_parent(parent),
|
||||
_function(function),
|
||||
_names(names),
|
||||
_has_dft(names.size())
|
||||
{
|
||||
if (! (_names.isUndefined() || _names.isArray()))
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL;
|
||||
callFail(" names must be an array, not ", names);
|
||||
}
|
||||
auto nparams(_names.size());
|
||||
// From _names generate _indexes.
|
||||
|
|
@ -218,8 +170,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
|
|||
// defaults is a (possibly empty) array. Right-align it with names.
|
||||
if (ndefaults > nparams)
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << function << " names array " << names
|
||||
<< " shorter than defaults array " << defaults << LL_ENDL;
|
||||
callFail(" names array ", names, " shorter than defaults array ", defaults);
|
||||
}
|
||||
|
||||
// Offset by which we slide defaults array right to right-align with
|
||||
|
|
@ -256,23 +207,20 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
|
|||
}
|
||||
if (bogus.size())
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params "
|
||||
<< formatlist(bogus) << LL_ENDL;
|
||||
callFail(" defaults specified for nonexistent params ", formatlist(bogus));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not "
|
||||
<< defaults << LL_ENDL;
|
||||
callFail(" defaults must be a map or an array, not ", defaults);
|
||||
}
|
||||
}
|
||||
|
||||
LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
|
||||
LLSD LLEventDispatcher::LLSDArgsMapper::map(const LLSD& argsmap) const
|
||||
{
|
||||
if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray()))
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not "
|
||||
<< argsmap << LL_ENDL;
|
||||
callFail(" map() needs a map or array, not ", argsmap);
|
||||
}
|
||||
// Initialize the args array. Indexing a non-const LLSD array grows it
|
||||
// to appropriate size, but we don't want to resize this one on each
|
||||
|
|
@ -369,15 +317,14 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
|
|||
// by argsmap, that's a problem.
|
||||
if (unfilled.size())
|
||||
{
|
||||
LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments "
|
||||
<< formatlist(unfilled) << " from " << argsmap << LL_ENDL;
|
||||
callFail(" missing required arguments ", formatlist(unfilled), " from ", argsmap);
|
||||
}
|
||||
|
||||
// done
|
||||
return args;
|
||||
}
|
||||
|
||||
std::string LLSDArgsMapper::formatlist(const LLSD& list)
|
||||
std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list)
|
||||
{
|
||||
std::ostringstream out;
|
||||
const char* delim = "";
|
||||
|
|
@ -390,23 +337,44 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list)
|
|||
return out.str();
|
||||
}
|
||||
|
||||
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
|
||||
mDesc(desc),
|
||||
mKey(key)
|
||||
template <typename... ARGS>
|
||||
[[noreturn]] void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const
|
||||
{
|
||||
_parent->callFail<LLEventDispatcher::DispatchError>
|
||||
(_function, std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* LLEventDispatcher
|
||||
*****************************************************************************/
|
||||
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
|
||||
LLEventDispatcher(desc, key, "args")
|
||||
{}
|
||||
|
||||
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key,
|
||||
const std::string& argskey):
|
||||
mDesc(desc),
|
||||
mKey(key),
|
||||
mArgskey(argskey)
|
||||
{}
|
||||
|
||||
LLEventDispatcher::~LLEventDispatcher()
|
||||
{
|
||||
}
|
||||
|
||||
LLEventDispatcher::DispatchEntry::DispatchEntry(LLEventDispatcher* parent, const std::string& desc):
|
||||
mParent(parent),
|
||||
mDesc(desc)
|
||||
{}
|
||||
|
||||
/**
|
||||
* DispatchEntry subclass used for callables accepting(const LLSD&)
|
||||
*/
|
||||
struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry
|
||||
{
|
||||
LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required):
|
||||
DispatchEntry(desc),
|
||||
LLSDDispatchEntry(LLEventDispatcher* parent, const std::string& desc,
|
||||
const Callable& func, const LLSD& required):
|
||||
DispatchEntry(parent, desc),
|
||||
mFunc(func),
|
||||
mRequired(required)
|
||||
{}
|
||||
|
|
@ -414,22 +382,21 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
|
|||
Callable mFunc;
|
||||
LLSD mRequired;
|
||||
|
||||
virtual void call(const std::string& desc, const LLSD& event) const
|
||||
LLSD call(const std::string& desc, const LLSD& event, bool, const std::string&) const override
|
||||
{
|
||||
// Validate the syntax of the event itself.
|
||||
std::string mismatch(llsd_matches(mRequired, event));
|
||||
if (! mismatch.empty())
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL;
|
||||
callFail(desc, ": bad request: ", mismatch);
|
||||
}
|
||||
// Event syntax looks good, go for it!
|
||||
mFunc(event);
|
||||
return mFunc(event);
|
||||
}
|
||||
|
||||
virtual LLSD addMetadata(LLSD meta) const
|
||||
LLSD getMetadata() const override
|
||||
{
|
||||
meta["required"] = mRequired;
|
||||
return meta;
|
||||
return llsd::map("required", mRequired);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -439,17 +406,27 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
|
|||
*/
|
||||
struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry
|
||||
{
|
||||
ParamsDispatchEntry(const std::string& desc, const invoker_function& func):
|
||||
DispatchEntry(desc),
|
||||
ParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
|
||||
const std::string& desc, const invoker_function& func):
|
||||
DispatchEntry(parent, desc),
|
||||
mName(name),
|
||||
mInvoker(func)
|
||||
{}
|
||||
|
||||
std::string mName;
|
||||
invoker_function mInvoker;
|
||||
|
||||
virtual void call(const std::string& desc, const LLSD& event) const
|
||||
LLSD call(const std::string&, const LLSD& event, bool, const std::string&) const override
|
||||
{
|
||||
LLSDArgsSource src(desc, event);
|
||||
mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src)));
|
||||
try
|
||||
{
|
||||
return mInvoker(event);
|
||||
}
|
||||
catch (const LL::apply_error& err)
|
||||
{
|
||||
// could hit runtime errors with LL::apply()
|
||||
callFail(err.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -459,23 +436,62 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc
|
|||
*/
|
||||
struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
|
||||
{
|
||||
ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func,
|
||||
ArrayParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
|
||||
const std::string& desc, const invoker_function& func,
|
||||
LLSD::Integer arity):
|
||||
ParamsDispatchEntry(desc, func),
|
||||
ParamsDispatchEntry(parent, name, desc, func),
|
||||
mArity(arity)
|
||||
{}
|
||||
|
||||
LLSD::Integer mArity;
|
||||
|
||||
virtual LLSD addMetadata(LLSD meta) const
|
||||
LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override
|
||||
{
|
||||
// std::string context { stringize(desc, "(", event, ") with argskey ", std::quoted(argskey), ": ") };
|
||||
// Whether we try to extract arguments from 'event' depends on whether
|
||||
// the LLEventDispatcher consumer called one of the (name, event)
|
||||
// methods (! fromMap) or one of the (event) methods (fromMap). If we
|
||||
// were called with (name, event), the passed event must itself be
|
||||
// suitable to pass to the registered callable, no args extraction
|
||||
// required or even attempted. Only if called with plain (event) do we
|
||||
// consider extracting args from that event. Initially assume 'event'
|
||||
// itself contains the arguments.
|
||||
LLSD args{ event };
|
||||
if (fromMap)
|
||||
{
|
||||
if (! mArity)
|
||||
{
|
||||
// When the target function is nullary, and we're called from
|
||||
// an (event) method, just ignore the rest of the map entries.
|
||||
args.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// We only require/retrieve argskey if the target function
|
||||
// isn't nullary. For all others, since we require an LLSD
|
||||
// array, we must have an argskey.
|
||||
if (argskey.empty())
|
||||
{
|
||||
callFail("LLEventDispatcher has no args key");
|
||||
}
|
||||
if ((! event.has(argskey)))
|
||||
{
|
||||
callFail("missing required key ", std::quoted(argskey));
|
||||
}
|
||||
args = event[argskey];
|
||||
}
|
||||
}
|
||||
return ParamsDispatchEntry::call(desc, args, fromMap, argskey);
|
||||
}
|
||||
|
||||
LLSD getMetadata() const override
|
||||
{
|
||||
LLSD array(LLSD::emptyArray());
|
||||
// Resize to number of arguments required
|
||||
if (mArity)
|
||||
array[mArity - 1] = LLSD();
|
||||
llassert_always(array.size() == mArity);
|
||||
meta["required"] = array;
|
||||
return meta;
|
||||
return llsd::map("required", array);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -485,11 +501,11 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa
|
|||
*/
|
||||
struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
|
||||
{
|
||||
MapParamsDispatchEntry(const std::string& name, const std::string& desc,
|
||||
const invoker_function& func,
|
||||
MapParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name,
|
||||
const std::string& desc, const invoker_function& func,
|
||||
const LLSD& params, const LLSD& defaults):
|
||||
ParamsDispatchEntry(desc, func),
|
||||
mMapper(name, params, defaults),
|
||||
ParamsDispatchEntry(parent, name, desc, func),
|
||||
mMapper(parent, name, params, defaults),
|
||||
mRequired(LLSD::emptyMap())
|
||||
{
|
||||
// Build the set of all param keys, then delete the ones that are
|
||||
|
|
@ -532,18 +548,27 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para
|
|||
LLSD mRequired;
|
||||
LLSD mOptional;
|
||||
|
||||
virtual void call(const std::string& desc, const LLSD& event) const
|
||||
LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override
|
||||
{
|
||||
// Just convert from LLSD::Map to LLSD::Array using mMapper, then pass
|
||||
// to base-class call() method.
|
||||
ParamsDispatchEntry::call(desc, mMapper.map(event));
|
||||
// by default, pass the whole event as the arguments map
|
||||
LLSD args{ event };
|
||||
// Were we called by one of the (event) methods (instead of the (name,
|
||||
// event) methods), do we have an argskey, and does the incoming event
|
||||
// have that key?
|
||||
if (fromMap && (! argskey.empty()) && event.has(argskey))
|
||||
{
|
||||
// if so, extract the value of argskey from the incoming event,
|
||||
// and use that as the arguments map
|
||||
args = event[argskey];
|
||||
}
|
||||
// Now convert args from LLSD map to LLSD array using mMapper, then
|
||||
// pass to base-class call() method.
|
||||
return ParamsDispatchEntry::call(desc, mMapper.map(args), fromMap, argskey);
|
||||
}
|
||||
|
||||
virtual LLSD addMetadata(LLSD meta) const
|
||||
LLSD getMetadata() const override
|
||||
{
|
||||
meta["required"] = mRequired;
|
||||
meta["optional"] = mOptional;
|
||||
return meta;
|
||||
return llsd::map("required", mRequired, "optional", mOptional);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -552,9 +577,9 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name,
|
|||
const invoker_function& invoker,
|
||||
LLSD::Integer arity)
|
||||
{
|
||||
mDispatch.insert(
|
||||
DispatchMap::value_type(name, DispatchMap::mapped_type(
|
||||
new ArrayParamsDispatchEntry(desc, invoker, arity))));
|
||||
mDispatch.emplace(
|
||||
name,
|
||||
new ArrayParamsDispatchEntry(this, "", desc, invoker, arity));
|
||||
}
|
||||
|
||||
void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
|
||||
|
|
@ -563,25 +588,25 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
|
|||
const LLSD& params,
|
||||
const LLSD& defaults)
|
||||
{
|
||||
mDispatch.insert(
|
||||
DispatchMap::value_type(name, DispatchMap::mapped_type(
|
||||
new MapParamsDispatchEntry(name, desc, invoker, params, defaults))));
|
||||
// Pass instance info as well as this entry name for error messages.
|
||||
mDispatch.emplace(
|
||||
name,
|
||||
new MapParamsDispatchEntry(this, "", desc, invoker, params, defaults));
|
||||
}
|
||||
|
||||
/// Register a callable by name
|
||||
void LLEventDispatcher::add(const std::string& name, const std::string& desc,
|
||||
const Callable& callable, const LLSD& required)
|
||||
void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc,
|
||||
const Callable& callable, const LLSD& required)
|
||||
{
|
||||
mDispatch.insert(
|
||||
DispatchMap::value_type(name, DispatchMap::mapped_type(
|
||||
new LLSDDispatchEntry(desc, callable, required))));
|
||||
mDispatch.emplace(name, new LLSDDispatchEntry(this, desc, callable, required));
|
||||
}
|
||||
|
||||
void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const
|
||||
void LLEventDispatcher::addFail(const std::string& name, const char* classname) const
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name
|
||||
<< "): " << classname << " is not a subclass "
|
||||
<< "of LLEventDispatcher" << LL_ENDL;
|
||||
<< "): " << LLError::Log::demangle(classname)
|
||||
<< " is not a subclass of LLEventDispatcher"
|
||||
<< LL_ENDL;
|
||||
}
|
||||
|
||||
/// Unregister a callable
|
||||
|
|
@ -596,48 +621,105 @@ bool LLEventDispatcher::remove(const std::string& name)
|
|||
return true;
|
||||
}
|
||||
|
||||
/// Call a registered callable with an explicitly-specified name. If no
|
||||
/// such callable exists, die with LL_ERRS.
|
||||
void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
|
||||
/// Call a registered callable with an explicitly-specified name. It is an
|
||||
/// error if no such callable exists.
|
||||
LLSD LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
|
||||
{
|
||||
if (! try_call(name, event))
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name
|
||||
<< "' not found" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the @a key value from the incoming @a event, and call the
|
||||
/// callable whose name is specified by that map @a key. If no such
|
||||
/// callable exists, die with LL_ERRS.
|
||||
void LLEventDispatcher::operator()(const LLSD& event) const
|
||||
{
|
||||
// This could/should be implemented in terms of the two-arg overload.
|
||||
// However -- we can produce a more informative error message.
|
||||
std::string name(event[mKey]);
|
||||
if (! try_call(name, event))
|
||||
{
|
||||
LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey
|
||||
<< " value '" << name << "'" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
bool LLEventDispatcher::try_call(const LLSD& event) const
|
||||
{
|
||||
return try_call(event[mKey], event);
|
||||
return try_call(std::string(), name, event);
|
||||
}
|
||||
|
||||
bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
|
||||
{
|
||||
DispatchMap::const_iterator found = mDispatch.find(name);
|
||||
if (found == mDispatch.end())
|
||||
try
|
||||
{
|
||||
try_call(std::string(), name, event);
|
||||
return true;
|
||||
}
|
||||
// Note that we don't catch the generic DispatchError, only the specific
|
||||
// DispatchMissing. try_call() only promises to return false if the
|
||||
// specified callable name isn't found -- not for general errors.
|
||||
catch (const DispatchMissing&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the @a key value from the incoming @a event, and call the callable
|
||||
/// whose name is specified by that map @a key. It is an error if no such
|
||||
/// callable exists.
|
||||
LLSD LLEventDispatcher::operator()(const LLSD& event) const
|
||||
{
|
||||
return try_call(mKey, event[mKey], event);
|
||||
}
|
||||
|
||||
bool LLEventDispatcher::try_call(const LLSD& event) const
|
||||
{
|
||||
try
|
||||
{
|
||||
try_call(mKey, event[mKey], event);
|
||||
return true;
|
||||
}
|
||||
catch (const DispatchMissing&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name,
|
||||
const LLSD& event) const
|
||||
{
|
||||
if (name.empty())
|
||||
{
|
||||
if (key.empty())
|
||||
{
|
||||
callFail<DispatchError>("attempting to call with no name");
|
||||
}
|
||||
else
|
||||
{
|
||||
callFail<DispatchError>("no ", key);
|
||||
}
|
||||
}
|
||||
|
||||
DispatchMap::const_iterator found = mDispatch.find(name);
|
||||
if (found == mDispatch.end())
|
||||
{
|
||||
// Here we were passed a non-empty name, but there's no registered
|
||||
// callable with that name. This is the one case in which we throw
|
||||
// DispatchMissing instead of the generic DispatchError.
|
||||
// Distinguish the public method by which our caller reached here:
|
||||
// key.empty() means the name was passed explicitly, non-empty means
|
||||
// we extracted the name from the incoming event using that key.
|
||||
if (key.empty())
|
||||
{
|
||||
callFail<DispatchMissing>(std::quoted(name), " not found");
|
||||
}
|
||||
else
|
||||
{
|
||||
callFail<DispatchMissing>("bad ", key, " value ", std::quoted(name));
|
||||
}
|
||||
}
|
||||
|
||||
// Found the name, so it's plausible to even attempt the call.
|
||||
found->second->call(STRINGIZE("LLEventDispatcher(" << mDesc << ") calling '" << name << "'"),
|
||||
event);
|
||||
return true; // tell caller we were able to call
|
||||
const char* delim = (key.empty()? "" : "=");
|
||||
// append either "[key=name]" or just "[name]"
|
||||
SetState transient(this, '[', key, delim, name, ']');
|
||||
return found->second->call("", event, (! key.empty()), mArgskey);
|
||||
}
|
||||
|
||||
template <typename EXCEPTION, typename... ARGS>
|
||||
//static
|
||||
[[noreturn]] void LLEventDispatcher::sCallFail(ARGS&&... args)
|
||||
{
|
||||
auto error = stringize(std::forward<ARGS>(args)...);
|
||||
LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
|
||||
LLTHROW(EXCEPTION(error));
|
||||
}
|
||||
|
||||
template <typename EXCEPTION, typename... ARGS>
|
||||
[[noreturn]] void LLEventDispatcher::callFail(ARGS&&... args) const
|
||||
{
|
||||
// Describe this instance in addition to the error itself.
|
||||
sCallFail<EXCEPTION>(*this, ": ", std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
LLSD LLEventDispatcher::getMetadata(const std::string& name) const
|
||||
|
|
@ -647,26 +729,243 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const
|
|||
{
|
||||
return LLSD();
|
||||
}
|
||||
LLSD meta;
|
||||
LLSD meta{ found->second->getMetadata() };
|
||||
meta["name"] = name;
|
||||
meta["desc"] = found->second->mDesc;
|
||||
return found->second->addMetadata(meta);
|
||||
return meta;
|
||||
}
|
||||
|
||||
LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
|
||||
LLEventDispatcher(pumpname, key),
|
||||
mPump(pumpname, true), // allow tweaking for uniqueness
|
||||
mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1)))
|
||||
std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self)
|
||||
{
|
||||
// If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that.
|
||||
// Also report whatever transient state is active.
|
||||
return out << LLError::Log::classname(self) << '(' << self.mDesc << ')'
|
||||
<< self.getState();
|
||||
}
|
||||
|
||||
bool LLDispatchListener::process(const LLSD& event)
|
||||
std::string LLEventDispatcher::getState() const
|
||||
{
|
||||
(*this)(event);
|
||||
// default value of fiber_specific_ptr is nullptr, and ~SetState() reverts
|
||||
// to that; infer empty string
|
||||
if (! mState.get())
|
||||
return {};
|
||||
else
|
||||
return *mState;
|
||||
}
|
||||
|
||||
bool LLEventDispatcher::setState(SetState&, const std::string& state) const
|
||||
{
|
||||
// If SetState is instantiated at multiple levels of function call, ignore
|
||||
// the lower-level call because the outer call presumably provides more
|
||||
// context.
|
||||
if (mState.get())
|
||||
return false;
|
||||
|
||||
// Pass us empty string (a la ~SetState()) to reset to nullptr, else take
|
||||
// a heap copy of the passed state string so we can delete it on
|
||||
// subsequent reset().
|
||||
mState.reset(state.empty()? nullptr : new std::string(state));
|
||||
return true;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* LLDispatchListener
|
||||
*****************************************************************************/
|
||||
std::string LLDispatchListener::mReplyKey{ "reply" };
|
||||
|
||||
bool LLDispatchListener::process(const LLSD& event) const
|
||||
{
|
||||
// Decide what to do based on the incoming value of the specified dispatch
|
||||
// key.
|
||||
LLSD name{ event[getDispatchKey()] };
|
||||
if (name.isMap())
|
||||
{
|
||||
call_map(name, event);
|
||||
}
|
||||
else if (name.isArray())
|
||||
{
|
||||
call_array(name, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
call_one(name, event);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
|
||||
mDesc(desc)
|
||||
{}
|
||||
void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const
|
||||
{
|
||||
LLSD result;
|
||||
try
|
||||
{
|
||||
result = (*this)(event);
|
||||
}
|
||||
catch (const DispatchError& err)
|
||||
{
|
||||
if (! event.has(mReplyKey))
|
||||
{
|
||||
// Without a reply key, let the exception propagate.
|
||||
throw;
|
||||
}
|
||||
|
||||
// Here there was an error and the incoming event has mReplyKey. Reply
|
||||
// with a map containing an "error" key explaining the problem.
|
||||
return reply(llsd::map("error", err.what()), event);
|
||||
}
|
||||
|
||||
// We seem to have gotten a valid result. But we don't know whether the
|
||||
// registered callable is void or non-void. If it's void,
|
||||
// LLEventDispatcher returned isUndefined(). Otherwise, try to send it
|
||||
// back to our invoker.
|
||||
if (result.isDefined())
|
||||
{
|
||||
if (! result.isMap())
|
||||
{
|
||||
// wrap the result in a map as the "data" key
|
||||
result = llsd::map("data", result);
|
||||
}
|
||||
reply(result, event);
|
||||
}
|
||||
}
|
||||
|
||||
void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const
|
||||
{
|
||||
// LLSD map containing returned values
|
||||
LLSD result;
|
||||
// cache dispatch key
|
||||
std::string key{ getDispatchKey() };
|
||||
// collect any error messages here
|
||||
std::ostringstream errors;
|
||||
const char* delim = "";
|
||||
|
||||
for (const auto& pair : llsd::inMap(reqmap))
|
||||
{
|
||||
const LLSD::String& name{ pair.first };
|
||||
const LLSD& args{ pair.second };
|
||||
try
|
||||
{
|
||||
// in case of errors, tell user the dispatch key, the fact that
|
||||
// we're processing a request map and the current key in that map
|
||||
SetState(this, '[', key, '[', name, "]]");
|
||||
// With this form, capture return value even if undefined:
|
||||
// presence of the key in the response map can be used to detect
|
||||
// which request keys succeeded.
|
||||
result[name] = (*this)(name, args);
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
// Catch not only DispatchError, but any C++ exception thrown by
|
||||
// the target callable. Collect exception name and message in
|
||||
// 'errors'.
|
||||
errors << delim << LLError::Log::classname(err) << ": " << err.what();
|
||||
delim = "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// so, were there any errors?
|
||||
std::string error = errors.str();
|
||||
if (! error.empty())
|
||||
{
|
||||
if (! event.has(mReplyKey))
|
||||
{
|
||||
// can't send reply, throw
|
||||
sCallFail<DispatchError>(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// reply key present
|
||||
result["error"] = error;
|
||||
}
|
||||
}
|
||||
|
||||
reply(result, event);
|
||||
}
|
||||
|
||||
void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) const
|
||||
{
|
||||
// LLSD array containing returned values
|
||||
LLSD results;
|
||||
// cache the dispatch key
|
||||
std::string key{ getDispatchKey() };
|
||||
// arguments array, if present -- const because, if it's shorter than
|
||||
// reqarray, we don't want to grow it
|
||||
const LLSD argsarray{ event[getArgsKey()] };
|
||||
// error message, if any
|
||||
std::string error;
|
||||
|
||||
// classic index loop because we need the index
|
||||
for (size_t i = 0, size = reqarray.size(); i < size; ++i)
|
||||
{
|
||||
const auto& reqentry{ reqarray[i] };
|
||||
std::string name;
|
||||
LLSD args;
|
||||
if (reqentry.isString())
|
||||
{
|
||||
name = reqentry.asString();
|
||||
args = argsarray[i];
|
||||
}
|
||||
else if (reqentry.isArray() && reqentry.size() == 2 && reqentry[0].isString())
|
||||
{
|
||||
name = reqentry[0].asString();
|
||||
args = reqentry[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// reqentry isn't in either of the documented forms
|
||||
error = stringize(*this, ": ", getDispatchKey(), '[', i, "] ",
|
||||
reqentry, " unsupported");
|
||||
break;
|
||||
}
|
||||
|
||||
// reqentry is one of the valid forms, got name and args
|
||||
try
|
||||
{
|
||||
// in case of errors, tell user the dispatch key, the fact that
|
||||
// we're processing a request array, the current entry in that
|
||||
// array and the corresponding callable name
|
||||
SetState(this, '[', key, '[', i, "]=", name, ']');
|
||||
// With this form, capture return value even if undefined
|
||||
results.append((*this)(name, args));
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
// Catch not only DispatchError, but any C++ exception thrown by
|
||||
// the target callable. Report the exception class as well as the
|
||||
// error string.
|
||||
error = stringize(LLError::Log::classname(err), ": ", err.what());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LLSD result;
|
||||
// was there an error?
|
||||
if (! error.empty())
|
||||
{
|
||||
if (! event.has(mReplyKey))
|
||||
{
|
||||
// can't send reply, throw
|
||||
sCallFail<DispatchError>(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// reply key present
|
||||
result["error"] = error;
|
||||
}
|
||||
}
|
||||
|
||||
// wrap the results array as response map "data" key, as promised
|
||||
if (results.isDefined())
|
||||
{
|
||||
result["data"] = results;
|
||||
}
|
||||
|
||||
reply(result, event);
|
||||
}
|
||||
|
||||
void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const
|
||||
{
|
||||
// Call sendReply() unconditionally: sendReply() itself tests whether the
|
||||
// specified reply key is present in the incoming request, and does
|
||||
// nothing if there's no such key.
|
||||
sendReply(reply, request, mReplyKey);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -435,16 +435,61 @@ public:
|
|||
// generic type-appropriate store through mTarget, construct an
|
||||
// LLSDParam<T> and store that, thus engaging LLSDParam's custom
|
||||
// conversions.
|
||||
mTarget = LLSDParam<T>(llsd::drill(event, mPath));
|
||||
storeTarget(LLSDParam<T>(llsd::drill(event, mPath)));
|
||||
return mConsume;
|
||||
}
|
||||
|
||||
private:
|
||||
// This method disambiguates LLStoreListener<LLSD>. Directly assigning
|
||||
// some_LLSD_var = LLSDParam<LLSD>(some_LLSD_value);
|
||||
// is problematic because the compiler has too many choices: LLSD has
|
||||
// multiple assignment operator overloads, and LLSDParam<LLSD> has a
|
||||
// templated conversion operator. But LLSDParam<LLSD> can convert to a
|
||||
// (const LLSD&) parameter, and LLSD::operator=(const LLSD&) works.
|
||||
void storeTarget(const T& value)
|
||||
{
|
||||
mTarget = value;
|
||||
}
|
||||
|
||||
T& mTarget;
|
||||
const LLSD mPath;
|
||||
const bool mConsume;
|
||||
};
|
||||
|
||||
/**
|
||||
* LLVarHolder bundles a target variable of the specified type. We use it as a
|
||||
* base class so the target variable will be fully constructed by the time a
|
||||
* subclass constructor tries to pass a reference to some other base class.
|
||||
*/
|
||||
template <typename T>
|
||||
struct LLVarHolder
|
||||
{
|
||||
T mVar;
|
||||
};
|
||||
|
||||
/**
|
||||
* LLCaptureListener isa LLStoreListener that bundles the target variable of
|
||||
* interest.
|
||||
*/
|
||||
template <typename T>
|
||||
class LLCaptureListener: public LLVarHolder<T>,
|
||||
public LLStoreListener<T>
|
||||
{
|
||||
private:
|
||||
using holder = LLVarHolder<T>;
|
||||
using super = LLStoreListener<T>;
|
||||
|
||||
public:
|
||||
LLCaptureListener(const LLSD& path=LLSD(), bool consume=false):
|
||||
super(*this, holder::mVar, path, consume)
|
||||
{}
|
||||
|
||||
void set(T&& newval=T()) { holder::mVar = std::forward<T>(newval); }
|
||||
|
||||
const T& get() const { return holder::mVar; }
|
||||
operator const T&() { return holder::mVar; }
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* LLEventLogProxy
|
||||
*****************************************************************************/
|
||||
|
|
|
|||
|
|
@ -68,19 +68,78 @@
|
|||
LLEventPumps::LLEventPumps():
|
||||
mFactories
|
||||
{
|
||||
{ "LLEventStream", [](const std::string& name, bool tweak)
|
||||
{ "LLEventStream", [](const std::string& name, bool tweak, const std::string& /*type*/)
|
||||
{ return new LLEventStream(name, tweak); } },
|
||||
{ "LLEventMailDrop", [](const std::string& name, bool tweak)
|
||||
{ "LLEventMailDrop", [](const std::string& name, bool tweak, const std::string& /*type*/)
|
||||
{ return new LLEventMailDrop(name, tweak); } }
|
||||
},
|
||||
mTypes
|
||||
{
|
||||
// LLEventStream is the default for obtain(), so even if somebody DOES
|
||||
// call obtain("placeholder"), this sample entry won't break anything.
|
||||
{ "placeholder", "LLEventStream" }
|
||||
// { "placeholder", "LLEventStream" }
|
||||
}
|
||||
{}
|
||||
|
||||
bool LLEventPumps::registerTypeFactory(const std::string& type, const TypeFactory& factory)
|
||||
{
|
||||
auto found = mFactories.find(type);
|
||||
// can't re-register a TypeFactory for a type name that's already registered
|
||||
if (found != mFactories.end())
|
||||
return false;
|
||||
// doesn't already exist, go ahead and register
|
||||
mFactories[type] = factory;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LLEventPumps::unregisterTypeFactory(const std::string& type)
|
||||
{
|
||||
auto found = mFactories.find(type);
|
||||
if (found != mFactories.end())
|
||||
mFactories.erase(found);
|
||||
}
|
||||
|
||||
bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactory& factory)
|
||||
{
|
||||
// Do we already have a pump by this name?
|
||||
if (mPumpMap.find(name) != mPumpMap.end())
|
||||
return false;
|
||||
// Do we already have an override for this pump name?
|
||||
if (mTypes.find(name) != mTypes.end())
|
||||
return false;
|
||||
// Leverage the two-level lookup implemented by mTypes (pump name -> type
|
||||
// name) and mFactories (type name -> factory). We could instead create a
|
||||
// whole separate (pump name -> factory) map, and look in both; or we
|
||||
// could change mTypes to (pump name -> factory) and, for typical type-
|
||||
// based lookups, use a "factory" that looks up the real factory in
|
||||
// mFactories. But this works, and we don't expect many calls to make() -
|
||||
// either explicit or implicit via obtain().
|
||||
// Create a bogus type name extremely unlikely to collide with an actual type.
|
||||
static std::string nul(1, '\0');
|
||||
std::string type_name{ nul + name };
|
||||
mTypes[name] = type_name;
|
||||
// TypeFactory is called with (name, tweak, type), whereas PumpFactory
|
||||
// accepts only name. We could adapt with std::bind(), but this lambda
|
||||
// does the trick.
|
||||
mFactories[type_name] =
|
||||
[factory]
|
||||
(const std::string& name, bool /*tweak*/, const std::string& /*type*/)
|
||||
{ return factory(name); };
|
||||
return true;
|
||||
}
|
||||
|
||||
void LLEventPumps::unregisterPumpFactory(const std::string& name)
|
||||
{
|
||||
auto tfound = mTypes.find(name);
|
||||
if (tfound != mTypes.end())
|
||||
{
|
||||
auto ffound = mFactories.find(tfound->second);
|
||||
if (ffound != mFactories.end())
|
||||
{
|
||||
mFactories.erase(ffound);
|
||||
}
|
||||
mTypes.erase(tfound);
|
||||
}
|
||||
}
|
||||
|
||||
LLEventPump& LLEventPumps::obtain(const std::string& name)
|
||||
{
|
||||
PumpMap::iterator found = mPumpMap.find(name);
|
||||
|
|
@ -114,7 +173,7 @@ LLEventPump& LLEventPumps::make(const std::string& name, bool tweak,
|
|||
// Passing an unrecognized type name is a no-no
|
||||
LLTHROW(BadType(type));
|
||||
}
|
||||
auto newInstance = (found->second)(name, tweak);
|
||||
auto newInstance = (found->second)(name, tweak, type);
|
||||
// LLEventPump's constructor implicitly registers each new instance in
|
||||
// mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll
|
||||
// delete it later.
|
||||
|
|
|
|||
|
|
@ -268,6 +268,45 @@ public:
|
|||
LLEventPump& make(const std::string& name, bool tweak=false,
|
||||
const std::string& type=std::string());
|
||||
|
||||
/// function passed to registerTypeFactory()
|
||||
typedef std::function<LLEventPump*(const std::string& name, bool tweak, const std::string& type)> TypeFactory;
|
||||
|
||||
/**
|
||||
* Register a TypeFactory for use with make(). When make() is called with
|
||||
* the specified @a type string, call @a factory(name, tweak, type) to
|
||||
* instantiate it.
|
||||
*
|
||||
* Returns true if successfully registered, false if there already exists
|
||||
* a TypeFactory for the specified @a type name.
|
||||
*/
|
||||
bool registerTypeFactory(const std::string& type, const TypeFactory& factory);
|
||||
void unregisterTypeFactory(const std::string& type);
|
||||
|
||||
/// function passed to registerPumpFactory()
|
||||
typedef std::function<LLEventPump*(const std::string&)> PumpFactory;
|
||||
|
||||
/**
|
||||
* Register a PumpFactory for use with obtain(). When obtain() is called
|
||||
* with the specified @a name string, if an LLEventPump with the specified
|
||||
* @a name doesn't already exist, call @a factory(name) to instantiate it.
|
||||
*
|
||||
* Returns true if successfully registered, false if there already exists
|
||||
* a factory override for the specified @a name.
|
||||
*
|
||||
* PumpFactory does not support @a tweak because it's only called when
|
||||
* <i>that particular</i> @a name is passed to obtain(). Bear in mind that
|
||||
* <tt>obtain(name)</tt> might still bypass the caller's PumpFactory for a
|
||||
* couple different reasons:
|
||||
*
|
||||
* * registerPumpFactory() returns false because there's already a factory
|
||||
* override for the specified @name
|
||||
* * between a successful <tt>registerPumpFactory(name)</tt> call (returns
|
||||
* true) and a call to <tt>obtain(name)</tt>, someone explicitly
|
||||
* instantiated an LLEventPump(name), so obtain(name) returned that.
|
||||
*/
|
||||
bool registerPumpFactory(const std::string& name, const PumpFactory& factory);
|
||||
void unregisterPumpFactory(const std::string& name);
|
||||
|
||||
/**
|
||||
* Find the named LLEventPump instance. If it exists post the message to it.
|
||||
* If the pump does not exist, do nothing.
|
||||
|
|
@ -325,13 +364,13 @@ testable:
|
|||
typedef std::set<LLEventPump*> PumpSet;
|
||||
PumpSet mOurPumps;
|
||||
// for make(), map string type name to LLEventPump subclass factory function
|
||||
typedef std::map<std::string, std::function<LLEventPump*(const std::string&, bool)>> PumpFactories;
|
||||
typedef std::map<std::string, TypeFactory> TypeFactories;
|
||||
// Data used by make().
|
||||
// One might think mFactories and mTypes could reasonably be static. So
|
||||
// they could -- if not for the fact that make() or obtain() might be
|
||||
// called before this module's static variables have been initialized.
|
||||
// This is why we use singletons in the first place.
|
||||
PumpFactories mFactories;
|
||||
TypeFactories mFactories;
|
||||
|
||||
// for obtain(), map desired string instance name to string type when
|
||||
// obtain() must create the instance
|
||||
|
|
|
|||
|
|
@ -29,11 +29,6 @@
|
|||
|
||||
#include "llframetimer.h"
|
||||
|
||||
// We don't bother building a stand alone lib; we just need to include the one source file for Tracy support
|
||||
#if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY || LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY_FAST_TIMER
|
||||
#include "TracyClient.cpp"
|
||||
#endif // LL_PROFILER_CONFIGURATION
|
||||
|
||||
// Static members
|
||||
//LLTimer LLFrameTimer::sInternalTimer;
|
||||
U64 LLFrameTimer::sStartTotalTime = totalTime();
|
||||
|
|
|
|||
|
|
@ -104,22 +104,26 @@ public:
|
|||
return LockStatic()->mMap.size();
|
||||
}
|
||||
|
||||
// snapshot of std::pair<const KEY, std::shared_ptr<T>> pairs
|
||||
class snapshot
|
||||
// snapshot of std::pair<const KEY, std::shared_ptr<SUBCLASS>> pairs, for
|
||||
// some SUBCLASS derived from T
|
||||
template <typename SUBCLASS>
|
||||
class snapshot_of
|
||||
{
|
||||
// It's very important that what we store in this snapshot are
|
||||
// weak_ptrs, NOT shared_ptrs. That's how we discover whether any
|
||||
// instance has been deleted during the lifespan of a snapshot.
|
||||
typedef std::vector<std::pair<const KEY, weak_t>> VectorType;
|
||||
// Dereferencing our iterator produces a std::shared_ptr for each
|
||||
// instance that still exists. Since we store weak_ptrs, that involves
|
||||
// two chained transformations:
|
||||
// Dereferencing the iterator we publish produces a
|
||||
// std::shared_ptr<SUBCLASS> for each instance that still exists.
|
||||
// Since we store weak_ptr<T>, that involves two chained
|
||||
// transformations:
|
||||
// - a transform_iterator to lock the weak_ptr and return a shared_ptr
|
||||
// - a filter_iterator to skip any shared_ptr that has become invalid.
|
||||
// - a filter_iterator to skip any shared_ptr<T> that has become
|
||||
// invalid or references any T instance that isn't SUBCLASS.
|
||||
// It is very important that we filter lazily, that is, during
|
||||
// traversal. Any one of our stored weak_ptrs might expire during
|
||||
// traversal.
|
||||
typedef std::pair<const KEY, ptr_t> strong_pair;
|
||||
typedef std::pair<const KEY, std::shared_ptr<SUBCLASS>> strong_pair;
|
||||
// Note for future reference: nat has not yet had any luck (up to
|
||||
// Boost 1.67) trying to use boost::transform_iterator with a hand-
|
||||
// coded functor, only with actual functions. In my experience, an
|
||||
|
|
@ -127,7 +131,7 @@ public:
|
|||
// result_type typedef. But this works.
|
||||
static strong_pair strengthen(typename VectorType::value_type& pair)
|
||||
{
|
||||
return { pair.first, pair.second.lock() };
|
||||
return { pair.first, std::dynamic_pointer_cast<SUBCLASS>(pair.second.lock()) };
|
||||
}
|
||||
static bool dead_skipper(const strong_pair& pair)
|
||||
{
|
||||
|
|
@ -135,7 +139,7 @@ public:
|
|||
}
|
||||
|
||||
public:
|
||||
snapshot():
|
||||
snapshot_of():
|
||||
// populate our vector with a snapshot of (locked!) InstanceMap
|
||||
// note, this assigns pair<KEY, shared_ptr> to pair<KEY, weak_ptr>
|
||||
mData(mLock->mMap.begin(), mLock->mMap.end())
|
||||
|
|
@ -184,44 +188,51 @@ public:
|
|||
#endif // LL_WINDOWS
|
||||
VectorType mData;
|
||||
};
|
||||
using snapshot = snapshot_of<T>;
|
||||
|
||||
// iterate over this for references to each instance
|
||||
class instance_snapshot: public snapshot
|
||||
// iterate over this for references to each SUBCLASS instance
|
||||
template <typename SUBCLASS>
|
||||
class instance_snapshot_of: public snapshot_of<SUBCLASS>
|
||||
{
|
||||
private:
|
||||
static T& instance_getter(typename snapshot::iterator::reference pair)
|
||||
using super = snapshot_of<SUBCLASS>;
|
||||
static T& instance_getter(typename super::iterator::reference pair)
|
||||
{
|
||||
return *pair.second;
|
||||
}
|
||||
public:
|
||||
typedef boost::transform_iterator<decltype(instance_getter)*,
|
||||
typename snapshot::iterator> iterator;
|
||||
iterator begin() { return iterator(snapshot::begin(), instance_getter); }
|
||||
iterator end() { return iterator(snapshot::end(), instance_getter); }
|
||||
typename super::iterator> iterator;
|
||||
iterator begin() { return iterator(super::begin(), instance_getter); }
|
||||
iterator end() { return iterator(super::end(), instance_getter); }
|
||||
|
||||
void deleteAll()
|
||||
{
|
||||
for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it)
|
||||
for (auto it(super::begin()), end(super::end()); it != end; ++it)
|
||||
{
|
||||
delete it->second.get();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
using instance_snapshot = instance_snapshot_of<T>;
|
||||
|
||||
// iterate over this for each key
|
||||
class key_snapshot: public snapshot
|
||||
template <typename SUBCLASS>
|
||||
class key_snapshot_of: public snapshot_of<SUBCLASS>
|
||||
{
|
||||
private:
|
||||
static KEY key_getter(typename snapshot::iterator::reference pair)
|
||||
using super = snapshot_of<SUBCLASS>;
|
||||
static KEY key_getter(typename super::iterator::reference pair)
|
||||
{
|
||||
return pair.first;
|
||||
}
|
||||
public:
|
||||
typedef boost::transform_iterator<decltype(key_getter)*,
|
||||
typename snapshot::iterator> iterator;
|
||||
iterator begin() { return iterator(snapshot::begin(), key_getter); }
|
||||
iterator end() { return iterator(snapshot::end(), key_getter); }
|
||||
typename super::iterator> iterator;
|
||||
iterator begin() { return iterator(super::begin(), key_getter); }
|
||||
iterator end() { return iterator(super::end(), key_getter); }
|
||||
};
|
||||
using key_snapshot = key_snapshot_of<T>;
|
||||
|
||||
static ptr_t getInstance(const KEY& k)
|
||||
{
|
||||
|
|
@ -368,22 +379,25 @@ public:
|
|||
return LockStatic()->mSet.size();
|
||||
}
|
||||
|
||||
// snapshot of std::shared_ptr<T> pointers
|
||||
class snapshot
|
||||
// snapshot of std::shared_ptr<SUBCLASS> pointers
|
||||
template <typename SUBCLASS>
|
||||
class snapshot_of
|
||||
{
|
||||
// It's very important that what we store in this snapshot are
|
||||
// weak_ptrs, NOT shared_ptrs. That's how we discover whether any
|
||||
// instance has been deleted during the lifespan of a snapshot.
|
||||
typedef std::vector<weak_t> VectorType;
|
||||
// Dereferencing our iterator produces a std::shared_ptr for each
|
||||
// instance that still exists. Since we store weak_ptrs, that involves
|
||||
// two chained transformations:
|
||||
// Dereferencing the iterator we publish produces a
|
||||
// std::shared_ptr<SUBCLASS> for each instance that still exists.
|
||||
// Since we store weak_ptrs, that involves two chained
|
||||
// transformations:
|
||||
// - a transform_iterator to lock the weak_ptr and return a shared_ptr
|
||||
// - a filter_iterator to skip any shared_ptr that has become invalid.
|
||||
typedef std::shared_ptr<T> strong_ptr;
|
||||
// - a filter_iterator to skip any shared_ptr that has become invalid
|
||||
// or references any T instance that isn't SUBCLASS.
|
||||
typedef std::shared_ptr<SUBCLASS> strong_ptr;
|
||||
static strong_ptr strengthen(typename VectorType::value_type& ptr)
|
||||
{
|
||||
return ptr.lock();
|
||||
return std::dynamic_pointer_cast<SUBCLASS>(ptr.lock());
|
||||
}
|
||||
static bool dead_skipper(const strong_ptr& ptr)
|
||||
{
|
||||
|
|
@ -391,7 +405,7 @@ public:
|
|||
}
|
||||
|
||||
public:
|
||||
snapshot():
|
||||
snapshot_of():
|
||||
// populate our vector with a snapshot of (locked!) InstanceSet
|
||||
// note, this assigns stored shared_ptrs to weak_ptrs for snapshot
|
||||
mData(mLock->mSet.begin(), mLock->mSet.end())
|
||||
|
|
@ -437,22 +451,33 @@ public:
|
|||
#endif // LL_WINDOWS
|
||||
VectorType mData;
|
||||
};
|
||||
using snapshot = snapshot_of<T>;
|
||||
|
||||
// iterate over this for references to each instance
|
||||
struct instance_snapshot: public snapshot
|
||||
template <typename SUBCLASS>
|
||||
class instance_snapshot_of: public snapshot_of<SUBCLASS>
|
||||
{
|
||||
typedef boost::indirect_iterator<typename snapshot::iterator> iterator;
|
||||
iterator begin() { return iterator(snapshot::begin()); }
|
||||
iterator end() { return iterator(snapshot::end()); }
|
||||
private:
|
||||
using super = snapshot_of<SUBCLASS>;
|
||||
|
||||
public:
|
||||
typedef boost::indirect_iterator<typename super::iterator> iterator;
|
||||
iterator begin() { return iterator(super::begin()); }
|
||||
iterator end() { return iterator(super::end()); }
|
||||
|
||||
void deleteAll()
|
||||
{
|
||||
for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it)
|
||||
for (auto it(super::begin()), end(super::end()); it != end; ++it)
|
||||
{
|
||||
delete it->get();
|
||||
}
|
||||
}
|
||||
};
|
||||
using instance_snapshot = instance_snapshot_of<T>;
|
||||
// key_snapshot_of isn't really meaningful, but define it anyway to avoid
|
||||
// requiring two different LLInstanceTrackerSubclass implementations.
|
||||
template <typename SUBCLASS>
|
||||
using key_snapshot_of = instance_snapshot_of<SUBCLASS>;
|
||||
|
||||
protected:
|
||||
LLInstanceTracker()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* @file llinstancetrackersubclass.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-12-09
|
||||
* @brief Intermediate class to get subclass-specific types from
|
||||
* LLInstanceTracker instance-retrieval methods.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLINSTANCETRACKERSUBCLASS_H)
|
||||
#define LL_LLINSTANCETRACKERSUBCLASS_H
|
||||
|
||||
#include <memory> // std::shared_ptr, std::weak_ptr
|
||||
|
||||
/**
|
||||
* Derive your subclass S of a subclass T of LLInstanceTracker<T> from
|
||||
* LLInstanceTrackerSubclass<S, T> to perform appropriate downcasting and
|
||||
* filtering for LLInstanceTracker access methods.
|
||||
*
|
||||
* LLInstanceTracker<T> uses CRTP, so that getWeak(), getInstance(), snapshot
|
||||
* and instance_snapshot return pointers and references to T. The trouble is
|
||||
* that subclasses T0 and T1 derived from T also get pointers and references
|
||||
* to their base class T, requiring explicit downcasting. Moreover,
|
||||
* T0::getInstance() shouldn't find an instance of any T subclass other than
|
||||
* T0. Nor should T0::snapshot.
|
||||
*
|
||||
* @code
|
||||
* class Tracked: public LLInstanceTracker<Tracked, std::string>
|
||||
* {
|
||||
* private:
|
||||
* using super = LLInstanceTracker<Tracked, std::string>;
|
||||
* public:
|
||||
* Tracked(const std::string& name): super(name) {}
|
||||
* // All references to Tracked::ptr_t, Tracked::getInstance() etc.
|
||||
* // appropriately use Tracked.
|
||||
* // ...
|
||||
* };
|
||||
*
|
||||
* // But now we derive SubTracked from Tracked. We need SubTracked::ptr_t,
|
||||
* // SubTracked::getInstance() etc. to use SubTracked, not Tracked.
|
||||
* // This LLInstanceTrackerSubclass specialization is itself derived from
|
||||
* // Tracked.
|
||||
* class SubTracked: public LLInstanceTrackerSubclass<SubTracked, Tracked>
|
||||
* {
|
||||
* private:
|
||||
* using super = LLInstanceTrackerSubclass<SubTracked, Tracked>;
|
||||
* public:
|
||||
* // LLInstanceTrackerSubclass's constructor forwards to Tracked's.
|
||||
* SubTracked(const std::string& name): super(name) {}
|
||||
* // SubTracked::getInstance() returns std::shared_ptr<SubTracked>, etc.
|
||||
* // ...
|
||||
* @endcode
|
||||
*/
|
||||
template <typename SUBCLASS, typename T>
|
||||
class LLInstanceTrackerSubclass: public T
|
||||
{
|
||||
public:
|
||||
using ptr_t = std::shared_ptr<SUBCLASS>;
|
||||
using weak_t = std::weak_ptr<SUBCLASS>;
|
||||
|
||||
// forward any constructor call to the corresponding T ctor
|
||||
template <typename... ARGS>
|
||||
LLInstanceTrackerSubclass(ARGS&&... args):
|
||||
T(std::forward<ARGS>(args)...)
|
||||
{}
|
||||
|
||||
weak_t getWeak()
|
||||
{
|
||||
// call base-class getWeak(), try to lock, downcast to SUBCLASS
|
||||
return std::dynamic_pointer_cast<SUBCLASS>(T::getWeak().lock());
|
||||
}
|
||||
|
||||
template <typename KEY>
|
||||
static ptr_t getInstance(const KEY& k)
|
||||
{
|
||||
return std::dynamic_pointer_cast<SUBCLASS>(T::getInstance(k));
|
||||
}
|
||||
|
||||
using snapshot = typename T::template snapshot_of<SUBCLASS>;
|
||||
using instance_snapshot = typename T::template instance_snapshot_of<SUBCLASS>;
|
||||
using key_snapshot = typename T::template key_snapshot_of<SUBCLASS>;
|
||||
|
||||
static size_t instanceCount()
|
||||
{
|
||||
// T::instanceCount() lies because our snapshot, et al., won't
|
||||
// necessarily return all the T instances -- only those that are also
|
||||
// SUBCLASS instances. Count those.
|
||||
size_t count = 0;
|
||||
for (const auto& pair : snapshot())
|
||||
++count;
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLINSTANCETRACKERSUBCLASS_H) */
|
||||
|
|
@ -340,11 +340,28 @@ public:
|
|||
}
|
||||
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.
|
||||
try
|
||||
{
|
||||
// The LLSD object we got from our stream contains the
|
||||
// keys we need.
|
||||
LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
// No plugin should be allowed to crash the viewer by
|
||||
// driving an exception -- intentionally or not.
|
||||
LOG_UNHANDLED_EXCEPTION(stringize("handling request ", data));
|
||||
// Whether or not the plugin added a "reply" key to the
|
||||
// request, send a reply. We happen to know who originated
|
||||
// this request, and the reply LLEventPump of interest.
|
||||
// Not our problem if the plugin ignores the reply event.
|
||||
data["reply"] = mReplyPump.getName();
|
||||
sendReply(llsd::map("error",
|
||||
stringize(LLError::Log::classname(err), ": ", err.what())),
|
||||
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())
|
||||
|
|
|
|||
|
|
@ -14,14 +14,16 @@
|
|||
// associated header
|
||||
#include "llleaplistener.h"
|
||||
// STL headers
|
||||
#include <map>
|
||||
#include <algorithm> // std::find_if
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
// std headers
|
||||
// external library headers
|
||||
#include <boost/foreach.hpp>
|
||||
// other Linden headers
|
||||
#include "lluuid.h"
|
||||
#include "lazyeventapi.h"
|
||||
#include "llsdutil.h"
|
||||
#include "lluuid.h"
|
||||
#include "stringize.h"
|
||||
|
||||
/*****************************************************************************
|
||||
|
|
@ -110,7 +112,7 @@ LLLeapListener::~LLLeapListener()
|
|||
// 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)
|
||||
for (ListenersMap::value_type& pair : mListeners)
|
||||
{
|
||||
pair.second.disconnect();
|
||||
}
|
||||
|
|
@ -208,31 +210,65 @@ void LLLeapListener::getAPIs(const LLSD& request) const
|
|||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
// first, traverse existing LLEventAPI instances
|
||||
std::set<std::string> instances;
|
||||
for (auto& ea : LLEventAPI::instance_snapshot())
|
||||
{
|
||||
LLSD info;
|
||||
info["desc"] = ea.getDesc();
|
||||
reply[ea.getName()] = info;
|
||||
// remember which APIs are actually instantiated
|
||||
instances.insert(ea.getName());
|
||||
reply[ea.getName()] = llsd::map("desc", ea.getDesc());
|
||||
}
|
||||
// supplement that with *potential* instances: that is, instances of
|
||||
// LazyEventAPI that can each instantiate an LLEventAPI on demand
|
||||
for (const auto& lea : LL::LazyEventAPIBase::instance_snapshot())
|
||||
{
|
||||
// skip any LazyEventAPI that's already instantiated its LLEventAPI
|
||||
if (instances.find(lea.getName()) == instances.end())
|
||||
{
|
||||
reply[lea.getName()] = llsd::map("desc", lea.getDesc());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Because LazyEventAPI deliberately mimics LLEventAPI's query API, this
|
||||
// function can be passed either -- even though they're unrelated types.
|
||||
template <typename API>
|
||||
void reportAPI(LLEventAPI::Response& reply, const API& api)
|
||||
{
|
||||
reply["name"] = api.getName();
|
||||
reply["desc"] = api.getDesc();
|
||||
reply["key"] = api.getDispatchKey();
|
||||
LLSD ops;
|
||||
for (const auto& namedesc : api)
|
||||
{
|
||||
ops.append(api.getMetadata(namedesc.first));
|
||||
}
|
||||
reply["ops"] = ops;
|
||||
}
|
||||
|
||||
void LLLeapListener::getAPI(const LLSD& request) const
|
||||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
auto found = LLEventAPI::getInstance(request["api"]);
|
||||
if (found)
|
||||
// check first among existing LLEventAPI instances
|
||||
auto foundea = LLEventAPI::getInstance(request["api"]);
|
||||
if (foundea)
|
||||
{
|
||||
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)
|
||||
reportAPI(reply, *foundea);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here the requested LLEventAPI doesn't yet exist, but do we have a
|
||||
// registered LazyEventAPI for it?
|
||||
LL::LazyEventAPIBase::instance_snapshot snap;
|
||||
auto foundlea = std::find_if(snap.begin(), snap.end(),
|
||||
[api = request["api"].asString()]
|
||||
(const auto& lea)
|
||||
{ return (lea.getName() == api); });
|
||||
if (foundlea != snap.end())
|
||||
{
|
||||
ops.append(found->getMetadata(oi->first));
|
||||
reportAPI(reply, *foundlea);
|
||||
}
|
||||
reply["ops"] = ops;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
# include <sys/types.h>
|
||||
# include <mach/task.h>
|
||||
# include <mach/mach_init.h>
|
||||
#include <mach/mach_host.h>
|
||||
#elif LL_LINUX
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
|
@ -109,6 +110,50 @@ void LLMemory::updateMemoryInfo()
|
|||
{
|
||||
sAvailPhysicalMemInKB = U32Kilobytes(0);
|
||||
}
|
||||
|
||||
#elif defined(LL_DARWIN)
|
||||
task_vm_info info;
|
||||
mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT;
|
||||
// MACH_TASK_BASIC_INFO reports the same resident_size, but does not tell us the reusable bytes or phys_footprint.
|
||||
if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&info), &infoCount) == KERN_SUCCESS)
|
||||
{
|
||||
// Our Windows definition of PagefileUsage is documented by Microsoft as "the total amount of
|
||||
// memory that the memory manager has committed for a running process", which is rss.
|
||||
sAllocatedPageSizeInKB = U32Bytes(info.resident_size);
|
||||
|
||||
// Activity Monitor => Inspect Process => Real Memory Size appears to report resident_size
|
||||
// Activity monitor => main window memory column appears to report phys_footprint, which spot checks as at least 30% less.
|
||||
// I think that is because of compression, which isn't going to give us a consistent measurement. We want uncompressed totals.
|
||||
//
|
||||
// In between is resident_size - reusable. This is what Chrome source code uses, with source comments saying it is 'the "Real Memory" value
|
||||
// reported for the app by the Memory Monitor in Instruments.' It is still about 8% bigger than phys_footprint.
|
||||
//
|
||||
// (On Windows, we use WorkingSetSize.)
|
||||
sAllocatedMemInKB = U32Bytes(info.resident_size - info.reusable);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS() << "task_info failed" << LL_ENDL;
|
||||
}
|
||||
|
||||
// Total installed and available physical memory are properties of the host, not just our process.
|
||||
vm_statistics64_data_t vmstat;
|
||||
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
|
||||
mach_port_t host = mach_host_self();
|
||||
vm_size_t page_size;
|
||||
host_page_size(host, &page_size);
|
||||
kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast<host_info_t>(&vmstat), &count);
|
||||
if (result == KERN_SUCCESS) {
|
||||
// This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.'
|
||||
// Note though that inactive pages are not included here and not yet free, but could become so under memory pressure.
|
||||
sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size);
|
||||
sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize();
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS() << "task_info failed" << LL_ENDL;
|
||||
}
|
||||
|
||||
#else
|
||||
//not valid for other systems for now.
|
||||
sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS());
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@
|
|||
|
||||
//============================================================================
|
||||
|
||||
#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
|
||||
//#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
|
||||
#define MUTEX_DEBUG 0 //disable mutex debugging as it's interfering with profiles
|
||||
|
||||
#if MUTEX_DEBUG
|
||||
#include <map>
|
||||
|
|
@ -61,7 +62,7 @@ protected:
|
|||
mutable LLThread::id_t mLockingThread;
|
||||
|
||||
#if MUTEX_DEBUG
|
||||
std::map<LLThread::id_t, BOOL> mIsLocked;
|
||||
std::unordered_map<LLThread::id_t, BOOL> mIsLocked;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -341,4 +341,28 @@ private:
|
|||
bool mStayUnique;
|
||||
};
|
||||
|
||||
|
||||
// boost hash adapter
|
||||
template <class Type>
|
||||
struct boost::hash<LLPointer<Type>>
|
||||
{
|
||||
typedef LLPointer<Type> argument_type;
|
||||
typedef std::size_t result_type;
|
||||
result_type operator()(argument_type const& s) const
|
||||
{
|
||||
return (std::size_t) s.get();
|
||||
}
|
||||
};
|
||||
|
||||
// Adapt boost hash to std hash
|
||||
namespace std
|
||||
{
|
||||
template<class Type> struct hash<LLPointer<Type>>
|
||||
{
|
||||
std::size_t operator()(LLPointer<Type> const& s) const noexcept
|
||||
{
|
||||
return boost::hash<LLPointer<Type>>()(s);
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -86,8 +86,12 @@ extern thread_local bool gProfilerEnabled;
|
|||
#define TRACY_ONLY_IPV4 1
|
||||
#include "Tracy.hpp"
|
||||
|
||||
// Mutually exclusive with detailed memory tracing
|
||||
// Enable OpenGL profiling
|
||||
#define LL_PROFILER_ENABLE_TRACY_OPENGL 0
|
||||
|
||||
// Enable RenderDoc labeling
|
||||
#define LL_PROFILER_ENABLE_RENDER_DOC 0
|
||||
|
||||
#endif
|
||||
|
||||
#if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY
|
||||
|
|
@ -104,14 +108,13 @@ extern thread_local bool gProfilerEnabled;
|
|||
#define LL_PROFILE_ZONE_ERR(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0XFF0000 ) // RGB yellow
|
||||
#define LL_PROFILE_ZONE_INFO(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0X00FFFF ) // RGB cyan
|
||||
#define LL_PROFILE_ZONE_WARN(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0x0FFFF00 ) // RGB red
|
||||
#define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size)
|
||||
#define LL_PROFILE_FREE(ptr) TracyFree(ptr)
|
||||
#endif
|
||||
#if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_FAST_TIMER
|
||||
#define LL_PROFILER_FRAME_END
|
||||
#define LL_PROFILER_SET_THREAD_NAME( name ) (void)(name)
|
||||
#define LL_RECORD_BLOCK_TIME(name) const LLTrace::BlockTimer& LL_GLUE_TOKENS(block_time_recorder, __LINE__)(LLTrace::timeThisBlock(name)); (void)LL_GLUE_TOKENS(block_time_recorder, __LINE__);
|
||||
#define LL_PROFILE_ZONE_NAMED(name) // LL_PROFILE_ZONE_NAMED is a no-op when Tracy is disabled
|
||||
#define LL_PROFILE_ZONE_NAMED_COLOR(name,color) // LL_PROFILE_ZONE_NAMED_COLOR is a no-op when Tracy is disabled
|
||||
#define LL_PROFILE_ZONE_SCOPED // LL_PROFILE_ZONE_SCOPED is a no-op when Tracy is disabled
|
||||
#define LL_PROFILE_ZONE_COLOR(name,color) // LL_RECORD_BLOCK_TIME(name)
|
||||
|
||||
|
|
@ -121,8 +124,6 @@ extern thread_local bool gProfilerEnabled;
|
|||
#define LL_PROFILE_ZONE_ERR(name) (void)(name); // Not supported
|
||||
#define LL_PROFILE_ZONE_INFO(name) (void)(name); // Not supported
|
||||
#define LL_PROFILE_ZONE_WARN(name) (void)(name); // Not supported
|
||||
#define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size);
|
||||
#define LL_PROFILE_FREE(ptr) (void)(ptr);
|
||||
#endif
|
||||
#if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY_FAST_TIMER
|
||||
#define LL_PROFILER_FRAME_END FrameMark
|
||||
|
|
@ -138,14 +139,45 @@ extern thread_local bool gProfilerEnabled;
|
|||
#define LL_PROFILE_ZONE_ERR(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0XFF0000 ) // RGB yellow
|
||||
#define LL_PROFILE_ZONE_INFO(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0X00FFFF ) // RGB cyan
|
||||
#define LL_PROFILE_ZONE_WARN(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0x0FFFF00 ) // RGB red
|
||||
#define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size)
|
||||
#define LL_PROFILE_FREE(ptr) TracyFree(ptr)
|
||||
#endif
|
||||
#else
|
||||
#define LL_PROFILER_FRAME_END
|
||||
#define LL_PROFILER_SET_THREAD_NAME( name ) (void)(name)
|
||||
#endif // LL_PROFILER
|
||||
|
||||
#if LL_PROFILER_ENABLE_TRACY_OPENGL
|
||||
#define LL_PROFILE_GPU_ZONE(name) TracyGpuZone(name)
|
||||
#define LL_PROFILE_GPU_ZONEC(name,color) TracyGpuZoneC(name,color)
|
||||
#define LL_PROFILER_GPU_COLLECT TracyGpuCollect
|
||||
#define LL_PROFILER_GPU_CONTEXT TracyGpuContext
|
||||
|
||||
// disable memory tracking (incompatible with GPU tracing
|
||||
#define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size);
|
||||
#define LL_PROFILE_FREE(ptr) (void)(ptr);
|
||||
#else
|
||||
#define LL_PROFILE_GPU_ZONE(name) (void)name;
|
||||
#define LL_PROFILE_GPU_ZONEC(name,color) (void)name;(void)color;
|
||||
#define LL_PROFILER_GPU_COLLECT
|
||||
#define LL_PROFILER_GPU_CONTEXT
|
||||
|
||||
#define LL_LABEL_OBJECT_GL(type, name, length, label)
|
||||
|
||||
#if LL_PROFILER_CONFIGURATION > 1
|
||||
#define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size)
|
||||
#define LL_PROFILE_FREE(ptr) TracyFree(ptr)
|
||||
#else
|
||||
#define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size);
|
||||
#define LL_PROFILE_FREE(ptr) (void)(ptr);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if LL_PROFILER_ENABLE_RENDER_DOC
|
||||
#define LL_LABEL_OBJECT_GL(type, name, length, label) glObjectLabel(type, name, length, label)
|
||||
#else
|
||||
#define LL_LABEL_OBJECT_GL(type, name, length, label)
|
||||
#endif
|
||||
|
||||
#include "llprofilercategories.h"
|
||||
|
||||
#endif // LL_PROFILER_H
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
#define LL_PROFILER_CATEGORY_ENABLE_LOGGING 1
|
||||
#define LL_PROFILER_CATEGORY_ENABLE_MATERIAL 1
|
||||
#define LL_PROFILER_CATEGORY_ENABLE_MEDIA 1
|
||||
#define LL_PROFILER_CATEGORY_ENABLE_MEMORY 1
|
||||
#define LL_PROFILER_CATEGORY_ENABLE_MEMORY 0
|
||||
#define LL_PROFILER_CATEGORY_ENABLE_NETWORK 1
|
||||
#define LL_PROFILER_CATEGORY_ENABLE_OCTREE 1
|
||||
#define LL_PROFILER_CATEGORY_ENABLE_PIPELINE 1
|
||||
|
|
|
|||
|
|
@ -33,9 +33,12 @@
|
|||
|
||||
#include "llpointer.h"
|
||||
#include "llrefcount.h" // LLRefCount
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/type_traits/is_base_of.hpp>
|
||||
#include <boost/type_traits/remove_pointer.hpp>
|
||||
#include <boost/utility/enable_if.hpp>
|
||||
#include <memory> // std::shared_ptr, std::unique_ptr
|
||||
#include <type_traits>
|
||||
|
||||
/**
|
||||
* LLPtrTo<TARGET>::type is either of two things:
|
||||
|
|
@ -55,14 +58,14 @@ struct LLPtrTo
|
|||
|
||||
/// specialize for subclasses of LLRefCount
|
||||
template <class T>
|
||||
struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLRefCount, T> >::type>
|
||||
struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLRefCount, T>::value >::type>
|
||||
{
|
||||
typedef LLPointer<T> type;
|
||||
};
|
||||
|
||||
/// specialize for subclasses of LLThreadSafeRefCount
|
||||
template <class T>
|
||||
struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLThreadSafeRefCount, T> >::type>
|
||||
struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLThreadSafeRefCount, T>::value >::type>
|
||||
{
|
||||
typedef LLPointer<T> type;
|
||||
};
|
||||
|
|
@ -83,4 +86,83 @@ struct LLRemovePointer< LLPointer<SOMECLASS> >
|
|||
typedef SOMECLASS type;
|
||||
};
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
||||
/*****************************************************************************
|
||||
* get_ref()
|
||||
*****************************************************************************/
|
||||
template <typename T>
|
||||
struct GetRef
|
||||
{
|
||||
// return const ref or non-const ref, depending on whether we can bind
|
||||
// a non-const lvalue ref to the argument
|
||||
const auto& operator()(const T& obj) const { return obj; }
|
||||
auto& operator()(T& obj) const { return obj; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct GetRef<const T*>
|
||||
{
|
||||
const auto& operator()(const T* ptr) const { return *ptr; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct GetRef<T*>
|
||||
{
|
||||
auto& operator()(T* ptr) const { return *ptr; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct GetRef< LLPointer<T> >
|
||||
{
|
||||
auto& operator()(LLPointer<T> ptr) const { return *ptr; }
|
||||
};
|
||||
|
||||
/// whether we're passed a pointer or a reference, return a reference
|
||||
template <typename T>
|
||||
auto& get_ref(T& ptr_or_ref)
|
||||
{
|
||||
return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const auto& get_ref(const T& ptr_or_ref)
|
||||
{
|
||||
return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* get_ptr()
|
||||
*****************************************************************************/
|
||||
// if T is any pointer type we recognize, return it unchanged
|
||||
template <typename T>
|
||||
const T* get_ptr(const T* ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
T* get_ptr(T* ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
const std::shared_ptr<T>& get_ptr(const std::shared_ptr<T>& ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
const std::unique_ptr<T>& get_ptr(const std::unique_ptr<T>& ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
const boost::shared_ptr<T>& get_ptr(const boost::shared_ptr<T>& ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
const boost::intrusive_ptr<T>& get_ptr(const boost::intrusive_ptr<T>& ptr) { return ptr; }
|
||||
|
||||
template <typename T>
|
||||
const LLPointer<T>& get_ptr(const LLPointer<T>& ptr) { return ptr; }
|
||||
|
||||
// T is not any pointer type we recognize, take a pointer to the parameter
|
||||
template <typename T>
|
||||
const T* get_ptr(const T& obj) { return &obj; }
|
||||
|
||||
template <typename T>
|
||||
T* get_ptr(T& obj) { return &obj; }
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_LLPTRTO_H) */
|
||||
|
|
|
|||
|
|
@ -26,20 +26,26 @@
|
|||
#include "linden_common.h"
|
||||
#include "llqueuedthread.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "llstl.h"
|
||||
#include "lltimer.h" // ms_sleep()
|
||||
#include "lltracethreadrecorder.h"
|
||||
#include "llmutex.h"
|
||||
|
||||
//============================================================================
|
||||
|
||||
// MAIN THREAD
|
||||
LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool should_pause) :
|
||||
LLThread(name),
|
||||
mThreaded(threaded),
|
||||
mIdleThread(TRUE),
|
||||
mNextHandle(0),
|
||||
mStarted(FALSE)
|
||||
LLThread(name),
|
||||
mIdleThread(TRUE),
|
||||
mNextHandle(0),
|
||||
mStarted(FALSE),
|
||||
mThreaded(threaded),
|
||||
mRequestQueue(name, 1024 * 1024)
|
||||
{
|
||||
llassert(threaded); // not threaded implementation is deprecated
|
||||
mMainQueue = LL::WorkQueue::getInstance("mainloop");
|
||||
|
||||
if (mThreaded)
|
||||
{
|
||||
if(should_pause)
|
||||
|
|
@ -69,6 +75,11 @@ void LLQueuedThread::shutdown()
|
|||
unpause(); // MAIN THREAD
|
||||
if (mThreaded)
|
||||
{
|
||||
if (mRequestQueue.size() == 0)
|
||||
{
|
||||
mRequestQueue.close();
|
||||
}
|
||||
|
||||
S32 timeout = 100;
|
||||
for ( ; timeout>0; timeout--)
|
||||
{
|
||||
|
|
@ -104,6 +115,8 @@ void LLQueuedThread::shutdown()
|
|||
{
|
||||
LL_WARNS() << "~LLQueuedThread() called with active requests: " << active_count << LL_ENDL;
|
||||
}
|
||||
|
||||
mRequestQueue.close();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
|
@ -112,6 +125,7 @@ void LLQueuedThread::shutdown()
|
|||
// virtual
|
||||
size_t LLQueuedThread::update(F32 max_time_ms)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
if (!mStarted)
|
||||
{
|
||||
if (!mThreaded)
|
||||
|
|
@ -125,29 +139,34 @@ size_t LLQueuedThread::update(F32 max_time_ms)
|
|||
|
||||
size_t LLQueuedThread::updateQueue(F32 max_time_ms)
|
||||
{
|
||||
F64 max_time = (F64)max_time_ms * .001;
|
||||
LLTimer timer;
|
||||
size_t pending = 1;
|
||||
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
// Frame Update
|
||||
if (mThreaded)
|
||||
{
|
||||
pending = getPending();
|
||||
if(pending > 0)
|
||||
// schedule a call to threadedUpdate for every call to updateQueue
|
||||
if (!isQuitting())
|
||||
{
|
||||
mRequestQueue.post([=]()
|
||||
{
|
||||
LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update");
|
||||
mIdleThread = FALSE;
|
||||
threadedUpdate();
|
||||
mIdleThread = TRUE;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if(getPending() > 0)
|
||||
{
|
||||
unpause();
|
||||
}
|
||||
unpause();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (pending > 0)
|
||||
{
|
||||
pending = processNextRequest();
|
||||
if (max_time && timer.getElapsedTimeF64() > max_time)
|
||||
break;
|
||||
}
|
||||
mRequestQueue.runFor(std::chrono::microseconds((int) (max_time_ms*1000.f)));
|
||||
threadedUpdate();
|
||||
}
|
||||
return pending;
|
||||
return getPending();
|
||||
}
|
||||
|
||||
void LLQueuedThread::incQueue()
|
||||
|
|
@ -166,11 +185,7 @@ void LLQueuedThread::incQueue()
|
|||
// May be called from any thread
|
||||
size_t LLQueuedThread::getPending()
|
||||
{
|
||||
size_t res;
|
||||
lockData();
|
||||
res = mRequestQueue.size();
|
||||
unlockData();
|
||||
return res;
|
||||
return mRequestQueue.size();
|
||||
}
|
||||
|
||||
// MAIN thread
|
||||
|
|
@ -195,35 +210,28 @@ void LLQueuedThread::waitOnPending()
|
|||
// MAIN thread
|
||||
void LLQueuedThread::printQueueStats()
|
||||
{
|
||||
lockData();
|
||||
if (!mRequestQueue.empty())
|
||||
U32 size = mRequestQueue.size();
|
||||
if (size > 0)
|
||||
{
|
||||
QueuedRequest *req = *mRequestQueue.begin();
|
||||
LL_INFOS() << llformat("Pending Requests:%d Current status:%d", mRequestQueue.size(), req->getStatus()) << LL_ENDL;
|
||||
LL_INFOS() << llformat("Pending Requests:%d ", mRequestQueue.size()) << LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_INFOS() << "Queued Thread Idle" << LL_ENDL;
|
||||
}
|
||||
unlockData();
|
||||
}
|
||||
|
||||
// MAIN thread
|
||||
LLQueuedThread::handle_t LLQueuedThread::generateHandle()
|
||||
{
|
||||
lockData();
|
||||
while ((mNextHandle == nullHandle()) || (mRequestHash.find(mNextHandle)))
|
||||
{
|
||||
mNextHandle++;
|
||||
}
|
||||
const LLQueuedThread::handle_t res = mNextHandle++;
|
||||
unlockData();
|
||||
U32 res = ++mNextHandle;
|
||||
return res;
|
||||
}
|
||||
|
||||
// MAIN thread
|
||||
bool LLQueuedThread::addRequest(QueuedRequest* req)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
if (mStatus == QUITTING)
|
||||
{
|
||||
return false;
|
||||
|
|
@ -231,14 +239,14 @@ bool LLQueuedThread::addRequest(QueuedRequest* req)
|
|||
|
||||
lockData();
|
||||
req->setStatus(STATUS_QUEUED);
|
||||
mRequestQueue.insert(req);
|
||||
mRequestHash.insert(req);
|
||||
mRequestHash.insert(req);
|
||||
#if _DEBUG
|
||||
// LL_INFOS() << llformat("LLQueuedThread::Added req [%08d]",handle) << LL_ENDL;
|
||||
#endif
|
||||
unlockData();
|
||||
|
||||
incQueue();
|
||||
llassert(!mDataLock->isSelfLocked());
|
||||
mRequestQueue.post([this, req]() { processRequest(req); });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -246,6 +254,7 @@ bool LLQueuedThread::addRequest(QueuedRequest* req)
|
|||
// MAIN thread
|
||||
bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
llassert (handle != nullHandle());
|
||||
bool res = false;
|
||||
bool waspaused = isPaused();
|
||||
|
|
@ -312,6 +321,7 @@ LLQueuedThread::status_t LLQueuedThread::getRequestStatus(handle_t handle)
|
|||
|
||||
void LLQueuedThread::abortRequest(handle_t handle, bool autocomplete)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
|
||||
lockData();
|
||||
QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
|
||||
if (req)
|
||||
|
|
@ -333,30 +343,9 @@ void LLQueuedThread::setFlags(handle_t handle, U32 flags)
|
|||
unlockData();
|
||||
}
|
||||
|
||||
void LLQueuedThread::setPriority(handle_t handle, U32 priority)
|
||||
{
|
||||
lockData();
|
||||
QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
|
||||
if (req)
|
||||
{
|
||||
if(req->getStatus() == STATUS_INPROGRESS)
|
||||
{
|
||||
// not in list
|
||||
req->setPriority(priority);
|
||||
}
|
||||
else if(req->getStatus() == STATUS_QUEUED)
|
||||
{
|
||||
// remove from list then re-insert
|
||||
llverify(mRequestQueue.erase(req) == 1);
|
||||
req->setPriority(priority);
|
||||
mRequestQueue.insert(req);
|
||||
}
|
||||
}
|
||||
unlockData();
|
||||
}
|
||||
|
||||
bool LLQueuedThread::completeRequest(handle_t handle)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
bool res = false;
|
||||
lockData();
|
||||
QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
|
||||
|
|
@ -399,88 +388,120 @@ bool LLQueuedThread::check()
|
|||
//============================================================================
|
||||
// Runs on its OWN thread
|
||||
|
||||
size_t LLQueuedThread::processNextRequest()
|
||||
void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req)
|
||||
{
|
||||
QueuedRequest *req;
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
|
||||
|
||||
mIdleThread = FALSE;
|
||||
//threadedUpdate();
|
||||
|
||||
// Get next request from pool
|
||||
lockData();
|
||||
|
||||
while(1)
|
||||
if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING))
|
||||
{
|
||||
req = NULL;
|
||||
if (mRequestQueue.empty())
|
||||
LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - abort");
|
||||
req->setStatus(STATUS_ABORTED);
|
||||
req->finishRequest(false);
|
||||
if (req->getFlags() & FLAG_AUTO_COMPLETE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
req = *mRequestQueue.begin();
|
||||
mRequestQueue.erase(mRequestQueue.begin());
|
||||
if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING))
|
||||
{
|
||||
req->setStatus(STATUS_ABORTED);
|
||||
req->finishRequest(false);
|
||||
if (req->getFlags() & FLAG_AUTO_COMPLETE)
|
||||
{
|
||||
mRequestHash.erase(req);
|
||||
req->deleteRequest();
|
||||
mRequestHash.erase(req);
|
||||
req->deleteRequest();
|
||||
// check();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
llassert_always(req->getStatus() == STATUS_QUEUED);
|
||||
break;
|
||||
unlockData();
|
||||
}
|
||||
U32 start_priority = 0 ;
|
||||
if (req)
|
||||
{
|
||||
req->setStatus(STATUS_INPROGRESS);
|
||||
start_priority = req->getPriority();
|
||||
}
|
||||
unlockData();
|
||||
else
|
||||
{
|
||||
llassert_always(req->getStatus() == STATUS_QUEUED);
|
||||
|
||||
// This is the only place we will call req->setStatus() after
|
||||
// it has initially been seet to STATUS_QUEUED, so it is
|
||||
// safe to access req.
|
||||
if (req)
|
||||
{
|
||||
// process request
|
||||
bool complete = req->processRequest();
|
||||
if (req)
|
||||
{
|
||||
req->setStatus(STATUS_INPROGRESS);
|
||||
}
|
||||
unlockData();
|
||||
|
||||
if (complete)
|
||||
{
|
||||
lockData();
|
||||
req->setStatus(STATUS_COMPLETE);
|
||||
req->finishRequest(true);
|
||||
if (req->getFlags() & FLAG_AUTO_COMPLETE)
|
||||
{
|
||||
mRequestHash.erase(req);
|
||||
req->deleteRequest();
|
||||
// check();
|
||||
}
|
||||
unlockData();
|
||||
}
|
||||
else
|
||||
{
|
||||
lockData();
|
||||
req->setStatus(STATUS_QUEUED);
|
||||
mRequestQueue.insert(req);
|
||||
unlockData();
|
||||
if (mThreaded && start_priority < PRIORITY_NORMAL)
|
||||
{
|
||||
ms_sleep(1); // sleep the thread a little
|
||||
}
|
||||
}
|
||||
|
||||
LLTrace::get_thread_recorder()->pushToParent();
|
||||
}
|
||||
// This is the only place we will call req->setStatus() after
|
||||
// it has initially been seet to STATUS_QUEUED, so it is
|
||||
// safe to access req.
|
||||
if (req)
|
||||
{
|
||||
// process request
|
||||
bool complete = req->processRequest();
|
||||
|
||||
return getPending();
|
||||
if (complete)
|
||||
{
|
||||
LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - complete");
|
||||
lockData();
|
||||
req->setStatus(STATUS_COMPLETE);
|
||||
req->finishRequest(true);
|
||||
if (req->getFlags() & FLAG_AUTO_COMPLETE)
|
||||
{
|
||||
mRequestHash.erase(req);
|
||||
req->deleteRequest();
|
||||
// check();
|
||||
}
|
||||
unlockData();
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - retry");
|
||||
//put back on queue and try again in 0.1ms
|
||||
lockData();
|
||||
req->setStatus(STATUS_QUEUED);
|
||||
|
||||
unlockData();
|
||||
|
||||
llassert(!mDataLock->isSelfLocked());
|
||||
|
||||
#if 0
|
||||
// try again on next frame
|
||||
// NOTE: tried using "post" with a time in the future, but this
|
||||
// would invariably cause this thread to wait for a long time (10+ ms)
|
||||
// while work is pending
|
||||
bool ret = LL::WorkQueue::postMaybe(
|
||||
mMainQueue,
|
||||
[=]()
|
||||
{
|
||||
LL_PROFILE_ZONE_NAMED("processRequest - retry");
|
||||
mRequestQueue.post([=]()
|
||||
{
|
||||
LL_PROFILE_ZONE_NAMED("processRequest - retry"); // <-- not redundant, track retry on both queues
|
||||
processRequest(req);
|
||||
});
|
||||
});
|
||||
llassert(ret);
|
||||
#else
|
||||
using namespace std::chrono_literals;
|
||||
auto retry_time = LL::WorkQueue::TimePoint::clock::now() + 16ms;
|
||||
mRequestQueue.post([=]
|
||||
{
|
||||
LL_PROFILE_ZONE_NAMED("processRequest - retry");
|
||||
if (LL::WorkQueue::TimePoint::clock::now() < retry_time)
|
||||
{
|
||||
auto sleep_time = std::chrono::duration_cast<std::chrono::milliseconds>(retry_time - LL::WorkQueue::TimePoint::clock::now());
|
||||
|
||||
if (sleep_time.count() > 0)
|
||||
{
|
||||
ms_sleep(sleep_time.count());
|
||||
}
|
||||
}
|
||||
processRequest(req);
|
||||
});
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mIdleThread = TRUE;
|
||||
}
|
||||
|
||||
// virtual
|
||||
bool LLQueuedThread::runCondition()
|
||||
{
|
||||
// mRunCondition must be locked here
|
||||
if (mRequestQueue.empty() && mIdleThread)
|
||||
if (mRequestQueue.size() == 0 && mIdleThread)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
|
|
@ -494,18 +515,13 @@ void LLQueuedThread::run()
|
|||
startThread();
|
||||
mStarted = TRUE;
|
||||
|
||||
while (1)
|
||||
|
||||
/*while (1)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
// this will block on the condition until runCondition() returns true, the thread is unpaused, or the thread leaves the RUNNING state.
|
||||
checkPause();
|
||||
|
||||
if (isQuitting())
|
||||
{
|
||||
LLTrace::get_thread_recorder()->pushToParent();
|
||||
endThread();
|
||||
break;
|
||||
}
|
||||
|
||||
mIdleThread = FALSE;
|
||||
|
||||
threadedUpdate();
|
||||
|
|
@ -514,12 +530,18 @@ void LLQueuedThread::run()
|
|||
|
||||
if (pending_work == 0)
|
||||
{
|
||||
//LL_PROFILE_ZONE_NAMED("LLQueuedThread - sleep");
|
||||
mIdleThread = TRUE;
|
||||
ms_sleep(1);
|
||||
//ms_sleep(1);
|
||||
}
|
||||
//LLThread::yield(); // thread should yield after each request
|
||||
}
|
||||
}*/
|
||||
mRequestQueue.runUntilClose();
|
||||
|
||||
endThread();
|
||||
LL_INFOS() << "LLQueuedThread " << mName << " EXITING." << LL_ENDL;
|
||||
|
||||
|
||||
}
|
||||
|
||||
// virtual
|
||||
|
|
@ -539,10 +561,9 @@ void LLQueuedThread::threadedUpdate()
|
|||
|
||||
//============================================================================
|
||||
|
||||
LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 priority, U32 flags) :
|
||||
LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 flags) :
|
||||
LLSimpleHashEntry<LLQueuedThread::handle_t>(handle),
|
||||
mStatus(STATUS_UNKNOWN),
|
||||
mPriority(priority),
|
||||
mFlags(flags)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
#include "llthread.h"
|
||||
#include "llsimplehash.h"
|
||||
#include "workqueue.h"
|
||||
|
||||
//============================================================================
|
||||
// Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small
|
||||
|
|
@ -45,15 +46,6 @@ class LL_COMMON_API LLQueuedThread : public LLThread
|
|||
{
|
||||
//------------------------------------------------------------------------
|
||||
public:
|
||||
enum priority_t {
|
||||
PRIORITY_IMMEDIATE = 0x7FFFFFFF,
|
||||
PRIORITY_URGENT = 0x40000000,
|
||||
PRIORITY_HIGH = 0x30000000,
|
||||
PRIORITY_NORMAL = 0x20000000,
|
||||
PRIORITY_LOW = 0x10000000,
|
||||
PRIORITY_LOWBITS = 0x0FFFFFFF,
|
||||
PRIORITY_HIGHBITS = 0x70000000
|
||||
};
|
||||
enum status_t {
|
||||
STATUS_EXPIRED = -1,
|
||||
STATUS_UNKNOWN = 0,
|
||||
|
|
@ -82,28 +74,17 @@ public:
|
|||
virtual ~QueuedRequest(); // use deleteRequest()
|
||||
|
||||
public:
|
||||
QueuedRequest(handle_t handle, U32 priority, U32 flags = 0);
|
||||
QueuedRequest(handle_t handle, U32 flags = 0);
|
||||
|
||||
status_t getStatus()
|
||||
{
|
||||
return mStatus;
|
||||
}
|
||||
U32 getPriority() const
|
||||
{
|
||||
return mPriority;
|
||||
}
|
||||
U32 getFlags() const
|
||||
{
|
||||
return mFlags;
|
||||
}
|
||||
bool higherPriority(const QueuedRequest& second) const
|
||||
{
|
||||
if ( mPriority == second.mPriority)
|
||||
return mHashKey < second.mHashKey;
|
||||
else
|
||||
return mPriority > second.mPriority;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
status_t setStatus(status_t newstatus)
|
||||
{
|
||||
|
|
@ -121,28 +102,11 @@ public:
|
|||
virtual void finishRequest(bool completed); // Always called from thread after request has completed or aborted
|
||||
virtual void deleteRequest(); // Only method to delete a request
|
||||
|
||||
void setPriority(U32 pri)
|
||||
{
|
||||
// Only do this on a request that is not in a queued list!
|
||||
mPriority = pri;
|
||||
};
|
||||
|
||||
protected:
|
||||
LLAtomicBase<status_t> mStatus;
|
||||
U32 mPriority;
|
||||
U32 mFlags;
|
||||
};
|
||||
|
||||
protected:
|
||||
struct queued_request_less
|
||||
{
|
||||
bool operator()(const QueuedRequest* lhs, const QueuedRequest* rhs) const
|
||||
{
|
||||
return lhs->higherPriority(*rhs); // higher priority in front of queue (set)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
public:
|
||||
|
|
@ -167,7 +131,7 @@ private:
|
|||
protected:
|
||||
handle_t generateHandle();
|
||||
bool addRequest(QueuedRequest* req);
|
||||
size_t processNextRequest(void);
|
||||
void processRequest(QueuedRequest* req);
|
||||
void incQueue();
|
||||
|
||||
public:
|
||||
|
|
@ -186,7 +150,6 @@ public:
|
|||
status_t getRequestStatus(handle_t handle);
|
||||
void abortRequest(handle_t handle, bool autocomplete);
|
||||
void setFlags(handle_t handle, U32 flags);
|
||||
void setPriority(handle_t handle, U32 priority);
|
||||
bool completeRequest(handle_t handle);
|
||||
// This is public for support classes like LLWorkerThread,
|
||||
// but generally the methods above should be used.
|
||||
|
|
@ -200,8 +163,10 @@ protected:
|
|||
BOOL mStarted; // required when mThreaded is false to call startThread() from update()
|
||||
LLAtomicBool mIdleThread; // request queue is empty (or we are quitting) and the thread is idle
|
||||
|
||||
typedef std::set<QueuedRequest*, queued_request_less> request_queue_t;
|
||||
request_queue_t mRequestQueue;
|
||||
//typedef std::set<QueuedRequest*, queued_request_less> request_queue_t;
|
||||
//request_queue_t mRequestQueue;
|
||||
LL::WorkQueue mRequestQueue;
|
||||
LL::WorkQueue::weak_t mMainQueue;
|
||||
|
||||
enum { REQUEST_HASH_SIZE = 512 }; // must be power of 2
|
||||
typedef LLSimpleHash<handle_t, REQUEST_HASH_SIZE> request_hash_t;
|
||||
|
|
|
|||
|
|
@ -475,6 +475,7 @@ LLSDNotationParser::~LLSDNotationParser()
|
|||
// virtual
|
||||
S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
|
||||
// map: { string:object, string:object }
|
||||
// array: [ object, object, object ]
|
||||
// undef: !
|
||||
|
|
@ -734,6 +735,7 @@ S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) c
|
|||
|
||||
S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
|
||||
// map: { string:object, string:object }
|
||||
map = LLSD::emptyMap();
|
||||
S32 parse_count = 0;
|
||||
|
|
@ -794,6 +796,7 @@ S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) c
|
|||
|
||||
S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
|
||||
// array: [ object, object, object ]
|
||||
array = LLSD::emptyArray();
|
||||
S32 parse_count = 0;
|
||||
|
|
@ -833,6 +836,7 @@ S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_dept
|
|||
|
||||
bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
|
||||
std::string value;
|
||||
auto count = deserialize_string(istr, value, mMaxBytesLeft);
|
||||
if(PARSE_FAILURE == count) return false;
|
||||
|
|
@ -843,6 +847,7 @@ bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const
|
|||
|
||||
bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
|
||||
// binary: b##"ff3120ab1"
|
||||
// or: b(len)"..."
|
||||
|
||||
|
|
@ -945,6 +950,7 @@ LLSDBinaryParser::~LLSDBinaryParser()
|
|||
// virtual
|
||||
S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
|
||||
/**
|
||||
* Undefined: '!'<br>
|
||||
* Boolean: '1' for true '0' for false<br>
|
||||
|
|
|
|||
|
|
@ -923,6 +923,8 @@ void LLSDXMLParser::parsePart(const char *buf, llssize len)
|
|||
// virtual
|
||||
S32 LLSDXMLParser::doParse(std::istream& input, LLSD& data, S32 max_depth) const
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
|
||||
|
||||
#ifdef XML_PARSER_PERFORMANCE_TESTS
|
||||
XML_Timer timer( &parseTime );
|
||||
#endif // XML_PARSER_PERFORMANCE_TESTS
|
||||
|
|
|
|||
|
|
@ -1046,3 +1046,38 @@ LLSD llsd_shallow(LLSD value, LLSD filter)
|
|||
|
||||
return shallow;
|
||||
}
|
||||
|
||||
LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args)
|
||||
{
|
||||
// LLSD supports a number of types, two of which are aggregates: Map and
|
||||
// Array. We don't try to support Map: supporting Map would seem to
|
||||
// promise that we could somehow match the string key to 'func's parameter
|
||||
// names. Uh sorry, maybe in some future version of C++ with reflection.
|
||||
if (args.isMap())
|
||||
{
|
||||
LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported"));
|
||||
}
|
||||
// We expect an LLSD array, but what the heck, treat isUndefined() as a
|
||||
// zero-length array for calling a nullary 'func'.
|
||||
if (args.isUndefined() || args.isArray())
|
||||
{
|
||||
// this works because LLSD().size() == 0
|
||||
if (args.size() != arity)
|
||||
{
|
||||
LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ",
|
||||
args.size(), "-entry LLSD array)")));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
// args is one of the scalar types
|
||||
// scalar_LLSD.size() == 0, so don't test that here.
|
||||
// You can pass a scalar LLSD only to a unary 'func'.
|
||||
if (arity != 1)
|
||||
{
|
||||
LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), "
|
||||
"LLSD ", LLSD::typeString(args.type()), ")")));
|
||||
}
|
||||
// make an array of it
|
||||
return llsd::array(args);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,14 @@
|
|||
#ifndef LL_LLSDUTIL_H
|
||||
#define LL_LLSDUTIL_H
|
||||
|
||||
#include "apply.h" // LL::invoke()
|
||||
#include "function_types.h" // LL::function_arity
|
||||
#include "llsd.h"
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <cassert>
|
||||
#include <memory> // std::shared_ptr
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
// U32
|
||||
LL_COMMON_API LLSD ll_sd_from_U32(const U32);
|
||||
|
|
@ -298,6 +304,11 @@ LLSD map(Ts&&... vs)
|
|||
/*****************************************************************************
|
||||
* LLSDParam
|
||||
*****************************************************************************/
|
||||
struct LLSDParamBase
|
||||
{
|
||||
virtual ~LLSDParamBase() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* LLSDParam is a customization point for passing LLSD values to function
|
||||
* parameters of more or less arbitrary type. LLSD provides a small set of
|
||||
|
|
@ -315,7 +326,7 @@ LLSD map(Ts&&... vs)
|
|||
* @endcode
|
||||
*/
|
||||
template <typename T>
|
||||
class LLSDParam
|
||||
class LLSDParam: public LLSDParamBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
|
|
@ -323,13 +334,66 @@ public:
|
|||
* value for later retrieval
|
||||
*/
|
||||
LLSDParam(const LLSD& value):
|
||||
_value(value)
|
||||
value_(value)
|
||||
{}
|
||||
|
||||
operator T() const { return _value; }
|
||||
operator T() const { return value_; }
|
||||
|
||||
private:
|
||||
T _value;
|
||||
T value_;
|
||||
};
|
||||
|
||||
/**
|
||||
* LLSDParam<LLSD> is for when you don't already have the target parameter
|
||||
* type in hand. Instantiate LLSDParam<LLSD>(your LLSD object), and the
|
||||
* templated conversion operator will try to select a more specific LLSDParam
|
||||
* specialization.
|
||||
*/
|
||||
template <>
|
||||
class LLSDParam<LLSD>: public LLSDParamBase
|
||||
{
|
||||
private:
|
||||
LLSD value_;
|
||||
// LLSDParam<LLSD>::operator T() works by instantiating an LLSDParam<T> on
|
||||
// demand. Returning that engages LLSDParam<T>::operator T(), producing
|
||||
// the desired result. But LLSDParam<const char*> owns a std::string whose
|
||||
// c_str() is returned by its operator const char*(). If we return a temp
|
||||
// LLSDParam<const char*>, the compiler can destroy it right away, as soon
|
||||
// as we've called operator const char*(). That's a problem! That
|
||||
// invalidates the const char* we've just passed to the subject function.
|
||||
// This LLSDParam<LLSD> is presumably guaranteed to survive until the
|
||||
// subject function has returned, so we must ensure that any constructed
|
||||
// LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting
|
||||
// each LLSDParam<T> on the heap and capturing a smart pointer in a vector
|
||||
// works. We would have liked to use std::unique_ptr, but vector entries
|
||||
// must be copyable.
|
||||
// (Alternatively we could assume that every instance of LLSDParam<LLSD>
|
||||
// will be asked for at most ONE conversion. We could store a scalar
|
||||
// std::unique_ptr and, when constructing an new LLSDParam<T>, assert that
|
||||
// the unique_ptr is empty. But some future change in usage patterns, and
|
||||
// consequent failure of that assertion, would be very mysterious. Instead
|
||||
// of explaining how to fix it, just fix it now.)
|
||||
mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_;
|
||||
|
||||
public:
|
||||
LLSDParam(const LLSD& value): value_(value) {}
|
||||
|
||||
/// if we're literally being asked for an LLSD parameter, avoid infinite
|
||||
/// recursion
|
||||
operator LLSD() const { return value_; }
|
||||
|
||||
/// otherwise, instantiate a more specific LLSDParam<T> to convert; that
|
||||
/// preserves the existing customization mechanism
|
||||
template <typename T>
|
||||
operator T() const
|
||||
{
|
||||
// capture 'ptr' with the specific subclass type because converters_
|
||||
// only stores LLSDParamBase pointers
|
||||
auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) };
|
||||
// keep the new converter alive until we ourselves are destroyed
|
||||
converters_.push_back(ptr);
|
||||
return *ptr;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -346,17 +410,17 @@ private:
|
|||
*/
|
||||
#define LLSDParam_for(T, AS) \
|
||||
template <> \
|
||||
class LLSDParam<T> \
|
||||
class LLSDParam<T>: public LLSDParamBase \
|
||||
{ \
|
||||
public: \
|
||||
LLSDParam(const LLSD& value): \
|
||||
_value((T)value.AS()) \
|
||||
value_((T)value.AS()) \
|
||||
{} \
|
||||
\
|
||||
operator T() const { return _value; } \
|
||||
operator T() const { return value_; } \
|
||||
\
|
||||
private: \
|
||||
T _value; \
|
||||
T value_; \
|
||||
}
|
||||
|
||||
LLSDParam_for(float, asReal);
|
||||
|
|
@ -372,31 +436,31 @@ LLSDParam_for(LLSD::Binary, asBinary);
|
|||
* safely pass an LLSDParam<const char*>(yourLLSD).
|
||||
*/
|
||||
template <>
|
||||
class LLSDParam<const char*>
|
||||
class LLSDParam<const char*>: public LLSDParamBase
|
||||
{
|
||||
private:
|
||||
// The difference here is that we store a std::string rather than a const
|
||||
// char*. It's important that the LLSDParam object own the std::string.
|
||||
std::string _value;
|
||||
std::string value_;
|
||||
// We don't bother storing the incoming LLSD object, but we do have to
|
||||
// distinguish whether _value is an empty string because the LLSD object
|
||||
// distinguish whether value_ is an empty string because the LLSD object
|
||||
// contains an empty string or because it's isUndefined().
|
||||
bool _undefined;
|
||||
bool undefined_;
|
||||
|
||||
public:
|
||||
LLSDParam(const LLSD& value):
|
||||
_value(value),
|
||||
_undefined(value.isUndefined())
|
||||
value_(value),
|
||||
undefined_(value.isUndefined())
|
||||
{}
|
||||
|
||||
// The const char* we retrieve is for storage owned by our _value member.
|
||||
// The const char* we retrieve is for storage owned by our value_ member.
|
||||
// That's how we guarantee that the const char* is valid for the lifetime
|
||||
// of this LLSDParam object. Constructing your LLSDParam in the argument
|
||||
// list should ensure that the LLSDParam object will persist for the
|
||||
// duration of the function call.
|
||||
operator const char*() const
|
||||
{
|
||||
if (_undefined)
|
||||
if (undefined_)
|
||||
{
|
||||
// By default, an isUndefined() LLSD object's asString() method
|
||||
// will produce an empty string. But for a function accepting
|
||||
|
|
@ -406,7 +470,7 @@ public:
|
|||
// case, though, no LLSD value could pass NULL.
|
||||
return NULL;
|
||||
}
|
||||
return _value.c_str();
|
||||
return value_.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -555,4 +619,56 @@ struct hash<LLSD>
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
||||
/*****************************************************************************
|
||||
* apply(function, LLSD array)
|
||||
*****************************************************************************/
|
||||
// validate incoming LLSD blob, and return an LLSD array suitable to pass to
|
||||
// the function of interest
|
||||
LLSD apply_llsd_fix(size_t arity, const LLSD& args);
|
||||
|
||||
// Derived from https://stackoverflow.com/a/20441189
|
||||
// and https://en.cppreference.com/w/cpp/utility/apply .
|
||||
// We can't simply make a tuple from the LLSD array and then apply() that
|
||||
// tuple to the function -- how would make_tuple() deduce the correct
|
||||
// parameter type for each entry? We must go directly to the target function.
|
||||
template <typename CALLABLE, std::size_t... I>
|
||||
auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
|
||||
{
|
||||
// call func(unpacked args), using generic LLSDParam<LLSD> to convert each
|
||||
// entry in 'array' to the target parameter type
|
||||
return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...);
|
||||
}
|
||||
|
||||
// use apply_n<ARITY>(function, LLSD) to call a specific arity of a variadic
|
||||
// function with (that many) items from the passed LLSD array
|
||||
template <size_t ARITY, typename CALLABLE>
|
||||
auto apply_n(CALLABLE&& func, const LLSD& args)
|
||||
{
|
||||
return apply_impl(std::forward<CALLABLE>(func),
|
||||
apply_llsd_fix(ARITY, args),
|
||||
std::make_index_sequence<ARITY>());
|
||||
}
|
||||
|
||||
/**
|
||||
* apply(function, LLSD) goes beyond C++17 std::apply(). For this case
|
||||
* @a function @emph cannot be variadic: the compiler must know at compile
|
||||
* time how many arguments to pass. This isn't Python. (But see apply_n() to
|
||||
* pass a specific number of args to a variadic function.)
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
auto apply(CALLABLE&& func, const LLSD& args)
|
||||
{
|
||||
// infer arity from the definition of func
|
||||
constexpr auto arity = function_arity<
|
||||
typename std::remove_reference<CALLABLE>::type>::value;
|
||||
// now that we have a compile-time arity, apply_n() works
|
||||
return apply_n<arity>(std::forward<CALLABLE>(func), args);
|
||||
}
|
||||
|
||||
} // namespace LL
|
||||
|
||||
#endif // LL_LLSDUTIL_H
|
||||
|
|
|
|||
|
|
@ -771,20 +771,28 @@ static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB)
|
|||
}
|
||||
#endif
|
||||
|
||||
#if LL_DARWIN
|
||||
// static
|
||||
U32Kilobytes LLMemoryInfo::getHardwareMemSize()
|
||||
{
|
||||
// This might work on Linux as well. Someone check...
|
||||
uint64_t phys = 0;
|
||||
int mib[2] = { CTL_HW, HW_MEMSIZE };
|
||||
|
||||
size_t len = sizeof(phys);
|
||||
sysctl(mib, 2, &phys, &len, NULL, 0);
|
||||
|
||||
return U64Bytes(phys);
|
||||
}
|
||||
#endif
|
||||
|
||||
U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger()));
|
||||
|
||||
#elif LL_DARWIN
|
||||
// This might work on Linux as well. Someone check...
|
||||
uint64_t phys = 0;
|
||||
int mib[2] = { CTL_HW, HW_MEMSIZE };
|
||||
|
||||
size_t len = sizeof(phys);
|
||||
sysctl(mib, 2, &phys, &len, NULL, 0);
|
||||
|
||||
return U64Bytes(phys);
|
||||
return getHardwareMemSize();
|
||||
|
||||
#elif LL_LINUX
|
||||
U64 phys = 0;
|
||||
|
|
|
|||
|
|
@ -129,7 +129,10 @@ public:
|
|||
LLMemoryInfo(); ///< Default constructor
|
||||
void stream(std::ostream& s) const; ///< output text info to s
|
||||
|
||||
U32Kilobytes getPhysicalMemoryKB() const;
|
||||
U32Kilobytes getPhysicalMemoryKB() const;
|
||||
#if LL_DARWIN
|
||||
static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib.
|
||||
#endif
|
||||
|
||||
//get the available memory infomation in KiloBytes.
|
||||
static void getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb);
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
|
||||
|
||||
#ifdef LL_WINDOWS
|
||||
|
||||
const DWORD MS_VC_EXCEPTION=0x406D1388;
|
||||
|
||||
#pragma pack(push,8)
|
||||
|
|
@ -134,6 +135,15 @@ void LLThread::threadRun()
|
|||
{
|
||||
#ifdef LL_WINDOWS
|
||||
set_thread_name(-1, mName.c_str());
|
||||
|
||||
#if 0 // probably a bad idea, see usage of SetThreadIdealProcessor in LLWindowWin32)
|
||||
HANDLE hThread = GetCurrentThread();
|
||||
if (hThread)
|
||||
{
|
||||
SetThreadAffinityMask(hThread, (DWORD_PTR) 0xFFFFFFFFFFFFFFFE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
LL_PROFILER_SET_THREAD_NAME( mName.c_str() );
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@
|
|||
|
||||
#include "u64.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#if LL_WINDOWS
|
||||
# include "llwin32headerslean.h"
|
||||
#elif LL_LINUX || LL_DARWIN
|
||||
|
|
@ -62,9 +65,18 @@ LLTimer* LLTimer::sTimer = NULL;
|
|||
//---------------------------------------------------------------------------
|
||||
|
||||
#if LL_WINDOWS
|
||||
|
||||
|
||||
#if 0
|
||||
void ms_sleep(U32 ms)
|
||||
{
|
||||
Sleep(ms);
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
using TimePoint = std::chrono::steady_clock::time_point;
|
||||
auto resume_time = TimePoint::clock::now() + std::chrono::milliseconds(ms);
|
||||
while (TimePoint::clock::now() < resume_time)
|
||||
{
|
||||
std::this_thread::yield(); //note: don't use LLThread::yield here to avoid yielding for too long
|
||||
}
|
||||
}
|
||||
|
||||
U32 micro_sleep(U64 us, U32 max_yields)
|
||||
|
|
@ -74,6 +86,35 @@ U32 micro_sleep(U64 us, U32 max_yields)
|
|||
ms_sleep((U32)(us / 1000));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
U32 micro_sleep(U64 us, U32 max_yields)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED
|
||||
#if 0
|
||||
LARGE_INTEGER ft;
|
||||
ft.QuadPart = -static_cast<S64>(us * 10); // '-' using relative time
|
||||
|
||||
HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL);
|
||||
SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
|
||||
WaitForSingleObject(timer, INFINITE);
|
||||
CloseHandle(timer);
|
||||
#else
|
||||
Sleep(us / 1000);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ms_sleep(U32 ms)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED
|
||||
micro_sleep(ms * 1000, 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#elif LL_LINUX || LL_DARWIN
|
||||
static void _sleep_loop(struct timespec& thiswait)
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -73,6 +73,7 @@ void LLWorkerThread::clearDeleteList()
|
|||
{
|
||||
worker->mRequestHandle = LLWorkerThread::nullHandle();
|
||||
worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK);
|
||||
worker->clearFlags(LLWorkerClass::WCF_WORKING);
|
||||
delete worker;
|
||||
}
|
||||
mDeleteList.clear() ;
|
||||
|
|
@ -97,6 +98,7 @@ size_t LLWorkerThread::update(F32 max_time_ms)
|
|||
{
|
||||
if (worker->getFlags(LLWorkerClass::WCF_WORK_FINISHED))
|
||||
{
|
||||
worker->setFlags(LLWorkerClass::WCF_DELETE_REQUESTED);
|
||||
delete_list.push_back(worker);
|
||||
mDeleteList.erase(curiter);
|
||||
}
|
||||
|
|
@ -130,11 +132,11 @@ size_t LLWorkerThread::update(F32 max_time_ms)
|
|||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param, U32 priority)
|
||||
LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param)
|
||||
{
|
||||
handle_t handle = generateHandle();
|
||||
|
||||
WorkRequest* req = new WorkRequest(handle, priority, workerclass, param);
|
||||
WorkRequest* req = new WorkRequest(handle, workerclass, param);
|
||||
|
||||
bool res = addRequest(req);
|
||||
if (!res)
|
||||
|
|
@ -157,8 +159,8 @@ void LLWorkerThread::deleteWorker(LLWorkerClass* workerclass)
|
|||
//============================================================================
|
||||
// Runs on its OWN thread
|
||||
|
||||
LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, U32 priority, LLWorkerClass* workerclass, S32 param) :
|
||||
LLQueuedThread::QueuedRequest(handle, priority),
|
||||
LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param) :
|
||||
LLQueuedThread::QueuedRequest(handle),
|
||||
mWorkerClass(workerclass),
|
||||
mParam(param)
|
||||
{
|
||||
|
|
@ -177,6 +179,7 @@ void LLWorkerThread::WorkRequest::deleteRequest()
|
|||
// virtual
|
||||
bool LLWorkerThread::WorkRequest::processRequest()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
LLWorkerClass* workerclass = getWorkerClass();
|
||||
workerclass->setWorking(true);
|
||||
bool complete = workerclass->doWork(getParam());
|
||||
|
|
@ -187,6 +190,7 @@ bool LLWorkerThread::WorkRequest::processRequest()
|
|||
// virtual
|
||||
void LLWorkerThread::WorkRequest::finishRequest(bool completed)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
LLWorkerClass* workerclass = getWorkerClass();
|
||||
workerclass->finishWork(getParam(), completed);
|
||||
U32 flags = LLWorkerClass::WCF_WORK_FINISHED | (completed ? 0 : LLWorkerClass::WCF_WORK_ABORTED);
|
||||
|
|
@ -200,7 +204,6 @@ LLWorkerClass::LLWorkerClass(LLWorkerThread* workerthread, const std::string& na
|
|||
: mWorkerThread(workerthread),
|
||||
mWorkerClassName(name),
|
||||
mRequestHandle(LLWorkerThread::nullHandle()),
|
||||
mRequestPriority(LLWorkerThread::PRIORITY_NORMAL),
|
||||
mMutex(),
|
||||
mWorkFlags(0)
|
||||
{
|
||||
|
|
@ -289,7 +292,7 @@ bool LLWorkerClass::yield()
|
|||
//----------------------------------------------------------------------------
|
||||
|
||||
// calls startWork, adds doWork() to queue
|
||||
void LLWorkerClass::addWork(S32 param, U32 priority)
|
||||
void LLWorkerClass::addWork(S32 param)
|
||||
{
|
||||
mMutex.lock();
|
||||
llassert_always(!(mWorkFlags & (WCF_WORKING|WCF_HAVE_WORK)));
|
||||
|
|
@ -303,7 +306,7 @@ void LLWorkerClass::addWork(S32 param, U32 priority)
|
|||
startWork(param);
|
||||
clearFlags(WCF_WORK_FINISHED|WCF_WORK_ABORTED);
|
||||
setFlags(WCF_HAVE_WORK);
|
||||
mRequestHandle = mWorkerThread->addWorkRequest(this, param, priority);
|
||||
mRequestHandle = mWorkerThread->addWorkRequest(this, param);
|
||||
mMutex.unlock();
|
||||
}
|
||||
|
||||
|
|
@ -318,7 +321,6 @@ void LLWorkerClass::abortWork(bool autocomplete)
|
|||
if (mRequestHandle != LLWorkerThread::nullHandle())
|
||||
{
|
||||
mWorkerThread->abortRequest(mRequestHandle, autocomplete);
|
||||
mWorkerThread->setPriority(mRequestHandle, LLQueuedThread::PRIORITY_IMMEDIATE);
|
||||
setFlags(WCF_ABORT_REQUESTED);
|
||||
}
|
||||
mMutex.unlock();
|
||||
|
|
@ -392,16 +394,5 @@ void LLWorkerClass::scheduleDelete()
|
|||
}
|
||||
}
|
||||
|
||||
void LLWorkerClass::setPriority(U32 priority)
|
||||
{
|
||||
mMutex.lock();
|
||||
if (mRequestHandle != LLWorkerThread::nullHandle() && mRequestPriority != priority)
|
||||
{
|
||||
mRequestPriority = priority;
|
||||
mWorkerThread->setPriority(mRequestHandle, priority);
|
||||
}
|
||||
mMutex.unlock();
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public:
|
|||
virtual ~WorkRequest(); // use deleteRequest()
|
||||
|
||||
public:
|
||||
WorkRequest(handle_t handle, U32 priority, LLWorkerClass* workerclass, S32 param);
|
||||
WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param);
|
||||
|
||||
S32 getParam()
|
||||
{
|
||||
|
|
@ -90,7 +90,7 @@ public:
|
|||
|
||||
/*virtual*/ size_t update(F32 max_time_ms);
|
||||
|
||||
handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param, U32 priority = PRIORITY_NORMAL);
|
||||
handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param);
|
||||
|
||||
S32 getNumDeletes() { return (S32)mDeleteList.size(); } // debug
|
||||
|
||||
|
|
@ -151,10 +151,6 @@ public:
|
|||
bool isWorking() { return getFlags(WCF_WORKING); }
|
||||
bool wasAborted() { return getFlags(WCF_ABORT_REQUESTED); }
|
||||
|
||||
// setPriority(): changes the priority of a request
|
||||
void setPriority(U32 priority);
|
||||
U32 getPriority() { return mRequestPriority; }
|
||||
|
||||
const std::string& getName() const { return mWorkerClassName; }
|
||||
|
||||
protected:
|
||||
|
|
@ -169,7 +165,7 @@ protected:
|
|||
void setWorkerThread(LLWorkerThread* workerthread);
|
||||
|
||||
// addWork(): calls startWork, adds doWork() to queue
|
||||
void addWork(S32 param, U32 priority = LLWorkerThread::PRIORITY_NORMAL);
|
||||
void addWork(S32 param);
|
||||
|
||||
// abortWork(): requests that work be aborted
|
||||
void abortWork(bool autocomplete);
|
||||
|
|
@ -193,7 +189,6 @@ protected:
|
|||
LLWorkerThread* mWorkerThread;
|
||||
std::string mWorkerClassName;
|
||||
handle_t mRequestHandle;
|
||||
U32 mRequestPriority; // last priority set
|
||||
|
||||
private:
|
||||
LLMutex mMutex;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,240 @@
|
|||
/**
|
||||
* @file apply_test.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-12-19
|
||||
* @brief Test for apply.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "apply.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
#include <iomanip>
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "llsd.h"
|
||||
#include "llsdutil.h"
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// for ensure_equals
|
||||
std::ostream& operator<<(std::ostream& out, const std::vector<std::string>& stringvec)
|
||||
{
|
||||
const char* delim = "[";
|
||||
for (const auto& str : stringvec)
|
||||
{
|
||||
out << delim << std::quoted(str);
|
||||
delim = ", ";
|
||||
}
|
||||
return out << ']';
|
||||
}
|
||||
|
||||
// the above must be declared BEFORE ensure_equals(std::vector<std::string>)
|
||||
#include "../test/lltut.h"
|
||||
|
||||
/*****************************************************************************
|
||||
* TUT
|
||||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
namespace statics
|
||||
{
|
||||
/*------------------------------ data ------------------------------*/
|
||||
// Although we're using types from the LLSD namespace, we're not
|
||||
// constructing LLSD values, but rather instances of the C++ types
|
||||
// supported by LLSD.
|
||||
static LLSD::Boolean b{true};
|
||||
static LLSD::Integer i{17};
|
||||
static LLSD::Real f{3.14};
|
||||
static LLSD::String s{ "hello" };
|
||||
static LLSD::UUID uu{ "baadf00d-dead-beef-baad-feedb0ef" };
|
||||
static LLSD::Date dt{ "2022-12-19" };
|
||||
static LLSD::URI uri{ "http://secondlife.com" };
|
||||
static LLSD::Binary bin{ 0x01, 0x02, 0x03, 0x04, 0x05 };
|
||||
|
||||
static std::vector<LLSD::String> quick
|
||||
{
|
||||
"The", "quick", "brown", "fox", "etc."
|
||||
};
|
||||
|
||||
static std::array<int, 5> fibs
|
||||
{
|
||||
0, 1, 1, 2, 3
|
||||
};
|
||||
|
||||
// ensure that apply() actually reaches the target method --
|
||||
// lack of ensure_equals() failure could be due to no-op apply()
|
||||
bool called{ false };
|
||||
// capture calls from collect()
|
||||
std::vector<std::string> collected;
|
||||
|
||||
/*------------------------- test functions -------------------------*/
|
||||
void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s,
|
||||
const LLSD::UUID& uu, const LLSD::Date& dt,
|
||||
const LLSD::URI& uri, const LLSD::Binary& bin)
|
||||
{
|
||||
called = true;
|
||||
ensure_equals( "b mismatch", b, statics::b);
|
||||
ensure_equals( "i mismatch", i, statics::i);
|
||||
ensure_equals( "f mismatch", f, statics::f);
|
||||
ensure_equals( "s mismatch", s, statics::s);
|
||||
ensure_equals( "uu mismatch", uu, statics::uu);
|
||||
ensure_equals( "dt mismatch", dt, statics::dt);
|
||||
ensure_equals("uri mismatch", uri, statics::uri);
|
||||
ensure_equals("bin mismatch", bin, statics::bin);
|
||||
}
|
||||
|
||||
void strings(std::string s0, std::string s1, std::string s2, std::string s3, std::string s4)
|
||||
{
|
||||
called = true;
|
||||
ensure_equals("s0 mismatch", s0, statics::quick[0]);
|
||||
ensure_equals("s1 mismatch", s1, statics::quick[1]);
|
||||
ensure_equals("s2 mismatch", s2, statics::quick[2]);
|
||||
ensure_equals("s3 mismatch", s3, statics::quick[3]);
|
||||
ensure_equals("s4 mismatch", s4, statics::quick[4]);
|
||||
}
|
||||
|
||||
void ints(int i0, int i1, int i2, int i3, int i4)
|
||||
{
|
||||
called = true;
|
||||
ensure_equals("i0 mismatch", i0, statics::fibs[0]);
|
||||
ensure_equals("i1 mismatch", i1, statics::fibs[1]);
|
||||
ensure_equals("i2 mismatch", i2, statics::fibs[2]);
|
||||
ensure_equals("i3 mismatch", i3, statics::fibs[3]);
|
||||
ensure_equals("i4 mismatch", i4, statics::fibs[4]);
|
||||
}
|
||||
|
||||
void sdfunc(const LLSD& sd)
|
||||
{
|
||||
called = true;
|
||||
ensure_equals("sd mismatch", sd.asInteger(), statics::i);
|
||||
}
|
||||
|
||||
void intfunc(int i)
|
||||
{
|
||||
called = true;
|
||||
ensure_equals("i mismatch", i, statics::i);
|
||||
}
|
||||
|
||||
void voidfunc()
|
||||
{
|
||||
called = true;
|
||||
}
|
||||
|
||||
// recursion tail
|
||||
void collect()
|
||||
{
|
||||
called = true;
|
||||
}
|
||||
|
||||
// collect(arbitrary)
|
||||
template <typename... ARGS>
|
||||
void collect(const std::string& first, ARGS&&... rest)
|
||||
{
|
||||
statics::collected.push_back(first);
|
||||
collect(std::forward<ARGS>(rest)...);
|
||||
}
|
||||
} // namespace statics
|
||||
|
||||
struct apply_data
|
||||
{
|
||||
apply_data()
|
||||
{
|
||||
// reset called before each test
|
||||
statics::called = false;
|
||||
statics::collected.clear();
|
||||
}
|
||||
};
|
||||
typedef test_group<apply_data> apply_group;
|
||||
typedef apply_group::object object;
|
||||
apply_group applygrp("apply");
|
||||
|
||||
template<> template<>
|
||||
void object::test<1>()
|
||||
{
|
||||
set_test_name("apply(tuple)");
|
||||
LL::apply(statics::various,
|
||||
std::make_tuple(statics::b, statics::i, statics::f, statics::s,
|
||||
statics::uu, statics::dt, statics::uri, statics::bin));
|
||||
ensure("apply(tuple) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<2>()
|
||||
{
|
||||
set_test_name("apply(array)");
|
||||
LL::apply(statics::ints, statics::fibs);
|
||||
ensure("apply(array) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<3>()
|
||||
{
|
||||
set_test_name("apply(vector)");
|
||||
LL::apply(statics::strings, statics::quick);
|
||||
ensure("apply(vector) failed", statics::called);
|
||||
}
|
||||
|
||||
// The various apply(LLSD) tests exercise only the success cases because
|
||||
// the failure cases trigger assert() fail, which is hard to catch.
|
||||
template<> template<>
|
||||
void object::test<4>()
|
||||
{
|
||||
set_test_name("apply(LLSD())");
|
||||
LL::apply(statics::voidfunc, LLSD());
|
||||
ensure("apply(LLSD()) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<5>()
|
||||
{
|
||||
set_test_name("apply(fn(int), LLSD scalar)");
|
||||
LL::apply(statics::intfunc, LLSD(statics::i));
|
||||
ensure("apply(fn(int), LLSD scalar) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<6>()
|
||||
{
|
||||
set_test_name("apply(fn(LLSD), LLSD scalar)");
|
||||
// This test verifies that LLSDParam<LLSD> doesn't send the compiler
|
||||
// into infinite recursion when the target is itself LLSD.
|
||||
LL::apply(statics::sdfunc, LLSD(statics::i));
|
||||
ensure("apply(fn(LLSD), LLSD scalar) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<7>()
|
||||
{
|
||||
set_test_name("apply(LLSD array)");
|
||||
LL::apply(statics::various,
|
||||
llsd::array(statics::b, statics::i, statics::f, statics::s,
|
||||
statics::uu, statics::dt, statics::uri, statics::bin));
|
||||
ensure("apply(LLSD array) failed", statics::called);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<8>()
|
||||
{
|
||||
set_test_name("VAPPLY()");
|
||||
// Make a std::array<std::string> from statics::quick. We can't call a
|
||||
// variadic function with a data structure of dynamic length.
|
||||
std::array<std::string, 5> strray;
|
||||
for (size_t i = 0; i < strray.size(); ++i)
|
||||
strray[i] = statics::quick[i];
|
||||
// This doesn't work: the compiler doesn't know which overload of
|
||||
// collect() to pass to LL::apply().
|
||||
// LL::apply(statics::collect, strray);
|
||||
// That's what VAPPLY() is for.
|
||||
VAPPLY(statics::collect, strray);
|
||||
ensure("VAPPLY() failed", statics::called);
|
||||
ensure_equals("collected mismatch", statics::collected, statics::quick);
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* @file lazyeventapi_test.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-06-18
|
||||
* @brief Test for lazyeventapi.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "lazyeventapi.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "llevents.h"
|
||||
#include "llsdutil.h"
|
||||
|
||||
// observable side effect, solely for testing
|
||||
static LLSD data;
|
||||
|
||||
// LLEventAPI listener subclass
|
||||
class MyListener: public LLEventAPI
|
||||
{
|
||||
public:
|
||||
// need this trivial forwarding constructor
|
||||
// (of course do any other initialization your subclass requires)
|
||||
MyListener(const LL::LazyEventAPIParams& params):
|
||||
LLEventAPI(params)
|
||||
{}
|
||||
|
||||
// example operation, registered by LazyEventAPI subclass below
|
||||
void set_data(const LLSD& event)
|
||||
{
|
||||
data = event["data"];
|
||||
}
|
||||
};
|
||||
|
||||
// LazyEventAPI registrar subclass
|
||||
class MyRegistrar: public LL::LazyEventAPI<MyListener>
|
||||
{
|
||||
using super = LL::LazyEventAPI<MyListener>;
|
||||
using super::listener;
|
||||
public:
|
||||
// LazyEventAPI subclass initializes like a classic LLEventAPI subclass
|
||||
// constructor, with API name and desc plus add() calls for the defined
|
||||
// operations
|
||||
MyRegistrar():
|
||||
super("Test", "This is a test LLEventAPI")
|
||||
{
|
||||
add("set", "This is a set operation", &listener::set_data);
|
||||
}
|
||||
};
|
||||
// Normally we'd declare a static instance of MyRegistrar -- but because we
|
||||
// want to test both with and without, defer declaration to individual test
|
||||
// methods.
|
||||
|
||||
/*****************************************************************************
|
||||
* TUT
|
||||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
struct lazyeventapi_data
|
||||
{
|
||||
lazyeventapi_data()
|
||||
{
|
||||
// before every test, reset 'data'
|
||||
data.clear();
|
||||
}
|
||||
~lazyeventapi_data()
|
||||
{
|
||||
// after every test, reset LLEventPumps
|
||||
LLEventPumps::deleteSingleton();
|
||||
}
|
||||
};
|
||||
typedef test_group<lazyeventapi_data> lazyeventapi_group;
|
||||
typedef lazyeventapi_group::object object;
|
||||
lazyeventapi_group lazyeventapigrp("lazyeventapi");
|
||||
|
||||
template<> template<>
|
||||
void object::test<1>()
|
||||
{
|
||||
set_test_name("LazyEventAPI");
|
||||
// this is where the magic (should) happen
|
||||
// 'register' still a keyword until C++17
|
||||
MyRegistrar regster;
|
||||
LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "hey"));
|
||||
ensure_equals("failed to set data", data.asString(), "hey");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<2>()
|
||||
{
|
||||
set_test_name("No LazyEventAPI");
|
||||
// Because the MyRegistrar declaration in test<1>() is local, because
|
||||
// it has been destroyed, we fully expect NOT to reach a MyListener
|
||||
// instance with this post.
|
||||
LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "moot"));
|
||||
ensure("accidentally set data", ! data.isDefined());
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<3>()
|
||||
{
|
||||
set_test_name("LazyEventAPI metadata");
|
||||
MyRegistrar regster;
|
||||
// Of course we have 'regster' in hand; we don't need to search for
|
||||
// it. But this next test verifies that we can find (all) LazyEventAPI
|
||||
// instances using LazyEventAPIBase::instance_snapshot. Normally we
|
||||
// wouldn't search; normally we'd just look at each instance in the
|
||||
// loop body.
|
||||
const MyRegistrar* found = nullptr;
|
||||
for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot())
|
||||
if ((found = dynamic_cast<const MyRegistrar*>(®istrar)))
|
||||
break;
|
||||
ensure("Failed to find MyRegistrar via LLInstanceTracker", found);
|
||||
|
||||
ensure_equals("wrong API name", found->getName(), "Test");
|
||||
ensure_contains("wrong API desc", found->getDesc(), "test LLEventAPI");
|
||||
ensure_equals("wrong API field", found->getDispatchKey(), "op");
|
||||
// Normally we'd just iterate over *found. But for test purposes,
|
||||
// actually capture the range of NameDesc pairs in a vector.
|
||||
std::vector<LL::LazyEventAPIBase::NameDesc> ops{ found->begin(), found->end() };
|
||||
ensure_equals("failed to find operations", ops.size(), 1);
|
||||
ensure_equals("wrong operation name", ops[0].first, "set");
|
||||
ensure_contains("wrong operation desc", ops[0].second, "set operation");
|
||||
LLSD metadata{ found->getMetadata(ops[0].first) };
|
||||
ensure_equals("bad metadata name", metadata["name"].asString(), ops[0].first);
|
||||
ensure_equals("bad metadata desc", metadata["desc"].asString(), ops[0].second);
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
@ -18,9 +18,12 @@
|
|||
// external library headers
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "lleventfilter.h"
|
||||
#include "llsd.h"
|
||||
#include "llsdutil.h"
|
||||
#include "llevents.h"
|
||||
#include "stringize.h"
|
||||
#include "StringVec.h"
|
||||
#include "tests/wrapllerrs.h"
|
||||
#include "../test/catch_and_store_what_in.h"
|
||||
#include "../test/debug.h"
|
||||
|
|
@ -32,8 +35,6 @@
|
|||
#include <boost/bind.hpp>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/range.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#define foreach BOOST_FOREACH
|
||||
|
||||
#include <boost/lambda/lambda.hpp>
|
||||
|
||||
|
|
@ -177,6 +178,7 @@ struct Vars
|
|||
/*-------- Arbitrary-params (non-const, const, static) methods ---------*/
|
||||
void methodna(NPARAMSa)
|
||||
{
|
||||
DEBUG;
|
||||
// Because our const char* param cp might be NULL, and because we
|
||||
// intend to capture the value in a std::string, have to distinguish
|
||||
// between the NULL value and any non-NULL value. Use a convention
|
||||
|
|
@ -188,7 +190,7 @@ struct Vars
|
|||
else
|
||||
vcp = std::string("'") + cp + "'";
|
||||
|
||||
debug()("methodna(", b,
|
||||
this->debug()("methodna(", b,
|
||||
", ", i,
|
||||
", ", f,
|
||||
", ", d,
|
||||
|
|
@ -205,7 +207,7 @@ struct Vars
|
|||
void methodnb(NPARAMSb)
|
||||
{
|
||||
std::ostringstream vbin;
|
||||
foreach(U8 byte, bin)
|
||||
for (U8 byte: bin)
|
||||
{
|
||||
vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte);
|
||||
}
|
||||
|
|
@ -226,7 +228,8 @@ struct Vars
|
|||
|
||||
void cmethodna(NPARAMSa) const
|
||||
{
|
||||
debug()('c', NONL);
|
||||
DEBUG;
|
||||
this->debug()('c', NONL);
|
||||
const_cast<Vars*>(this)->methodna(NARGSa);
|
||||
}
|
||||
|
||||
|
|
@ -315,6 +318,31 @@ void freenb(NPARAMSb)
|
|||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
void ensure_has(const std::string& outer, const std::string& inner)
|
||||
{
|
||||
ensure(stringize("'", outer, "' does not contain '", inner, "'"),
|
||||
outer.find(inner) != std::string::npos);
|
||||
}
|
||||
|
||||
template <typename CALLABLE>
|
||||
std::string call_exc(CALLABLE&& func, const std::string& exc_frag)
|
||||
{
|
||||
std::string what =
|
||||
catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
|
||||
ensure_has(what, exc_frag);
|
||||
return what;
|
||||
}
|
||||
|
||||
template <typename CALLABLE>
|
||||
void call_logerr(CALLABLE&& func, const std::string& frag)
|
||||
{
|
||||
CaptureLog capture;
|
||||
// the error should be logged; we just need to stop the exception
|
||||
// propagating
|
||||
catch_what<LLEventDispatcher::DispatchError>(std::forward<CALLABLE>(func));
|
||||
capture.messageWith(frag);
|
||||
}
|
||||
|
||||
struct lleventdispatcher_data
|
||||
{
|
||||
Debug debug{"test"};
|
||||
|
|
@ -397,9 +425,9 @@ namespace tut
|
|||
work.add(name, desc, &Dispatcher::cmethod1, required);
|
||||
// Non-subclass method with/out required params
|
||||
addf("method1", "method1", &v);
|
||||
work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1));
|
||||
work.add(name, desc, [this](const LLSD& args){ return v.method1(args); });
|
||||
addf("method1_req", "method1", &v);
|
||||
work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1), required);
|
||||
work.add(name, desc, [this](const LLSD& args){ return v.method1(args); }, required);
|
||||
|
||||
/*--------------- Arbitrary params, array style ----------------*/
|
||||
|
||||
|
|
@ -461,7 +489,7 @@ namespace tut
|
|||
debug("dft_array_full:\n",
|
||||
dft_array_full);
|
||||
// Partial defaults arrays.
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
LLSD::Integer partition(std::min(partial_offset, dft_array_full[a].size()));
|
||||
dft_array_partial[a] =
|
||||
|
|
@ -471,7 +499,7 @@ namespace tut
|
|||
debug("dft_array_partial:\n",
|
||||
dft_array_partial);
|
||||
|
||||
foreach(LLSD::String a, ab)
|
||||
for(LLSD::String a: ab)
|
||||
{
|
||||
// Generate full defaults maps by zipping (params, dft_array_full).
|
||||
dft_map_full[a] = zipmap(params[a], dft_array_full[a]);
|
||||
|
|
@ -583,6 +611,7 @@ namespace tut
|
|||
|
||||
void addf(const std::string& n, const std::string& d, Vars* v)
|
||||
{
|
||||
debug("addf('", n, "', '", d, "')");
|
||||
// This method is to capture in our own DescMap the name and
|
||||
// description of every registered function, for metadata query
|
||||
// testing.
|
||||
|
|
@ -598,19 +627,14 @@ namespace tut
|
|||
{
|
||||
// Copy descs to a temp map of same type.
|
||||
DescMap forgotten(descs.begin(), descs.end());
|
||||
// LLEventDispatcher intentionally provides only const_iterator:
|
||||
// since dereferencing that iterator generates values on the fly,
|
||||
// it's meaningless to have a modifiable iterator. But since our
|
||||
// 'work' object isn't const, by default BOOST_FOREACH() wants to
|
||||
// use non-const iterators. Persuade it to use the const_iterator.
|
||||
foreach(LLEventDispatcher::NameDesc nd, const_cast<const Dispatcher&>(work))
|
||||
for (LLEventDispatcher::NameDesc nd: work)
|
||||
{
|
||||
DescMap::iterator found = forgotten.find(nd.first);
|
||||
ensure(STRINGIZE("LLEventDispatcher records function '" << nd.first
|
||||
<< "' we didn't enter"),
|
||||
ensure(stringize("LLEventDispatcher records function '", nd.first,
|
||||
"' we didn't enter"),
|
||||
found != forgotten.end());
|
||||
ensure_equals(STRINGIZE("LLEventDispatcher desc '" << nd.second <<
|
||||
"' doesn't match what we entered: '" << found->second << "'"),
|
||||
ensure_equals(stringize("LLEventDispatcher desc '", nd.second,
|
||||
"' doesn't match what we entered: '", found->second, "'"),
|
||||
nd.second, found->second);
|
||||
// found in our map the name from LLEventDispatcher, good, erase
|
||||
// our map entry
|
||||
|
|
@ -621,41 +645,49 @@ namespace tut
|
|||
std::ostringstream out;
|
||||
out << "LLEventDispatcher failed to report";
|
||||
const char* delim = ": ";
|
||||
foreach(const DescMap::value_type& fme, forgotten)
|
||||
for (const DescMap::value_type& fme: forgotten)
|
||||
{
|
||||
out << delim << fme.first;
|
||||
delim = ", ";
|
||||
}
|
||||
ensure(out.str(), false);
|
||||
throw failure(out.str());
|
||||
}
|
||||
}
|
||||
|
||||
Vars* varsfor(const std::string& name)
|
||||
{
|
||||
VarsMap::const_iterator found = funcvars.find(name);
|
||||
ensure(STRINGIZE("No Vars* for " << name), found != funcvars.end());
|
||||
ensure(STRINGIZE("NULL Vars* for " << name), found->second);
|
||||
ensure(stringize("No Vars* for ", name), found != funcvars.end());
|
||||
ensure(stringize("NULL Vars* for ", name), found->second);
|
||||
return found->second;
|
||||
}
|
||||
|
||||
void ensure_has(const std::string& outer, const std::string& inner)
|
||||
std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
|
||||
{
|
||||
ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(),
|
||||
outer.find(inner) != std::string::npos);
|
||||
return tut::call_exc(
|
||||
[this, func, args]()
|
||||
{
|
||||
if (func.empty())
|
||||
{
|
||||
work(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
work(func, args);
|
||||
}
|
||||
},
|
||||
exc_frag);
|
||||
}
|
||||
|
||||
void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag)
|
||||
void call_logerr(const std::string& func, const LLSD& args, const std::string& frag)
|
||||
{
|
||||
std::string threw = catch_what<std::runtime_error>([this, &func, &args](){
|
||||
work(func, args);
|
||||
});
|
||||
ensure_has(threw, exc_frag);
|
||||
tut::call_logerr([this, func, args](){ work(func, args); }, frag);
|
||||
}
|
||||
|
||||
LLSD getMetadata(const std::string& name)
|
||||
{
|
||||
LLSD meta(work.getMetadata(name));
|
||||
ensure(STRINGIZE("No metadata for " << name), meta.isDefined());
|
||||
ensure(stringize("No metadata for ", name), meta.isDefined());
|
||||
return meta;
|
||||
}
|
||||
|
||||
|
|
@ -724,7 +756,7 @@ namespace tut
|
|||
set_test_name("map-style registration with non-array params");
|
||||
// Pass "param names" as scalar or as map
|
||||
LLSD attempts(llsd::array(17, LLSDMap("pi", 3.14)("two", 2)));
|
||||
foreach(LLSD ae, inArray(attempts))
|
||||
for (LLSD ae: inArray(attempts))
|
||||
{
|
||||
std::string threw = catch_what<std::exception>([this, &ae](){
|
||||
work.add("freena_err", "freena", freena, ae);
|
||||
|
|
@ -799,7 +831,7 @@ namespace tut
|
|||
{
|
||||
set_test_name("query Callables with/out required params");
|
||||
LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1"));
|
||||
foreach(LLSD nm, inArray(names))
|
||||
for (LLSD nm: inArray(names))
|
||||
{
|
||||
LLSD metadata(getMetadata(nm));
|
||||
ensure_equals("name mismatch", metadata["name"], nm);
|
||||
|
|
@ -828,19 +860,19 @@ namespace tut
|
|||
(5, llsd::array("freena_array", "smethodna_array", "methodna_array")),
|
||||
llsd::array
|
||||
(5, llsd::array("freenb_array", "smethodnb_array", "methodnb_array"))));
|
||||
foreach(LLSD ae, inArray(expected))
|
||||
for (LLSD ae: inArray(expected))
|
||||
{
|
||||
LLSD::Integer arity(ae[0].asInteger());
|
||||
LLSD names(ae[1]);
|
||||
LLSD req(LLSD::emptyArray());
|
||||
if (arity)
|
||||
req[arity - 1] = LLSD();
|
||||
foreach(LLSD nm, inArray(names))
|
||||
for (LLSD nm: inArray(names))
|
||||
{
|
||||
LLSD metadata(getMetadata(nm));
|
||||
ensure_equals("name mismatch", metadata["name"], nm);
|
||||
ensure_equals(metadata["desc"].asString(), descs[nm]);
|
||||
ensure_equals(STRINGIZE("mismatched required for " << nm.asString()),
|
||||
ensure_equals(stringize("mismatched required for ", nm.asString()),
|
||||
metadata["required"], req);
|
||||
ensure("should not have optional", metadata["optional"].isUndefined());
|
||||
}
|
||||
|
|
@ -854,7 +886,7 @@ namespace tut
|
|||
// - (Free function | non-static method), map style, no params (ergo
|
||||
// no defaults)
|
||||
LLSD names(llsd::array("free0_map", "smethod0_map", "method0_map"));
|
||||
foreach(LLSD nm, inArray(names))
|
||||
for (LLSD nm: inArray(names))
|
||||
{
|
||||
LLSD metadata(getMetadata(nm));
|
||||
ensure_equals("name mismatch", metadata["name"], nm);
|
||||
|
|
@ -884,7 +916,7 @@ namespace tut
|
|||
llsd::array("smethodnb_map_adft", "smethodnb_map_mdft"),
|
||||
llsd::array("methodna_map_adft", "methodna_map_mdft"),
|
||||
llsd::array("methodnb_map_adft", "methodnb_map_mdft")));
|
||||
foreach(LLSD eq, inArray(equivalences))
|
||||
for (LLSD eq: inArray(equivalences))
|
||||
{
|
||||
LLSD adft(eq[0]);
|
||||
LLSD mdft(eq[1]);
|
||||
|
|
@ -898,8 +930,8 @@ namespace tut
|
|||
ensure_equals("mdft name", mdft, mmeta["name"]);
|
||||
ameta.erase("name");
|
||||
mmeta.erase("name");
|
||||
ensure_equals(STRINGIZE("metadata for " << adft.asString()
|
||||
<< " vs. " << mdft.asString()),
|
||||
ensure_equals(stringize("metadata for ", adft.asString(),
|
||||
" vs. ", mdft.asString()),
|
||||
ameta, mmeta);
|
||||
}
|
||||
}
|
||||
|
|
@ -915,7 +947,7 @@ namespace tut
|
|||
// params are required. Also maps containing left requirements for
|
||||
// partial defaults arrays. Also defaults maps from defaults arrays.
|
||||
LLSD allreq, leftreq, rightdft;
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
// The map in which all params are required uses params[a] as
|
||||
// keys, with all isUndefined() as values. We can accomplish that
|
||||
|
|
@ -943,9 +975,9 @@ namespace tut
|
|||
// Generate maps containing parameter names not provided by the
|
||||
// dft_map_partial maps.
|
||||
LLSD skipreq(allreq);
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
foreach(const MapEntry& me, inMap(dft_map_partial[a]))
|
||||
for (const MapEntry& me: inMap(dft_map_partial[a]))
|
||||
{
|
||||
skipreq[a].erase(me.first);
|
||||
}
|
||||
|
|
@ -990,7 +1022,7 @@ namespace tut
|
|||
(llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"),
|
||||
llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional
|
||||
|
||||
foreach(LLSD grp, inArray(groups))
|
||||
for (LLSD grp: inArray(groups))
|
||||
{
|
||||
// Internal structure of each group in 'groups':
|
||||
LLSD names(grp[0]);
|
||||
|
|
@ -1003,14 +1035,14 @@ namespace tut
|
|||
optional);
|
||||
|
||||
// Loop through 'names'
|
||||
foreach(LLSD nm, inArray(names))
|
||||
for (LLSD nm: inArray(names))
|
||||
{
|
||||
LLSD metadata(getMetadata(nm));
|
||||
ensure_equals("name mismatch", metadata["name"], nm);
|
||||
ensure_equals(nm.asString(), metadata["desc"].asString(), descs[nm]);
|
||||
ensure_equals(STRINGIZE(nm << " required mismatch"),
|
||||
ensure_equals(stringize(nm, " required mismatch"),
|
||||
metadata["required"], required);
|
||||
ensure_equals(STRINGIZE(nm << " optional mismatch"),
|
||||
ensure_equals(stringize(nm, " optional mismatch"),
|
||||
metadata["optional"], optional);
|
||||
}
|
||||
}
|
||||
|
|
@ -1031,13 +1063,7 @@ namespace tut
|
|||
{
|
||||
set_test_name("call with bad name");
|
||||
call_exc("freek", LLSD(), "not found");
|
||||
// We don't have a comparable helper function for the one-arg
|
||||
// operator() method, and it's not worth building one just for this
|
||||
// case. Write it out.
|
||||
std::string threw = catch_what<std::runtime_error>([this](){
|
||||
work(LLSDMap("op", "freek"));
|
||||
});
|
||||
ensure_has(threw, "bad");
|
||||
std::string threw = call_exc("", LLSDMap("op", "freek"), "bad");
|
||||
ensure_has(threw, "op");
|
||||
ensure_has(threw, "freek");
|
||||
}
|
||||
|
|
@ -1079,7 +1105,7 @@ namespace tut
|
|||
// LLSD value matching 'required' according to llsd_matches() rules.
|
||||
LLSD matching(LLSDMap("d", 3.14)("array", llsd::array("answer", true, answer)));
|
||||
// Okay, walk through 'tests'.
|
||||
foreach(const CallablesTriple& tr, tests)
|
||||
for (const CallablesTriple& tr: tests)
|
||||
{
|
||||
// Should be able to pass 'answer' to Callables registered
|
||||
// without 'required'.
|
||||
|
|
@ -1087,7 +1113,7 @@ namespace tut
|
|||
ensure_equals("answer mismatch", tr.llsd, answer);
|
||||
// Should NOT be able to pass 'answer' to Callables registered
|
||||
// with 'required'.
|
||||
call_exc(tr.name_req, answer, "bad request");
|
||||
call_logerr(tr.name_req, answer, "bad request");
|
||||
// But SHOULD be able to pass 'matching' to Callables registered
|
||||
// with 'required'.
|
||||
work(tr.name_req, matching);
|
||||
|
|
@ -1101,17 +1127,20 @@ namespace tut
|
|||
set_test_name("passing wrong args to (map | array)-style registrations");
|
||||
|
||||
// Pass scalar/map to array-style functions, scalar/array to map-style
|
||||
// functions. As that validation happens well before we engage the
|
||||
// argument magic, it seems pointless to repeat this with every
|
||||
// variation: (free function | non-static method), (no | arbitrary)
|
||||
// args. We should only need to engage it for one map-style
|
||||
// registration and one array-style registration.
|
||||
std::string array_exc("needs an args array");
|
||||
call_exc("free0_array", 17, array_exc);
|
||||
call_exc("free0_array", LLSDMap("pi", 3.14), array_exc);
|
||||
// functions. It seems pointless to repeat this with every variation:
|
||||
// (free function | non-static method), (no | arbitrary) args. We
|
||||
// should only need to engage it for one map-style registration and
|
||||
// one array-style registration.
|
||||
// Now that LLEventDispatcher has been extended to treat an LLSD
|
||||
// scalar as a single-entry array, the error we expect in this case is
|
||||
// that apply() is trying to pass that non-empty array to a nullary
|
||||
// function.
|
||||
call_logerr("free0_array", 17, "LL::apply");
|
||||
// similarly, apply() doesn't accept an LLSD Map
|
||||
call_logerr("free0_array", LLSDMap("pi", 3.14), "unsupported");
|
||||
|
||||
std::string map_exc("needs a map");
|
||||
call_exc("free0_map", 17, map_exc);
|
||||
call_logerr("free0_map", 17, map_exc);
|
||||
// Passing an array to a map-style function works now! No longer an
|
||||
// error case!
|
||||
// call_exc("free0_map", llsd::array("a", "b"), map_exc);
|
||||
|
|
@ -1125,7 +1154,7 @@ namespace tut
|
|||
("free0_array", "free0_map",
|
||||
"smethod0_array", "smethod0_map",
|
||||
"method0_array", "method0_map"));
|
||||
foreach(LLSD name, inArray(names))
|
||||
for (LLSD name: inArray(names))
|
||||
{
|
||||
// Look up the Vars instance for this function.
|
||||
Vars* vars(varsfor(name));
|
||||
|
|
@ -1150,15 +1179,21 @@ namespace tut
|
|||
template<> template<>
|
||||
void object::test<19>()
|
||||
{
|
||||
set_test_name("call array-style functions with too-short arrays");
|
||||
// Could have two different too-short arrays, one for *na and one for
|
||||
// *nb, but since they both take 5 params...
|
||||
set_test_name("call array-style functions with wrong-length arrays");
|
||||
// Could have different wrong-length arrays for *na and for *nb, but
|
||||
// since they both take 5 params...
|
||||
LLSD tooshort(llsd::array("this", "array", "too", "short"));
|
||||
foreach(const LLSD& funcsab, inArray(array_funcs))
|
||||
LLSD toolong (llsd::array("this", "array", "is", "one", "too", "long"));
|
||||
LLSD badargs (llsd::array(tooshort, toolong));
|
||||
for (const LLSD& toosomething: inArray(badargs))
|
||||
{
|
||||
foreach(const llsd::MapEntry& e, inMap(funcsab))
|
||||
for (const LLSD& funcsab: inArray(array_funcs))
|
||||
{
|
||||
call_exc(e.second, tooshort, "requires more arguments");
|
||||
for (const llsd::MapEntry& e: inMap(funcsab))
|
||||
{
|
||||
// apply() complains about wrong number of array entries
|
||||
call_logerr(e.second, toosomething, "LL::apply");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1166,7 +1201,7 @@ namespace tut
|
|||
template<> template<>
|
||||
void object::test<20>()
|
||||
{
|
||||
set_test_name("call array-style functions with (just right | too long) arrays");
|
||||
set_test_name("call array-style functions with right-size arrays");
|
||||
std::vector<U8> binary;
|
||||
for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i)
|
||||
{
|
||||
|
|
@ -1178,40 +1213,25 @@ namespace tut
|
|||
LLDate("2011-02-03T15:07:00Z"),
|
||||
LLURI("http://secondlife.com"),
|
||||
binary)));
|
||||
LLSD argsplus(args);
|
||||
argsplus["a"].append("bogus");
|
||||
argsplus["b"].append("bogus");
|
||||
LLSD expect;
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
expect[a] = zipmap(params[a], args[a]);
|
||||
}
|
||||
// Adjust expect["a"]["cp"] for special Vars::cp treatment.
|
||||
expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'";
|
||||
expect["a"]["cp"] = stringize("'", expect["a"]["cp"].asString(), "'");
|
||||
debug("expect: ", expect);
|
||||
|
||||
// Use substantially the same logic for args and argsplus
|
||||
LLSD argsarrays(llsd::array(args, argsplus));
|
||||
// So i==0 selects 'args', i==1 selects argsplus
|
||||
for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i)
|
||||
for (const LLSD& funcsab: inArray(array_funcs))
|
||||
{
|
||||
foreach(const LLSD& funcsab, inArray(array_funcs))
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
foreach(LLSD::String a, ab)
|
||||
{
|
||||
// Reset the Vars instance before each call
|
||||
Vars* vars(varsfor(funcsab[a]));
|
||||
*vars = Vars();
|
||||
work(funcsab[a], argsarrays[i][a]);
|
||||
ensure_llsd(STRINGIZE(funcsab[a].asString() <<
|
||||
": expect[\"" << a << "\"] mismatch"),
|
||||
vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
|
||||
|
||||
// TODO: in the i==1 or argsplus case, intercept LL_WARNS
|
||||
// output? Even without that, using argsplus verifies that
|
||||
// passing too many args isn't fatal; it works -- but
|
||||
// would be nice to notice the warning too.
|
||||
}
|
||||
// Reset the Vars instance before each call
|
||||
Vars* vars(varsfor(funcsab[a]));
|
||||
*vars = Vars();
|
||||
work(funcsab[a], args[a]);
|
||||
ensure_llsd(stringize(funcsab[a].asString(), ": expect[\"", a, "\"] mismatch"),
|
||||
vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1239,7 +1259,7 @@ namespace tut
|
|||
("a", llsd::array(false, 255, 98.6, 1024.5, "pointer"))
|
||||
("b", llsd::array("object", LLUUID::generateNewID(), LLDate::now(), LLURI("http://wiki.lindenlab.com/wiki"), LLSD::Binary(boost::begin(binary), boost::end(binary)))));
|
||||
LLSD array_overfull(array_full);
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
array_overfull[a].append("bogus");
|
||||
}
|
||||
|
|
@ -1253,7 +1273,7 @@ namespace tut
|
|||
ensure_not_equals("UUID collision",
|
||||
array_full["b"][1].asUUID(), dft_array_full["b"][1].asUUID());
|
||||
LLSD map_full, map_overfull;
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
map_full[a] = zipmap(params[a], array_full[a]);
|
||||
map_overfull[a] = map_full[a];
|
||||
|
|
@ -1294,21 +1314,360 @@ namespace tut
|
|||
"freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft")));
|
||||
// Treat (full | overfull) (array | map) the same.
|
||||
LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull));
|
||||
foreach(const LLSD& args, inArray(argssets))
|
||||
for (const LLSD& args: inArray(argssets))
|
||||
{
|
||||
foreach(LLSD::String a, ab)
|
||||
for (LLSD::String a: ab)
|
||||
{
|
||||
foreach(LLSD::String name, inArray(names[a]))
|
||||
for (LLSD::String name: inArray(names[a]))
|
||||
{
|
||||
// Reset the Vars instance
|
||||
Vars* vars(varsfor(name));
|
||||
*vars = Vars();
|
||||
work(name, args[a]);
|
||||
ensure_llsd(STRINGIZE(name << ": expect[\"" << a << "\"] mismatch"),
|
||||
ensure_llsd(stringize(name, ": expect[\"", a, "\"] mismatch"),
|
||||
vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits
|
||||
// intercept LL_WARNS for the two overfull cases?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DispatchResult: public LLDispatchListener
|
||||
{
|
||||
using DR = DispatchResult;
|
||||
|
||||
DispatchResult(): LLDispatchListener("results", "op")
|
||||
{
|
||||
add("strfunc", "return string", &DR::strfunc);
|
||||
add("voidfunc", "void function", &DR::voidfunc);
|
||||
add("emptyfunc", "return empty LLSD", &DR::emptyfunc);
|
||||
add("intfunc", "return Integer LLSD", &DR::intfunc);
|
||||
add("llsdfunc", "return passed LLSD", &DR::llsdfunc);
|
||||
add("mapfunc", "return map LLSD", &DR::mapfunc);
|
||||
add("arrayfunc", "return array LLSD", &DR::arrayfunc);
|
||||
}
|
||||
|
||||
std::string strfunc(const std::string& str) const { return "got " + str; }
|
||||
void voidfunc() const {}
|
||||
LLSD emptyfunc() const { return {}; }
|
||||
int intfunc(int i) const { return -i; }
|
||||
LLSD llsdfunc(const LLSD& event) const
|
||||
{
|
||||
LLSD result{ event };
|
||||
result["with"] = "string";
|
||||
return result;
|
||||
}
|
||||
LLSD mapfunc(int i, const std::string& str) const
|
||||
{
|
||||
return llsd::map("i", intfunc(i), "str", strfunc(str));
|
||||
}
|
||||
LLSD arrayfunc(int i, const std::string& str) const
|
||||
{
|
||||
return llsd::array(intfunc(i), strfunc(str));
|
||||
}
|
||||
};
|
||||
|
||||
template<> template<>
|
||||
void object::test<23>()
|
||||
{
|
||||
set_test_name("string result");
|
||||
DispatchResult service;
|
||||
LLSD result{ service("strfunc", "a string") };
|
||||
ensure_equals("strfunc() mismatch", result.asString(), "got a string");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<24>()
|
||||
{
|
||||
set_test_name("void result");
|
||||
DispatchResult service;
|
||||
LLSD result{ service("voidfunc", LLSD()) };
|
||||
ensure("voidfunc() returned defined", result.isUndefined());
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<25>()
|
||||
{
|
||||
set_test_name("Integer result");
|
||||
DispatchResult service;
|
||||
LLSD result{ service("intfunc", -17) };
|
||||
ensure_equals("intfunc() mismatch", result.asInteger(), 17);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<26>()
|
||||
{
|
||||
set_test_name("LLSD echo");
|
||||
DispatchResult service;
|
||||
LLSD result{ service("llsdfunc", llsd::map("op", "llsdfunc", "reqid", 17)) };
|
||||
ensure_equals("llsdfunc() mismatch", result,
|
||||
llsd::map("op", "llsdfunc", "reqid", 17, "with", "string"));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<27>()
|
||||
{
|
||||
set_test_name("map LLSD result");
|
||||
DispatchResult service;
|
||||
LLSD result{ service("mapfunc", llsd::array(-12, "value")) };
|
||||
ensure_equals("mapfunc() mismatch", result, llsd::map("i", 12, "str", "got value"));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<28>()
|
||||
{
|
||||
set_test_name("array LLSD result");
|
||||
DispatchResult service;
|
||||
LLSD result{ service("arrayfunc", llsd::array(-8, "word")) };
|
||||
ensure_equals("arrayfunc() mismatch", result, llsd::array(8, "got word"));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<29>()
|
||||
{
|
||||
set_test_name("listener error, no reply");
|
||||
DispatchResult service;
|
||||
tut::call_exc(
|
||||
[&service]()
|
||||
{ service.post(llsd::map("op", "nosuchfunc", "reqid", 17)); },
|
||||
"nosuchfunc");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<30>()
|
||||
{
|
||||
set_test_name("listener error with reply");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map("op", "nosuchfunc", "reqid", 17, "reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure("no reply", reply.isDefined());
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
ensure_has(reply["error"].asString(), "nosuchfunc");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<31>()
|
||||
{
|
||||
set_test_name("listener call to void function");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
result.set("non-empty");
|
||||
for (const auto& func: StringVec{ "voidfunc", "emptyfunc" })
|
||||
{
|
||||
service.post(llsd::map(
|
||||
"op", func,
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
ensure_equals("reply from " + func, result.get().asString(), "non-empty");
|
||||
}
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<32>()
|
||||
{
|
||||
set_test_name("listener call to string function");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", "strfunc",
|
||||
"args", llsd::array("a string"),
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
ensure_equals("bad reply from strfunc", reply["data"].asString(), "got a string");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<33>()
|
||||
{
|
||||
set_test_name("listener call to map function");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", "mapfunc",
|
||||
"args", llsd::array(-7, "value"),
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
ensure_equals("bad i from mapfunc", reply["i"].asInteger(), 7);
|
||||
ensure_equals("bad str from mapfunc", reply["str"], "got value");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<34>()
|
||||
{
|
||||
set_test_name("batched map success");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", llsd::map(
|
||||
"strfunc", "some string",
|
||||
"intfunc", 2,
|
||||
"voidfunc", LLSD(),
|
||||
"arrayfunc", llsd::array(-5, "other string")),
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
reply.erase("reqid");
|
||||
ensure_equals(
|
||||
"bad map batch",
|
||||
reply,
|
||||
llsd::map(
|
||||
"strfunc", "got some string",
|
||||
"intfunc", -2,
|
||||
"voidfunc", LLSD(),
|
||||
"arrayfunc", llsd::array(5, "got other string")));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<35>()
|
||||
{
|
||||
set_test_name("batched map error");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", llsd::map(
|
||||
"badfunc", 34, // !
|
||||
"strfunc", "some string",
|
||||
"intfunc", 2,
|
||||
"missing", LLSD(), // !
|
||||
"voidfunc", LLSD(),
|
||||
"arrayfunc", llsd::array(-5, "other string")),
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
reply.erase("reqid");
|
||||
auto error{ reply["error"].asString() };
|
||||
reply.erase("error");
|
||||
ensure_has(error, "badfunc");
|
||||
ensure_has(error, "missing");
|
||||
ensure_equals(
|
||||
"bad partial batch",
|
||||
reply,
|
||||
llsd::map(
|
||||
"strfunc", "got some string",
|
||||
"intfunc", -2,
|
||||
"voidfunc", LLSD(),
|
||||
"arrayfunc", llsd::array(5, "got other string")));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<36>()
|
||||
{
|
||||
set_test_name("batched map exception");
|
||||
DispatchResult service;
|
||||
auto error = tut::call_exc(
|
||||
[&service]()
|
||||
{
|
||||
service.post(llsd::map(
|
||||
"op", llsd::map(
|
||||
"badfunc", 34, // !
|
||||
"strfunc", "some string",
|
||||
"intfunc", 2,
|
||||
"missing", LLSD(), // !
|
||||
"voidfunc", LLSD(),
|
||||
"arrayfunc", llsd::array(-5, "other string")),
|
||||
"reqid", 17));
|
||||
// no "reply"
|
||||
},
|
||||
"badfunc");
|
||||
ensure_has(error, "missing");
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<37>()
|
||||
{
|
||||
set_test_name("batched array success");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", llsd::array(
|
||||
llsd::array("strfunc", "some string"),
|
||||
llsd::array("intfunc", 2),
|
||||
"arrayfunc",
|
||||
"voidfunc"),
|
||||
"args", llsd::array(
|
||||
LLSD(),
|
||||
LLSD(),
|
||||
llsd::array(-5, "other string")),
|
||||
// args array deliberately short, since the default
|
||||
// [3] is undefined, which should work for voidfunc
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
reply.erase("reqid");
|
||||
ensure_equals(
|
||||
"bad array batch",
|
||||
reply,
|
||||
llsd::map(
|
||||
"data", llsd::array(
|
||||
"got some string",
|
||||
-2,
|
||||
llsd::array(5, "got other string"),
|
||||
LLSD())));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<38>()
|
||||
{
|
||||
set_test_name("batched array error");
|
||||
DispatchResult service;
|
||||
LLCaptureListener<LLSD> result;
|
||||
service.post(llsd::map(
|
||||
"op", llsd::array(
|
||||
llsd::array("strfunc", "some string"),
|
||||
llsd::array("intfunc", 2, "whoops"), // bad form
|
||||
"arrayfunc",
|
||||
"voidfunc"),
|
||||
"args", llsd::array(
|
||||
LLSD(),
|
||||
LLSD(),
|
||||
llsd::array(-5, "other string")),
|
||||
// args array deliberately short, since the default
|
||||
// [3] is undefined, which should work for voidfunc
|
||||
"reqid", 17,
|
||||
"reply", result.getName()));
|
||||
LLSD reply{ result.get() };
|
||||
ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17);
|
||||
reply.erase("reqid");
|
||||
auto error{ reply["error"] };
|
||||
reply.erase("error");
|
||||
ensure_has(error, "[1]");
|
||||
ensure_has(error, "unsupported");
|
||||
ensure_equals("bad array batch", reply,
|
||||
llsd::map("data", llsd::array("got some string")));
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<39>()
|
||||
{
|
||||
set_test_name("batched array exception");
|
||||
DispatchResult service;
|
||||
auto error = tut::call_exc(
|
||||
[&service]()
|
||||
{
|
||||
service.post(llsd::map(
|
||||
"op", llsd::array(
|
||||
llsd::array("strfunc", "some string"),
|
||||
llsd::array("intfunc", 2, "whoops"), // bad form
|
||||
"arrayfunc",
|
||||
"voidfunc"),
|
||||
"args", llsd::array(
|
||||
LLSD(),
|
||||
LLSD(),
|
||||
llsd::array(-5, "other string")),
|
||||
// args array deliberately short, since the default
|
||||
// [3] is undefined, which should work for voidfunc
|
||||
"reqid", 17));
|
||||
// no "reply"
|
||||
},
|
||||
"[1]");
|
||||
ensure_has(error, "unsupported");
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ namespace tut
|
|||
{
|
||||
struct workqueue_data
|
||||
{
|
||||
WorkQueue queue{"queue"};
|
||||
WorkSchedule queue{"queue"};
|
||||
};
|
||||
typedef test_group<workqueue_data> workqueue_group;
|
||||
typedef workqueue_group::object object;
|
||||
|
|
@ -49,8 +49,8 @@ namespace tut
|
|||
{
|
||||
set_test_name("name");
|
||||
ensure_equals("didn't capture name", queue.getKey(), "queue");
|
||||
ensure("not findable", WorkQueue::getInstance("queue") == queue.getWeak().lock());
|
||||
WorkQueue q2;
|
||||
ensure("not findable", WorkSchedule::getInstance("queue") == queue.getWeak().lock());
|
||||
WorkSchedule q2;
|
||||
ensure("has no name", LLStringUtil::startsWith(q2.getKey(), "WorkQueue"));
|
||||
}
|
||||
|
||||
|
|
@ -73,16 +73,16 @@ namespace tut
|
|||
{
|
||||
set_test_name("postEvery");
|
||||
// record of runs
|
||||
using Shared = std::deque<WorkQueue::TimePoint>;
|
||||
using Shared = std::deque<WorkSchedule::TimePoint>;
|
||||
// This is an example of how to share data between the originator of
|
||||
// postEvery(work) and the work item itself, since usually a WorkQueue
|
||||
// postEvery(work) and the work item itself, since usually a WorkSchedule
|
||||
// is used to dispatch work to a different thread. Neither of them
|
||||
// should call any of LLCond's wait methods: you don't want to stall
|
||||
// either the worker thread or the originating thread (conventionally
|
||||
// main). Use LLCond or a subclass even if all you want to do is
|
||||
// signal the work item that it can quit; consider LLOneShotCond.
|
||||
LLCond<Shared> data;
|
||||
auto start = WorkQueue::TimePoint::clock::now();
|
||||
auto start = WorkSchedule::TimePoint::clock::now();
|
||||
// 2s seems like a long time to wait, since it directly impacts the
|
||||
// duration of this test program. Unfortunately GitHub's Mac runners
|
||||
// are pretty wimpy, and we're getting spurious "too late" errors just
|
||||
|
|
@ -97,7 +97,7 @@ namespace tut
|
|||
data.update_one(
|
||||
[](Shared& data)
|
||||
{
|
||||
data.push_back(WorkQueue::TimePoint::clock::now());
|
||||
data.push_back(WorkSchedule::TimePoint::clock::now());
|
||||
});
|
||||
// by the 3rd call, return false to stop
|
||||
return (++count < 3);
|
||||
|
|
@ -106,7 +106,7 @@ namespace tut
|
|||
// postEvery() running, so run until we have exhausted the iterations
|
||||
// or we time out waiting
|
||||
for (auto finish = start + 10*interval;
|
||||
WorkQueue::TimePoint::clock::now() < finish &&
|
||||
WorkSchedule::TimePoint::clock::now() < finish &&
|
||||
data.get([](const Shared& data){ return data.size(); }) < 3; )
|
||||
{
|
||||
queue.runPending();
|
||||
|
|
@ -143,8 +143,8 @@ namespace tut
|
|||
void object::test<4>()
|
||||
{
|
||||
set_test_name("postTo");
|
||||
WorkQueue main("main");
|
||||
auto qptr = WorkQueue::getInstance("queue");
|
||||
WorkSchedule main("main");
|
||||
auto qptr = WorkSchedule::getInstance("queue");
|
||||
int result = 0;
|
||||
main.postTo(
|
||||
qptr,
|
||||
|
|
@ -175,8 +175,8 @@ namespace tut
|
|||
void object::test<5>()
|
||||
{
|
||||
set_test_name("postTo with void return");
|
||||
WorkQueue main("main");
|
||||
auto qptr = WorkQueue::getInstance("queue");
|
||||
WorkSchedule main("main");
|
||||
auto qptr = WorkSchedule::getInstance("queue");
|
||||
std::string observe;
|
||||
main.postTo(
|
||||
qptr,
|
||||
|
|
@ -198,7 +198,7 @@ namespace tut
|
|||
std::string stored;
|
||||
// Try to call waitForResult() on this thread's main coroutine. It
|
||||
// should throw because the main coroutine must service the queue.
|
||||
auto what{ catch_what<WorkQueue::Error>(
|
||||
auto what{ catch_what<WorkSchedule::Error>(
|
||||
[this, &stored](){ stored = queue.waitForResult(
|
||||
[](){ return "should throw"; }); }) };
|
||||
ensure("lambda should not have run", stored.empty());
|
||||
|
|
|
|||
|
|
@ -226,6 +226,11 @@ public:
|
|||
return boost::dynamic_pointer_cast<CaptureLogRecorder>(mRecorder)->streamto(out);
|
||||
}
|
||||
|
||||
friend inline std::ostream& operator<<(std::ostream& out, const CaptureLog& self)
|
||||
{
|
||||
return self.streamto(out);
|
||||
}
|
||||
|
||||
private:
|
||||
LLError::FatalFunction mFatalFunction;
|
||||
LLError::SettingsStoragePtr mOldSettings;
|
||||
|
|
|
|||
|
|
@ -17,18 +17,58 @@
|
|||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "commoncontrol.h"
|
||||
#include "llerror.h"
|
||||
#include "llevents.h"
|
||||
#include "llsd.h"
|
||||
#include "stringize.h"
|
||||
|
||||
LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity):
|
||||
#include <boost/fiber/algo/round_robin.hpp>
|
||||
|
||||
/*****************************************************************************
|
||||
* Custom fiber scheduler for worker threads
|
||||
*****************************************************************************/
|
||||
// As of 2022-12-06, each of our worker threads only runs a single (default)
|
||||
// fiber: we don't launch explicit fibers within worker threads, nor do we
|
||||
// anticipate doing so. So a worker thread that's simply waiting for incoming
|
||||
// tasks should really sleep a little. Override the default fiber scheduler to
|
||||
// implement that.
|
||||
struct sleepy_robin: public boost::fibers::algo::round_robin
|
||||
{
|
||||
virtual void suspend_until( std::chrono::steady_clock::time_point const&) noexcept
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
// round_robin holds a std::condition_variable, and
|
||||
// round_robin::suspend_until() calls
|
||||
// std::condition_variable::wait_until(). On Windows, that call seems
|
||||
// busier than it ought to be. Try just sleeping.
|
||||
Sleep(1);
|
||||
#else
|
||||
// currently unused other than windows, but might as well have something here
|
||||
// different units than Sleep(), but we actually just want to sleep for any de-minimis duration
|
||||
usleep(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void notify() noexcept
|
||||
{
|
||||
// Since our Sleep() call above will wake up on its own, we need not
|
||||
// take any special action to wake it.
|
||||
}
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* ThreadPoolBase
|
||||
*****************************************************************************/
|
||||
LL::ThreadPoolBase::ThreadPoolBase(const std::string& name, size_t threads,
|
||||
WorkQueueBase* queue):
|
||||
super(name),
|
||||
mQueue(name, capacity),
|
||||
mName("ThreadPool:" + name),
|
||||
mThreadCount(threads)
|
||||
mThreadCount(getConfiguredWidth(name, threads)),
|
||||
mQueue(queue)
|
||||
{}
|
||||
|
||||
void LL::ThreadPool::start()
|
||||
void LL::ThreadPoolBase::start()
|
||||
{
|
||||
for (size_t i = 0; i < mThreadCount; ++i)
|
||||
{
|
||||
|
|
@ -56,17 +96,17 @@ void LL::ThreadPool::start()
|
|||
});
|
||||
}
|
||||
|
||||
LL::ThreadPool::~ThreadPool()
|
||||
LL::ThreadPoolBase::~ThreadPoolBase()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void LL::ThreadPool::close()
|
||||
void LL::ThreadPoolBase::close()
|
||||
{
|
||||
if (! mQueue.isClosed())
|
||||
if (! mQueue->isClosed())
|
||||
{
|
||||
LL_DEBUGS("ThreadPool") << mName << " closing queue and joining threads" << LL_ENDL;
|
||||
mQueue.close();
|
||||
mQueue->close();
|
||||
for (auto& pair: mThreads)
|
||||
{
|
||||
LL_DEBUGS("ThreadPool") << mName << " waiting on thread " << pair.first << LL_ENDL;
|
||||
|
|
@ -76,14 +116,74 @@ void LL::ThreadPool::close()
|
|||
}
|
||||
}
|
||||
|
||||
void LL::ThreadPool::run(const std::string& name)
|
||||
void LL::ThreadPoolBase::run(const std::string& name)
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
// Try using sleepy_robin fiber scheduler.
|
||||
boost::fibers::use_scheduling_algorithm<sleepy_robin>();
|
||||
#endif // LL_WINDOWS
|
||||
|
||||
LL_DEBUGS("ThreadPool") << name << " starting" << LL_ENDL;
|
||||
run();
|
||||
LL_DEBUGS("ThreadPool") << name << " stopping" << LL_ENDL;
|
||||
}
|
||||
|
||||
void LL::ThreadPool::run()
|
||||
void LL::ThreadPoolBase::run()
|
||||
{
|
||||
mQueue.runUntilClose();
|
||||
mQueue->runUntilClose();
|
||||
}
|
||||
|
||||
//static
|
||||
size_t LL::ThreadPoolBase::getConfiguredWidth(const std::string& name, size_t dft)
|
||||
{
|
||||
LLSD poolSizes;
|
||||
try
|
||||
{
|
||||
poolSizes = LL::CommonControl::get("Global", "ThreadPoolSizes");
|
||||
// "ThreadPoolSizes" is actually a map containing the sizes of
|
||||
// interest -- or should be, if this process has an
|
||||
// LLViewerControlListener instance and its settings include
|
||||
// "ThreadPoolSizes". If we failed to retrieve it, perhaps we're in a
|
||||
// program that doesn't define that, or perhaps there's no such
|
||||
// setting, or perhaps we're asking too early, before the LLEventAPI
|
||||
// itself has been instantiated. In any of those cases, it seems worth
|
||||
// warning.
|
||||
if (! poolSizes.isDefined())
|
||||
{
|
||||
// Note: we don't warn about absence of an override key for a
|
||||
// particular ThreadPool name, that's fine. This warning is about
|
||||
// complete absence of a ThreadPoolSizes setting, which we expect
|
||||
// in a normal viewer session.
|
||||
LL_WARNS("ThreadPool") << "No 'ThreadPoolSizes' setting for ThreadPool '"
|
||||
<< name << "'" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
catch (const LL::CommonControl::Error& exc)
|
||||
{
|
||||
// We don't want ThreadPool to *require* LLViewerControlListener.
|
||||
// Just log it and carry on.
|
||||
LL_WARNS("ThreadPool") << "Can't check 'ThreadPoolSizes': " << exc.what() << LL_ENDL;
|
||||
}
|
||||
|
||||
LL_DEBUGS("ThreadPool") << "ThreadPoolSizes = " << poolSizes << LL_ENDL;
|
||||
// LLSD treats an undefined value as an empty map when asked to retrieve a
|
||||
// key, so we don't need this to be conditional.
|
||||
LLSD sizeSpec{ poolSizes[name] };
|
||||
// We retrieve sizeSpec as LLSD, rather than immediately as LLSD::Integer,
|
||||
// so we can distinguish the case when it's undefined.
|
||||
return sizeSpec.isInteger() ? sizeSpec.asInteger() : dft;
|
||||
}
|
||||
|
||||
//static
|
||||
size_t LL::ThreadPoolBase::getWidth(const std::string& name, size_t dft)
|
||||
{
|
||||
auto instance{ getInstance(name) };
|
||||
if (instance)
|
||||
{
|
||||
return instance->getWidth();
|
||||
}
|
||||
else
|
||||
{
|
||||
return getConfiguredWidth(name, dft);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@
|
|||
#if ! defined(LL_THREADPOOL_H)
|
||||
#define LL_THREADPOOL_H
|
||||
|
||||
#include "threadpool_fwd.h"
|
||||
#include "workqueue.h"
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility> // std::pair
|
||||
|
|
@ -22,17 +24,24 @@
|
|||
namespace LL
|
||||
{
|
||||
|
||||
class ThreadPool: public LLInstanceTracker<ThreadPool, std::string>
|
||||
class ThreadPoolBase: public LLInstanceTracker<ThreadPoolBase, std::string>
|
||||
{
|
||||
private:
|
||||
using super = LLInstanceTracker<ThreadPool, std::string>;
|
||||
using super = LLInstanceTracker<ThreadPoolBase, std::string>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Pass ThreadPool a string name. This can be used to look up the
|
||||
* Pass ThreadPoolBase a string name. This can be used to look up the
|
||||
* relevant WorkQueue.
|
||||
*
|
||||
* The number of threads you pass sets the compile-time default. But
|
||||
* if the user has overridden the LLSD map in the "ThreadPoolSizes"
|
||||
* setting with a key matching this ThreadPool name, that setting
|
||||
* overrides this parameter.
|
||||
*/
|
||||
ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024);
|
||||
virtual ~ThreadPool();
|
||||
ThreadPoolBase(const std::string& name, size_t threads,
|
||||
WorkQueueBase* queue);
|
||||
virtual ~ThreadPoolBase();
|
||||
|
||||
/**
|
||||
* Launch the ThreadPool. Until this call, a constructed ThreadPool
|
||||
|
|
@ -50,8 +59,6 @@ namespace LL
|
|||
|
||||
std::string getName() const { return mName; }
|
||||
size_t getWidth() const { return mThreads.size(); }
|
||||
/// obtain a non-const reference to the WorkQueue to post work to it
|
||||
WorkQueue& getQueue() { return mQueue; }
|
||||
|
||||
/**
|
||||
* Override run() if you need special processing. The default run()
|
||||
|
|
@ -59,15 +66,72 @@ namespace LL
|
|||
*/
|
||||
virtual void run();
|
||||
|
||||
/**
|
||||
* getConfiguredWidth() returns the setting, if any, for the specified
|
||||
* ThreadPool name. Returns dft if the "ThreadPoolSizes" map does not
|
||||
* contain the specified name.
|
||||
*/
|
||||
static
|
||||
size_t getConfiguredWidth(const std::string& name, size_t dft=0);
|
||||
|
||||
/**
|
||||
* This getWidth() returns the width of the instantiated ThreadPool
|
||||
* with the specified name, if any. If no instance exists, returns its
|
||||
* getConfiguredWidth() if any. If there's no instance and no relevant
|
||||
* override, return dft. Presumably dft should match the threads
|
||||
* parameter passed to the ThreadPool constructor call that will
|
||||
* eventually instantiate the ThreadPool with that name.
|
||||
*/
|
||||
static
|
||||
size_t getWidth(const std::string& name, size_t dft);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<WorkQueueBase> mQueue;
|
||||
|
||||
private:
|
||||
void run(const std::string& name);
|
||||
|
||||
WorkQueue mQueue;
|
||||
std::string mName;
|
||||
size_t mThreadCount;
|
||||
std::vector<std::pair<std::string, std::thread>> mThreads;
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialize with WorkQueue or, for timestamped tasks, WorkSchedule
|
||||
*/
|
||||
template <class QUEUE>
|
||||
struct ThreadPoolUsing: public ThreadPoolBase
|
||||
{
|
||||
using queue_t = QUEUE;
|
||||
|
||||
/**
|
||||
* Pass ThreadPoolUsing a string name. This can be used to look up the
|
||||
* relevant WorkQueue.
|
||||
*
|
||||
* The number of threads you pass sets the compile-time default. But
|
||||
* if the user has overridden the LLSD map in the "ThreadPoolSizes"
|
||||
* setting with a key matching this ThreadPool name, that setting
|
||||
* overrides this parameter.
|
||||
*
|
||||
* Pass an explicit capacity to limit the size of the queue.
|
||||
* Constraining the queue can cause a submitter to block. Do not
|
||||
* constrain any ThreadPool accepting work from the main thread.
|
||||
*/
|
||||
ThreadPoolUsing(const std::string& name, size_t threads=1, size_t capacity=1024*1024):
|
||||
ThreadPoolBase(name, threads, new queue_t(name, capacity))
|
||||
{}
|
||||
~ThreadPoolUsing() override {}
|
||||
|
||||
/**
|
||||
* obtain a non-const reference to the specific WorkQueue subclass to
|
||||
* post work to it
|
||||
*/
|
||||
queue_t& getQueue() { return static_cast<queue_t&>(*mQueue); }
|
||||
};
|
||||
|
||||
/// ThreadPool is shorthand for using the simpler WorkQueue
|
||||
using ThreadPool = ThreadPoolUsing<WorkQueue>;
|
||||
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_THREADPOOL_H) */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* @file threadpool_fwd.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2022-12-09
|
||||
* @brief Forward declarations for ThreadPool et al.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
||||
* Copyright (c) 2022, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_THREADPOOL_FWD_H)
|
||||
#define LL_THREADPOOL_FWD_H
|
||||
|
||||
#include "workqueue.h"
|
||||
|
||||
namespace LL
|
||||
{
|
||||
template <class QUEUE>
|
||||
struct ThreadPoolUsing;
|
||||
|
||||
using ThreadPool = ThreadPoolUsing<WorkQueue>;
|
||||
} // namespace LL
|
||||
|
||||
#endif /* ! defined(LL_THREADPOOL_FWD_H) */
|
||||
|
|
@ -26,14 +26,121 @@
|
|||
using Mutex = LLCoros::Mutex;
|
||||
using Lock = LLCoros::LockType;
|
||||
|
||||
LL::WorkQueue::WorkQueue(const std::string& name, size_t capacity):
|
||||
super(makeName(name)),
|
||||
mQueue(capacity)
|
||||
/*****************************************************************************
|
||||
* WorkQueueBase
|
||||
*****************************************************************************/
|
||||
LL::WorkQueueBase::WorkQueueBase(const std::string& name):
|
||||
super(makeName(name))
|
||||
{
|
||||
// TODO: register for "LLApp" events so we can implicitly close() on
|
||||
// viewer shutdown.
|
||||
}
|
||||
|
||||
void LL::WorkQueueBase::runUntilClose()
|
||||
{
|
||||
try
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
|
||||
callWork(pop_());
|
||||
}
|
||||
}
|
||||
catch (const Closed&)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
bool LL::WorkQueueBase::runPending()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
|
||||
for (Work work; tryPop_(work); )
|
||||
{
|
||||
callWork(work);
|
||||
}
|
||||
return ! done();
|
||||
}
|
||||
|
||||
bool LL::WorkQueueBase::runOne()
|
||||
{
|
||||
Work work;
|
||||
if (tryPop_(work))
|
||||
{
|
||||
callWork(work);
|
||||
}
|
||||
return ! done();
|
||||
}
|
||||
|
||||
bool LL::WorkQueueBase::runUntil(const TimePoint& until)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
|
||||
// Should we subtract some slop to allow for typical Work execution time?
|
||||
// How much slop?
|
||||
// runUntil() is simply a time-bounded runPending().
|
||||
for (Work work; TimePoint::clock::now() < until && tryPop_(work); )
|
||||
{
|
||||
callWork(work);
|
||||
}
|
||||
return ! done();
|
||||
}
|
||||
|
||||
std::string LL::WorkQueueBase::makeName(const std::string& name)
|
||||
{
|
||||
if (! name.empty())
|
||||
return name;
|
||||
|
||||
static U32 discriminator = 0;
|
||||
static Mutex mutex;
|
||||
U32 num;
|
||||
{
|
||||
// Protect discriminator from concurrent access by different threads.
|
||||
// It can't be thread_local, else two racing threads will come up with
|
||||
// the same name.
|
||||
Lock lk(mutex);
|
||||
num = discriminator++;
|
||||
}
|
||||
return STRINGIZE("WorkQueue" << num);
|
||||
}
|
||||
|
||||
void LL::WorkQueueBase::callWork(const Work& work)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
|
||||
try
|
||||
{
|
||||
work();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// No matter what goes wrong with any individual work item, the worker
|
||||
// thread must go on! Log our own instance name with the exception.
|
||||
LOG_UNHANDLED_EXCEPTION(getKey());
|
||||
}
|
||||
}
|
||||
|
||||
void LL::WorkQueueBase::error(const std::string& msg)
|
||||
{
|
||||
LL_ERRS("WorkQueue") << msg << LL_ENDL;
|
||||
}
|
||||
|
||||
void LL::WorkQueueBase::checkCoroutine(const std::string& method)
|
||||
{
|
||||
// By convention, the default coroutine on each thread has an empty name
|
||||
// string. See also LLCoros::logname().
|
||||
if (LLCoros::getName().empty())
|
||||
{
|
||||
LLTHROW(Error("Do not call " + method + " from a thread's default coroutine"));
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* WorkQueue
|
||||
*****************************************************************************/
|
||||
LL::WorkQueue::WorkQueue(const std::string& name, size_t capacity):
|
||||
super(name),
|
||||
mQueue(capacity)
|
||||
{
|
||||
}
|
||||
|
||||
void LL::WorkQueue::close()
|
||||
{
|
||||
mQueue.close();
|
||||
|
|
@ -54,105 +161,85 @@ bool LL::WorkQueue::done()
|
|||
return mQueue.done();
|
||||
}
|
||||
|
||||
void LL::WorkQueue::runUntilClose()
|
||||
bool LL::WorkQueue::post(const Work& callable)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
|
||||
callWork(mQueue.pop());
|
||||
}
|
||||
}
|
||||
catch (const Queue::Closed&)
|
||||
{
|
||||
}
|
||||
return mQueue.pushIfOpen(callable);
|
||||
}
|
||||
|
||||
bool LL::WorkQueue::runPending()
|
||||
bool LL::WorkQueue::tryPost(const Work& callable)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
|
||||
for (Work work; mQueue.tryPop(work); )
|
||||
{
|
||||
callWork(work);
|
||||
}
|
||||
return ! mQueue.done();
|
||||
return mQueue.tryPush(callable);
|
||||
}
|
||||
|
||||
bool LL::WorkQueue::runOne()
|
||||
LL::WorkQueue::Work LL::WorkQueue::pop_()
|
||||
{
|
||||
Work work;
|
||||
if (mQueue.tryPop(work))
|
||||
{
|
||||
callWork(work);
|
||||
}
|
||||
return ! mQueue.done();
|
||||
return mQueue.pop();
|
||||
}
|
||||
|
||||
bool LL::WorkQueue::runUntil(const TimePoint& until)
|
||||
bool LL::WorkQueue::tryPop_(Work& work)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
|
||||
// Should we subtract some slop to allow for typical Work execution time?
|
||||
// How much slop?
|
||||
// runUntil() is simply a time-bounded runPending().
|
||||
for (Work work; TimePoint::clock::now() < until && mQueue.tryPop(work); )
|
||||
{
|
||||
callWork(work);
|
||||
}
|
||||
return ! mQueue.done();
|
||||
return mQueue.tryPop(work);
|
||||
}
|
||||
|
||||
std::string LL::WorkQueue::makeName(const std::string& name)
|
||||
/*****************************************************************************
|
||||
* WorkSchedule
|
||||
*****************************************************************************/
|
||||
LL::WorkSchedule::WorkSchedule(const std::string& name, size_t capacity):
|
||||
super(name),
|
||||
mQueue(capacity)
|
||||
{
|
||||
if (! name.empty())
|
||||
return name;
|
||||
|
||||
static U32 discriminator = 0;
|
||||
static Mutex mutex;
|
||||
U32 num;
|
||||
{
|
||||
// Protect discriminator from concurrent access by different threads.
|
||||
// It can't be thread_local, else two racing threads will come up with
|
||||
// the same name.
|
||||
Lock lk(mutex);
|
||||
num = discriminator++;
|
||||
}
|
||||
return STRINGIZE("WorkQueue" << num);
|
||||
}
|
||||
|
||||
void LL::WorkQueue::callWork(const Queue::DataTuple& work)
|
||||
void LL::WorkSchedule::close()
|
||||
{
|
||||
// ThreadSafeSchedule::pop() always delivers a tuple, even when
|
||||
// there's only one data field per item, as for us.
|
||||
callWork(std::get<0>(work));
|
||||
mQueue.close();
|
||||
}
|
||||
|
||||
void LL::WorkQueue::callWork(const Work& work)
|
||||
size_t LL::WorkSchedule::size()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
|
||||
try
|
||||
{
|
||||
work();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// No matter what goes wrong with any individual work item, the worker
|
||||
// thread must go on! Log our own instance name with the exception.
|
||||
LOG_UNHANDLED_EXCEPTION(getKey());
|
||||
}
|
||||
return mQueue.size();
|
||||
}
|
||||
|
||||
void LL::WorkQueue::error(const std::string& msg)
|
||||
bool LL::WorkSchedule::isClosed()
|
||||
{
|
||||
LL_ERRS("WorkQueue") << msg << LL_ENDL;
|
||||
return mQueue.isClosed();
|
||||
}
|
||||
|
||||
void LL::WorkQueue::checkCoroutine(const std::string& method)
|
||||
bool LL::WorkSchedule::done()
|
||||
{
|
||||
// By convention, the default coroutine on each thread has an empty name
|
||||
// string. See also LLCoros::logname().
|
||||
if (LLCoros::getName().empty())
|
||||
{
|
||||
LLTHROW(Error("Do not call " + method + " from a thread's default coroutine"));
|
||||
}
|
||||
return mQueue.done();
|
||||
}
|
||||
|
||||
bool LL::WorkSchedule::post(const Work& callable)
|
||||
{
|
||||
// Use TimePoint::clock::now() instead of TimePoint's representation of
|
||||
// the epoch because this WorkSchedule may contain a mix of past-due
|
||||
// TimedWork items and TimedWork items scheduled for the future. Sift this
|
||||
// new item into the correct place.
|
||||
return post(callable, TimePoint::clock::now());
|
||||
}
|
||||
|
||||
bool LL::WorkSchedule::post(const Work& callable, const TimePoint& time)
|
||||
{
|
||||
return mQueue.pushIfOpen(TimedWork(time, callable));
|
||||
}
|
||||
|
||||
bool LL::WorkSchedule::tryPost(const Work& callable)
|
||||
{
|
||||
return tryPost(callable, TimePoint::clock::now());
|
||||
}
|
||||
|
||||
bool LL::WorkSchedule::tryPost(const Work& callable, const TimePoint& time)
|
||||
{
|
||||
return mQueue.tryPush(TimedWork(time, callable));
|
||||
}
|
||||
|
||||
LL::WorkSchedule::Work LL::WorkSchedule::pop_()
|
||||
{
|
||||
return std::get<0>(mQueue.pop());
|
||||
}
|
||||
|
||||
bool LL::WorkSchedule::tryPop_(Work& work)
|
||||
{
|
||||
return mQueue.tryPop(work);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include "llcoros.h"
|
||||
#include "llexception.h"
|
||||
#include "llinstancetracker.h"
|
||||
#include "llinstancetrackersubclass.h"
|
||||
#include "threadsafeschedule.h"
|
||||
#include <chrono>
|
||||
#include <exception> // std::current_exception
|
||||
|
|
@ -23,27 +24,23 @@
|
|||
|
||||
namespace LL
|
||||
{
|
||||
|
||||
/*****************************************************************************
|
||||
* WorkQueueBase: API for WorkQueue and WorkSchedule
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* A typical WorkQueue has a string name that can be used to find it.
|
||||
*/
|
||||
class WorkQueue: public LLInstanceTracker<WorkQueue, std::string>
|
||||
class WorkQueueBase: public LLInstanceTracker<WorkQueueBase, std::string>
|
||||
{
|
||||
private:
|
||||
using super = LLInstanceTracker<WorkQueue, std::string>;
|
||||
using super = LLInstanceTracker<WorkQueueBase, std::string>;
|
||||
|
||||
public:
|
||||
using Work = std::function<void()>;
|
||||
|
||||
private:
|
||||
using Queue = ThreadSafeSchedule<Work>;
|
||||
// helper for postEvery()
|
||||
template <typename Rep, typename Period, typename CALLABLE>
|
||||
class BackJack;
|
||||
|
||||
public:
|
||||
using TimePoint = Queue::TimePoint;
|
||||
using TimedWork = Queue::TimeTuple;
|
||||
using Closed = Queue::Closed;
|
||||
using Closed = LLThreadSafeQueueInterrupt;
|
||||
// for runFor()
|
||||
using TimePoint = std::chrono::steady_clock::time_point;
|
||||
|
||||
struct Error: public LLException
|
||||
{
|
||||
|
|
@ -51,18 +48,18 @@ namespace LL
|
|||
};
|
||||
|
||||
/**
|
||||
* You may omit the WorkQueue name, in which case a unique name is
|
||||
* You may omit the WorkQueueBase name, in which case a unique name is
|
||||
* synthesized; for practical purposes that makes it anonymous.
|
||||
*/
|
||||
WorkQueue(const std::string& name = std::string(), size_t capacity=1024);
|
||||
WorkQueueBase(const std::string& name);
|
||||
|
||||
/**
|
||||
* Since the point of WorkQueue is to pass work to some other worker
|
||||
* thread(s) asynchronously, it's important that the WorkQueue continue
|
||||
* to exist until the worker thread(s) have drained it. To communicate
|
||||
* that it's time for them to quit, close() the queue.
|
||||
* thread(s) asynchronously, it's important that it continue to exist
|
||||
* until the worker thread(s) have drained it. To communicate that
|
||||
* it's time for them to quit, close() the queue.
|
||||
*/
|
||||
void close();
|
||||
virtual void close() = 0;
|
||||
|
||||
/**
|
||||
* WorkQueue supports multiple producers and multiple consumers. In
|
||||
|
|
@ -78,152 +75,57 @@ namespace LL
|
|||
* * If you're the only consumer, noticing that size() > 0 is
|
||||
* meaningful.
|
||||
*/
|
||||
size_t size();
|
||||
virtual size_t size() = 0;
|
||||
/// producer end: are we prevented from pushing any additional items?
|
||||
bool isClosed();
|
||||
virtual bool isClosed() = 0;
|
||||
/// consumer end: are we done, is the queue entirely drained?
|
||||
bool done();
|
||||
virtual bool done() = 0;
|
||||
|
||||
/*---------------------- fire and forget API -----------------------*/
|
||||
|
||||
/// fire-and-forget, but at a particular (future?) time
|
||||
template <typename CALLABLE>
|
||||
void post(const TimePoint& time, CALLABLE&& callable)
|
||||
{
|
||||
// Defer reifying an arbitrary CALLABLE until we hit this or
|
||||
// postIfOpen(). All other methods should accept CALLABLEs of
|
||||
// arbitrary type to avoid multiple levels of std::function
|
||||
// indirection.
|
||||
mQueue.push(TimedWork(time, std::move(callable)));
|
||||
}
|
||||
|
||||
/// fire-and-forget
|
||||
template <typename CALLABLE>
|
||||
void post(CALLABLE&& callable)
|
||||
{
|
||||
// We use TimePoint::clock::now() instead of TimePoint's
|
||||
// representation of the epoch because this WorkQueue may contain
|
||||
// a mix of past-due TimedWork items and TimedWork items scheduled
|
||||
// for the future. Sift this new item into the correct place.
|
||||
post(TimePoint::clock::now(), std::move(callable));
|
||||
}
|
||||
|
||||
/**
|
||||
* post work for a particular time, unless the queue is closed before
|
||||
* we can post
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
bool postIfOpen(const TimePoint& time, CALLABLE&& callable)
|
||||
{
|
||||
// Defer reifying an arbitrary CALLABLE until we hit this or
|
||||
// post(). All other methods should accept CALLABLEs of arbitrary
|
||||
// type to avoid multiple levels of std::function indirection.
|
||||
return mQueue.pushIfOpen(TimedWork(time, std::move(callable)));
|
||||
}
|
||||
|
||||
/**
|
||||
* post work, unless the queue is closed before we can post
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
bool postIfOpen(CALLABLE&& callable)
|
||||
{
|
||||
return postIfOpen(TimePoint::clock::now(), std::move(callable));
|
||||
}
|
||||
virtual bool post(const Work&) = 0;
|
||||
|
||||
/**
|
||||
* Post work to be run at a specified time to another WorkQueue, which
|
||||
* may or may not still exist and be open. Return true if we were able
|
||||
* to post.
|
||||
* post work, unless the queue is full
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
static bool postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable);
|
||||
virtual bool tryPost(const Work&) = 0;
|
||||
|
||||
/**
|
||||
* Post work to another WorkQueue, which may or may not still exist
|
||||
* and be open. Return true if we were able to post.
|
||||
* and be open. Support any post() overload. Return true if we were
|
||||
* able to post.
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
static bool postMaybe(weak_t target, CALLABLE&& callable)
|
||||
{
|
||||
return postMaybe(target, TimePoint::clock::now(),
|
||||
std::forward<CALLABLE>(callable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a callable returning bool that will trigger repeatedly at
|
||||
* specified interval, until the callable returns false.
|
||||
*
|
||||
* If you need to signal that callable from outside, DO NOT bind a
|
||||
* reference to a simple bool! That's not thread-safe. Instead, bind
|
||||
* an LLCond variant, e.g. LLOneShotCond or LLBoolCond.
|
||||
*/
|
||||
template <typename Rep, typename Period, typename CALLABLE>
|
||||
void postEvery(const std::chrono::duration<Rep, Period>& interval,
|
||||
CALLABLE&& callable);
|
||||
|
||||
template <typename CALLABLE>
|
||||
bool tryPost(CALLABLE&& callable)
|
||||
{
|
||||
return mQueue.tryPush(TimedWork(TimePoint::clock::now(), std::move(callable)));
|
||||
}
|
||||
template <typename... ARGS>
|
||||
static bool postMaybe(weak_t target, ARGS&&... args);
|
||||
|
||||
/*------------------------- handshake API --------------------------*/
|
||||
|
||||
/**
|
||||
* Post work to another WorkQueue to be run at a specified time,
|
||||
* requesting a specific callback to be run on this WorkQueue on
|
||||
* completion.
|
||||
*
|
||||
* Returns true if able to post, false if the other WorkQueue is
|
||||
* inaccessible.
|
||||
*/
|
||||
// Apparently some Microsoft header file defines a macro CALLBACK? The
|
||||
// natural template argument name CALLBACK produces very weird Visual
|
||||
// Studio compile errors that seem utterly unrelated to this source
|
||||
// code.
|
||||
template <typename CALLABLE, typename FOLLOWUP>
|
||||
bool postTo(weak_t target,
|
||||
const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback);
|
||||
|
||||
/**
|
||||
* Post work to another WorkQueue, requesting a specific callback to
|
||||
* be run on this WorkQueue on completion.
|
||||
* be run on this WorkQueue on completion. Optional final argument is
|
||||
* TimePoint for WorkSchedule.
|
||||
*
|
||||
* Returns true if able to post, false if the other WorkQueue is
|
||||
* inaccessible.
|
||||
*/
|
||||
template <typename CALLABLE, typename FOLLOWUP>
|
||||
bool postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback)
|
||||
{
|
||||
return postTo(target, TimePoint::clock::now(),
|
||||
std::move(callable), std::move(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Post work to another WorkQueue to be run at a specified time,
|
||||
* blocking the calling coroutine until then, returning the result to
|
||||
* caller on completion.
|
||||
*
|
||||
* In general, we assume that each thread's default coroutine is busy
|
||||
* servicing its WorkQueue or whatever. To try to prevent mistakes, we
|
||||
* forbid calling waitForResult() from a thread's default coroutine.
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
auto waitForResult(const TimePoint& time, CALLABLE&& callable);
|
||||
template <typename CALLABLE, typename FOLLOWUP, typename... ARGS>
|
||||
bool postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback,
|
||||
ARGS&&... args);
|
||||
|
||||
/**
|
||||
* Post work to another WorkQueue, blocking the calling coroutine
|
||||
* until then, returning the result to caller on completion.
|
||||
* until then, returning the result to caller on completion. Optional
|
||||
* final argument is TimePoint for WorkSchedule.
|
||||
*
|
||||
* In general, we assume that each thread's default coroutine is busy
|
||||
* servicing its WorkQueue or whatever. To try to prevent mistakes, we
|
||||
* forbid calling waitForResult() from a thread's default coroutine.
|
||||
*/
|
||||
template <typename CALLABLE>
|
||||
auto waitForResult(CALLABLE&& callable)
|
||||
{
|
||||
return waitForResult(TimePoint::clock::now(), std::move(callable));
|
||||
}
|
||||
template <typename CALLABLE, typename... ARGS>
|
||||
auto waitForResult(CALLABLE&& callable, ARGS&&... args);
|
||||
|
||||
/*--------------------------- worker API ---------------------------*/
|
||||
|
||||
|
|
@ -270,7 +172,7 @@ namespace LL
|
|||
*/
|
||||
bool runUntil(const TimePoint& until);
|
||||
|
||||
private:
|
||||
protected:
|
||||
template <typename CALLABLE, typename FOLLOWUP>
|
||||
static auto makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback);
|
||||
/// general case: arbitrary C++ return type
|
||||
|
|
@ -290,13 +192,170 @@ namespace LL
|
|||
static void checkCoroutine(const std::string& method);
|
||||
static void error(const std::string& msg);
|
||||
static std::string makeName(const std::string& name);
|
||||
void callWork(const Queue::DataTuple& work);
|
||||
void callWork(const Work& work);
|
||||
|
||||
private:
|
||||
virtual Work pop_() = 0;
|
||||
virtual bool tryPop_(Work&) = 0;
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* WorkQueue: no timestamped task support
|
||||
*****************************************************************************/
|
||||
class WorkQueue: public LLInstanceTrackerSubclass<WorkQueue, WorkQueueBase>
|
||||
{
|
||||
private:
|
||||
using super = LLInstanceTrackerSubclass<WorkQueue, WorkQueueBase>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* You may omit the WorkQueue name, in which case a unique name is
|
||||
* synthesized; for practical purposes that makes it anonymous.
|
||||
*/
|
||||
WorkQueue(const std::string& name = std::string(), size_t capacity=1024);
|
||||
|
||||
/**
|
||||
* Since the point of WorkQueue is to pass work to some other worker
|
||||
* thread(s) asynchronously, it's important that it continue to exist
|
||||
* until the worker thread(s) have drained it. To communicate that
|
||||
* it's time for them to quit, close() the queue.
|
||||
*/
|
||||
void close() override;
|
||||
|
||||
/**
|
||||
* WorkQueue supports multiple producers and multiple consumers. In
|
||||
* the general case it's misleading to test size(), since any other
|
||||
* thread might change it the nanosecond the lock is released. On that
|
||||
* basis, some might argue against publishing a size() method at all.
|
||||
*
|
||||
* But there are two specific cases in which a test based on size()
|
||||
* might be reasonable:
|
||||
*
|
||||
* * If you're the only producer, noticing that size() == 0 is
|
||||
* meaningful.
|
||||
* * If you're the only consumer, noticing that size() > 0 is
|
||||
* meaningful.
|
||||
*/
|
||||
size_t size() override;
|
||||
/// producer end: are we prevented from pushing any additional items?
|
||||
bool isClosed() override;
|
||||
/// consumer end: are we done, is the queue entirely drained?
|
||||
bool done() override;
|
||||
|
||||
/*---------------------- fire and forget API -----------------------*/
|
||||
|
||||
/**
|
||||
* post work, unless the queue is closed before we can post
|
||||
*/
|
||||
bool post(const Work&) override;
|
||||
|
||||
/**
|
||||
* post work, unless the queue is full
|
||||
*/
|
||||
bool tryPost(const Work&) override;
|
||||
|
||||
private:
|
||||
using Queue = LLThreadSafeQueue<Work>;
|
||||
Queue mQueue;
|
||||
|
||||
Work pop_() override;
|
||||
bool tryPop_(Work&) override;
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* WorkSchedule: add support for timestamped tasks
|
||||
*****************************************************************************/
|
||||
class WorkSchedule: public LLInstanceTrackerSubclass<WorkSchedule, WorkQueueBase>
|
||||
{
|
||||
private:
|
||||
using super = LLInstanceTrackerSubclass<WorkSchedule, WorkQueueBase>;
|
||||
using Queue = ThreadSafeSchedule<Work>;
|
||||
// helper for postEvery()
|
||||
template <typename Rep, typename Period, typename CALLABLE>
|
||||
class BackJack;
|
||||
|
||||
public:
|
||||
using TimePoint = Queue::TimePoint;
|
||||
using TimedWork = Queue::TimeTuple;
|
||||
|
||||
/**
|
||||
* You may omit the WorkSchedule name, in which case a unique name is
|
||||
* synthesized; for practical purposes that makes it anonymous.
|
||||
*/
|
||||
WorkSchedule(const std::string& name = std::string(), size_t capacity=1024);
|
||||
|
||||
/**
|
||||
* Since the point of WorkSchedule is to pass work to some other worker
|
||||
* thread(s) asynchronously, it's important that the WorkSchedule continue
|
||||
* to exist until the worker thread(s) have drained it. To communicate
|
||||
* that it's time for them to quit, close() the queue.
|
||||
*/
|
||||
void close() override;
|
||||
|
||||
/**
|
||||
* WorkSchedule supports multiple producers and multiple consumers. In
|
||||
* the general case it's misleading to test size(), since any other
|
||||
* thread might change it the nanosecond the lock is released. On that
|
||||
* basis, some might argue against publishing a size() method at all.
|
||||
*
|
||||
* But there are two specific cases in which a test based on size()
|
||||
* might be reasonable:
|
||||
*
|
||||
* * If you're the only producer, noticing that size() == 0 is
|
||||
* meaningful.
|
||||
* * If you're the only consumer, noticing that size() > 0 is
|
||||
* meaningful.
|
||||
*/
|
||||
size_t size() override;
|
||||
/// producer end: are we prevented from pushing any additional items?
|
||||
bool isClosed() override;
|
||||
/// consumer end: are we done, is the queue entirely drained?
|
||||
bool done() override;
|
||||
|
||||
/*---------------------- fire and forget API -----------------------*/
|
||||
|
||||
/**
|
||||
* post work, unless the queue is closed before we can post
|
||||
*/
|
||||
bool post(const Work& callable) override;
|
||||
|
||||
/**
|
||||
* post work for a particular time, unless the queue is closed before
|
||||
* we can post
|
||||
*/
|
||||
bool post(const Work& callable, const TimePoint& time);
|
||||
|
||||
/**
|
||||
* post work, unless the queue is full
|
||||
*/
|
||||
bool tryPost(const Work& callable) override;
|
||||
|
||||
/**
|
||||
* post work for a particular time, unless the queue is full
|
||||
*/
|
||||
bool tryPost(const Work& callable, const TimePoint& time);
|
||||
|
||||
/**
|
||||
* Launch a callable returning bool that will trigger repeatedly at
|
||||
* specified interval, until the callable returns false.
|
||||
*
|
||||
* If you need to signal that callable from outside, DO NOT bind a
|
||||
* reference to a simple bool! That's not thread-safe. Instead, bind
|
||||
* an LLCond variant, e.g. LLOneShotCond or LLBoolCond.
|
||||
*/
|
||||
template <typename Rep, typename Period, typename CALLABLE>
|
||||
bool postEvery(const std::chrono::duration<Rep, Period>& interval,
|
||||
CALLABLE&& callable);
|
||||
|
||||
private:
|
||||
Queue mQueue;
|
||||
|
||||
Work pop_() override;
|
||||
bool tryPop_(Work&) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* BackJack is, in effect, a hand-rolled lambda, binding a WorkQueue, a
|
||||
* BackJack is, in effect, a hand-rolled lambda, binding a WorkSchedule, a
|
||||
* CALLABLE that returns bool, a TimePoint and an interval at which to
|
||||
* relaunch it. As long as the callable continues returning true, BackJack
|
||||
* keeps resubmitting it to the target WorkQueue.
|
||||
|
|
@ -305,7 +364,7 @@ namespace LL
|
|||
// class method gets its own 'this' pointer -- which we need to resubmit
|
||||
// the whole BackJack callable.
|
||||
template <typename Rep, typename Period, typename CALLABLE>
|
||||
class WorkQueue::BackJack
|
||||
class WorkSchedule::BackJack
|
||||
{
|
||||
public:
|
||||
// bind the desired data
|
||||
|
|
@ -319,9 +378,10 @@ namespace LL
|
|||
mCallable(std::move(callable))
|
||||
{}
|
||||
|
||||
// Call by target WorkQueue -- note that although WE require a
|
||||
// callable returning bool, WorkQueue wants a void callable. We
|
||||
// consume the bool.
|
||||
// This operator() method, called by target WorkSchedule, is what
|
||||
// makes this object a Work item. Although WE require a callable
|
||||
// returning bool, WorkSchedule wants a void callable. We consume the
|
||||
// bool.
|
||||
void operator()()
|
||||
{
|
||||
// If mCallable() throws an exception, don't catch it here: if it
|
||||
|
|
@ -337,7 +397,7 @@ namespace LL
|
|||
// register our intent to fire at exact mIntervals.
|
||||
mStart += mInterval;
|
||||
|
||||
// We're being called at this moment by the target WorkQueue.
|
||||
// We're being called at this moment by the target WorkSchedule.
|
||||
// Assume it still exists, rather than checking the result of
|
||||
// lock().
|
||||
// Resubmit the whole *this callable: that's why we're a class
|
||||
|
|
@ -345,14 +405,10 @@ namespace LL
|
|||
// move-only callable; but naturally this statement must be
|
||||
// the last time we reference this instance, which may become
|
||||
// moved-from.
|
||||
try
|
||||
{
|
||||
mTarget.lock()->post(mStart, std::move(*this));
|
||||
}
|
||||
catch (const Closed&)
|
||||
{
|
||||
// Once this queue is closed, oh well, just stop
|
||||
}
|
||||
auto target{ std::dynamic_pointer_cast<WorkSchedule>(mTarget.lock()) };
|
||||
// Discard bool return: once this queue is closed, oh well,
|
||||
// just stop
|
||||
target->post(std::move(*this), mStart);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -364,8 +420,8 @@ namespace LL
|
|||
};
|
||||
|
||||
template <typename Rep, typename Period, typename CALLABLE>
|
||||
void WorkQueue::postEvery(const std::chrono::duration<Rep, Period>& interval,
|
||||
CALLABLE&& callable)
|
||||
bool WorkSchedule::postEvery(const std::chrono::duration<Rep, Period>& interval,
|
||||
CALLABLE&& callable)
|
||||
{
|
||||
if (interval.count() <= 0)
|
||||
{
|
||||
|
|
@ -381,14 +437,14 @@ namespace LL
|
|||
// Instantiate and post a suitable BackJack, binding a weak_ptr to
|
||||
// self, the current time, the desired interval and the desired
|
||||
// callable.
|
||||
post(
|
||||
return post(
|
||||
BackJack<Rep, Period, CALLABLE>(
|
||||
getWeak(), TimePoint::clock::now(), interval, std::move(callable)));
|
||||
}
|
||||
|
||||
/// general case: arbitrary C++ return type
|
||||
template <typename CALLABLE, typename FOLLOWUP, typename RETURNTYPE>
|
||||
struct WorkQueue::MakeReplyLambda
|
||||
struct WorkQueueBase::MakeReplyLambda
|
||||
{
|
||||
auto operator()(CALLABLE&& callable, FOLLOWUP&& callback)
|
||||
{
|
||||
|
|
@ -409,7 +465,7 @@ namespace LL
|
|||
|
||||
/// specialize for CALLABLE returning void
|
||||
template <typename CALLABLE, typename FOLLOWUP>
|
||||
struct WorkQueue::MakeReplyLambda<CALLABLE, FOLLOWUP, void>
|
||||
struct WorkQueueBase::MakeReplyLambda<CALLABLE, FOLLOWUP, void>
|
||||
{
|
||||
auto operator()(CALLABLE&& callable, FOLLOWUP&& callback)
|
||||
{
|
||||
|
|
@ -421,16 +477,16 @@ namespace LL
|
|||
};
|
||||
|
||||
template <typename CALLABLE, typename FOLLOWUP>
|
||||
auto WorkQueue::makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback)
|
||||
auto WorkQueueBase::makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback)
|
||||
{
|
||||
return MakeReplyLambda<CALLABLE, FOLLOWUP,
|
||||
decltype(std::forward<CALLABLE>(callable)())>()
|
||||
(std::move(callable), std::move(callback));
|
||||
}
|
||||
|
||||
template <typename CALLABLE, typename FOLLOWUP>
|
||||
bool WorkQueue::postTo(weak_t target,
|
||||
const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback)
|
||||
template <typename CALLABLE, typename FOLLOWUP, typename... ARGS>
|
||||
bool WorkQueueBase::postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback,
|
||||
ARGS&&... args)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
// We're being asked to post to the WorkQueue at target.
|
||||
|
|
@ -443,13 +499,12 @@ namespace LL
|
|||
// Here we believe target WorkQueue still exists. Post to it a
|
||||
// lambda that packages our callable, our callback and a weak_ptr
|
||||
// to this originating WorkQueue.
|
||||
tptr->post(
|
||||
time,
|
||||
return tptr->post(
|
||||
[reply = super::getWeak(),
|
||||
callable = std::move(callable),
|
||||
callback = std::move(callback)]
|
||||
()
|
||||
mutable {
|
||||
() mutable
|
||||
{
|
||||
// Use postMaybe() below in case this originating WorkQueue
|
||||
// has been closed or destroyed. Remember, the outer lambda is
|
||||
// now running on a thread servicing the target WorkQueue, and
|
||||
|
|
@ -472,44 +527,34 @@ namespace LL
|
|||
// originating WorkQueue. Once there, rethrow it.
|
||||
[exc = std::current_exception()](){ std::rethrow_exception(exc); });
|
||||
}
|
||||
});
|
||||
|
||||
// looks like we were able to post()
|
||||
return true;
|
||||
},
|
||||
// if caller passed a TimePoint, pass it along to post()
|
||||
std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
template <typename CALLABLE>
|
||||
bool WorkQueue::postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable)
|
||||
template <typename... ARGS>
|
||||
bool WorkQueueBase::postMaybe(weak_t target, ARGS&&... args)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
// target is a weak_ptr: have to lock it to check it
|
||||
auto tptr = target.lock();
|
||||
if (tptr)
|
||||
{
|
||||
try
|
||||
{
|
||||
tptr->post(time, std::forward<CALLABLE>(callable));
|
||||
// we were able to post()
|
||||
return true;
|
||||
}
|
||||
catch (const Closed&)
|
||||
{
|
||||
// target WorkQueue still exists, but is Closed
|
||||
}
|
||||
return tptr->post(std::forward<ARGS>(args)...);
|
||||
}
|
||||
// either target no longer exists, or its WorkQueue is Closed
|
||||
// target no longer exists
|
||||
return false;
|
||||
}
|
||||
|
||||
/// general case: arbitrary C++ return type
|
||||
template <typename CALLABLE, typename RETURNTYPE>
|
||||
struct WorkQueue::WaitForResult
|
||||
struct WorkQueueBase::WaitForResult
|
||||
{
|
||||
auto operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable)
|
||||
template <typename... ARGS>
|
||||
auto operator()(WorkQueueBase* self, CALLABLE&& callable, ARGS&&... args)
|
||||
{
|
||||
LLCoros::Promise<RETURNTYPE> promise;
|
||||
self->post(
|
||||
time,
|
||||
bool posted = self->post(
|
||||
// We dare to bind a reference to Promise because it's
|
||||
// specifically designed for cross-thread communication.
|
||||
[&promise, callable = std::move(callable)]()
|
||||
|
|
@ -523,7 +568,13 @@ namespace LL
|
|||
{
|
||||
promise.set_exception(std::current_exception());
|
||||
}
|
||||
});
|
||||
},
|
||||
// if caller passed a TimePoint, pass it to post()
|
||||
std::forward<ARGS>(args)...);
|
||||
if (! posted)
|
||||
{
|
||||
LLTHROW(WorkQueueBase::Closed());
|
||||
}
|
||||
auto future{ LLCoros::getFuture(promise) };
|
||||
// now, on the calling thread, wait for that result
|
||||
LLCoros::TempStatus st("waiting for WorkQueue::waitForResult()");
|
||||
|
|
@ -533,13 +584,13 @@ namespace LL
|
|||
|
||||
/// specialize for CALLABLE returning void
|
||||
template <typename CALLABLE>
|
||||
struct WorkQueue::WaitForResult<CALLABLE, void>
|
||||
struct WorkQueueBase::WaitForResult<CALLABLE, void>
|
||||
{
|
||||
void operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable)
|
||||
template <typename... ARGS>
|
||||
void operator()(WorkQueueBase* self, CALLABLE&& callable, ARGS&&... args)
|
||||
{
|
||||
LLCoros::Promise<void> promise;
|
||||
self->post(
|
||||
time,
|
||||
bool posted = self->post(
|
||||
// &promise is designed for cross-thread access
|
||||
[&promise, callable = std::move(callable)]()
|
||||
mutable {
|
||||
|
|
@ -552,7 +603,13 @@ namespace LL
|
|||
{
|
||||
promise.set_exception(std::current_exception());
|
||||
}
|
||||
});
|
||||
},
|
||||
// if caller passed a TimePoint, pass it to post()
|
||||
std::forward<ARGS>(args)...);
|
||||
if (! posted)
|
||||
{
|
||||
LLTHROW(WorkQueueBase::Closed());
|
||||
}
|
||||
auto future{ LLCoros::getFuture(promise) };
|
||||
// block until set_value()
|
||||
LLCoros::TempStatus st("waiting for void WorkQueue::waitForResult()");
|
||||
|
|
@ -560,13 +617,13 @@ namespace LL
|
|||
}
|
||||
};
|
||||
|
||||
template <typename CALLABLE>
|
||||
auto WorkQueue::waitForResult(const TimePoint& time, CALLABLE&& callable)
|
||||
template <typename CALLABLE, typename... ARGS>
|
||||
auto WorkQueueBase::waitForResult(CALLABLE&& callable, ARGS&&... args)
|
||||
{
|
||||
checkCoroutine("waitForResult()");
|
||||
// derive callable's return type so we can specialize for void
|
||||
return WaitForResult<CALLABLE, decltype(std::forward<CALLABLE>(callable)())>()
|
||||
(this, time, std::forward<CALLABLE>(callable));
|
||||
(this, std::forward<CALLABLE>(callable), std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
} // namespace LL
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ void HttpLibcurl::shutdown()
|
|||
|
||||
void HttpLibcurl::start(int policy_count)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
llassert_always(policy_count <= HTTP_POLICY_CLASS_LIMIT);
|
||||
llassert_always(! mMultiHandles); // One-time call only
|
||||
|
||||
|
|
@ -143,6 +144,7 @@ void HttpLibcurl::start(int policy_count)
|
|||
// sleep otherwise ask for a normal polling interval.
|
||||
HttpService::ELoopSpeed HttpLibcurl::processTransport()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpService::ELoopSpeed ret(HttpService::REQUEST_SLEEP);
|
||||
|
||||
// Give libcurl some cycles to do I/O & callbacks
|
||||
|
|
@ -168,6 +170,7 @@ HttpService::ELoopSpeed HttpLibcurl::processTransport()
|
|||
CURLMcode status(CURLM_CALL_MULTI_PERFORM);
|
||||
do
|
||||
{
|
||||
LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("httppt - curl_multi_perform");
|
||||
running = 0;
|
||||
status = curl_multi_perform(mMultiHandles[policy_class], &running);
|
||||
}
|
||||
|
|
@ -176,31 +179,34 @@ HttpService::ELoopSpeed HttpLibcurl::processTransport()
|
|||
// Run completion on anything done
|
||||
CURLMsg * msg(NULL);
|
||||
int msgs_in_queue(0);
|
||||
while ((msg = curl_multi_info_read(mMultiHandles[policy_class], &msgs_in_queue)))
|
||||
{
|
||||
if (CURLMSG_DONE == msg->msg)
|
||||
{
|
||||
CURL * handle(msg->easy_handle);
|
||||
CURLcode result(msg->data.result);
|
||||
{
|
||||
LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("httppt - curl_multi_info_read");
|
||||
while ((msg = curl_multi_info_read(mMultiHandles[policy_class], &msgs_in_queue)))
|
||||
{
|
||||
if (CURLMSG_DONE == msg->msg)
|
||||
{
|
||||
CURL* handle(msg->easy_handle);
|
||||
CURLcode result(msg->data.result);
|
||||
|
||||
completeRequest(mMultiHandles[policy_class], handle, result);
|
||||
handle = NULL; // No longer valid on return
|
||||
ret = HttpService::NORMAL; // If anything completes, we may have a free slot.
|
||||
// Turning around quickly reduces connection gap by 7-10mS.
|
||||
}
|
||||
else if (CURLMSG_NONE == msg->msg)
|
||||
{
|
||||
// Ignore this... it shouldn't mean anything.
|
||||
;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS_ONCE(LOG_CORE) << "Unexpected message from libcurl. Msg code: "
|
||||
<< msg->msg
|
||||
<< LL_ENDL;
|
||||
}
|
||||
msgs_in_queue = 0;
|
||||
}
|
||||
completeRequest(mMultiHandles[policy_class], handle, result);
|
||||
handle = NULL; // No longer valid on return
|
||||
ret = HttpService::NORMAL; // If anything completes, we may have a free slot.
|
||||
// Turning around quickly reduces connection gap by 7-10mS.
|
||||
}
|
||||
else if (CURLMSG_NONE == msg->msg)
|
||||
{
|
||||
// Ignore this... it shouldn't mean anything.
|
||||
;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS_ONCE(LOG_CORE) << "Unexpected message from libcurl. Msg code: "
|
||||
<< msg->msg
|
||||
<< LL_ENDL;
|
||||
}
|
||||
msgs_in_queue = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! mActiveOps.empty())
|
||||
|
|
@ -214,6 +220,7 @@ HttpService::ELoopSpeed HttpLibcurl::processTransport()
|
|||
// Caller has provided us with a ref count on op.
|
||||
void HttpLibcurl::addOp(const HttpOpRequest::ptr_t &op)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
llassert_always(op->mReqPolicy < mPolicyCount);
|
||||
llassert_always(mMultiHandles[op->mReqPolicy] != NULL);
|
||||
|
||||
|
|
@ -257,6 +264,7 @@ void HttpLibcurl::addOp(const HttpOpRequest::ptr_t &op)
|
|||
// method to kill the request.
|
||||
bool HttpLibcurl::cancel(HttpHandle handle)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpOpRequest::ptr_t op = HttpOpRequest::fromHandle<HttpOpRequest>(handle);
|
||||
active_set_t::iterator it(mActiveOps.find(op));
|
||||
if (mActiveOps.end() == it)
|
||||
|
|
@ -282,6 +290,7 @@ bool HttpLibcurl::cancel(HttpHandle handle)
|
|||
// op to the reply queue with refcount intact.
|
||||
void HttpLibcurl::cancelRequest(const HttpOpRequest::ptr_t &op)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
// Deactivate request
|
||||
op->mCurlActive = false;
|
||||
|
||||
|
|
@ -308,6 +317,7 @@ void HttpLibcurl::cancelRequest(const HttpOpRequest::ptr_t &op)
|
|||
// Keep them synchronized as necessary.
|
||||
bool HttpLibcurl::completeRequest(CURLM * multi_handle, CURL * handle, CURLcode status)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpHandle ophandle(NULL);
|
||||
|
||||
CURLcode ccode(CURLE_OK);
|
||||
|
|
@ -445,6 +455,7 @@ int HttpLibcurl::getActiveCountInClass(int policy_class) const
|
|||
|
||||
void HttpLibcurl::policyUpdated(int policy_class)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
if (policy_class < 0 || policy_class >= mPolicyCount || ! mMultiHandles)
|
||||
{
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ HttpOperation::HttpOperation():
|
|||
mReplyQueue(),
|
||||
mUserHandler(),
|
||||
mReqPolicy(HttpRequest::DEFAULT_POLICY_ID),
|
||||
mReqPriority(0U),
|
||||
mTracing(HTTP_TRACE_OFF),
|
||||
mMyHandle(LLCORE_HTTP_HANDLE_INVALID)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -181,7 +181,6 @@ protected:
|
|||
public:
|
||||
// Request Data
|
||||
HttpRequest::policy_t mReqPolicy;
|
||||
HttpRequest::priority_t mReqPriority;
|
||||
|
||||
// Reply Data
|
||||
HttpStatus mStatus;
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@ HttpOpRequest::~HttpOpRequest()
|
|||
|
||||
void HttpOpRequest::stageFromRequest(HttpService * service)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpOpRequest::ptr_t self(boost::dynamic_pointer_cast<HttpOpRequest>(shared_from_this()));
|
||||
service->getPolicy().addOp(self); // transfers refcount
|
||||
}
|
||||
|
|
@ -207,6 +208,7 @@ void HttpOpRequest::stageFromRequest(HttpService * service)
|
|||
|
||||
void HttpOpRequest::stageFromReady(HttpService * service)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpOpRequest::ptr_t self(boost::dynamic_pointer_cast<HttpOpRequest>(shared_from_this()));
|
||||
service->getTransport().addOp(self); // transfers refcount
|
||||
}
|
||||
|
|
@ -214,6 +216,7 @@ void HttpOpRequest::stageFromReady(HttpService * service)
|
|||
|
||||
void HttpOpRequest::stageFromActive(HttpService * service)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
if (mReplyLength)
|
||||
{
|
||||
// If non-zero, we received and processed a Content-Range
|
||||
|
|
@ -250,6 +253,7 @@ void HttpOpRequest::stageFromActive(HttpService * service)
|
|||
|
||||
void HttpOpRequest::visitNotifier(HttpRequest * request)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
if (mUserHandler)
|
||||
{
|
||||
HttpResponse * response = new HttpResponse();
|
||||
|
|
@ -292,6 +296,7 @@ void HttpOpRequest::visitNotifier(HttpRequest * request)
|
|||
|
||||
HttpStatus HttpOpRequest::cancel()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
mStatus = HttpStatus(HttpStatus::LLCORE, HE_OP_CANCELED);
|
||||
|
||||
addAsReply();
|
||||
|
|
@ -301,12 +306,12 @@ HttpStatus HttpOpRequest::cancel()
|
|||
|
||||
|
||||
HttpStatus HttpOpRequest::setupGet(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers)
|
||||
{
|
||||
setupCommon(policy_id, priority, url, NULL, options, headers);
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
setupCommon(policy_id, url, NULL, options, headers);
|
||||
mReqMethod = HOR_GET;
|
||||
|
||||
return HttpStatus();
|
||||
|
|
@ -314,14 +319,14 @@ HttpStatus HttpOpRequest::setupGet(HttpRequest::policy_t policy_id,
|
|||
|
||||
|
||||
HttpStatus HttpOpRequest::setupGetByteRange(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
size_t offset,
|
||||
size_t len,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers)
|
||||
{
|
||||
setupCommon(policy_id, priority, url, NULL, options, headers);
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
setupCommon(policy_id, url, NULL, options, headers);
|
||||
mReqMethod = HOR_GET;
|
||||
mReqOffset = offset;
|
||||
mReqLength = len;
|
||||
|
|
@ -335,13 +340,13 @@ HttpStatus HttpOpRequest::setupGetByteRange(HttpRequest::policy_t policy_id,
|
|||
|
||||
|
||||
HttpStatus HttpOpRequest::setupPost(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers)
|
||||
{
|
||||
setupCommon(policy_id, priority, url, body, options, headers);
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
setupCommon(policy_id, url, body, options, headers);
|
||||
mReqMethod = HOR_POST;
|
||||
|
||||
return HttpStatus();
|
||||
|
|
@ -349,13 +354,13 @@ HttpStatus HttpOpRequest::setupPost(HttpRequest::policy_t policy_id,
|
|||
|
||||
|
||||
HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers)
|
||||
{
|
||||
setupCommon(policy_id, priority, url, body, options, headers);
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
setupCommon(policy_id, url, body, options, headers);
|
||||
mReqMethod = HOR_PUT;
|
||||
|
||||
return HttpStatus();
|
||||
|
|
@ -363,12 +368,12 @@ HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id,
|
|||
|
||||
|
||||
HttpStatus HttpOpRequest::setupDelete(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers)
|
||||
{
|
||||
setupCommon(policy_id, priority, url, NULL, options, headers);
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
setupCommon(policy_id, url, NULL, options, headers);
|
||||
mReqMethod = HOR_DELETE;
|
||||
|
||||
return HttpStatus();
|
||||
|
|
@ -376,13 +381,13 @@ HttpStatus HttpOpRequest::setupDelete(HttpRequest::policy_t policy_id,
|
|||
|
||||
|
||||
HttpStatus HttpOpRequest::setupPatch(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers)
|
||||
{
|
||||
setupCommon(policy_id, priority, url, body, options, headers);
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
setupCommon(policy_id, url, body, options, headers);
|
||||
mReqMethod = HOR_PATCH;
|
||||
|
||||
return HttpStatus();
|
||||
|
|
@ -390,12 +395,12 @@ HttpStatus HttpOpRequest::setupPatch(HttpRequest::policy_t policy_id,
|
|||
|
||||
|
||||
HttpStatus HttpOpRequest::setupCopy(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t &headers)
|
||||
{
|
||||
setupCommon(policy_id, priority, url, NULL, options, headers);
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
setupCommon(policy_id, url, NULL, options, headers);
|
||||
mReqMethod = HOR_COPY;
|
||||
|
||||
return HttpStatus();
|
||||
|
|
@ -403,12 +408,12 @@ HttpStatus HttpOpRequest::setupCopy(HttpRequest::policy_t policy_id,
|
|||
|
||||
|
||||
HttpStatus HttpOpRequest::setupMove(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t &headers)
|
||||
{
|
||||
setupCommon(policy_id, priority, url, NULL, options, headers);
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
setupCommon(policy_id, url, NULL, options, headers);
|
||||
mReqMethod = HOR_MOVE;
|
||||
|
||||
return HttpStatus();
|
||||
|
|
@ -416,15 +421,14 @@ HttpStatus HttpOpRequest::setupMove(HttpRequest::policy_t policy_id,
|
|||
|
||||
|
||||
void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
mProcFlags = 0U;
|
||||
mReqPolicy = policy_id;
|
||||
mReqPriority = priority;
|
||||
mReqURL = url;
|
||||
if (body)
|
||||
{
|
||||
|
|
@ -465,6 +469,7 @@ void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id,
|
|||
// *TODO: Move this to _httplibcurl where it belongs.
|
||||
HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
// Scrub transport and result data for retried op case
|
||||
mCurlActive = false;
|
||||
mCurlHandle = NULL;
|
||||
|
|
@ -773,6 +778,7 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
|
|||
|
||||
size_t HttpOpRequest::writeCallback(void * data, size_t size, size_t nmemb, void * userdata)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(userdata));
|
||||
|
||||
if (! op->mReplyBody)
|
||||
|
|
@ -788,6 +794,7 @@ size_t HttpOpRequest::writeCallback(void * data, size_t size, size_t nmemb, void
|
|||
|
||||
size_t HttpOpRequest::readCallback(void * data, size_t size, size_t nmemb, void * userdata)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(userdata));
|
||||
|
||||
if (! op->mReqBody)
|
||||
|
|
@ -819,6 +826,7 @@ size_t HttpOpRequest::readCallback(void * data, size_t size, size_t nmemb, void
|
|||
|
||||
int HttpOpRequest::seekCallback(void *userdata, curl_off_t offset, int origin)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(userdata));
|
||||
|
||||
if (!op->mReqBody)
|
||||
|
|
@ -850,6 +858,7 @@ int HttpOpRequest::seekCallback(void *userdata, curl_off_t offset, int origin)
|
|||
|
||||
size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, void * userdata)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
static const char status_line[] = "HTTP/";
|
||||
static const size_t status_line_len = sizeof(status_line) - 1;
|
||||
static const char con_ran_line[] = "content-range";
|
||||
|
|
@ -999,6 +1008,7 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi
|
|||
|
||||
CURLcode HttpOpRequest::curlSslCtxCallback(CURL *curl, void *sslctx, void *userdata)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(userdata));
|
||||
|
||||
if (op->mCallbackSSLVerify)
|
||||
|
|
@ -1025,6 +1035,7 @@ CURLcode HttpOpRequest::curlSslCtxCallback(CURL *curl, void *sslctx, void *userd
|
|||
|
||||
int HttpOpRequest::sslCertVerifyCallback(X509_STORE_CTX *ctx, void *param)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(param));
|
||||
|
||||
if (op->mCallbackSSLVerify)
|
||||
|
|
@ -1037,6 +1048,7 @@ int HttpOpRequest::sslCertVerifyCallback(X509_STORE_CTX *ctx, void *param)
|
|||
|
||||
int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffer, size_t len, void * userdata)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpOpRequest::ptr_t op(HttpOpRequest::fromHandle<HttpOpRequest>(userdata));
|
||||
|
||||
std::string safe_line;
|
||||
|
|
|
|||
|
|
@ -105,13 +105,11 @@ public:
|
|||
/// Threading: called by application thread
|
||||
///
|
||||
HttpStatus setupGet(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers);
|
||||
|
||||
HttpStatus setupGetByteRange(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
size_t offset,
|
||||
size_t len,
|
||||
|
|
@ -119,40 +117,34 @@ public:
|
|||
const HttpHeaders::ptr_t & headers);
|
||||
|
||||
HttpStatus setupPost(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers);
|
||||
|
||||
HttpStatus setupPut(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers);
|
||||
|
||||
HttpStatus setupDelete(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers);
|
||||
|
||||
HttpStatus setupPatch(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers);
|
||||
|
||||
HttpStatus setupCopy(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers);
|
||||
|
||||
HttpStatus setupMove(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers);
|
||||
|
|
@ -172,7 +164,6 @@ protected:
|
|||
// Threading: called by application thread
|
||||
//
|
||||
void setupCommon(HttpRequest::policy_t policy_id,
|
||||
HttpRequest::priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
|
|
@ -239,19 +230,6 @@ public:
|
|||
|
||||
|
||||
|
||||
/// HttpOpRequestCompare isn't an operation but a uniform comparison
|
||||
/// functor for STL containers that order by priority. Mainly
|
||||
/// used for the ready queue container but defined here.
|
||||
class HttpOpRequestCompare
|
||||
{
|
||||
public:
|
||||
bool operator()(const HttpOpRequest * lhs, const HttpOpRequest * rhs)
|
||||
{
|
||||
return lhs->mReqPriority > rhs->mReqPriority;
|
||||
}
|
||||
}; // end class HttpOpRequestCompare
|
||||
|
||||
|
||||
// ---------------------------------------
|
||||
// Free functions
|
||||
// ---------------------------------------
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if 0 // DEPRECATED
|
||||
#include "_httpopsetpriority.h"
|
||||
|
||||
#include "httpresponse.h"
|
||||
|
|
@ -61,3 +62,5 @@ void HttpOpSetPriority::stageFromRequest(HttpService * service)
|
|||
|
||||
|
||||
} // end namespace LLCore
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
#ifndef _LLCORE_HTTP_SETPRIORITY_H_
|
||||
#define _LLCORE_HTTP_SETPRIORITY_H_
|
||||
|
||||
|
||||
#if 0 // DEPRECATED
|
||||
#include "httpcommon.h"
|
||||
#include "httprequest.h"
|
||||
#include "_httpoperation.h"
|
||||
|
|
@ -49,7 +49,7 @@ namespace LLCore
|
|||
class HttpOpSetPriority : public HttpOperation
|
||||
{
|
||||
public:
|
||||
HttpOpSetPriority(HttpHandle handle, HttpRequest::priority_t priority);
|
||||
HttpOpSetPriority(HttpHandle handle);
|
||||
|
||||
virtual ~HttpOpSetPriority();
|
||||
|
||||
|
|
@ -63,10 +63,10 @@ public:
|
|||
protected:
|
||||
// Request Data
|
||||
HttpHandle mHandle;
|
||||
HttpRequest::priority_t mPriority;
|
||||
}; // end class HttpOpSetPriority
|
||||
|
||||
} // end namespace LLCore
|
||||
#endif
|
||||
|
||||
#endif // _LLCORE_HTTP_SETPRIORITY_H_
|
||||
|
||||
|
|
|
|||
|
|
@ -330,37 +330,6 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue()
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool HttpPolicy::changePriority(HttpHandle handle, HttpRequest::priority_t priority)
|
||||
{
|
||||
for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)
|
||||
{
|
||||
ClassState & state(*mClasses[policy_class]);
|
||||
// We don't scan retry queue because a priority change there
|
||||
// is meaningless. The request will be issued based on retry
|
||||
// intervals not priority value, which is now moot.
|
||||
|
||||
// Scan ready queue for requests that match policy
|
||||
HttpReadyQueue::container_type & c(state.mReadyQueue.get_container());
|
||||
for (HttpReadyQueue::container_type::iterator iter(c.begin()); c.end() != iter;)
|
||||
{
|
||||
HttpReadyQueue::container_type::iterator cur(iter++);
|
||||
|
||||
if ((*cur)->getHandle() == handle)
|
||||
{
|
||||
HttpOpRequest::ptr_t op(*cur);
|
||||
c.erase(cur); // All iterators are now invalidated
|
||||
op->mReqPriority = priority;
|
||||
state.mReadyQueue.push(op); // Re-insert using adapter class
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool HttpPolicy::cancel(HttpHandle handle)
|
||||
{
|
||||
for (int policy_class(0); policy_class < mClasses.size(); ++policy_class)
|
||||
|
|
|
|||
|
|
@ -110,12 +110,6 @@ public:
|
|||
/// Threading: called by worker thread
|
||||
void retryOp(const opReqPtr_t &);
|
||||
|
||||
/// Attempt to change the priority of an earlier request.
|
||||
/// Request that Shadows HttpService's method
|
||||
///
|
||||
/// Threading: called by worker thread
|
||||
bool changePriority(HttpHandle handle, HttpRequest::priority_t priority);
|
||||
|
||||
/// Attempt to cancel a previous request.
|
||||
/// Shadows HttpService's method as well
|
||||
///
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ HttpService::HttpService()
|
|||
|
||||
HttpService::~HttpService()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
mExitRequested = 1U;
|
||||
if (RUNNING == sState)
|
||||
{
|
||||
|
|
@ -131,6 +132,7 @@ HttpService::~HttpService()
|
|||
|
||||
void HttpService::init(HttpRequestQueue * queue)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
llassert_always(! sInstance);
|
||||
llassert_always(NOT_INITIALIZED == sState);
|
||||
sInstance = new HttpService();
|
||||
|
|
@ -145,6 +147,7 @@ void HttpService::init(HttpRequestQueue * queue)
|
|||
|
||||
void HttpService::term()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
if (sInstance)
|
||||
{
|
||||
if (RUNNING == sState && sInstance->mThread)
|
||||
|
|
@ -196,6 +199,7 @@ bool HttpService::isStopped()
|
|||
/// Threading: callable by consumer thread *once*.
|
||||
void HttpService::startThread()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
llassert_always(! mThread || STOPPED == sState);
|
||||
llassert_always(INITIALIZED == sState || STOPPED == sState);
|
||||
|
||||
|
|
@ -220,22 +224,6 @@ void HttpService::stopRequested()
|
|||
}
|
||||
|
||||
|
||||
/// Threading: callable by worker thread.
|
||||
bool HttpService::changePriority(HttpHandle handle, HttpRequest::priority_t priority)
|
||||
{
|
||||
bool found(false);
|
||||
|
||||
// Skip the request queue as we currently don't leave earlier
|
||||
// requests sitting there. Start with the ready queue...
|
||||
found = mPolicy->changePriority(handle, priority);
|
||||
|
||||
// If not there, we could try the transport/active queue but priority
|
||||
// doesn't really have much effect there so we don't waste cycles.
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
/// Try to find the given request handle on any of the request
|
||||
/// queues and cancel the operation.
|
||||
///
|
||||
|
|
@ -244,6 +232,7 @@ bool HttpService::changePriority(HttpHandle handle, HttpRequest::priority_t prio
|
|||
/// Threading: callable by worker thread.
|
||||
bool HttpService::cancel(HttpHandle handle)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
bool canceled(false);
|
||||
|
||||
// Request can't be on request queue so skip that.
|
||||
|
|
@ -264,6 +253,7 @@ bool HttpService::cancel(HttpHandle handle)
|
|||
/// Threading: callable by worker thread.
|
||||
void HttpService::shutdown()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
// Disallow future enqueue of requests
|
||||
mRequestQueue->stopQueue();
|
||||
|
||||
|
|
@ -293,6 +283,8 @@ void HttpService::shutdown()
|
|||
// requested to stop.
|
||||
void HttpService::threadRun(LLCoreInt::HttpThread * thread)
|
||||
{
|
||||
LL_PROFILER_SET_THREAD_NAME("HttpService");
|
||||
|
||||
boost::this_thread::disable_interruption di;
|
||||
|
||||
LLThread::registerThreadID();
|
||||
|
|
@ -300,6 +292,7 @@ void HttpService::threadRun(LLCoreInt::HttpThread * thread)
|
|||
ELoopSpeed loop(REQUEST_SLEEP);
|
||||
while (! mExitRequested)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
try
|
||||
{
|
||||
loop = processRequestQueue(loop);
|
||||
|
|
@ -344,6 +337,7 @@ void HttpService::threadRun(LLCoreInt::HttpThread * thread)
|
|||
|
||||
HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpRequestQueue::OpContainer ops;
|
||||
const bool wait_for_req(REQUEST_SLEEP == loop);
|
||||
|
||||
|
|
@ -384,6 +378,7 @@ HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop)
|
|||
HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
|
||||
long * ret_value)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
|
||||
|| opt >= HttpRequest::PO_LAST // ditto
|
||||
|| (! sOptionDesc[opt].mIsLong) // datatype is long
|
||||
|
|
@ -416,6 +411,7 @@ HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequ
|
|||
HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
|
||||
std::string * ret_value)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
|
||||
|
||||
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
|
||||
|
|
@ -443,6 +439,7 @@ HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequ
|
|||
HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
|
||||
HttpRequest::policyCallback_t * ret_value)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
|
||||
|
||||
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
|
||||
|
|
@ -472,6 +469,7 @@ HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequ
|
|||
HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
|
||||
long value, long * ret_value)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
|
||||
|
||||
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
|
||||
|
|
@ -517,6 +515,7 @@ HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequ
|
|||
HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
|
||||
const std::string & value, std::string * ret_value)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
|
||||
|
||||
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
|
||||
|
|
@ -548,6 +547,7 @@ HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequ
|
|||
HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
|
||||
HttpRequest::policyCallback_t value, HttpRequest::policyCallback_t * ret_value)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
|
||||
|
||||
if (opt < HttpRequest::PO_CONNECTION_LIMIT // option must be in range
|
||||
|
|
|
|||
|
|
@ -146,15 +146,6 @@ public:
|
|||
/// Threading: callable by worker thread.
|
||||
void shutdown();
|
||||
|
||||
/// Try to find the given request handle on any of the request
|
||||
/// queues and reset the priority (and queue position) of the
|
||||
/// request if found.
|
||||
///
|
||||
/// @return True if the request was found somewhere.
|
||||
///
|
||||
/// Threading: callable by worker thread.
|
||||
bool changePriority(HttpHandle handle, HttpRequest::priority_t priority);
|
||||
|
||||
/// Try to find the given request handle on any of the request
|
||||
/// queues and cancel the operation.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -469,11 +469,11 @@ bool WorkingSet::reload(LLCore::HttpRequest * hr, LLCore::HttpOptions::ptr_t & o
|
|||
LLCore::HttpHandle handle;
|
||||
if (offset || length)
|
||||
{
|
||||
handle = hr->requestGetByteRange(0, 0, buffer, offset, length, opt, mHeaders, LLCore::HttpHandler::ptr_t(this, NoOpDeletor));
|
||||
handle = hr->requestGetByteRange(0, buffer, offset, length, opt, mHeaders, LLCore::HttpHandler::ptr_t(this, NoOpDeletor));
|
||||
}
|
||||
else
|
||||
{
|
||||
handle = hr->requestGet(0, 0, buffer, opt, mHeaders, LLCore::HttpHandler::ptr_t(this, NoOpDeletor));
|
||||
handle = hr->requestGet(0, buffer, opt, mHeaders, LLCore::HttpHandler::ptr_t(this, NoOpDeletor));
|
||||
}
|
||||
if (! handle)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@
|
|||
#include "_httppolicy.h"
|
||||
#include "_httpoperation.h"
|
||||
#include "_httpoprequest.h"
|
||||
#include "_httpopsetpriority.h"
|
||||
#include "_httpopcancel.h"
|
||||
#include "_httpopsetget.h"
|
||||
|
||||
|
|
@ -183,16 +182,16 @@ HttpStatus HttpRequest::getStatus() const
|
|||
|
||||
|
||||
HttpHandle HttpRequest::requestGet(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers,
|
||||
HttpHandler::ptr_t user_handler)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpStatus status;
|
||||
|
||||
HttpOpRequest::ptr_t op(new HttpOpRequest());
|
||||
if (! (status = op->setupGet(policy_id, priority, url, options, headers)))
|
||||
if (! (status = op->setupGet(policy_id, url, options, headers)))
|
||||
{
|
||||
mLastReqStatus = status;
|
||||
return LLCORE_HTTP_HANDLE_INVALID;
|
||||
|
|
@ -210,7 +209,6 @@ HttpHandle HttpRequest::requestGet(policy_t policy_id,
|
|||
|
||||
|
||||
HttpHandle HttpRequest::requestGetByteRange(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
size_t offset,
|
||||
size_t len,
|
||||
|
|
@ -218,10 +216,11 @@ HttpHandle HttpRequest::requestGetByteRange(policy_t policy_id,
|
|||
const HttpHeaders::ptr_t & headers,
|
||||
HttpHandler::ptr_t user_handler)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
|
||||
HttpStatus status;
|
||||
|
||||
HttpOpRequest::ptr_t op(new HttpOpRequest());
|
||||
if (! (status = op->setupGetByteRange(policy_id, priority, url, offset, len, options, headers)))
|
||||
if (! (status = op->setupGetByteRange(policy_id, url, offset, len, options, headers)))
|
||||
{
|
||||
mLastReqStatus = status;
|
||||
return LLCORE_HTTP_HANDLE_INVALID;
|
||||
|
|
@ -239,7 +238,6 @@ HttpHandle HttpRequest::requestGetByteRange(policy_t policy_id,
|
|||
|
||||
|
||||
HttpHandle HttpRequest::requestPost(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
|
|
@ -249,7 +247,7 @@ HttpHandle HttpRequest::requestPost(policy_t policy_id,
|
|||
HttpStatus status;
|
||||
|
||||
HttpOpRequest::ptr_t op(new HttpOpRequest());
|
||||
if (! (status = op->setupPost(policy_id, priority, url, body, options, headers)))
|
||||
if (! (status = op->setupPost(policy_id, url, body, options, headers)))
|
||||
{
|
||||
mLastReqStatus = status;
|
||||
return LLCORE_HTTP_HANDLE_INVALID;
|
||||
|
|
@ -267,7 +265,6 @@ HttpHandle HttpRequest::requestPost(policy_t policy_id,
|
|||
|
||||
|
||||
HttpHandle HttpRequest::requestPut(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
|
|
@ -277,7 +274,7 @@ HttpHandle HttpRequest::requestPut(policy_t policy_id,
|
|||
HttpStatus status;
|
||||
|
||||
HttpOpRequest::ptr_t op (new HttpOpRequest());
|
||||
if (! (status = op->setupPut(policy_id, priority, url, body, options, headers)))
|
||||
if (! (status = op->setupPut(policy_id, url, body, options, headers)))
|
||||
{
|
||||
mLastReqStatus = status;
|
||||
return LLCORE_HTTP_HANDLE_INVALID;
|
||||
|
|
@ -294,7 +291,6 @@ HttpHandle HttpRequest::requestPut(policy_t policy_id,
|
|||
}
|
||||
|
||||
HttpHandle HttpRequest::requestDelete(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers,
|
||||
|
|
@ -303,7 +299,7 @@ HttpHandle HttpRequest::requestDelete(policy_t policy_id,
|
|||
HttpStatus status;
|
||||
|
||||
HttpOpRequest::ptr_t op(new HttpOpRequest());
|
||||
if (!(status = op->setupDelete(policy_id, priority, url, options, headers)))
|
||||
if (!(status = op->setupDelete(policy_id, url, options, headers)))
|
||||
{
|
||||
mLastReqStatus = status;
|
||||
return LLCORE_HTTP_HANDLE_INVALID;
|
||||
|
|
@ -320,7 +316,6 @@ HttpHandle HttpRequest::requestDelete(policy_t policy_id,
|
|||
}
|
||||
|
||||
HttpHandle HttpRequest::requestPatch(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
|
|
@ -330,7 +325,7 @@ HttpHandle HttpRequest::requestPatch(policy_t policy_id,
|
|||
HttpStatus status;
|
||||
|
||||
HttpOpRequest::ptr_t op (new HttpOpRequest());
|
||||
if (!(status = op->setupPatch(policy_id, priority, url, body, options, headers)))
|
||||
if (!(status = op->setupPatch(policy_id, url, body, options, headers)))
|
||||
{
|
||||
mLastReqStatus = status;
|
||||
return LLCORE_HTTP_HANDLE_INVALID;
|
||||
|
|
@ -347,7 +342,6 @@ HttpHandle HttpRequest::requestPatch(policy_t policy_id,
|
|||
}
|
||||
|
||||
HttpHandle HttpRequest::requestCopy(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers,
|
||||
|
|
@ -356,7 +350,7 @@ HttpHandle HttpRequest::requestCopy(policy_t policy_id,
|
|||
HttpStatus status;
|
||||
|
||||
HttpOpRequest::ptr_t op(new HttpOpRequest());
|
||||
if (!(status = op->setupCopy(policy_id, priority, url, options, headers)))
|
||||
if (!(status = op->setupCopy(policy_id, url, options, headers)))
|
||||
{
|
||||
mLastReqStatus = status;
|
||||
return LLCORE_HTTP_HANDLE_INVALID;
|
||||
|
|
@ -374,7 +368,6 @@ HttpHandle HttpRequest::requestCopy(policy_t policy_id,
|
|||
}
|
||||
|
||||
HttpHandle HttpRequest::requestMove(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers,
|
||||
|
|
@ -383,7 +376,7 @@ HttpHandle HttpRequest::requestMove(policy_t policy_id,
|
|||
HttpStatus status;
|
||||
|
||||
HttpOpRequest::ptr_t op (new HttpOpRequest());
|
||||
if (!(status = op->setupMove(policy_id, priority, url, options, headers)))
|
||||
if (!(status = op->setupMove(policy_id, url, options, headers)))
|
||||
{
|
||||
mLastReqStatus = status;
|
||||
return LLCORE_HTTP_HANDLE_INVALID;
|
||||
|
|
@ -483,24 +476,6 @@ HttpHandle HttpRequest::requestCancel(HttpHandle request, HttpHandler::ptr_t use
|
|||
}
|
||||
|
||||
|
||||
HttpHandle HttpRequest::requestSetPriority(HttpHandle request, priority_t priority,
|
||||
HttpHandler::ptr_t handler)
|
||||
{
|
||||
HttpStatus status;
|
||||
|
||||
HttpOperation::ptr_t op (new HttpOpSetPriority(request, priority));
|
||||
op->setReplyPath(mReplyQueue, handler);
|
||||
if (! (status = mRequestQueue->addOp(op))) // transfers refcount
|
||||
{
|
||||
mLastReqStatus = status;
|
||||
return LLCORE_HTTP_HANDLE_INVALID;
|
||||
}
|
||||
|
||||
mLastReqStatus = status;
|
||||
return op->getHandle();
|
||||
}
|
||||
|
||||
|
||||
// ====================================
|
||||
// Utility Methods
|
||||
// ====================================
|
||||
|
|
|
|||
|
|
@ -95,7 +95,6 @@ private:
|
|||
|
||||
public:
|
||||
typedef unsigned int policy_t;
|
||||
typedef unsigned int priority_t;
|
||||
|
||||
typedef boost::shared_ptr<HttpRequest> ptr_t;
|
||||
typedef boost::weak_ptr<HttpRequest> wptr_t;
|
||||
|
|
@ -316,8 +315,6 @@ public:
|
|||
///
|
||||
/// @param policy_id Default or user-defined policy class under
|
||||
/// which this request is to be serviced.
|
||||
/// @param priority Standard priority scheme inherited from
|
||||
/// Indra code base (U32-type scheme).
|
||||
/// @param url URL with any encoded query parameters to
|
||||
/// be accessed.
|
||||
/// @param options Optional instance of an HttpOptions object
|
||||
|
|
@ -346,7 +343,6 @@ public:
|
|||
/// case, @see getStatus() will return more info.
|
||||
///
|
||||
HttpHandle requestGet(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers,
|
||||
|
|
@ -377,7 +373,6 @@ public:
|
|||
/// - Referer:
|
||||
///
|
||||
/// @param policy_id @see requestGet()
|
||||
/// @param priority "
|
||||
/// @param url "
|
||||
/// @param offset Offset of first byte into resource to be returned.
|
||||
/// @param len Count of bytes to be returned
|
||||
|
|
@ -387,7 +382,6 @@ public:
|
|||
/// @return "
|
||||
///
|
||||
HttpHandle requestGetByteRange(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
size_t offset,
|
||||
size_t len,
|
||||
|
|
@ -418,7 +412,6 @@ public:
|
|||
/// - Expect:
|
||||
///
|
||||
/// @param policy_id @see requestGet()
|
||||
/// @param priority "
|
||||
/// @param url "
|
||||
/// @param body Byte stream to be sent as the body. No
|
||||
/// further encoding or escaping will be done
|
||||
|
|
@ -429,7 +422,6 @@ public:
|
|||
/// @return "
|
||||
///
|
||||
HttpHandle requestPost(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
|
|
@ -459,7 +451,6 @@ public:
|
|||
/// - Content-Type:
|
||||
///
|
||||
/// @param policy_id @see requestGet()
|
||||
/// @param priority "
|
||||
/// @param url "
|
||||
/// @param body Byte stream to be sent as the body. No
|
||||
/// further encoding or escaping will be done
|
||||
|
|
@ -470,7 +461,6 @@ public:
|
|||
/// @return "
|
||||
///
|
||||
HttpHandle requestPut(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
|
|
@ -483,7 +473,6 @@ public:
|
|||
/// encoding and communicating the content types.
|
||||
///
|
||||
/// @param policy_id @see requestGet()
|
||||
/// @param priority "
|
||||
/// @param url "
|
||||
/// @param options @see requestGet()K(optional)
|
||||
/// @param headers "
|
||||
|
|
@ -491,7 +480,6 @@ public:
|
|||
/// @return "
|
||||
///
|
||||
HttpHandle requestDelete(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers,
|
||||
|
|
@ -502,7 +490,6 @@ public:
|
|||
/// encoding and communicating the content types.
|
||||
///
|
||||
/// @param policy_id @see requestGet()
|
||||
/// @param priority "
|
||||
/// @param url "
|
||||
/// @param body Byte stream to be sent as the body. No
|
||||
/// further encoding or escaping will be done
|
||||
|
|
@ -513,7 +500,6 @@ public:
|
|||
/// @return "
|
||||
///
|
||||
HttpHandle requestPatch(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
BufferArray * body,
|
||||
const HttpOptions::ptr_t & options,
|
||||
|
|
@ -525,7 +511,6 @@ public:
|
|||
/// encoding and communicating the content types.
|
||||
///
|
||||
/// @param policy_id @see requestGet()
|
||||
/// @param priority "
|
||||
/// @param url "
|
||||
/// @param options @see requestGet()K(optional)
|
||||
/// @param headers "
|
||||
|
|
@ -533,7 +518,6 @@ public:
|
|||
/// @return "
|
||||
///
|
||||
HttpHandle requestCopy(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers,
|
||||
|
|
@ -544,7 +528,6 @@ public:
|
|||
/// encoding and communicating the content types.
|
||||
///
|
||||
/// @param policy_id @see requestGet()
|
||||
/// @param priority "
|
||||
/// @param url "
|
||||
/// @param options @see requestGet()K(optional)
|
||||
/// @param headers "
|
||||
|
|
@ -552,7 +535,6 @@ public:
|
|||
/// @return "
|
||||
///
|
||||
HttpHandle requestMove(policy_t policy_id,
|
||||
priority_t priority,
|
||||
const std::string & url,
|
||||
const HttpOptions::ptr_t & options,
|
||||
const HttpHeaders::ptr_t & headers,
|
||||
|
|
@ -593,18 +575,6 @@ public:
|
|||
|
||||
HttpHandle requestCancel(HttpHandle request, HttpHandler::ptr_t);
|
||||
|
||||
/// Request that a previously-issued request be reprioritized.
|
||||
/// The status of whether the change itself succeeded arrives
|
||||
/// via notification.
|
||||
///
|
||||
/// @param request Handle of previously-issued request to
|
||||
/// be changed.
|
||||
/// @param priority New priority value.
|
||||
/// @param handler @see requestGet()
|
||||
/// @return "
|
||||
///
|
||||
HttpHandle requestSetPriority(HttpHandle request, priority_t priority, HttpHandler::ptr_t handler);
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name UtilityMethods
|
||||
|
|
|
|||
|
|
@ -614,7 +614,6 @@ void HttpRequestTestObjectType::test<7>()
|
|||
// Issue a GET that can't connect
|
||||
mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT);
|
||||
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
"http://127.0.0.1:2/nothing/here",
|
||||
0,
|
||||
0,
|
||||
|
|
@ -716,7 +715,6 @@ void HttpRequestTestObjectType::test<8>()
|
|||
// Issue a GET that *can* connect
|
||||
mStatus = HttpStatus(200);
|
||||
HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base,
|
||||
HttpOptions::ptr_t(),
|
||||
HttpHeaders::ptr_t(),
|
||||
|
|
@ -812,7 +810,6 @@ void HttpRequestTestObjectType::test<9>()
|
|||
// Issue a GET that *can* connect
|
||||
mStatus = HttpStatus(200);
|
||||
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base,
|
||||
0,
|
||||
0,
|
||||
|
|
@ -913,7 +910,6 @@ void HttpRequestTestObjectType::test<10>()
|
|||
body->append(body_text, strlen(body_text));
|
||||
mStatus = HttpStatus(200);
|
||||
HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base,
|
||||
body,
|
||||
HttpOptions::ptr_t(),
|
||||
|
|
@ -1020,7 +1016,6 @@ void HttpRequestTestObjectType::test<11>()
|
|||
body->append(body_text, strlen(body_text));
|
||||
mStatus = HttpStatus(200);
|
||||
HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base,
|
||||
body,
|
||||
HttpOptions::ptr_t(),
|
||||
|
|
@ -1127,7 +1122,6 @@ void HttpRequestTestObjectType::test<12>()
|
|||
// Issue a GET that *can* connect
|
||||
mStatus = HttpStatus(200);
|
||||
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base,
|
||||
0,
|
||||
0,
|
||||
|
|
@ -1240,7 +1234,6 @@ void HttpRequestTestObjectType::test<13>()
|
|||
regex_container_t::value_type(boost::regex("X-LL-Special", boost::regex::icase),
|
||||
boost::regex(".*", boost::regex::icase)));
|
||||
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base,
|
||||
0,
|
||||
0,
|
||||
|
|
@ -1346,7 +1339,6 @@ void HttpRequestTestObjectType::test<14>()
|
|||
// Issue a GET that sleeps
|
||||
mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT);
|
||||
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base,
|
||||
0,
|
||||
0,
|
||||
|
|
@ -1454,7 +1446,6 @@ void HttpRequestTestObjectType::test<15>()
|
|||
mStatus = HttpStatus(200);
|
||||
handler.mCheckContentType = "application/llsd+xml";
|
||||
HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base,
|
||||
HttpOptions::ptr_t(),
|
||||
HttpHeaders::ptr_t(),
|
||||
|
|
@ -1609,7 +1600,6 @@ void HttpRequestTestObjectType::test<16>()
|
|||
boost::regex("X-Reflect-content-encoding", boost::regex::icase),
|
||||
boost::regex(".*", boost::regex::icase)));
|
||||
HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base + "reflect/",
|
||||
options,
|
||||
HttpHeaders::ptr_t(),
|
||||
|
|
@ -1684,7 +1674,6 @@ void HttpRequestTestObjectType::test<16>()
|
|||
boost::regex("X-Reflect-content-encoding", boost::regex::icase),
|
||||
boost::regex(".*", boost::regex::icase)));
|
||||
handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base + "reflect/",
|
||||
0,
|
||||
47,
|
||||
|
|
@ -1863,7 +1852,6 @@ void HttpRequestTestObjectType::test<17>()
|
|||
boost::regex("X-Reflect-transfer_encoding", boost::regex::icase),
|
||||
boost::regex(".*chunked.*", boost::regex::icase)));
|
||||
HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base + "reflect/",
|
||||
ba,
|
||||
options,
|
||||
|
|
@ -2049,7 +2037,6 @@ void HttpRequestTestObjectType::test<18>()
|
|||
boost::regex(".*", boost::regex::icase)));
|
||||
|
||||
HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base + "reflect/",
|
||||
ba,
|
||||
options,
|
||||
|
|
@ -2249,7 +2236,6 @@ void HttpRequestTestObjectType::test<19>()
|
|||
boost::regex("X-Reflect-content-encoding", boost::regex::icase),
|
||||
boost::regex(".*", boost::regex::icase)));
|
||||
HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base + "reflect/",
|
||||
options,
|
||||
headers,
|
||||
|
|
@ -2457,7 +2443,6 @@ void HttpRequestTestObjectType::test<20>()
|
|||
boost::regex(".*", boost::regex::icase)));
|
||||
|
||||
HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base + "reflect/",
|
||||
ba,
|
||||
options,
|
||||
|
|
@ -2666,7 +2651,6 @@ void HttpRequestTestObjectType::test<21>()
|
|||
boost::regex("X-Reflect-content-type", boost::regex::icase),
|
||||
boost::regex("text/html", boost::regex::icase)));
|
||||
HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base + "reflect/",
|
||||
ba,
|
||||
options,
|
||||
|
|
@ -2797,7 +2781,6 @@ void HttpRequestTestObjectType::test<22>()
|
|||
char buffer[128];
|
||||
sprintf(buffer, "/bug2295/%d/", i);
|
||||
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base + buffer,
|
||||
0,
|
||||
25,
|
||||
|
|
@ -2829,7 +2812,6 @@ void HttpRequestTestObjectType::test<22>()
|
|||
char buffer[128];
|
||||
sprintf(buffer, "/bug2295/00000012/%d/", i);
|
||||
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base + buffer,
|
||||
0,
|
||||
25,
|
||||
|
|
@ -2861,7 +2843,6 @@ void HttpRequestTestObjectType::test<22>()
|
|||
char buffer[128];
|
||||
sprintf(buffer, "/bug2295/inv_cont_range/%d/", i);
|
||||
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url_base + buffer,
|
||||
0,
|
||||
25,
|
||||
|
|
@ -2984,7 +2965,6 @@ void HttpRequestTestObjectType::test<23>()
|
|||
std::ostringstream url;
|
||||
url << url_base << i << "/";
|
||||
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
|
||||
0U,
|
||||
url.str(),
|
||||
0,
|
||||
0,
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ bool LLCrashLogger::runCrashLogPost(std::string host, LLSD data, std::string msg
|
|||
updateApplication(llformat("%s, try %d...", msg.c_str(), i+1));
|
||||
|
||||
LL_INFOS("CRASHREPORT") << "POST crash data to " << host << LL_ENDL;
|
||||
LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(httpRequest.get(), LLCore::HttpRequest::DEFAULT_POLICY_ID, 0,
|
||||
LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(httpRequest.get(), LLCore::HttpRequest::DEFAULT_POLICY_ID,
|
||||
host, data, httpOpts, LLCore::HttpHeaders::ptr_t(), LLCore::HttpHandler::ptr_t(new LLCrashLoggerHandler));
|
||||
|
||||
if (handle == LLCORE_HTTP_HANDLE_INVALID)
|
||||
|
|
|
|||
|
|
@ -221,6 +221,7 @@ const std::string LLDiskCache::assetTypeToString(LLAssetType::EType at)
|
|||
{ LLAssetType::AT_PERSON, "PERSON" },
|
||||
{ LLAssetType::AT_MESH, "MESH" },
|
||||
{ LLAssetType::AT_SETTINGS, "SETTINGS" },
|
||||
{ LLAssetType::AT_MATERIAL, "MATERIAL" },
|
||||
{ LLAssetType::AT_UNKNOWN, "UNKNOWN" }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -45,8 +45,7 @@ void LLLFSThread::initClass(bool local_is_threaded)
|
|||
//static
|
||||
S32 LLLFSThread::updateClass(U32 ms_elapsed)
|
||||
{
|
||||
sLocal->update((F32)ms_elapsed);
|
||||
return sLocal->getPending();
|
||||
return sLocal->update((F32)ms_elapsed);
|
||||
}
|
||||
|
||||
//static
|
||||
|
|
@ -58,6 +57,7 @@ void LLLFSThread::cleanupClass()
|
|||
{
|
||||
sLocal->update(0);
|
||||
}
|
||||
sLocal->shutdown();
|
||||
delete sLocal;
|
||||
sLocal = NULL;
|
||||
}
|
||||
|
|
@ -65,8 +65,7 @@ void LLLFSThread::cleanupClass()
|
|||
//----------------------------------------------------------------------------
|
||||
|
||||
LLLFSThread::LLLFSThread(bool threaded) :
|
||||
LLQueuedThread("LFS", threaded),
|
||||
mPriorityCounter(PRIORITY_LOWBITS)
|
||||
LLQueuedThread("LFS", threaded)
|
||||
{
|
||||
if(!mLocalAPRFilePoolp)
|
||||
{
|
||||
|
|
@ -84,14 +83,12 @@ LLLFSThread::~LLLFSThread()
|
|||
|
||||
LLLFSThread::handle_t LLLFSThread::read(const std::string& filename, /* Flawfinder: ignore */
|
||||
U8* buffer, S32 offset, S32 numbytes,
|
||||
Responder* responder, U32 priority)
|
||||
Responder* responder)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
handle_t handle = generateHandle();
|
||||
|
||||
if (priority == 0) priority = PRIORITY_NORMAL | priorityCounter();
|
||||
else if (priority < PRIORITY_LOW) priority |= PRIORITY_LOW; // All reads are at least PRIORITY_LOW
|
||||
|
||||
Request* req = new Request(this, handle, priority,
|
||||
Request* req = new Request(this, handle,
|
||||
FILE_READ, filename,
|
||||
buffer, offset, numbytes,
|
||||
responder);
|
||||
|
|
@ -107,13 +104,12 @@ LLLFSThread::handle_t LLLFSThread::read(const std::string& filename, /* Flawfind
|
|||
|
||||
LLLFSThread::handle_t LLLFSThread::write(const std::string& filename,
|
||||
U8* buffer, S32 offset, S32 numbytes,
|
||||
Responder* responder, U32 priority)
|
||||
Responder* responder)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
handle_t handle = generateHandle();
|
||||
|
||||
if (priority == 0) priority = PRIORITY_LOW | priorityCounter();
|
||||
|
||||
Request* req = new Request(this, handle, priority,
|
||||
Request* req = new Request(this, handle,
|
||||
FILE_WRITE, filename,
|
||||
buffer, offset, numbytes,
|
||||
responder);
|
||||
|
|
@ -130,11 +126,11 @@ LLLFSThread::handle_t LLLFSThread::write(const std::string& filename,
|
|||
//============================================================================
|
||||
|
||||
LLLFSThread::Request::Request(LLLFSThread* thread,
|
||||
handle_t handle, U32 priority,
|
||||
handle_t handle,
|
||||
operation_t op, const std::string& filename,
|
||||
U8* buffer, S32 offset, S32 numbytes,
|
||||
Responder* responder) :
|
||||
QueuedRequest(handle, priority, FLAG_AUTO_COMPLETE),
|
||||
QueuedRequest(handle, FLAG_AUTO_COMPLETE),
|
||||
mThread(thread),
|
||||
mOperation(op),
|
||||
mFileName(filename),
|
||||
|
|
@ -157,6 +153,7 @@ LLLFSThread::Request::~Request()
|
|||
// virtual, called from own thread
|
||||
void LLLFSThread::Request::finishRequest(bool completed)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
if (mResponder.notNull())
|
||||
{
|
||||
mResponder->completed(completed ? mBytesRead : 0);
|
||||
|
|
@ -166,6 +163,7 @@ void LLLFSThread::Request::finishRequest(bool completed)
|
|||
|
||||
void LLLFSThread::Request::deleteRequest()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
if (getStatus() == STATUS_QUEUED)
|
||||
{
|
||||
LL_ERRS() << "Attempt to delete a queued LLLFSThread::Request!" << LL_ENDL;
|
||||
|
|
@ -180,6 +178,7 @@ void LLLFSThread::Request::deleteRequest()
|
|||
|
||||
bool LLLFSThread::Request::processRequest()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED;
|
||||
bool complete = false;
|
||||
if (mOperation == FILE_READ)
|
||||
{
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue