Merge branch 'release/materials_featurette' into DRTVWR-592
commit
6dd260c369
|
|
@ -0,0 +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:
|
||||
- '*'
|
||||
|
|
@ -4,7 +4,7 @@ on:
|
|||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main, contribute]
|
||||
branches: ["*"]
|
||||
tags: ["*"]
|
||||
|
||||
jobs:
|
||||
|
|
@ -12,47 +12,85 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
runner: [windows-large, macos-12-xl]
|
||||
configuration: [ReleaseOS]
|
||||
addrsize: [64]
|
||||
configuration: [Release, ReleaseOS]
|
||||
python-version: ["3.11"]
|
||||
include:
|
||||
- runner: macos-12-xl
|
||||
developer_dir: "/Applications/Xcode_14.0.1.app/Contents/Developer"
|
||||
exclude:
|
||||
- runner: macos-12-xl
|
||||
configuration: ReleaseOS
|
||||
runs-on: ${{ matrix.runner }}
|
||||
outputs:
|
||||
viewer_channel: ${{ steps.build.outputs.viewer_channel }}
|
||||
viewer_version: ${{ steps.build.outputs.viewer_version }}
|
||||
imagename: ${{ steps.build.outputs.imagename }}
|
||||
env:
|
||||
AUTOBUILD_ADDRSIZE: 64
|
||||
AUTOBUILD_BUILD_ID: ${{ github.run_id }}
|
||||
AUTOBUILD_CONFIGURATION: ${{ matrix.configuration }}
|
||||
AUTOBUILD_ADDRSIZE: ${{ matrix.addrsize }}
|
||||
# authorizes fetching private constituent packages
|
||||
AUTOBUILD_GITHUB_TOKEN: ${{ secrets.SHARED_AUTOBUILD_GITHUB_TOKEN }}
|
||||
AUTOBUILD_INSTALLABLE_CACHE: ${{ github.workspace }}/.autobuild-installables
|
||||
AUTOBUILD_VARIABLES_FILE: ${{ github.workspace }}/.build-variables/variables
|
||||
AUTOBUILD_VSVER: "170" # vs2k22
|
||||
AUTOBUILD_VSVER: "170"
|
||||
DEVELOPER_DIR: ${{ matrix.developer_dir }}
|
||||
LOGFAIL: debug # Show details when tests fail
|
||||
# Ensure that Linden viewer builds engage Bugsplat.
|
||||
BUGSPLAT_DB: ${{ matrix.configuration != 'ReleaseOS' && 'SecondLife_Viewer_2018' || '' }}
|
||||
BUGSPLAT_PASS: ${{ secrets.BUGSPLAT_PASS }}
|
||||
BUGSPLAT_USER: ${{ secrets.BUGSPLAT_USER }}
|
||||
build_coverity: false
|
||||
build_log_dir: ${{ github.workspace }}/.logs
|
||||
build_viewer: true
|
||||
BUILDSCRIPTS_SHARED: ${{ github.workspace }}/.shared
|
||||
# extracted and committed to viewer repo
|
||||
BUILDSCRIPTS_SUPPORT_FUNCTIONS: ${{ github.workspace }}/buildscripts_support_functions
|
||||
GIT_REF: ${{ github.head_ref || github.ref }}
|
||||
LL_SKIP_REQUIRE_SYSROOT: 1
|
||||
# Setting this variable directs Linden's TUT test driver code to capture
|
||||
# test-program log output at the specified level, but to display it only if
|
||||
# the individual test fails.
|
||||
LOGFAIL: DEBUG
|
||||
master_message_template_checkout: ${{ github.workspace }}/.master-message-template
|
||||
# Only set variants to the one configuration: don't let build.sh loop
|
||||
# over variants, let GitHub distribute variants over multiple hosts.
|
||||
variants: ${{ matrix.configuration }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Setup python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Checkout build variables
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: secondlife/build-variables
|
||||
ref: viewer
|
||||
path: .build-variables
|
||||
|
||||
- name: Checkout master-message-template
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: secondlife/master-message-template
|
||||
path: .master-message-template
|
||||
|
||||
- name: Install autobuild and python dependencies
|
||||
run: pip3 install autobuild llbase
|
||||
run: pip3 install autobuild llsd
|
||||
|
||||
- name: Cache autobuild packages
|
||||
uses: actions/cache@v3
|
||||
id: cache-installables
|
||||
with:
|
||||
path: .autobuild-installables
|
||||
key: ${{ runner.os }}-${{ matrix.addrsize }}-${{ matrix.configuration }}-${{ hashFiles('autobuild.xml') }}
|
||||
key: ${{ runner.os }}-64-${{ matrix.configuration }}-${{ hashFiles('autobuild.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.addrsize }}-${{ matrix.configuration }}-
|
||||
${{ runner.os }}-${{ matrix.addrsize }}-
|
||||
${{ runner.os }}-64-${{ matrix.configuration }}-
|
||||
${{ runner.os }}-64-
|
||||
|
||||
- name: Install windows dependencies
|
||||
if: runner.os == 'Windows'
|
||||
|
|
@ -64,31 +102,270 @@ jobs:
|
|||
env:
|
||||
RUNNER_OS: ${{ runner.os }}
|
||||
run: |
|
||||
# set up things the viewer's build.sh script expects
|
||||
set -x
|
||||
mkdir -p "$build_log_dir"
|
||||
mkdir -p "$BUILDSCRIPTS_SHARED/packages/lib/python"
|
||||
source "$BUILDSCRIPTS_SUPPORT_FUNCTIONS"
|
||||
if [[ "$OSTYPE" =~ cygwin|msys ]]
|
||||
then
|
||||
native_path() { cygpath --windows "$1"; }
|
||||
shell_path() { cygpath --unix "$1"; }
|
||||
else
|
||||
native_path() { echo "$1"; }
|
||||
shell_path() { echo "$1"; }
|
||||
fi
|
||||
finalize()
|
||||
{
|
||||
case "$1" in
|
||||
true|0)
|
||||
record_success "Build Succeeded"
|
||||
;;
|
||||
*)
|
||||
record_failure "Build Failed with $1"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
initialize_build()
|
||||
{
|
||||
echo "initialize_build"
|
||||
}
|
||||
initialize_version()
|
||||
{
|
||||
export revision="$AUTOBUILD_BUILD_ID"
|
||||
}
|
||||
python_cmd()
|
||||
{
|
||||
if [[ "x${1:0:1}" == "x-" ]] # -m, -c, etc.
|
||||
then # if $1 is a switch, don't try to twiddle paths
|
||||
"$(shell_path "$PYTHON_COMMAND")" "$@"
|
||||
elif [[ "$(basename "$1")" == "codeticket.py" ]]
|
||||
then # ignore any attempt to contact codeticket
|
||||
echo "## $@"
|
||||
else # running a script at an explicit path: fix path for Python
|
||||
local script="$1"
|
||||
shift
|
||||
"$(shell_path "$PYTHON_COMMAND")" "$(native_path "$script")" "$@"
|
||||
fi
|
||||
}
|
||||
repo_branch()
|
||||
{
|
||||
git -C "$1" branch | grep '^* ' | cut -c 3-
|
||||
}
|
||||
record_dependencies_graph()
|
||||
{
|
||||
echo "TODO: generate and post dependency graph"
|
||||
}
|
||||
# Since we're not uploading to codeticket, DO NOT sleep for minutes.
|
||||
sleep()
|
||||
{
|
||||
echo "Not sleeping for $1 seconds"
|
||||
}
|
||||
export -f native_path shell_path finalize initialize_build initialize_version
|
||||
export -f python_cmd repo_branch record_dependencies_graph sleep
|
||||
## Useful for diagnosing Windows LLProcess/LLLeap test failures
|
||||
##export APR_LOG="${RUNNER_TEMP}/apr.log"
|
||||
export arch=$(uname | cut -b-6)
|
||||
# Surprise! GH Windows runner's MINGW6 is a $arch value we've never
|
||||
# seen before, so numerous tests don't know about it.
|
||||
[[ "$arch" == "MINGW6" ]] && arch=CYGWIN
|
||||
export AUTOBUILD="$(which autobuild)"
|
||||
# Build with a tag like "Second_Life_Project_Shiny#abcdef0" to get a
|
||||
# viewer channel "Second Life Project Shiny" (ignoring "#hash",
|
||||
# needed to disambiguate tags).
|
||||
if [[ "$GITHUB_REF_TYPE" == "tag" && "${GITHUB_REF_NAME:0:12}" == "Second_Life_" ]]
|
||||
then viewer_channel="${GITHUB_REF_NAME%#*}"
|
||||
export viewer_channel="${viewer_channel//_/ }"
|
||||
else export viewer_channel="Second Life Test"
|
||||
fi
|
||||
echo "viewer_channel=$viewer_channel" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# On windows we need to point the build to the correct python
|
||||
# as neither CMake's FindPython nor our custom Python.cmake module
|
||||
# will resolve the correct interpreter location.
|
||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||
export PYTHON="$(cygpath -m "$(which python)")"
|
||||
export PYTHON="$(native_path "$(which python)")"
|
||||
echo "Python location: $PYTHON"
|
||||
export PYTHON_COMMAND="$PYTHON"
|
||||
else
|
||||
export PYTHON_COMMAND="python3"
|
||||
fi
|
||||
|
||||
autobuild configure -- -DVIEWER_CHANNEL="Second Life Test ${GIT_REF##*/}"
|
||||
autobuild build --no-configure
|
||||
export PYTHON_COMMAND_NATIVE="$(native_path "$PYTHON_COMMAND")"
|
||||
|
||||
# Find artifacts
|
||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||
installer_path=$(find ./build-*/newview/ | grep '_Setup\.exe')
|
||||
installer_name="$(basename $installer_path)"
|
||||
elif [[ "$RUNNER_OS" == "macOS" ]]; then
|
||||
installer_path=$(find ./build-*/newview/ | grep '\.dmg')
|
||||
installer_name="$(basename $installer_path)"
|
||||
./build.sh
|
||||
|
||||
# Each artifact is downloaded as a distinct .zip file. Multiple jobs
|
||||
# (per the matrix above) writing the same filepath to the same
|
||||
# artifact name will *overwrite* that file. Moreover, they can
|
||||
# interfere with each other, causing the upload to fail.
|
||||
# https://github.com/actions/upload-artifact#uploading-to-the-same-artifact
|
||||
# Given the size of our installers, and the fact that we typically
|
||||
# only want to download just one instead of a single zip containing
|
||||
# several, generate a distinct artifact name for each installer.
|
||||
# If the matrix above can run multiple builds on the same
|
||||
# platform, we must disambiguate on more than the platform name.
|
||||
# e.g. if we were still running Windows 32-bit builds, we'd need to
|
||||
# qualify the artifact with bit width.
|
||||
if [[ "$AUTOBUILD_CONFIGURATION" == "ReleaseOS" ]]
|
||||
then cfg_suffix='OS'
|
||||
else cfg_suffix=''
|
||||
fi
|
||||
echo "artifact=$RUNNER_OS$cfg_suffix" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "installer_path=$installer_path" >> $GITHUB_OUTPUT
|
||||
echo "installer_name=$installer_name" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload installer
|
||||
- name: Upload executable
|
||||
if: matrix.configuration != 'ReleaseOS' && steps.build.outputs.viewer_app
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ steps.build.outputs.installer_name }}
|
||||
path: ${{ steps.build.outputs.installer_path }}
|
||||
name: "${{ steps.build.outputs.artifact }}-app"
|
||||
path: |
|
||||
${{ steps.build.outputs.viewer_app }}
|
||||
|
||||
# The other upload of nontrivial size is the symbol file. Use a distinct
|
||||
# artifact for that too.
|
||||
- name: Upload symbol file
|
||||
if: matrix.configuration != 'ReleaseOS'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "${{ steps.build.outputs.artifact }}-symbols"
|
||||
path: |
|
||||
${{ steps.build.outputs.symbolfile }}
|
||||
|
||||
- name: Upload metadata
|
||||
if: matrix.configuration != 'ReleaseOS'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "${{ steps.build.outputs.artifact }}-metadata"
|
||||
# emitted by build.sh, possibly multiple lines
|
||||
path: |
|
||||
${{ steps.build.outputs.metadata }}
|
||||
|
||||
- name: Upload physics package
|
||||
uses: actions/upload-artifact@v3
|
||||
# should only be set for viewer-private
|
||||
if: matrix.configuration != 'ReleaseOS' && steps.build.outputs.physicstpv
|
||||
with:
|
||||
name: "${{ steps.build.outputs.artifact }}-physics"
|
||||
# emitted by build.sh, zero or one lines
|
||||
path: |
|
||||
${{ steps.build.outputs.physicstpv }}
|
||||
|
||||
sign-and-package-windows:
|
||||
needs: build
|
||||
runs-on: windows
|
||||
steps:
|
||||
- name: Sign and package Windows viewer
|
||||
uses: secondlife/viewer-build-util/sign-pkg-windows@v1
|
||||
with:
|
||||
vault_uri: "${{ secrets.AZURE_KEY_VAULT_URI }}"
|
||||
cert_name: "${{ secrets.AZURE_CERT_NAME }}"
|
||||
client_id: "${{ secrets.AZURE_CLIENT_ID }}"
|
||||
client_secret: "${{ secrets.AZURE_CLIENT_SECRET }}"
|
||||
tenant_id: "${{ secrets.AZURE_TENANT_ID }}"
|
||||
|
||||
sign-and-package-mac:
|
||||
needs: build
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Unpack Mac notarization credentials
|
||||
id: note-creds
|
||||
shell: bash
|
||||
run: |
|
||||
# In NOTARIZE_CREDS_MACOS we expect to find:
|
||||
# USERNAME="..."
|
||||
# PASSWORD="..."
|
||||
# TEAM_ID="..."
|
||||
eval "${{ secrets.NOTARIZE_CREDS_MACOS }}"
|
||||
echo "::add-mask::$USERNAME"
|
||||
echo "::add-mask::$PASSWORD"
|
||||
echo "::add-mask::$TEAM_ID"
|
||||
echo "note_user=$USERNAME" >> "$GITHUB_OUTPUT"
|
||||
echo "note_pass=$PASSWORD" >> "$GITHUB_OUTPUT"
|
||||
echo "note_team=$TEAM_ID" >> "$GITHUB_OUTPUT"
|
||||
# If we didn't manage to retrieve all of these credentials, better
|
||||
# find out sooner than later.
|
||||
[[ -n "$USERNAME" && -n "$PASSWORD" && -n "$TEAM_ID" ]]
|
||||
|
||||
- name: Sign and package Mac viewer
|
||||
uses: secondlife/viewer-build-util/sign-pkg-mac@v1
|
||||
with:
|
||||
channel: ${{ needs.build.outputs.viewer_channel }}
|
||||
imagename: ${{ needs.build.outputs.imagename }}
|
||||
cert_base64: ${{ secrets.SIGNING_CERT_MACOS }}
|
||||
cert_name: ${{ secrets.SIGNING_CERT_MACOS_IDENTITY }}
|
||||
cert_pass: ${{ secrets.SIGNING_CERT_MACOS_PASSWORD }}
|
||||
note_user: ${{ steps.note-creds.outputs.note_user }}
|
||||
note_pass: ${{ steps.note-creds.outputs.note_pass }}
|
||||
note_team: ${{ steps.note-creds.outputs.note_team }}
|
||||
|
||||
post-windows-symbols:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Post Windows symbols
|
||||
uses: secondlife/viewer-build-util/post-bugsplat-windows@v1
|
||||
with:
|
||||
username: ${{ secrets.BUGSPLAT_USER }}
|
||||
password: ${{ secrets.BUGSPLAT_PASS }}
|
||||
database: "SecondLife_Viewer_2018"
|
||||
channel: ${{ needs.build.outputs.viewer_channel }}
|
||||
version: ${{ needs.build.outputs.viewer_version }}
|
||||
|
||||
post-mac-symbols:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Post Mac symbols
|
||||
uses: secondlife/viewer-build-util/post-bugsplat-mac@v1
|
||||
with:
|
||||
username: ${{ secrets.BUGSPLAT_USER }}
|
||||
password: ${{ secrets.BUGSPLAT_PASS }}
|
||||
database: "SecondLife_Viewer_2018"
|
||||
channel: ${{ needs.build.outputs.viewer_channel }}
|
||||
version: ${{ needs.build.outputs.viewer_version }}
|
||||
|
||||
release:
|
||||
needs: [sign-and-package-windows, sign-and-package-mac]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref_type == 'tag' && startsWith(github.ref_name, 'Second_Life_')
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: Windows-installer
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
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
|
||||
with:
|
||||
# name the release page for the build number so we can find it
|
||||
# easily (analogous to looking up a codeticket build page)
|
||||
name: "v${{ github.run_id }}"
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
# the only reason we generate a GH release is to post build products
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
*.dmg
|
||||
*.exe
|
||||
*-autobuild-package.xml
|
||||
*-viewer_version.txt
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
pre-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
|
|
|||
|
|
@ -86,4 +86,7 @@ tarfile_tmp
|
|||
trivial_change_force_build
|
||||
web/config.*
|
||||
web/locale.*
|
||||
web/secondlife.com.*
|
||||
web/secondlife.com.*
|
||||
|
||||
.env
|
||||
.vscode
|
||||
|
|
|
|||
2958
autobuild.xml
2958
autobuild.xml
File diff suppressed because it is too large
Load Diff
173
build.sh
173
build.sh
|
|
@ -16,6 +16,8 @@
|
|||
# * The special style in which python is invoked is intentional to permit
|
||||
# use of a native python install on windows - which requires paths in DOS form
|
||||
|
||||
cleanup="true"
|
||||
|
||||
retry_cmd()
|
||||
{
|
||||
max_attempts="$1"; shift
|
||||
|
|
@ -110,6 +112,34 @@ installer_CYGWIN()
|
|||
fi
|
||||
}
|
||||
|
||||
[[ -n "$GITHUB_OUTPUT" ]] || fatal "Need to export GITHUB_OUTPUT"
|
||||
# The following is based on the Warning for GitHub multiline output strings:
|
||||
# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
|
||||
|
||||
# Build up these arrays as we go
|
||||
metadata=()
|
||||
symbolfile=()
|
||||
physicstpv=()
|
||||
# and dump them to GITHUB_OUTPUT when done
|
||||
cleanup="$cleanup ; \
|
||||
arrayoutput metadata ; \
|
||||
arrayoutput symbolfile ; \
|
||||
arrayoutput physicstpv"
|
||||
trap "$cleanup" EXIT
|
||||
|
||||
arrayoutput()
|
||||
{
|
||||
local outputname="$1"
|
||||
# append "[*]" to the array name so array indirection works
|
||||
local array="$1[*]"
|
||||
local IFS='
|
||||
'
|
||||
echo "$outputname<<$EOF
|
||||
${!array}
|
||||
$EOF" >> "$GITHUB_OUTPUT"
|
||||
}
|
||||
|
||||
pre_build()
|
||||
{
|
||||
local variant="$1"
|
||||
|
|
@ -121,7 +151,7 @@ pre_build()
|
|||
RELEASE_CRASH_REPORTING=ON
|
||||
HAVOK=ON
|
||||
SIGNING=()
|
||||
if [ "$arch" == "Darwin" -a "$variant" == "Release" ]
|
||||
if [[ "$arch" == "Darwin" && "$variant" == "Release" ]]
|
||||
then SIGNING=("-DENABLE_SIGNING:BOOL=YES" \
|
||||
"-DSIGNING_IDENTITY:STRING=Developer ID Application: Linden Research, Inc.")
|
||||
fi
|
||||
|
|
@ -145,15 +175,27 @@ pre_build()
|
|||
VIEWER_SYMBOL_FILE="$(native_path "$abs_build_dir/newview/$variant/secondlife-symbols-$symplat-${AUTOBUILD_ADDRSIZE}.tar.bz2")"
|
||||
fi
|
||||
|
||||
# don't spew credentials into build log
|
||||
bugsplat_sh="$build_secrets_checkout/bugsplat/bugsplat.sh"
|
||||
set +x
|
||||
if [ -r "$bugsplat_sh" ]
|
||||
then # show that we're doing this, just not the contents
|
||||
echo source "$bugsplat_sh"
|
||||
source "$bugsplat_sh"
|
||||
# expect these variables to be set in the environment from GitHub secrets
|
||||
if [[ -n "$BUGSPLAT_DB" ]]
|
||||
then
|
||||
# don't spew credentials into build log
|
||||
set +x
|
||||
if [[ -z "$BUGSPLAT_USER" || -z "$BUGSPLAT_PASS" ]]
|
||||
then
|
||||
# older mechanism involving build-secrets repo -
|
||||
# if build_secrets_checkout isn't set, report its name
|
||||
bugsplat_sh="${build_secrets_checkout:-\$build_secrets_checkout}/bugsplat/bugsplat.sh"
|
||||
if [ -r "$bugsplat_sh" ]
|
||||
then # show that we're doing this, just not the contents
|
||||
echo source "$bugsplat_sh"
|
||||
source "$bugsplat_sh"
|
||||
else
|
||||
fatal "BUGSPLAT_USER or BUGSPLAT_PASS missing, and no $bugsplat_sh"
|
||||
fi
|
||||
fi
|
||||
set -x
|
||||
export BUGSPLAT_USER BUGSPLAT_PASS
|
||||
fi
|
||||
set -x
|
||||
|
||||
# honor autobuild_configure_parameters same as sling-buildscripts
|
||||
eval_autobuild_configure_parameters=$(eval $(echo echo $autobuild_configure_parameters))
|
||||
|
|
@ -181,13 +223,17 @@ package_llphysicsextensions_tpv()
|
|||
# nat 2016-12-21: without HAVOK, can't build PhysicsExtensions_TPV.
|
||||
if [ "$variant" = "Release" -a "${HAVOK:-}" != "OFF" ]
|
||||
then
|
||||
test -r "$build_dir/packages/llphysicsextensions/autobuild-tpv.xml" || fatal "No llphysicsextensions_tpv autobuild configuration found"
|
||||
tpvconfig=$(native_path "$build_dir/packages/llphysicsextensions/autobuild-tpv.xml")
|
||||
"$autobuild" build --quiet --config-file "$tpvconfig" -c Tpv || fatal "failed to build llphysicsextensions_tpv"
|
||||
tpvconfig="$build_dir/packages/llphysicsextensions/autobuild-tpv.xml"
|
||||
test -r "$tpvconfig" || fatal "No llphysicsextensions_tpv autobuild configuration found"
|
||||
# SL-19942: autobuild ignores -c switch if AUTOBUILD_CONFIGURATION set
|
||||
unset AUTOBUILD_CONFIGURATION
|
||||
"$autobuild" build --quiet --config-file "$(native_path "$tpvconfig")" -c Tpv \
|
||||
|| fatal "failed to build llphysicsextensions_tpv"
|
||||
|
||||
# capture the package file name for use in upload later...
|
||||
PKGTMP=`mktemp -t pgktpv.XXXXXX`
|
||||
trap "rm $PKGTMP* 2>/dev/null" 0
|
||||
cleanup="$cleanup ; rm $PKGTMP* 2>/dev/null"
|
||||
trap "$cleanup" EXIT
|
||||
"$autobuild" package --quiet --config-file "$tpvconfig" --results-file "$(native_path $PKGTMP)" || fatal "failed to package llphysicsextensions_tpv"
|
||||
tpv_status=$?
|
||||
if [ -r "${PKGTMP}" ]
|
||||
|
|
@ -313,12 +359,20 @@ begin_section "coding policy check"
|
|||
# this far. Running coding policy checks on one platform *should* suffice...
|
||||
if [[ "$arch" == "Darwin" ]]
|
||||
then
|
||||
# install the git-hooks dependencies
|
||||
pip install -r "$(native_path "$git_hooks_checkout/requirements.txt")" || \
|
||||
fatal "pip install git-hooks failed"
|
||||
# validate the branch we're about to build
|
||||
python_cmd "$git_hooks_checkout/coding_policy_git.py" --all_files || \
|
||||
fatal "coding policy check failed"
|
||||
git_hooks_reqs="$git_hooks_checkout/requirements.txt"
|
||||
if [[ -r "$(shell_path "$git_hooks_reqs")" ]]
|
||||
then
|
||||
# install the git-hooks dependencies
|
||||
pip install -r "$(native_path "$git_hooks_reqs")" || \
|
||||
fatal "pip install git-hooks failed"
|
||||
fi
|
||||
git_hooks_script="$git_hooks_checkout/coding_policy_git.py"
|
||||
if [[ -r "$(shell_path "$git_hooks_script")" ]]
|
||||
then
|
||||
# validate the branch we're about to build
|
||||
python_cmd "$(native_path "$git_hooks_script")" --all_files || \
|
||||
fatal "coding policy check failed"
|
||||
fi
|
||||
fi
|
||||
end_section "coding policy check"
|
||||
|
||||
|
|
@ -353,6 +407,7 @@ do
|
|||
begin_section "Autobuild metadata"
|
||||
python_cmd "$helpers/codeticket.py" addoutput "Autobuild Metadata" "$build_dir/autobuild-package.xml" --mimetype text/xml \
|
||||
|| fatal "Upload of autobuild metadata failed"
|
||||
metadata+=("$build_dir/autobuild-package.xml")
|
||||
if [ "$arch" != "Linux" ]
|
||||
then
|
||||
record_dependencies_graph "$build_dir/autobuild-package.xml" # defined in buildscripts/hg/bin/build.sh
|
||||
|
|
@ -366,8 +421,11 @@ do
|
|||
if [ -r "$build_dir/newview/viewer_version.txt" ]
|
||||
then
|
||||
begin_section "Viewer Version"
|
||||
python_cmd "$helpers/codeticket.py" addoutput "Viewer Version" "$(<"$build_dir/newview/viewer_version.txt")" --mimetype inline-text \
|
||||
viewer_version="$(<"$build_dir/newview/viewer_version.txt")"
|
||||
python_cmd "$helpers/codeticket.py" addoutput "Viewer Version" "$viewer_version" --mimetype inline-text \
|
||||
|| fatal "Upload of viewer version failed"
|
||||
metadata+=("$build_dir/newview/viewer_version.txt")
|
||||
echo "viewer_version=$viewer_version" >> "$GITHUB_OUTPUT"
|
||||
end_section "Viewer Version"
|
||||
fi
|
||||
;;
|
||||
|
|
@ -376,12 +434,14 @@ do
|
|||
then
|
||||
record_event "Doxygen warnings generated; see doxygen_warnings.log"
|
||||
python_cmd "$helpers/codeticket.py" addoutput "Doxygen Log" "$build_dir/doxygen_warnings.log" --mimetype text/plain ## TBD
|
||||
metadata+=("$build_dir/doxygen_warnings.log")
|
||||
fi
|
||||
if [ -d "$build_dir/doxygen/html" ]
|
||||
then
|
||||
tar -c -f "$build_dir/viewer-doxygen.tar.bz2" --strip-components 3 "$build_dir/doxygen/html"
|
||||
python_cmd "$helpers/codeticket.py" addoutput "Doxygen Tarball" "$build_dir/viewer-doxygen.tar.bz2" \
|
||||
|| fatal "Upload of doxygen tarball failed"
|
||||
metadata+=("$build_dir/viewer-doxygen.tar.bz2")
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
|
|
@ -486,64 +546,29 @@ then
|
|||
if $build_viewer
|
||||
then
|
||||
begin_section "Uploads"
|
||||
# Upload installer
|
||||
package=$(installer_$arch)
|
||||
if [ x"$package" = x ] || test -d "$package"
|
||||
# nat 2016-12-22: without RELEASE_CRASH_REPORTING, we have no symbol file.
|
||||
if [ "${RELEASE_CRASH_REPORTING:-}" != "OFF" ]
|
||||
then
|
||||
fatal "No installer found from `pwd`"
|
||||
succeeded=$build_coverity
|
||||
else
|
||||
# Upload base package.
|
||||
retry_cmd 4 30 python_cmd "$helpers/codeticket.py" addoutput Installer "$package" \
|
||||
|| fatal "Upload of installer failed"
|
||||
wait_for_codeticket
|
||||
|
||||
# Upload additional packages.
|
||||
for package_id in $additional_packages
|
||||
do
|
||||
package=$(installer_$arch "$package_id")
|
||||
if [ x"$package" != x ]
|
||||
# BugSplat wants to see xcarchive.zip
|
||||
# e.g. build-darwin-x86_64/newview/Release/Second Life Test.xcarchive.zip
|
||||
symbol_file="${build_dir}/newview/${variant}/${viewer_channel}.xcarchive.zip"
|
||||
if [[ ! -f "$symbol_file" ]]
|
||||
then
|
||||
retry_cmd 4 30 python_cmd "$helpers/codeticket.py" addoutput "Installer $package_id" "$package" \
|
||||
|| fatal "Upload of installer $package_id failed"
|
||||
wait_for_codeticket
|
||||
else
|
||||
record_failure "Failed to find additional package for '$package_id'."
|
||||
# symbol tarball we prep for (e.g.) Breakpad
|
||||
symbol_file="$VIEWER_SYMBOL_FILE"
|
||||
fi
|
||||
done
|
||||
# Upload crash reporter file
|
||||
symbolfile+=("$symbol_file")
|
||||
fi
|
||||
|
||||
if [ "$last_built_variant" = "Release" ]
|
||||
then
|
||||
# nat 2016-12-22: without RELEASE_CRASH_REPORTING, we have no symbol file.
|
||||
if [ "${RELEASE_CRASH_REPORTING:-}" != "OFF" ]
|
||||
then
|
||||
# Upload crash reporter file
|
||||
retry_cmd 4 30 python_cmd "$helpers/codeticket.py" addoutput "Symbolfile" "$VIEWER_SYMBOL_FILE" \
|
||||
|| fatal "Upload of symbolfile failed"
|
||||
wait_for_codeticket
|
||||
fi
|
||||
|
||||
# Upload the llphysicsextensions_tpv package, if one was produced
|
||||
# *TODO: Make this an upload-extension
|
||||
if [ -r "$build_dir/llphysicsextensions_package" ]
|
||||
then
|
||||
llphysicsextensions_package=$(cat $build_dir/llphysicsextensions_package)
|
||||
retry_cmd 4 30 python_cmd "$helpers/codeticket.py" addoutput "Physics Extensions Package" "$llphysicsextensions_package" --private \
|
||||
|| fatal "Upload of physics extensions package failed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run upload extensions
|
||||
# Ex: bugsplat
|
||||
if [ -d ${build_dir}/packages/upload-extensions ]; then
|
||||
for extension in ${build_dir}/packages/upload-extensions/*.sh; do
|
||||
begin_section "Upload Extension $extension"
|
||||
. $extension
|
||||
[ $? -eq 0 ] || fatal "Upload of extension $extension failed"
|
||||
wait_for_codeticket
|
||||
end_section "Upload Extension $extension"
|
||||
done
|
||||
fi
|
||||
# Upload the llphysicsextensions_tpv package, if one was produced
|
||||
# Only upload this package when building the private repo so the
|
||||
# artifact is private.
|
||||
if [[ "x$GITHUB_REPOSITORY" == "xsecondlife/viewer-private" && \
|
||||
-r "$build_dir/llphysicsextensions_package" ]]
|
||||
then
|
||||
llphysicsextensions_package=$(cat $build_dir/llphysicsextensions_package)
|
||||
physicstpv+=("$llphysicsextensions_package")
|
||||
fi
|
||||
end_section "Uploads"
|
||||
else
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
# standalone functions from sling-buildscripts
|
||||
|
||||
set_build_number_to_revision()
|
||||
{
|
||||
record_event "buildNumber $revision"
|
||||
}
|
||||
|
||||
record_event()
|
||||
{
|
||||
echo "=== $@"
|
||||
}
|
||||
|
||||
begin_section()
|
||||
{
|
||||
record_event "START $*"
|
||||
sections+=("$*")
|
||||
}
|
||||
|
||||
end_section()
|
||||
{
|
||||
# accommodate dumb Mac bash 3, which doesn't understand array[-1]
|
||||
local last=$(( ${#sections[@]} - 1 ))
|
||||
record_event "END ${*:-${sections[$last]}}"
|
||||
unset "sections[$last]"
|
||||
}
|
||||
|
||||
record_success()
|
||||
{
|
||||
record_event "SUCCESS $*"
|
||||
}
|
||||
|
||||
record_failure()
|
||||
{
|
||||
record_event "FAILURE $*" >&2
|
||||
}
|
||||
|
||||
fatal()
|
||||
{
|
||||
record_failure "$@"
|
||||
finalize false
|
||||
exit 1
|
||||
}
|
||||
|
||||
# redefined fail for backward compatibility
|
||||
alias fail=fatal
|
||||
|
||||
pass()
|
||||
{
|
||||
exit 0
|
||||
}
|
||||
|
||||
export -f set_build_number_to_revision
|
||||
export -f record_event
|
||||
export -f begin_section
|
||||
export -f end_section
|
||||
export -f record_success
|
||||
export -f record_failure
|
||||
export -f fatal
|
||||
export -f pass
|
||||
export sections
|
||||
|
|
@ -240,6 +240,7 @@ Ansariel Hiller
|
|||
SL-18432
|
||||
SL-19140
|
||||
SL-4126
|
||||
SL-20224
|
||||
Aralara Rajal
|
||||
Arare Chantilly
|
||||
CHUIBUG-191
|
||||
|
|
@ -596,6 +597,7 @@ Henri Beauchamp
|
|||
SL-15175
|
||||
SL-19110
|
||||
SL-19159
|
||||
[NO JIRA] (fullbright HUD alpha fix)
|
||||
herina Bode
|
||||
Hikkoshi Sakai
|
||||
VWR-429
|
||||
|
|
@ -928,6 +930,8 @@ LSL Scientist
|
|||
Lamorna Proctor
|
||||
Lares Carter
|
||||
Larry Pixel
|
||||
Lars Næsbye Christensen
|
||||
SL-20054
|
||||
Laurent Bechir
|
||||
Leal Choche
|
||||
Lenae Munz
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{LL_BUILD}")
|
|||
|
||||
# Portable compilation flags.
|
||||
add_compile_definitions( ADDRESS_SIZE=${ADDRESS_SIZE})
|
||||
# Because older versions of Boost.Bind dumped placeholders _1, _2 et al. into
|
||||
# the global namespace, Boost now requires either BOOST_BIND_NO_PLACEHOLDERS
|
||||
# to avoid that or BOOST_BIND_GLOBAL_PLACEHOLDERS to state that we require it
|
||||
# -- which we do. Without one or the other, we get a ton of Boost warnings.
|
||||
add_compile_definitions(BOOST_BIND_GLOBAL_PLACEHOLDERS)
|
||||
|
||||
# Configure crash reporting
|
||||
set(RELEASE_CRASH_REPORTING OFF CACHE BOOL "Enable use of crash reporting in release builds")
|
||||
|
|
@ -55,15 +60,6 @@ if (WINDOWS)
|
|||
# http://www.cmake.org/pipermail/cmake/2009-September/032143.html
|
||||
string(REPLACE "/Zm1000" " " CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
|
||||
|
||||
# Without PreferredToolArchitecture=x64, as of 2020-06-26 the 32-bit
|
||||
# compiler on our TeamCity build hosts has started running out of virtual
|
||||
# memory for the precompiled header file.
|
||||
# CP changed to only append the flag for 32bit builds - on 64bit builds,
|
||||
# locally at least, the build output is spammed with 1000s of 'D9002'
|
||||
# warnings about this switch being ignored.
|
||||
if(ADDRESS_SIZE EQUAL 32 AND DEFINED ENV{"TEAMCITY_PROJECT_NAME"})
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /p:PreferredToolArchitecture=x64")
|
||||
endif()
|
||||
# zlib has assembly-language object files incompatible with SAFESEH
|
||||
add_link_options(/LARGEADDRESSAWARE
|
||||
/SAFESEH:NO
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ if (WINDOWS)
|
|||
endif (LLCOMMON_LINK_SHARED)
|
||||
target_link_libraries( ll::apr INTERFACE
|
||||
${ARCH_PREBUILT_DIRS_RELEASE}/${APR_selector}apr-1.lib
|
||||
${ARCH_PREBUILT_DIRS_RELEASE}/${APR_selector}apriconv-1.lib
|
||||
${ARCH_PREBUILT_DIRS_RELEASE}/${APR_selector}aprutil-1.lib
|
||||
)
|
||||
elseif (DARWIN)
|
||||
|
|
@ -37,7 +36,6 @@ else (WINDOWS)
|
|||
target_link_libraries( ll::apr INTERFACE
|
||||
apr-1
|
||||
aprutil-1
|
||||
iconv
|
||||
uuid
|
||||
rt
|
||||
)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ if(WINDOWS)
|
|||
openjp2.dll
|
||||
libapr-1.dll
|
||||
libaprutil-1.dll
|
||||
libapriconv-1.dll
|
||||
nghttp2.dll
|
||||
libhunspell.dll
|
||||
uriparser.dll
|
||||
|
|
@ -87,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 ()
|
||||
|
||||
|
|
@ -172,7 +172,6 @@ elseif(DARWIN)
|
|||
libndofdev.dylib
|
||||
libnghttp2.dylib
|
||||
libnghttp2.14.dylib
|
||||
libnghttp2.14.19.0.dylib
|
||||
liburiparser.dylib
|
||||
liburiparser.1.dylib
|
||||
liburiparser.1.0.27.dylib
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ elseif (WINDOWS)
|
|||
user32
|
||||
ole32
|
||||
dbghelp
|
||||
rpcrt4.lib
|
||||
legacy_stdio_definitions
|
||||
)
|
||||
else()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import itertools
|
|||
import operator
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -531,15 +532,15 @@ class LLManifest(object, metaclass=LLManifestRegistry):
|
|||
self.cmakedirs(path)
|
||||
return path
|
||||
|
||||
def run_command(self, command):
|
||||
def run_command(self, command, **kwds):
|
||||
"""
|
||||
Runs an external command.
|
||||
Raises ManifestError exception if the command returns a nonzero status.
|
||||
"""
|
||||
print("Running command:", command)
|
||||
print("Running command:", shlex.join(command))
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
subprocess.check_call(command)
|
||||
subprocess.check_call(command, **kwds)
|
||||
except subprocess.CalledProcessError as err:
|
||||
raise ManifestError( "Command %s returned non-zero status (%s)"
|
||||
% (command, err.returncode) )
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <array>
|
||||
|
||||
#include "v3math.h"
|
||||
#include "v3dmath.h"
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class LLAudioEngine_OpenAL : public LLAudioEngine
|
|||
|
||||
virtual bool init(void *user_data, const std::string &app_title);
|
||||
virtual std::string getDriverName(bool verbose);
|
||||
virtual LLStreamingAudioInterface* createDefaultStreamingAudioImpl() const { return nullptr; }
|
||||
virtual void allocateListener();
|
||||
|
||||
virtual void shutdown();
|
||||
|
|
@ -56,7 +57,6 @@ class LLAudioEngine_OpenAL : public LLAudioEngine
|
|||
/*virtual*/ void updateWind(LLVector3 direction, F32 camera_altitude);
|
||||
|
||||
private:
|
||||
void * windDSP(void *newbuffer, int length);
|
||||
typedef S16 WIND_SAMPLE_T;
|
||||
LLWindGen<WIND_SAMPLE_T> *mWindGen;
|
||||
S16 *mWindBuf;
|
||||
|
|
|
|||
|
|
@ -16,8 +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
|
||||
|
|
@ -115,12 +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
|
||||
|
|
@ -292,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,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) */
|
||||
|
|
@ -38,6 +38,12 @@ const S32 FULL_VOLATILE_APR_POOL = 1024 ; //number of references to LLVolatileAP
|
|||
|
||||
bool gAPRInitialized = false;
|
||||
|
||||
int abortfunc(int retcode)
|
||||
{
|
||||
LL_WARNS("APR") << "Allocation failure in apr pool with code " << (S32)retcode << LL_ENDL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ll_init_apr()
|
||||
{
|
||||
// Initialize APR and create the global pool
|
||||
|
|
@ -45,7 +51,7 @@ void ll_init_apr()
|
|||
|
||||
if (!gAPRPoolp)
|
||||
{
|
||||
apr_pool_create(&gAPRPoolp, NULL);
|
||||
apr_pool_create_ex(&gAPRPoolp, NULL, abortfunc, NULL);
|
||||
}
|
||||
|
||||
if(!LLAPRFile::sAPRFilePoolp)
|
||||
|
|
|
|||
|
|
@ -123,11 +123,7 @@ LLCoros::LLCoros():
|
|||
// Previously we used
|
||||
// boost::context::guarded_stack_allocator::default_stacksize();
|
||||
// empirically this is insufficient.
|
||||
#if ADDRESS_SIZE == 64
|
||||
mStackSize(512*1024),
|
||||
#else
|
||||
mStackSize(256*1024),
|
||||
#endif
|
||||
mStackSize(768*1024),
|
||||
// mCurrent does NOT own the current CoroData instance -- it simply
|
||||
// points to it. So initialize it with a no-op deleter.
|
||||
mCurrent{ [](CoroData*){} }
|
||||
|
|
@ -282,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,5 +1603,18 @@ namespace LLError
|
|||
}
|
||||
}
|
||||
|
||||
void crashdriver(void (*callback)(int*))
|
||||
{
|
||||
// The LLERROR_CRASH macro used to have inline code of the form:
|
||||
//int* make_me_crash = NULL;
|
||||
//*make_me_crash = 0;
|
||||
|
||||
|
||||
// But compilers are getting smart enough to recognize that, so we must
|
||||
// assign to an address supplied by a separate source file. We could do
|
||||
// the assignment here in crashdriver() -- but then BugSplat would group
|
||||
// all LL_ERRS() crashes as the fault of this one function, instead of
|
||||
// identifying the specific LL_ERRS() source line. So instead, do the
|
||||
// assignment in a lambda in the caller's source. We just provide the
|
||||
// nullptr target.
|
||||
callback(nullptr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -385,11 +385,9 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
|
|||
#define LL_NEWLINE '\n'
|
||||
|
||||
// Use this only in LL_ERRS or in a place that LL_ERRS may not be used
|
||||
#define LLERROR_CRASH \
|
||||
{ \
|
||||
int* make_me_crash = NULL;\
|
||||
*make_me_crash = 0; \
|
||||
exit(*make_me_crash); \
|
||||
#define LLERROR_CRASH \
|
||||
{ \
|
||||
crashdriver([](int* ptr){ *ptr = 0; exit(*ptr); }); \
|
||||
}
|
||||
|
||||
#define LL_ENDL \
|
||||
|
|
@ -491,4 +489,7 @@ 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*));
|
||||
|
||||
#endif // LL_LLERROR_H
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
@ -389,6 +406,17 @@ public:
|
|||
// Read all remaining bytes and log.
|
||||
LL_INFOS("LLLeap") << mDesc << ": " << rest << LL_ENDL;
|
||||
}
|
||||
/*--------------------------- diagnostic ---------------------------*/
|
||||
else if (data["eof"].asBoolean())
|
||||
{
|
||||
LL_DEBUGS("LLLeap") << mDesc << " ended, no partial line" << LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_DEBUGS("LLLeap") << mDesc << " (still running, " << childerr.size()
|
||||
<< " bytes pending)" << LL_ENDL;
|
||||
}
|
||||
/*------------------------- end diagnostic -------------------------*/
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -529,6 +529,7 @@ LLProcess::LLProcess(const LLSDOrParams& params):
|
|||
// preserve existing semantics, we promise that mAttached defaults to the
|
||||
// same setting as mAutokill.
|
||||
mAttached(params.attached.isProvided()? params.attached : params.autokill),
|
||||
mPool(NULL),
|
||||
mPipes(NSLOTS)
|
||||
{
|
||||
// Hmm, when you construct a ptr_vector with a size, it merely reserves
|
||||
|
|
@ -549,8 +550,14 @@ LLProcess::LLProcess(const LLSDOrParams& params):
|
|||
|
||||
mPostend = params.postend;
|
||||
|
||||
apr_pool_create(&mPool, gAPRPoolp);
|
||||
if (!mPool)
|
||||
{
|
||||
LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool")));
|
||||
}
|
||||
|
||||
apr_procattr_t *procattr = NULL;
|
||||
chkapr(apr_procattr_create(&procattr, gAPRPoolp));
|
||||
chkapr(apr_procattr_create(&procattr, mPool));
|
||||
|
||||
// IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to
|
||||
// constrain the set of handles passed to the child process. Before we
|
||||
|
|
@ -689,14 +696,14 @@ LLProcess::LLProcess(const LLSDOrParams& params):
|
|||
// one. Hand-expand chkapr() macro so we can fill in the actual command
|
||||
// string instead of the variable names.
|
||||
if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr,
|
||||
gAPRPoolp)))
|
||||
mPool)))
|
||||
{
|
||||
LLTHROW(LLProcessError(STRINGIZE(params << " failed")));
|
||||
}
|
||||
|
||||
// arrange to call status_callback()
|
||||
apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in,
|
||||
gAPRPoolp);
|
||||
mPool);
|
||||
// and make sure we poll it once per "mainloop" tick
|
||||
sProcessListener.addPoll(*this);
|
||||
mStatus.mState = RUNNING;
|
||||
|
|
@ -815,6 +822,12 @@ LLProcess::~LLProcess()
|
|||
{
|
||||
kill("destructor");
|
||||
}
|
||||
|
||||
if (mPool)
|
||||
{
|
||||
apr_pool_destroy(mPool);
|
||||
mPool = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool LLProcess::kill(const std::string& who)
|
||||
|
|
|
|||
|
|
@ -568,6 +568,7 @@ private:
|
|||
// explicitly want this ptr_vector to be able to store NULLs
|
||||
typedef boost::ptr_vector< boost::nullable<BasePipe> > PipeVector;
|
||||
PipeVector mPipes;
|
||||
apr_pool_t* mPool;
|
||||
};
|
||||
|
||||
/// for logging
|
||||
|
|
|
|||
|
|
@ -746,7 +746,7 @@ private:
|
|||
__cpuid(0x1, eax, ebx, ecx, edx);
|
||||
if(feature_infos[0] != (S32)edx)
|
||||
{
|
||||
LL_ERRS() << "machdep.cpu.feature_bits doesn't match expected cpuid result!" << LL_ENDL;
|
||||
LL_WARNS() << "machdep.cpu.feature_bits doesn't match expected cpuid result!" << LL_ENDL;
|
||||
}
|
||||
#endif // LL_RELEASE_FOR_DOWNLOAD
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ extern thread_local bool gProfilerEnabled;
|
|||
#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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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) */
|
||||
|
|
|
|||
|
|
@ -58,46 +58,14 @@
|
|||
* to restore uniform distribution.
|
||||
*/
|
||||
|
||||
// *NOTE: The system rand implementation is probably not correct.
|
||||
#define LL_USE_SYSTEM_RAND 0
|
||||
|
||||
#if LL_USE_SYSTEM_RAND
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
#if LL_USE_SYSTEM_RAND
|
||||
class LLSeedRand
|
||||
{
|
||||
public:
|
||||
LLSeedRand()
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
srand(LLUUID::getRandomSeed());
|
||||
#else
|
||||
srand48(LLUUID::getRandomSeed());
|
||||
#endif
|
||||
}
|
||||
};
|
||||
static LLSeedRand sRandomSeeder;
|
||||
inline F64 ll_internal_random_double()
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
return (F64)rand() / (F64)RAND_MAX;
|
||||
#else
|
||||
return drand48();
|
||||
#endif
|
||||
}
|
||||
inline F32 ll_internal_random_float()
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
return (F32)rand() / (F32)RAND_MAX;
|
||||
#else
|
||||
return (F32)drand48();
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
static LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed());
|
||||
inline F64 ll_internal_random_double()
|
||||
|
||||
// no default implementation, only specific F64 and F32 specializations
|
||||
template <typename REAL>
|
||||
inline REAL ll_internal_random();
|
||||
|
||||
template <>
|
||||
inline F64 ll_internal_random<F64>()
|
||||
{
|
||||
// *HACK: Through experimentation, we have found that dual core
|
||||
// CPUs (or at least multi-threaded processes) seem to
|
||||
|
|
@ -108,15 +76,35 @@ inline F64 ll_internal_random_double()
|
|||
return rv;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline F32 ll_internal_random<F32>()
|
||||
{
|
||||
return F32(ll_internal_random<F64>());
|
||||
}
|
||||
|
||||
/*------------------------------ F64 aliases -------------------------------*/
|
||||
inline F64 ll_internal_random_double()
|
||||
{
|
||||
return ll_internal_random<F64>();
|
||||
}
|
||||
|
||||
F64 ll_drand()
|
||||
{
|
||||
return ll_internal_random_double();
|
||||
}
|
||||
|
||||
/*------------------------------ F32 aliases -------------------------------*/
|
||||
inline F32 ll_internal_random_float()
|
||||
{
|
||||
// The clamping rules are described above.
|
||||
F32 rv = (F32)gRandomGenerator();
|
||||
if(!((rv >= 0.0f) && (rv < 1.0f))) return fmod(rv, 1.f);
|
||||
return rv;
|
||||
return ll_internal_random<F32>();
|
||||
}
|
||||
#endif
|
||||
|
||||
F32 ll_frand()
|
||||
{
|
||||
return ll_internal_random_float();
|
||||
}
|
||||
|
||||
/*-------------------------- clamped random range --------------------------*/
|
||||
S32 ll_rand()
|
||||
{
|
||||
return ll_rand(RAND_MAX);
|
||||
|
|
@ -130,42 +118,28 @@ S32 ll_rand(S32 val)
|
|||
return rv;
|
||||
}
|
||||
|
||||
F32 ll_frand()
|
||||
template <typename REAL>
|
||||
REAL ll_grand(REAL val)
|
||||
{
|
||||
return ll_internal_random_float();
|
||||
// The clamping rules are described above.
|
||||
REAL rv = ll_internal_random<REAL>() * val;
|
||||
if(val > 0)
|
||||
{
|
||||
if(rv >= val) return REAL();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(rv <= val) return REAL();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
F32 ll_frand(F32 val)
|
||||
{
|
||||
// The clamping rules are described above.
|
||||
F32 rv = ll_internal_random_float() * val;
|
||||
if(val > 0)
|
||||
{
|
||||
if(rv >= val) return 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(rv <= val) return 0.0f;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
F64 ll_drand()
|
||||
{
|
||||
return ll_internal_random_double();
|
||||
return ll_grand<F32>(val);
|
||||
}
|
||||
|
||||
F64 ll_drand(F64 val)
|
||||
{
|
||||
// The clamping rules are described above.
|
||||
F64 rv = ll_internal_random_double() * val;
|
||||
if(val > 0)
|
||||
{
|
||||
if(rv >= val) return 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(rv <= val) return 0.0;
|
||||
}
|
||||
return rv;
|
||||
return ll_grand<F64>(val);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -455,7 +455,7 @@ public:
|
|||
|
||||
static DERIVED_TYPE* getInstance()
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
|
||||
//LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; // TODO -- reenable this when we have a fix for using Tracy with coroutines
|
||||
// We know the viewer has LLSingleton dependency circularities. If you
|
||||
// feel strongly motivated to eliminate them, cheers and good luck.
|
||||
// (At that point we could consider a much simpler locking mechanism.)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@
|
|||
// std headers
|
||||
#include <functional>
|
||||
// external library headers
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include <boost/phoenix/core/argument.hpp>
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "../test/namedtempfile.h"
|
||||
|
|
@ -30,10 +28,6 @@
|
|||
#include "stringize.h"
|
||||
#include "StringVec.h"
|
||||
|
||||
using boost::assign::list_of;
|
||||
|
||||
StringVec sv(const StringVec& listof) { return listof; }
|
||||
|
||||
#if defined(LL_WINDOWS)
|
||||
#define sleep(secs) _sleep((secs) * 1000)
|
||||
|
||||
|
|
@ -104,17 +98,12 @@ namespace tut
|
|||
llleap_data():
|
||||
reader(".py",
|
||||
// This logic is adapted from vita.viewerclient.receiveEvent()
|
||||
boost::phoenix::placeholders::arg1 <<
|
||||
[](std::ostream& out){ out <<
|
||||
"import re\n"
|
||||
"import os\n"
|
||||
"import sys\n"
|
||||
"\n"
|
||||
"try:\n"
|
||||
// new freestanding llsd package
|
||||
" import llsd\n"
|
||||
"except ImportError:\n"
|
||||
// older llbase.llsd module
|
||||
" from llbase import llsd\n"
|
||||
"import llsd\n"
|
||||
"\n"
|
||||
"class ProtocolError(Exception):\n"
|
||||
" def __init__(self, msg, data):\n"
|
||||
|
|
@ -193,7 +182,7 @@ namespace tut
|
|||
"def request(pump, data):\n"
|
||||
" # we expect 'data' is a dict\n"
|
||||
" data['reply'] = _reply\n"
|
||||
" send(pump, data)\n"),
|
||||
" send(pump, data)\n";}),
|
||||
// Get the actual pathname of the NamedExtTempFile and trim off
|
||||
// the ".py" extension. (We could cache reader.getName() in a
|
||||
// separate member variable, but I happen to know getName() just
|
||||
|
|
@ -218,14 +207,14 @@ namespace tut
|
|||
void object::test<1>()
|
||||
{
|
||||
set_test_name("multiple LLLeap instances");
|
||||
NamedTempFile script("py",
|
||||
"import time\n"
|
||||
"time.sleep(1)\n");
|
||||
NamedExtTempFile script("py",
|
||||
"import time\n"
|
||||
"time.sleep(1)\n");
|
||||
LLLeapVector instances;
|
||||
instances.push_back(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName())))->getWeak());
|
||||
StringVec{PYTHON, script.getName()})->getWeak());
|
||||
instances.push_back(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName())))->getWeak());
|
||||
StringVec{PYTHON, script.getName()})->getWeak());
|
||||
// In this case we're simply establishing that two LLLeap instances
|
||||
// can coexist without throwing exceptions or bombing in any other
|
||||
// way. Wait for them to terminate.
|
||||
|
|
@ -236,10 +225,10 @@ namespace tut
|
|||
void object::test<2>()
|
||||
{
|
||||
set_test_name("stderr to log");
|
||||
NamedTempFile script("py",
|
||||
"import sys\n"
|
||||
"sys.stderr.write('''Hello from Python!\n"
|
||||
"note partial line''')\n");
|
||||
NamedExtTempFile script("py",
|
||||
"import sys\n"
|
||||
"sys.stderr.write('''Hello from Python!\n"
|
||||
"note partial line''')\n");
|
||||
StringVec vcommand{ PYTHON, script.getName() };
|
||||
CaptureLog log(LLError::LEVEL_INFO);
|
||||
waitfor(LLLeap::create(get_test_name(), vcommand));
|
||||
|
|
@ -251,11 +240,11 @@ namespace tut
|
|||
void object::test<3>()
|
||||
{
|
||||
set_test_name("bad stdout protocol");
|
||||
NamedTempFile script("py",
|
||||
"print('Hello from Python!')\n");
|
||||
NamedExtTempFile script("py",
|
||||
"print('Hello from Python!')\n");
|
||||
CaptureLog log(LLError::LEVEL_WARN);
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
StringVec{PYTHON, script.getName()}));
|
||||
ensure_contains("error log line",
|
||||
log.messageWith("invalid protocol"), "Hello from Python!");
|
||||
}
|
||||
|
|
@ -264,13 +253,13 @@ namespace tut
|
|||
void object::test<4>()
|
||||
{
|
||||
set_test_name("leftover stdout");
|
||||
NamedTempFile script("py",
|
||||
"import sys\n"
|
||||
// note lack of newline
|
||||
"sys.stdout.write('Hello from Python!')\n");
|
||||
NamedExtTempFile script("py",
|
||||
"import sys\n"
|
||||
// note lack of newline
|
||||
"sys.stdout.write('Hello from Python!')\n");
|
||||
CaptureLog log(LLError::LEVEL_WARN);
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
StringVec{PYTHON, script.getName()}));
|
||||
ensure_contains("error log line",
|
||||
log.messageWith("Discarding"), "Hello from Python!");
|
||||
}
|
||||
|
|
@ -279,12 +268,12 @@ namespace tut
|
|||
void object::test<5>()
|
||||
{
|
||||
set_test_name("bad stdout len prefix");
|
||||
NamedTempFile script("py",
|
||||
"import sys\n"
|
||||
"sys.stdout.write('5a2:something')\n");
|
||||
NamedExtTempFile script("py",
|
||||
"import sys\n"
|
||||
"sys.stdout.write('5a2:something')\n");
|
||||
CaptureLog log(LLError::LEVEL_WARN);
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
StringVec{PYTHON, script.getName()}));
|
||||
ensure_contains("error log line",
|
||||
log.messageWith("invalid protocol"), "5a2:");
|
||||
}
|
||||
|
|
@ -386,17 +375,18 @@ namespace tut
|
|||
set_test_name("round trip");
|
||||
AckAPI api;
|
||||
Result result;
|
||||
NamedTempFile script("py",
|
||||
boost::phoenix::placeholders::arg1 <<
|
||||
"from " << reader_module << " import *\n"
|
||||
// make a request on our little API
|
||||
"request(pump='" << api.getName() << "', data={})\n"
|
||||
// wait for its response
|
||||
"resp = get()\n"
|
||||
"result = '' if resp == dict(pump=replypump(), data='ack')\\\n"
|
||||
" else 'bad: ' + str(resp)\n"
|
||||
"send(pump='" << result.getName() << "', data=result)\n");
|
||||
waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))));
|
||||
NamedExtTempFile script("py",
|
||||
[&](std::ostream& out){ out <<
|
||||
"from " << reader_module << " import *\n"
|
||||
// make a request on our little API
|
||||
"request(pump='" << api.getName() << "', data={})\n"
|
||||
// wait for its response
|
||||
"resp = get()\n"
|
||||
"result = '' if resp == dict(pump=replypump(), data='ack')\\\n"
|
||||
" else 'bad: ' + str(resp)\n"
|
||||
"send(pump='" << result.getName() << "', data=result)\n";});
|
||||
waitfor(LLLeap::create(get_test_name(),
|
||||
StringVec{PYTHON, script.getName()}));
|
||||
result.ensure();
|
||||
}
|
||||
|
||||
|
|
@ -424,38 +414,38 @@ namespace tut
|
|||
// iterations etc. in OS pipes and the LLLeap/LLProcess implementation.
|
||||
ReqIDAPI api;
|
||||
Result result;
|
||||
NamedTempFile script("py",
|
||||
boost::phoenix::placeholders::arg1 <<
|
||||
"import sys\n"
|
||||
"from " << reader_module << " import *\n"
|
||||
// Note that since reader imports llsd, this
|
||||
// 'import *' gets us llsd too.
|
||||
"sample = llsd.format_notation(dict(pump='" <<
|
||||
api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n"
|
||||
// The whole packet has length prefix too: "len:data"
|
||||
"samplen = len(str(len(sample))) + 1 + len(sample)\n"
|
||||
// guess how many messages it will take to
|
||||
// accumulate BUFFERED_LENGTH
|
||||
"count = int(" << BUFFERED_LENGTH << "/samplen)\n"
|
||||
"print('Sending %s requests' % count, file=sys.stderr)\n"
|
||||
"for i in range(count):\n"
|
||||
" request('" << api.getName() << "', dict(reqid=i))\n"
|
||||
// The assumption in this specific test that
|
||||
// replies will arrive in the same order as
|
||||
// requests is ONLY valid because the API we're
|
||||
// invoking sends replies instantly. If the API
|
||||
// had to wait for some external event before
|
||||
// sending its reply, replies could arrive in
|
||||
// arbitrary order, and we'd have to tick them
|
||||
// off from a set.
|
||||
"result = ''\n"
|
||||
"for i in range(count):\n"
|
||||
" resp = get()\n"
|
||||
" if resp['data']['reqid'] != i:\n"
|
||||
" result = 'expected reqid=%s in %s' % (i, resp)\n"
|
||||
" break\n"
|
||||
"send(pump='" << result.getName() << "', data=result)\n");
|
||||
waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))),
|
||||
NamedExtTempFile script("py",
|
||||
[&](std::ostream& out){ out <<
|
||||
"import sys\n"
|
||||
"from " << reader_module << " import *\n"
|
||||
// Note that since reader imports llsd, this
|
||||
// 'import *' gets us llsd too.
|
||||
"sample = llsd.format_notation(dict(pump='" <<
|
||||
api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n"
|
||||
// The whole packet has length prefix too: "len:data"
|
||||
"samplen = len(str(len(sample))) + 1 + len(sample)\n"
|
||||
// guess how many messages it will take to
|
||||
// accumulate BUFFERED_LENGTH
|
||||
"count = int(" << BUFFERED_LENGTH << "/samplen)\n"
|
||||
"print('Sending %s requests' % count, file=sys.stderr)\n"
|
||||
"for i in range(count):\n"
|
||||
" request('" << api.getName() << "', dict(reqid=i))\n"
|
||||
// The assumption in this specific test that
|
||||
// replies will arrive in the same order as
|
||||
// requests is ONLY valid because the API we're
|
||||
// invoking sends replies instantly. If the API
|
||||
// had to wait for some external event before
|
||||
// sending its reply, replies could arrive in
|
||||
// arbitrary order, and we'd have to tick them
|
||||
// off from a set.
|
||||
"result = ''\n"
|
||||
"for i in range(count):\n"
|
||||
" resp = get()\n"
|
||||
" if resp['data']['reqid'] != i:\n"
|
||||
" result = 'expected reqid=%s in %s' % (i, resp)\n"
|
||||
" break\n"
|
||||
"send(pump='" << result.getName() << "', data=result)\n";});
|
||||
waitfor(LLLeap::create(get_test_name(), StringVec{PYTHON, script.getName()}),
|
||||
300); // needs more realtime than most tests
|
||||
result.ensure();
|
||||
}
|
||||
|
|
@ -467,65 +457,62 @@ namespace tut
|
|||
{
|
||||
ReqIDAPI api;
|
||||
Result result;
|
||||
NamedTempFile script("py",
|
||||
boost::phoenix::placeholders::arg1 <<
|
||||
"import sys\n"
|
||||
"from " << reader_module << " import *\n"
|
||||
// Generate a very large string value.
|
||||
"desired = int(sys.argv[1])\n"
|
||||
// 7 chars per item: 6 digits, 1 comma
|
||||
"count = int((desired - 50)/7)\n"
|
||||
"large = ''.join('%06d,' % i for i in range(count))\n"
|
||||
// Pass 'large' as reqid because we know the API
|
||||
// will echo reqid, and we want to receive it back.
|
||||
"request('" << api.getName() << "', dict(reqid=large))\n"
|
||||
"try:\n"
|
||||
" resp = get()\n"
|
||||
"except ParseError as e:\n"
|
||||
" # try to find where e.data diverges from expectation\n"
|
||||
// Normally we'd expect a 'pump' key in there,
|
||||
// too, with value replypump(). But Python
|
||||
// serializes keys in a different order than C++,
|
||||
// so incoming data start with 'data'.
|
||||
// Truthfully, though, if we get as far as 'pump'
|
||||
// before we find a difference, something's very
|
||||
// strange.
|
||||
" expect = llsd.format_notation(dict(data=dict(reqid=large)))\n"
|
||||
" chunk = 40\n"
|
||||
" for offset in range(0, max(len(e.data), len(expect)), chunk):\n"
|
||||
" if e.data[offset:offset+chunk] != \\\n"
|
||||
" expect[offset:offset+chunk]:\n"
|
||||
" print('Offset %06d: expect %r,\\n'\\\n"
|
||||
" ' get %r' %\\\n"
|
||||
" (offset,\n"
|
||||
" expect[offset:offset+chunk],\n"
|
||||
" e.data[offset:offset+chunk]),\n"
|
||||
" file=sys.stderr)\n"
|
||||
" break\n"
|
||||
" else:\n"
|
||||
" print('incoming data matches expect?!', file=sys.stderr)\n"
|
||||
" send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n"
|
||||
" sys.exit(1)\n"
|
||||
"\n"
|
||||
"echoed = resp['data']['reqid']\n"
|
||||
"if echoed == large:\n"
|
||||
" send('" << result.getName() << "', '')\n"
|
||||
" sys.exit(0)\n"
|
||||
// Here we know echoed did NOT match; try to find where
|
||||
"for i in range(count):\n"
|
||||
" start = 7*i\n"
|
||||
" end = 7*(i+1)\n"
|
||||
" if end > len(echoed)\\\n"
|
||||
" or echoed[start:end] != large[start:end]:\n"
|
||||
" send('" << result.getName() << "',\n"
|
||||
" 'at offset %s, expected %r but got %r' %\n"
|
||||
" (start, large[start:end], echoed[start:end]))\n"
|
||||
"sys.exit(1)\n");
|
||||
NamedExtTempFile script("py",
|
||||
[&](std::ostream& out){ out <<
|
||||
"import sys\n"
|
||||
"from " << reader_module << " import *\n"
|
||||
// Generate a very large string value.
|
||||
"desired = int(sys.argv[1])\n"
|
||||
// 7 chars per item: 6 digits, 1 comma
|
||||
"count = int((desired - 50)/7)\n"
|
||||
"large = ''.join('%06d,' % i for i in range(count))\n"
|
||||
// Pass 'large' as reqid because we know the API
|
||||
// will echo reqid, and we want to receive it back.
|
||||
"request('" << api.getName() << "', dict(reqid=large))\n"
|
||||
"try:\n"
|
||||
" resp = get()\n"
|
||||
"except ParseError as e:\n"
|
||||
" # try to find where e.data diverges from expectation\n"
|
||||
// Normally we'd expect a 'pump' key in there,
|
||||
// too, with value replypump(). But Python
|
||||
// serializes keys in a different order than C++,
|
||||
// so incoming data start with 'data'.
|
||||
// Truthfully, though, if we get as far as 'pump'
|
||||
// before we find a difference, something's very
|
||||
// strange.
|
||||
" expect = llsd.format_notation(dict(data=dict(reqid=large)))\n"
|
||||
" chunk = 40\n"
|
||||
" for offset in range(0, max(len(e.data), len(expect)), chunk):\n"
|
||||
" if e.data[offset:offset+chunk] != \\\n"
|
||||
" expect[offset:offset+chunk]:\n"
|
||||
" print('Offset %06d: expect %r,\\n'\\\n"
|
||||
" ' get %r' %\\\n"
|
||||
" (offset,\n"
|
||||
" expect[offset:offset+chunk],\n"
|
||||
" e.data[offset:offset+chunk]),\n"
|
||||
" file=sys.stderr)\n"
|
||||
" break\n"
|
||||
" else:\n"
|
||||
" print('incoming data matches expect?!', file=sys.stderr)\n"
|
||||
" send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n"
|
||||
" sys.exit(1)\n"
|
||||
"\n"
|
||||
"echoed = resp['data']['reqid']\n"
|
||||
"if echoed == large:\n"
|
||||
" send('" << result.getName() << "', '')\n"
|
||||
" sys.exit(0)\n"
|
||||
// Here we know echoed did NOT match; try to find where
|
||||
"for i in range(count):\n"
|
||||
" start = 7*i\n"
|
||||
" end = 7*(i+1)\n"
|
||||
" if end > len(echoed)\\\n"
|
||||
" or echoed[start:end] != large[start:end]:\n"
|
||||
" send('" << result.getName() << "',\n"
|
||||
" 'at offset %s, expected %r but got %r' %\n"
|
||||
" (start, large[start:end], echoed[start:end]))\n"
|
||||
"sys.exit(1)\n";});
|
||||
waitfor(LLLeap::create(test_name,
|
||||
sv(list_of
|
||||
(PYTHON)
|
||||
(script.getName())
|
||||
(stringize(size)))),
|
||||
StringVec{PYTHON, script.getName(), stringize(size)}),
|
||||
180); // try a longer timeout
|
||||
result.ensure();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,8 +151,38 @@ struct PythonProcessLauncher
|
|||
/// Launch Python script; verify that it launched
|
||||
void launch()
|
||||
{
|
||||
mPy = LLProcess::create(mParams);
|
||||
tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy));
|
||||
try
|
||||
{
|
||||
mPy = LLProcess::create(mParams);
|
||||
tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy));
|
||||
}
|
||||
catch (const tut::failure&)
|
||||
{
|
||||
// On Windows, if APR_LOG is set, our version of APR's
|
||||
// apr_create_proc() logs to the specified file. If this test
|
||||
// failed, try to report that log.
|
||||
const char* APR_LOG = getenv("APR_LOG");
|
||||
if (APR_LOG && *APR_LOG)
|
||||
{
|
||||
std::ifstream inf(APR_LOG);
|
||||
if (! inf.is_open())
|
||||
{
|
||||
LL_WARNS() << "Couldn't open '" << APR_LOG << "'" << LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS() << "==============================" << LL_ENDL;
|
||||
LL_WARNS() << "From '" << APR_LOG << "':" << LL_ENDL;
|
||||
std::string line;
|
||||
while (std::getline(inf, line))
|
||||
{
|
||||
LL_WARNS() << line << LL_ENDL;
|
||||
}
|
||||
LL_WARNS() << "==============================" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// Run Python script and wait for it to complete.
|
||||
|
|
@ -191,7 +221,7 @@ struct PythonProcessLauncher
|
|||
LLProcess::Params mParams;
|
||||
LLProcessPtr mPy;
|
||||
std::string mDesc;
|
||||
NamedTempFile mScript;
|
||||
NamedExtTempFile mScript;
|
||||
};
|
||||
|
||||
/// convenience function for PythonProcessLauncher::run()
|
||||
|
|
@ -214,30 +244,26 @@ static std::string python_out(const std::string& desc, const CONTENT& script)
|
|||
class NamedTempDir: public boost::noncopyable
|
||||
{
|
||||
public:
|
||||
// Use python() function to create a temp directory: I've found
|
||||
// nothing in either Boost.Filesystem or APR quite like Python's
|
||||
// tempfile.mkdtemp().
|
||||
// Special extra bonus: on Mac, mkdtemp() reports a pathname
|
||||
// starting with /var/folders/something, whereas that's really a
|
||||
// symlink to /private/var/folders/something. Have to use
|
||||
// realpath() to compare properly.
|
||||
NamedTempDir():
|
||||
mPath(python_out("mkdtemp()",
|
||||
"from __future__ import with_statement\n"
|
||||
"import os.path, sys, tempfile\n"
|
||||
"with open(sys.argv[1], 'w') as f:\n"
|
||||
" f.write(os.path.normcase(os.path.normpath(os.path.realpath(tempfile.mkdtemp()))))\n"))
|
||||
{}
|
||||
mPath(NamedTempFile::temp_path()),
|
||||
mCreated(boost::filesystem::create_directories(mPath))
|
||||
{
|
||||
mPath = boost::filesystem::canonical(mPath);
|
||||
}
|
||||
|
||||
~NamedTempDir()
|
||||
{
|
||||
aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp));
|
||||
if (mCreated)
|
||||
{
|
||||
boost::filesystem::remove_all(mPath);
|
||||
}
|
||||
}
|
||||
|
||||
std::string getName() const { return mPath; }
|
||||
std::string getName() const { return mPath.string(); }
|
||||
|
||||
private:
|
||||
std::string mPath;
|
||||
boost::filesystem::path mPath;
|
||||
bool mCreated;
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
|
|
@ -355,7 +381,7 @@ namespace tut
|
|||
set_test_name("raw APR nonblocking I/O");
|
||||
|
||||
// Create a script file in a temporary place.
|
||||
NamedTempFile script("py",
|
||||
NamedExtTempFile script("py",
|
||||
"from __future__ import print_function" EOL
|
||||
"import sys" EOL
|
||||
"import time" EOL
|
||||
|
|
@ -565,7 +591,13 @@ namespace tut
|
|||
" f.write(os.path.normcase(os.path.normpath(os.getcwd())))\n");
|
||||
// Before running, call setWorkingDirectory()
|
||||
py.mParams.cwd = tempdir.getName();
|
||||
ensure_equals("os.getcwd()", py.run_read(), tempdir.getName());
|
||||
std::string expected{ tempdir.getName() };
|
||||
#if LL_WINDOWS
|
||||
// SIGH, don't get tripped up by "C:" != "c:" --
|
||||
// but on the Mac, using tolower() fails because "/users" != "/Users"!
|
||||
expected = utf8str_tolower(expected);
|
||||
#endif
|
||||
ensure_equals("os.getcwd()", py.run_read(), expected);
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,23 @@
|
|||
#include "../test/lltut.h"
|
||||
|
||||
#include "../llrand.h"
|
||||
#include "stringize.h"
|
||||
|
||||
// In llrand.h, every function is documented to return less than the high end
|
||||
// -- specifically, because you can pass a negative extent, they're documented
|
||||
// never to return a value equal to the extent.
|
||||
// So that we don't need two different versions of ensure_in_range(), when
|
||||
// testing extent < 0, negate the return value and the extent before passing
|
||||
// into ensure_in_range().
|
||||
template <typename NUMBER>
|
||||
void ensure_in_range(const std::string_view& name,
|
||||
NUMBER value, NUMBER low, NUMBER high)
|
||||
{
|
||||
auto failmsg{ stringize(name, " >= ", low, " (", value, ')') };
|
||||
tut::ensure(failmsg, (value >= low));
|
||||
failmsg = stringize(name, " < ", high, " (", value, ')');
|
||||
tut::ensure(failmsg, (value < high));
|
||||
}
|
||||
|
||||
namespace tut
|
||||
{
|
||||
|
|
@ -44,84 +60,65 @@ namespace tut
|
|||
template<> template<>
|
||||
void random_object_t::test<1>()
|
||||
{
|
||||
F32 number = 0.0f;
|
||||
for(S32 ii = 0; ii < 100000; ++ii)
|
||||
{
|
||||
number = ll_frand();
|
||||
ensure("frand >= 0", (number >= 0.0f));
|
||||
ensure("frand < 1", (number < 1.0f));
|
||||
ensure_in_range("frand", ll_frand(), 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void random_object_t::test<2>()
|
||||
{
|
||||
F64 number = 0.0f;
|
||||
for(S32 ii = 0; ii < 100000; ++ii)
|
||||
{
|
||||
number = ll_drand();
|
||||
ensure("drand >= 0", (number >= 0.0));
|
||||
ensure("drand < 1", (number < 1.0));
|
||||
ensure_in_range("drand", ll_drand(), 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void random_object_t::test<3>()
|
||||
{
|
||||
F32 number = 0.0f;
|
||||
for(S32 ii = 0; ii < 100000; ++ii)
|
||||
{
|
||||
number = ll_frand(2.0f) - 1.0f;
|
||||
ensure("frand >= 0", (number >= -1.0f));
|
||||
ensure("frand < 1", (number <= 1.0f));
|
||||
ensure_in_range("frand(2.0f)", ll_frand(2.0f) - 1.0f, -1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void random_object_t::test<4>()
|
||||
{
|
||||
F32 number = 0.0f;
|
||||
for(S32 ii = 0; ii < 100000; ++ii)
|
||||
{
|
||||
number = ll_frand(-7.0);
|
||||
ensure("drand <= 0", (number <= 0.0));
|
||||
ensure("drand > -7", (number > -7.0));
|
||||
// Negate the result so we don't have to allow a templated low-end
|
||||
// comparison as well.
|
||||
ensure_in_range("-frand(-7.0)", -ll_frand(-7.0), 0.0f, 7.0f);
|
||||
}
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void random_object_t::test<5>()
|
||||
{
|
||||
F64 number = 0.0f;
|
||||
for(S32 ii = 0; ii < 100000; ++ii)
|
||||
{
|
||||
number = ll_drand(-2.0);
|
||||
ensure("drand <= 0", (number <= 0.0));
|
||||
ensure("drand > -2", (number > -2.0));
|
||||
ensure_in_range("-drand(-2.0)", -ll_drand(-2.0), 0.0, 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void random_object_t::test<6>()
|
||||
{
|
||||
S32 number = 0;
|
||||
for(S32 ii = 0; ii < 100000; ++ii)
|
||||
{
|
||||
number = ll_rand(100);
|
||||
ensure("rand >= 0", (number >= 0));
|
||||
ensure("rand < 100", (number < 100));
|
||||
ensure_in_range("rand(100)", ll_rand(100), 0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void random_object_t::test<7>()
|
||||
{
|
||||
S32 number = 0;
|
||||
for(S32 ii = 0; ii < 100000; ++ii)
|
||||
{
|
||||
number = ll_rand(-127);
|
||||
ensure("rand <= 0", (number <= 0));
|
||||
ensure("rand > -127", (number > -127));
|
||||
ensure_in_range("-rand(-127)", -ll_rand(-127), 0, 127);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,11 +45,6 @@ typedef U32 uint32_t;
|
|||
#endif
|
||||
|
||||
#include "boost/range.hpp"
|
||||
#include "boost/foreach.hpp"
|
||||
#include "boost/bind.hpp"
|
||||
#include "boost/phoenix/bind/bind_function.hpp"
|
||||
#include "boost/phoenix/core/argument.hpp"
|
||||
using namespace boost::phoenix;
|
||||
|
||||
#include "llsd.h"
|
||||
#include "llsdserialize.h"
|
||||
|
|
@ -57,9 +52,11 @@ using namespace boost::phoenix;
|
|||
#include "llformat.h"
|
||||
#include "llmemorystream.h"
|
||||
|
||||
#include "../test/hexdump.h"
|
||||
#include "../test/lltut.h"
|
||||
#include "../test/namedtempfile.h"
|
||||
#include "stringize.h"
|
||||
#include "StringVec.h"
|
||||
#include <functional>
|
||||
|
||||
typedef std::function<void(const LLSD& data, std::ostream& str)> FormatterFunction;
|
||||
|
|
@ -1796,16 +1793,12 @@ namespace tut
|
|||
// helper for TestPythonCompatible
|
||||
static std::string import_llsd("import os.path\n"
|
||||
"import sys\n"
|
||||
"try:\n"
|
||||
// new freestanding llsd package
|
||||
" import llsd\n"
|
||||
"except ImportError:\n"
|
||||
// older llbase.llsd module
|
||||
" from llbase import llsd\n");
|
||||
"import llsd\n");
|
||||
|
||||
// helper for TestPythonCompatible
|
||||
template <typename CONTENT>
|
||||
void python(const std::string& desc, const CONTENT& script, int expect=0)
|
||||
template <typename CONTENT, typename... ARGS>
|
||||
void python_expect(const std::string& desc, const CONTENT& script, int expect=0,
|
||||
ARGS&&... args)
|
||||
{
|
||||
auto PYTHON(LLStringUtil::getenv("PYTHON"));
|
||||
ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty());
|
||||
|
|
@ -1816,7 +1809,8 @@ namespace tut
|
|||
std::string q("\"");
|
||||
std::string qPYTHON(q + PYTHON + q);
|
||||
std::string qscript(q + scriptfile.getName() + q);
|
||||
int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), NULL);
|
||||
int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(),
|
||||
std::forward<ARGS>(args)..., NULL);
|
||||
if (rc == -1)
|
||||
{
|
||||
char buffer[256];
|
||||
|
|
@ -1832,6 +1826,10 @@ namespace tut
|
|||
LLProcess::Params params;
|
||||
params.executable = PYTHON;
|
||||
params.args.add(scriptfile.getName());
|
||||
for (const std::string& arg : StringVec{ std::forward<ARGS>(args)... })
|
||||
{
|
||||
params.args.add(arg);
|
||||
}
|
||||
LLProcessPtr py(LLProcess::create(params));
|
||||
ensure(STRINGIZE("Couldn't launch " << desc << " script"), bool(py));
|
||||
// Implementing timeout would mean messing with alarm() and
|
||||
|
|
@ -1866,6 +1864,14 @@ namespace tut
|
|||
#endif
|
||||
}
|
||||
|
||||
// helper for TestPythonCompatible
|
||||
template <typename CONTENT, typename... ARGS>
|
||||
void python(const std::string& desc, const CONTENT& script, ARGS&&... args)
|
||||
{
|
||||
// plain python() expects rc 0
|
||||
python_expect(desc, script, 0, std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
struct TestPythonCompatible
|
||||
{
|
||||
TestPythonCompatible() {}
|
||||
|
|
@ -1880,10 +1886,10 @@ namespace tut
|
|||
void TestPythonCompatibleObject::test<1>()
|
||||
{
|
||||
set_test_name("verify python()");
|
||||
python("hello",
|
||||
"import sys\n"
|
||||
"sys.exit(17)\n",
|
||||
17); // expect nonzero rc
|
||||
python_expect("hello",
|
||||
"import sys\n"
|
||||
"sys.exit(17)\n",
|
||||
17); // expect nonzero rc
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
|
|
@ -1899,7 +1905,7 @@ namespace tut
|
|||
static void writeLLSDArray(const FormatterFunction& serialize,
|
||||
std::ostream& out, const LLSD& array)
|
||||
{
|
||||
for (const LLSD& item : llsd::inArray(array))
|
||||
for (const LLSD& item: llsd::inArray(array))
|
||||
{
|
||||
// It's important to delimit the entries in this file somehow
|
||||
// because, although Python's llsd.parse() can accept a file
|
||||
|
|
@ -1914,7 +1920,14 @@ namespace tut
|
|||
auto buffstr{ buffer.str() };
|
||||
int bufflen{ static_cast<int>(buffstr.length()) };
|
||||
out.write(reinterpret_cast<const char*>(&bufflen), sizeof(bufflen));
|
||||
LL_DEBUGS() << "Wrote length: "
|
||||
<< hexdump(reinterpret_cast<const char*>(&bufflen),
|
||||
sizeof(bufflen))
|
||||
<< LL_ENDL;
|
||||
out.write(buffstr.c_str(), buffstr.length());
|
||||
LL_DEBUGS() << "Wrote data: "
|
||||
<< hexmix(buffstr.c_str(), buffstr.length())
|
||||
<< LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1943,10 +1956,10 @@ namespace tut
|
|||
" else:\n"
|
||||
" raise AssertionError('Too many data items')\n";
|
||||
|
||||
// Create an llsdXXXXXX file containing 'data' serialized to
|
||||
// notation.
|
||||
// Create an llsdXXXXXX file containing 'data' serialized per
|
||||
// FormatterFunction.
|
||||
NamedTempFile file("llsd",
|
||||
// NamedTempFile's boost::function constructor
|
||||
// NamedTempFile's function constructor
|
||||
// takes a callable. To this callable it passes the
|
||||
// std::ostream with which it's writing the
|
||||
// NamedTempFile.
|
||||
|
|
@ -1954,34 +1967,50 @@ namespace tut
|
|||
(std::ostream& out)
|
||||
{ writeLLSDArray(serialize, out, cdata); });
|
||||
|
||||
python("read C++ " + desc,
|
||||
placeholders::arg1 <<
|
||||
import_llsd <<
|
||||
"from functools import partial\n"
|
||||
"import io\n"
|
||||
"import struct\n"
|
||||
"lenformat = struct.Struct('i')\n"
|
||||
"def parse_each(inf):\n"
|
||||
" for rawlen in iter(partial(inf.read, lenformat.size), b''):\n"
|
||||
" len = lenformat.unpack(rawlen)[0]\n"
|
||||
// Since llsd.parse() has no max_bytes argument, instead of
|
||||
// passing the input stream directly to parse(), read the item
|
||||
// into a distinct bytes object and parse that.
|
||||
" data = inf.read(len)\n"
|
||||
" try:\n"
|
||||
" frombytes = llsd.parse(data)\n"
|
||||
" except llsd.LLSDParseError as err:\n"
|
||||
" print(f'*** {err}')\n"
|
||||
" print(f'Bad content:\\n{data!r}')\n"
|
||||
" raise\n"
|
||||
// Also try parsing from a distinct stream.
|
||||
" stream = io.BytesIO(data)\n"
|
||||
" fromstream = llsd.parse(stream)\n"
|
||||
" assert frombytes == fromstream\n"
|
||||
" yield frombytes\n"
|
||||
<< pydata <<
|
||||
// Don't forget raw-string syntax for Windows pathnames.
|
||||
"verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n");
|
||||
// 'debug' starts empty because it's intended as an output file
|
||||
NamedTempFile debug("debug", "");
|
||||
|
||||
try
|
||||
{
|
||||
python("read C++ " + desc,
|
||||
[&](std::ostream& out){ out <<
|
||||
import_llsd <<
|
||||
"from functools import partial\n"
|
||||
"import io\n"
|
||||
"import struct\n"
|
||||
"lenformat = struct.Struct('i')\n"
|
||||
"def parse_each(inf):\n"
|
||||
" for rawlen in iter(partial(inf.read, lenformat.size), b''):\n"
|
||||
" print('Read length:', ''.join(('%02x' % b) for b in rawlen),\n"
|
||||
" file=debug)\n"
|
||||
" len = lenformat.unpack(rawlen)[0]\n"
|
||||
// Since llsd.parse() has no max_bytes argument, instead of
|
||||
// passing the input stream directly to parse(), read the item
|
||||
// into a distinct bytes object and parse that.
|
||||
" data = inf.read(len)\n"
|
||||
" print('Read data: ', repr(data), file=debug)\n"
|
||||
" try:\n"
|
||||
" frombytes = llsd.parse(data)\n"
|
||||
" except llsd.LLSDParseError as err:\n"
|
||||
" print(f'*** {err}')\n"
|
||||
" print(f'Bad content:\\n{data!r}')\n"
|
||||
" raise\n"
|
||||
// Also try parsing from a distinct stream.
|
||||
" stream = io.BytesIO(data)\n"
|
||||
" fromstream = llsd.parse(stream)\n"
|
||||
" assert frombytes == fromstream\n"
|
||||
" yield frombytes\n"
|
||||
<< pydata <<
|
||||
// Don't forget raw-string syntax for Windows pathnames.
|
||||
"debug = open(r'" << debug.getName() << "', 'w')\n"
|
||||
"verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n";});
|
||||
}
|
||||
catch (const failure&)
|
||||
{
|
||||
LL_DEBUGS() << "Script debug output:" << LL_ENDL;
|
||||
debug.peep_log();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
|
|
@ -2068,7 +2097,7 @@ namespace tut
|
|||
NamedTempFile file("llsd", "");
|
||||
|
||||
python("Python " + pyformatter,
|
||||
placeholders::arg1 <<
|
||||
[&](std::ostream& out){ out <<
|
||||
import_llsd <<
|
||||
"import struct\n"
|
||||
"lenformat = struct.Struct('i')\n"
|
||||
|
|
@ -2086,7 +2115,7 @@ namespace tut
|
|||
" for item in DATA:\n"
|
||||
" serialized = llsd." << pyformatter << "(item)\n"
|
||||
" f.write(lenformat.pack(len(serialized)))\n"
|
||||
" f.write(serialized)\n");
|
||||
" f.write(serialized)\n";});
|
||||
|
||||
std::ifstream inf(file.getName().c_str());
|
||||
LLSD item;
|
||||
|
|
|
|||
|
|
@ -83,7 +83,11 @@ namespace tut
|
|||
// signal the work item that it can quit; consider LLOneShotCond.
|
||||
LLCond<Shared> data;
|
||||
auto start = WorkSchedule::TimePoint::clock::now();
|
||||
auto interval = 100ms;
|
||||
// 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
|
||||
// because the thread doesn't wake up as soon as we want.
|
||||
auto interval = 2s;
|
||||
queue.postEvery(
|
||||
interval,
|
||||
[&data, count = 0]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
#include "lltrace.h"
|
||||
|
||||
const S32 MIN_IMAGE_MIP = 2; // 4x4, only used for expand/contract power of 2
|
||||
const S32 MAX_IMAGE_MIP = 11; // 2048x2048
|
||||
const S32 MAX_IMAGE_MIP = 12; // 4096x4096
|
||||
|
||||
// *TODO : Use MAX_IMAGE_MIP as max discard level and modify j2c management so that the number
|
||||
// of levels is read from the header's file, not inferred from its size.
|
||||
|
|
@ -44,7 +44,7 @@ const S32 MAX_DISCARD_LEVEL = 5;
|
|||
// and declared right here. Some come from the JPEG2000 spec, some conventions specific to SL.
|
||||
const S32 MAX_DECOMPOSITION_LEVELS = 32; // Number of decomposition levels cannot exceed 32 according to jpeg2000 spec
|
||||
const S32 MIN_DECOMPOSITION_LEVELS = 5; // the SL viewer will *crash* trying to decode images with fewer than 5 decomposition levels (unless image is small that is)
|
||||
const S32 MAX_PRECINCT_SIZE = 2048; // No reason to be bigger than MAX_IMAGE_SIZE
|
||||
const S32 MAX_PRECINCT_SIZE = 4096; // No reason to be bigger than MAX_IMAGE_SIZE
|
||||
const S32 MIN_PRECINCT_SIZE = 4; // Can't be smaller than MIN_BLOCK_SIZE
|
||||
const S32 MAX_BLOCK_SIZE = 64; // Max total block size is 4096, hence 64x64 when using square blocks
|
||||
const S32 MIN_BLOCK_SIZE = 4; // Min block dim is 4 according to jpeg2000 spec
|
||||
|
|
@ -52,11 +52,11 @@ const S32 MIN_LAYER_SIZE = 2000; // Size of the first quality layer (after hea
|
|||
const S32 MAX_NB_LAYERS = 64; // Max number of layers we'll entertain in SL (practical limit)
|
||||
|
||||
const S32 MIN_IMAGE_SIZE = (1<<MIN_IMAGE_MIP); // 4, only used for expand/contract power of 2
|
||||
const S32 MAX_IMAGE_SIZE = (1<<MAX_IMAGE_MIP); // 2048
|
||||
const S32 MAX_IMAGE_SIZE = (1<<MAX_IMAGE_MIP); // 4096
|
||||
const S32 MIN_IMAGE_AREA = MIN_IMAGE_SIZE * MIN_IMAGE_SIZE;
|
||||
const S32 MAX_IMAGE_AREA = MAX_IMAGE_SIZE * MAX_IMAGE_SIZE;
|
||||
const S32 MAX_IMAGE_COMPONENTS = 8;
|
||||
const S32 MAX_IMAGE_DATA_SIZE = MAX_IMAGE_AREA * MAX_IMAGE_COMPONENTS; //2048 * 2048 * 8 = 16 MB
|
||||
const S32 MAX_IMAGE_DATA_SIZE = MAX_IMAGE_AREA * MAX_IMAGE_COMPONENTS; //4096 * 4096 * 8 = 128 MB
|
||||
|
||||
// Note! These CANNOT be changed without modifying simulator code
|
||||
// *TODO: change both to 1024 when SIM texture fetching is deprecated
|
||||
|
|
|
|||
|
|
@ -904,152 +904,173 @@ bool LLInventoryItem::fromLLSD(const LLSD& sd, bool is_new)
|
|||
mInventoryType = LLInventoryType::IT_NONE;
|
||||
mAssetUUID.setNull();
|
||||
}
|
||||
std::string w;
|
||||
|
||||
w = INV_ITEM_ID_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
mUUID = sd[w];
|
||||
}
|
||||
w = INV_PARENT_ID_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
mParentUUID = sd[w];
|
||||
}
|
||||
// TODO - figure out if this should be moved into the noclobber fields above
|
||||
mThumbnailUUID.setNull();
|
||||
w = INV_THUMBNAIL_LABEL;
|
||||
if (sd.has(w))
|
||||
|
||||
// iterate as map to avoid making unnecessary temp copies of everything
|
||||
LLSD::map_const_iterator i, end;
|
||||
end = sd.endMap();
|
||||
for (i = sd.beginMap(); i != end; ++i)
|
||||
{
|
||||
const LLSD &thumbnail_map = sd[w];
|
||||
w = INV_ASSET_ID_LABEL;
|
||||
if (thumbnail_map.has(w))
|
||||
if (i->first == INV_ITEM_ID_LABEL)
|
||||
{
|
||||
mThumbnailUUID = thumbnail_map[w];
|
||||
mUUID = i->second;
|
||||
continue;
|
||||
}
|
||||
/* Example:
|
||||
<key> asset_id </key>
|
||||
<uuid> acc0ec86 - 17f2 - 4b92 - ab41 - 6718b1f755f7 </uuid>
|
||||
<key> perms </key>
|
||||
<integer> 8 </integer>
|
||||
<key>service</key>
|
||||
<integer> 3 </integer>
|
||||
<key>version</key>
|
||||
<integer> 1 </key>
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
w = INV_THUMBNAIL_ID_LABEL;
|
||||
if (sd.has(w))
|
||||
|
||||
if (i->first == INV_PARENT_ID_LABEL)
|
||||
{
|
||||
mThumbnailUUID = sd[w].asUUID();
|
||||
mParentUUID = i->second;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_THUMBNAIL_LABEL)
|
||||
{
|
||||
const LLSD &thumbnail_map = i->second;
|
||||
const std::string w = INV_ASSET_ID_LABEL;
|
||||
if (thumbnail_map.has(w))
|
||||
{
|
||||
mThumbnailUUID = thumbnail_map[w];
|
||||
}
|
||||
/* Example:
|
||||
<key> asset_id </key>
|
||||
<uuid> acc0ec86 - 17f2 - 4b92 - ab41 - 6718b1f755f7 </uuid>
|
||||
<key> perms </key>
|
||||
<integer> 8 </integer>
|
||||
<key>service</key>
|
||||
<integer> 3 </integer>
|
||||
<key>version</key>
|
||||
<integer> 1 </key>
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_THUMBNAIL_ID_LABEL)
|
||||
{
|
||||
mThumbnailUUID = i->second.asUUID();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_PERMISSIONS_LABEL)
|
||||
{
|
||||
mPermissions = ll_permissions_from_sd(i->second);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_SALE_INFO_LABEL)
|
||||
{
|
||||
// Sale info used to contain next owner perm. It is now in
|
||||
// the permissions. Thus, we read that out, and fix legacy
|
||||
// objects. It's possible this op would fail, but it
|
||||
// should pick up the vast majority of the tasks.
|
||||
BOOL has_perm_mask = FALSE;
|
||||
U32 perm_mask = 0;
|
||||
if (!mSaleInfo.fromLLSD(i->second, has_perm_mask, perm_mask))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (has_perm_mask)
|
||||
{
|
||||
if (perm_mask == PERM_NONE)
|
||||
{
|
||||
perm_mask = mPermissions.getMaskOwner();
|
||||
}
|
||||
// fair use fix.
|
||||
if (!(perm_mask & PERM_COPY))
|
||||
{
|
||||
perm_mask |= PERM_TRANSFER;
|
||||
}
|
||||
mPermissions.setMaskNext(perm_mask);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_SHADOW_ID_LABEL)
|
||||
{
|
||||
mAssetUUID = i->second;
|
||||
LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES);
|
||||
cipher.decrypt(mAssetUUID.mData, UUID_BYTES);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_ASSET_ID_LABEL)
|
||||
{
|
||||
mAssetUUID = i->second;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_LINKED_ID_LABEL)
|
||||
{
|
||||
mAssetUUID = i->second;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_ASSET_TYPE_LABEL)
|
||||
{
|
||||
LLSD const &label = i->second;
|
||||
if (label.isString())
|
||||
{
|
||||
mType = LLAssetType::lookup(label.asString().c_str());
|
||||
}
|
||||
else if (label.isInteger())
|
||||
{
|
||||
S8 type = (U8) label.asInteger();
|
||||
mType = static_cast<LLAssetType::EType>(type);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_INVENTORY_TYPE_LABEL)
|
||||
{
|
||||
LLSD const &label = i->second;
|
||||
if (label.isString())
|
||||
{
|
||||
mInventoryType = LLInventoryType::lookup(label.asString().c_str());
|
||||
}
|
||||
else if (label.isInteger())
|
||||
{
|
||||
S8 type = (U8) label.asInteger();
|
||||
mInventoryType = static_cast<LLInventoryType::EType>(type);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_FLAGS_LABEL)
|
||||
{
|
||||
LLSD const &label = i->second;
|
||||
if (label.isBinary())
|
||||
{
|
||||
mFlags = ll_U32_from_sd(label);
|
||||
}
|
||||
else if (label.isInteger())
|
||||
{
|
||||
mFlags = label.asInteger();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_NAME_LABEL)
|
||||
{
|
||||
mName = i->second.asString();
|
||||
LLStringUtil::replaceNonstandardASCII(mName, ' ');
|
||||
LLStringUtil::replaceChar(mName, '|', ' ');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_DESC_LABEL)
|
||||
{
|
||||
mDescription = i->second.asString();
|
||||
LLStringUtil::replaceNonstandardASCII(mDescription, ' ');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i->first == INV_CREATION_DATE_LABEL)
|
||||
{
|
||||
mCreationDate = i->second.asInteger();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
w = INV_PERMISSIONS_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
mPermissions = ll_permissions_from_sd(sd[w]);
|
||||
}
|
||||
w = INV_SALE_INFO_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
// Sale info used to contain next owner perm. It is now in
|
||||
// the permissions. Thus, we read that out, and fix legacy
|
||||
// objects. It's possible this op would fail, but it
|
||||
// should pick up the vast majority of the tasks.
|
||||
BOOL has_perm_mask = FALSE;
|
||||
U32 perm_mask = 0;
|
||||
if (!mSaleInfo.fromLLSD(sd[w], has_perm_mask, perm_mask))
|
||||
{
|
||||
goto fail;
|
||||
}
|
||||
if (has_perm_mask)
|
||||
{
|
||||
if(perm_mask == PERM_NONE)
|
||||
{
|
||||
perm_mask = mPermissions.getMaskOwner();
|
||||
}
|
||||
// fair use fix.
|
||||
if(!(perm_mask & PERM_COPY))
|
||||
{
|
||||
perm_mask |= PERM_TRANSFER;
|
||||
}
|
||||
mPermissions.setMaskNext(perm_mask);
|
||||
}
|
||||
}
|
||||
w = INV_SHADOW_ID_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
mAssetUUID = sd[w];
|
||||
LLXORCipher cipher(MAGIC_ID.mData, UUID_BYTES);
|
||||
cipher.decrypt(mAssetUUID.mData, UUID_BYTES);
|
||||
}
|
||||
w = INV_ASSET_ID_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
mAssetUUID = sd[w];
|
||||
}
|
||||
w = INV_LINKED_ID_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
mAssetUUID = sd[w];
|
||||
}
|
||||
w = INV_ASSET_TYPE_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
if (sd[w].isString())
|
||||
{
|
||||
mType = LLAssetType::lookup(sd[w].asString().c_str());
|
||||
}
|
||||
else if (sd[w].isInteger())
|
||||
{
|
||||
S8 type = (U8)sd[w].asInteger();
|
||||
mType = static_cast<LLAssetType::EType>(type);
|
||||
}
|
||||
}
|
||||
w = INV_INVENTORY_TYPE_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
if (sd[w].isString())
|
||||
{
|
||||
mInventoryType = LLInventoryType::lookup(sd[w].asString().c_str());
|
||||
}
|
||||
else if (sd[w].isInteger())
|
||||
{
|
||||
S8 type = (U8)sd[w].asInteger();
|
||||
mInventoryType = static_cast<LLInventoryType::EType>(type);
|
||||
}
|
||||
}
|
||||
w = INV_FLAGS_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
if (sd[w].isBinary())
|
||||
{
|
||||
mFlags = ll_U32_from_sd(sd[w]);
|
||||
}
|
||||
else if(sd[w].isInteger())
|
||||
{
|
||||
mFlags = sd[w].asInteger();
|
||||
}
|
||||
}
|
||||
w = INV_NAME_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
mName = sd[w].asString();
|
||||
LLStringUtil::replaceNonstandardASCII(mName, ' ');
|
||||
LLStringUtil::replaceChar(mName, '|', ' ');
|
||||
}
|
||||
w = INV_DESC_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
mDescription = sd[w].asString();
|
||||
LLStringUtil::replaceNonstandardASCII(mDescription, ' ');
|
||||
}
|
||||
w = INV_CREATION_DATE_LABEL;
|
||||
if (sd.has(w))
|
||||
{
|
||||
mCreationDate = sd[w].asInteger();
|
||||
}
|
||||
|
||||
// Need to convert 1.0 simstate files to a useful inventory type
|
||||
// and potentially deal with bad inventory tyes eg, a landmark
|
||||
|
|
@ -1064,9 +1085,6 @@ bool LLInventoryItem::fromLLSD(const LLSD& sd, bool is_new)
|
|||
mPermissions.initMasks(mInventoryType);
|
||||
|
||||
return true;
|
||||
fail:
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
///----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include "llsdserialize.h"
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
//=========================================================================
|
||||
namespace
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include "lltrace.h"
|
||||
#include "llfasttimer.h"
|
||||
#include "v3colorutil.h"
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
|
||||
//=========================================================================
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "llfasttimer.h"
|
||||
#include "v3colorutil.h"
|
||||
#include "indra_constants.h"
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
const std::string LLSettingsWater::SETTING_BLUR_MULTIPLIER("blur_multiplier");
|
||||
const std::string LLSettingsWater::SETTING_FOG_COLOR("water_fog_color");
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import os
|
|||
import sys
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
from llsd.fastest_elementtree import parse as xml_parse
|
||||
import llsd
|
||||
from testrunner import freeport, run, debug, VERBOSE
|
||||
import time
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ void LLCocoaPlugin::processEvents()
|
|||
{
|
||||
// Some plugins (webkit at least) will want an event loop. This qualifies.
|
||||
NSEvent * event;
|
||||
event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
|
||||
event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
|
||||
[NSApp sendEvent: event];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2584,7 +2584,8 @@ bool LLDAELoader::loadModelsFromDomMesh(domMesh* mesh, std::vector<LLModel*>& mo
|
|||
next->mLabel = model_name + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod];
|
||||
next->getVolumeFaces() = remainder;
|
||||
next->mNormalizedScale = ret->mNormalizedScale;
|
||||
|
||||
next->mNormalizedTranslation = ret->mNormalizedTranslation;
|
||||
|
||||
if ( ret->mMaterialList.size() > LL_SCULPT_MESH_MAX_FACES)
|
||||
{
|
||||
next->mMaterialList.assign(ret->mMaterialList.begin() + LL_SCULPT_MESH_MAX_FACES, ret->mMaterialList.end());
|
||||
|
|
|
|||
|
|
@ -89,6 +89,10 @@ LLGLTFMaterial& LLGLTFMaterial::operator=(const LLGLTFMaterial& rhs)
|
|||
mOverrideDoubleSided = rhs.mOverrideDoubleSided;
|
||||
mOverrideAlphaMode = rhs.mOverrideAlphaMode;
|
||||
|
||||
mTrackingIdToLocalTexture = rhs.mTrackingIdToLocalTexture;
|
||||
|
||||
updateTextureTracking();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
@ -601,6 +605,10 @@ void LLGLTFMaterial::applyOverride(const LLGLTFMaterial& override_mat)
|
|||
mTextureTransform[i].mRotation = override_mat.mTextureTransform[i].mRotation;
|
||||
}
|
||||
}
|
||||
|
||||
mTrackingIdToLocalTexture.insert(override_mat.mTrackingIdToLocalTexture.begin(), override_mat.mTrackingIdToLocalTexture.begin());
|
||||
|
||||
updateTextureTracking();
|
||||
}
|
||||
|
||||
void LLGLTFMaterial::getOverrideLLSD(const LLGLTFMaterial& override_mat, LLSD& data)
|
||||
|
|
@ -691,36 +699,62 @@ void LLGLTFMaterial::applyOverrideLLSD(const LLSD& data)
|
|||
if (bc.isDefined())
|
||||
{
|
||||
mBaseColor.setValue(bc);
|
||||
if (mBaseColor == getDefaultBaseColor())
|
||||
{
|
||||
// HACK -- nudge by epsilon if we receive a default value (indicates override to default)
|
||||
mBaseColor.mV[3] -= FLT_EPSILON;
|
||||
}
|
||||
}
|
||||
|
||||
const LLSD& ec = data["ec"];
|
||||
if (ec.isDefined())
|
||||
{
|
||||
mEmissiveColor.setValue(ec);
|
||||
if (mEmissiveColor == getDefaultEmissiveColor())
|
||||
{
|
||||
// HACK -- nudge by epsilon if we receive a default value (indicates override to default)
|
||||
mEmissiveColor.mV[0] += FLT_EPSILON;
|
||||
}
|
||||
}
|
||||
|
||||
const LLSD& mf = data["mf"];
|
||||
if (mf.isReal())
|
||||
{
|
||||
mMetallicFactor = mf.asReal();
|
||||
if (mMetallicFactor == getDefaultMetallicFactor())
|
||||
{
|
||||
// HACK -- nudge by epsilon if we receive a default value (indicates override to default)
|
||||
mMetallicFactor -= FLT_EPSILON;
|
||||
}
|
||||
}
|
||||
|
||||
const LLSD& rf = data["rf"];
|
||||
if (rf.isReal())
|
||||
{
|
||||
mRoughnessFactor = rf.asReal();
|
||||
if (mRoughnessFactor == getDefaultRoughnessFactor())
|
||||
{
|
||||
// HACK -- nudge by epsilon if we receive a default value (indicates override to default)
|
||||
mRoughnessFactor -= FLT_EPSILON;
|
||||
}
|
||||
}
|
||||
|
||||
const LLSD& am = data["am"];
|
||||
if (am.isInteger())
|
||||
{
|
||||
mAlphaMode = (AlphaMode) am.asInteger();
|
||||
mOverrideAlphaMode = true;
|
||||
}
|
||||
|
||||
const LLSD& ac = data["ac"];
|
||||
if (ac.isReal())
|
||||
{
|
||||
mAlphaCutoff = ac.asReal();
|
||||
if (mAlphaCutoff == getDefaultAlphaCutoff())
|
||||
{
|
||||
// HACK -- nudge by epsilon if we receive a default value (indicates override to default)
|
||||
mAlphaCutoff -= FLT_EPSILON;
|
||||
}
|
||||
}
|
||||
|
||||
const LLSD& ds = data["ds"];
|
||||
|
|
@ -765,3 +799,47 @@ LLUUID LLGLTFMaterial::getHash() const
|
|||
return hash;
|
||||
}
|
||||
|
||||
void LLGLTFMaterial::addLocalTextureTracking(const LLUUID& tracking_id, const LLUUID& tex_id)
|
||||
{
|
||||
mTrackingIdToLocalTexture[tracking_id] = tex_id;
|
||||
}
|
||||
|
||||
void LLGLTFMaterial::removeLocalTextureTracking(const LLUUID& tracking_id)
|
||||
{
|
||||
mTrackingIdToLocalTexture.erase(tracking_id);
|
||||
}
|
||||
|
||||
bool LLGLTFMaterial::replaceLocalTexture(const LLUUID& tracking_id, const LLUUID& old_id, const LLUUID& new_id)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i)
|
||||
{
|
||||
if (mTextureId[i] == old_id)
|
||||
{
|
||||
mTextureId[i] = new_id;
|
||||
res = true;
|
||||
}
|
||||
else if (mTextureId[i] == new_id)
|
||||
{
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (res)
|
||||
{
|
||||
mTrackingIdToLocalTexture[tracking_id] = new_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
mTrackingIdToLocalTexture.erase(tracking_id);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void LLGLTFMaterial::updateTextureTracking()
|
||||
{
|
||||
// setTEGLTFMaterialOverride is responsible for tracking
|
||||
// for material overrides editor will set it
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include "lluuid.h"
|
||||
#include "hbxxh.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
|
|
@ -195,7 +196,7 @@ public:
|
|||
// write to given tinygltf::Model
|
||||
void writeToModel(tinygltf::Model& model, S32 mat_index) const;
|
||||
|
||||
void applyOverride(const LLGLTFMaterial& override_mat);
|
||||
virtual void applyOverride(const LLGLTFMaterial& override_mat);
|
||||
|
||||
// apply the given LLSD override data
|
||||
void applyOverrideLLSD(const LLSD& data);
|
||||
|
|
@ -221,6 +222,17 @@ public:
|
|||
virtual void addTextureEntry(LLTextureEntry* te) {};
|
||||
virtual void removeTextureEntry(LLTextureEntry* te) {};
|
||||
|
||||
// For local textures so that editor will know to track changes
|
||||
void addLocalTextureTracking(const LLUUID& tracking_id, const LLUUID &tex_id);
|
||||
void removeLocalTextureTracking(const LLUUID& tracking_id);
|
||||
bool hasLocalTextures() { return !mTrackingIdToLocalTexture.empty(); }
|
||||
virtual bool replaceLocalTexture(const LLUUID& tracking_id, const LLUUID &old_id, const LLUUID& new_id);
|
||||
virtual void updateTextureTracking();
|
||||
|
||||
// These fields are local to viewer and are a part of local bitmap support
|
||||
typedef std::map<LLUUID, LLUUID> local_tex_map_t;
|
||||
local_tex_map_t mTrackingIdToLocalTexture;
|
||||
|
||||
protected:
|
||||
static LLVector2 vec2FromJson(const std::map<std::string, tinygltf::Value>& object, const char* key, const LLVector2& default_value);
|
||||
static F32 floatFromJson(const std::map<std::string, tinygltf::Value>& object, const char* key, const F32 default_value);
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ const int MODEL_NAMES_LENGTH = sizeof(model_names) / sizeof(std::string);
|
|||
|
||||
LLModel::LLModel(LLVolumeParams& params, F32 detail)
|
||||
: LLVolume(params, detail),
|
||||
mNormalizedScale(1,1,1),
|
||||
mNormalizedScale(1,1,1),
|
||||
mNormalizedTranslation(0, 0, 0),
|
||||
mPelvisOffset( 0.0f ),
|
||||
mStatus(NO_ERRORS),
|
||||
mSubmodelID(0)
|
||||
|
|
|
|||
|
|
@ -2376,42 +2376,6 @@ void LLRenderMaterialParams::copy(const LLNetworkData& data)
|
|||
mEntries = param.mEntries;
|
||||
}
|
||||
|
||||
LLSD LLRenderMaterialParams::asLLSD() const
|
||||
{
|
||||
LLSD ret;
|
||||
|
||||
for (int i = 0; i < mEntries.size(); ++i)
|
||||
{
|
||||
ret[i]["te_idx"] = mEntries[i].te_idx;
|
||||
ret[i]["id"] = mEntries[i].id;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool LLRenderMaterialParams::fromLLSD(LLSD& sd)
|
||||
{
|
||||
if (sd.isArray())
|
||||
{
|
||||
mEntries.resize(sd.size());
|
||||
for (int i = 0; i < sd.size(); ++i)
|
||||
{
|
||||
if (sd[i].has("te_idx") && sd.has("id"))
|
||||
{
|
||||
mEntries[i].te_idx = sd[i]["te_idx"].asInteger();
|
||||
mEntries[i].id = sd[i]["id"].asUUID();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void LLRenderMaterialParams::setMaterial(U8 te, const LLUUID& id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -382,10 +382,7 @@ public:
|
|||
BOOL unpack(LLDataPacker& dp) override;
|
||||
bool operator==(const LLNetworkData& data) const override;
|
||||
void copy(const LLNetworkData& data) override;
|
||||
LLSD asLLSD() const;
|
||||
operator LLSD() const { return asLLSD(); }
|
||||
bool fromLLSD(LLSD& sd);
|
||||
|
||||
|
||||
void setMaterial(U8 te_idx, const LLUUID& id);
|
||||
const LLUUID& getMaterial(U8 te_idx) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ LLCubeMapArray::~LLCubeMapArray()
|
|||
void LLCubeMapArray::allocate(U32 resolution, U32 components, U32 count, BOOL use_mips)
|
||||
{
|
||||
U32 texname = 0;
|
||||
mWidth = resolution;
|
||||
mCount = count;
|
||||
|
||||
LLImageGL::generateTextures(1, &texname);
|
||||
|
||||
|
|
|
|||
|
|
@ -60,9 +60,17 @@ public:
|
|||
|
||||
void destroyGL();
|
||||
|
||||
// get width of cubemaps in array (they're cubes, so this is also the height)
|
||||
U32 getWidth() const { return mWidth; }
|
||||
|
||||
// get number of cubemaps in the array
|
||||
U32 getCount() const { return mCount; }
|
||||
|
||||
protected:
|
||||
friend class LLTexUnit;
|
||||
~LLCubeMapArray();
|
||||
LLPointer<LLImageGL> mImage;
|
||||
U32 mWidth = 0;
|
||||
U32 mCount = 0;
|
||||
S32 mTextureStage;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ public:
|
|||
bool hasLighting = false; // implies no transport (it's possible to have neither though)
|
||||
bool isAlphaLighting = false; // indicates lighting shaders need not be linked in (lighting performed directly in alpha shader to match deferred lighting functions)
|
||||
bool isSpecular = false;
|
||||
bool hasWaterFog = false; // implies no gamma
|
||||
bool hasTransport = false; // implies no lighting (it's possible to have neither though)
|
||||
bool hasSkinning = false;
|
||||
bool hasObjectSkinning = false;
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ void LLGLTexture::setBoostLevel(S32 level)
|
|||
{
|
||||
mBoostLevel = level ;
|
||||
if(mBoostLevel != LLGLTexture::BOOST_NONE
|
||||
&& mBoostLevel != LLGLTexture::BOOST_ICON)
|
||||
&& mBoostLevel != LLGLTexture::BOOST_ICON
|
||||
&& mBoostLevel != LLGLTexture::BOOST_THUMBNAIL)
|
||||
{
|
||||
setNoDelete() ;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class LLGLTexture : public LLTexture
|
|||
public:
|
||||
enum
|
||||
{
|
||||
MAX_IMAGE_SIZE_DEFAULT = 1024,
|
||||
MAX_IMAGE_SIZE_DEFAULT = 2048,
|
||||
INVALID_DISCARD_LEVEL = 0x7fff
|
||||
};
|
||||
|
||||
|
|
@ -62,6 +62,7 @@ public:
|
|||
BOOST_SUPER_HIGH , //textures higher than this need to be downloaded at the required resolution without delay.
|
||||
BOOST_HUD ,
|
||||
BOOST_ICON ,
|
||||
BOOST_THUMBNAIL ,
|
||||
BOOST_UI ,
|
||||
BOOST_PREVIEW ,
|
||||
BOOST_MAP ,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@
|
|||
#include "llmatrix4a.h"
|
||||
#include "glh/glh_linear.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
class LLVertexBuffer;
|
||||
class LLCubeMap;
|
||||
class LLImageGL;
|
||||
|
|
|
|||
|
|
@ -40,20 +40,20 @@ LLRenderNavPrim gRenderNav;
|
|||
//=============================================================================
|
||||
void LLRenderNavPrim::renderLLTri( const LLVector3& a, const LLVector3& b, const LLVector3& c, const LLColor4U& color ) const
|
||||
{
|
||||
LLColor4 cV(color);
|
||||
gGL.color4fv( cV.mV );
|
||||
gGL.begin(LLRender::TRIANGLES);
|
||||
gGL.color4ubv(color.mV);
|
||||
|
||||
gGL.begin(LLRender::TRIANGLES);
|
||||
{
|
||||
gGL.vertex3fv( a.mV );
|
||||
gGL.vertex3fv( b.mV );
|
||||
gGL.vertex3fv( c.mV );
|
||||
}
|
||||
gGL.end();
|
||||
gGL.end();
|
||||
}
|
||||
//=============================================================================
|
||||
void LLRenderNavPrim::renderNavMeshVB( U32 mode, LLVertexBuffer* pVBO, int vertCnt )
|
||||
{
|
||||
pVBO->setBuffer();
|
||||
pVBO->drawArrays( mode, 0, vertCnt );
|
||||
pVBO->drawArrays( mode, 0, vertCnt );
|
||||
}
|
||||
//=============================================================================
|
||||
|
|
|
|||
|
|
@ -81,14 +81,7 @@ BOOL LLShaderMgr::attachShaderFeatures(LLGLSLShader * shader)
|
|||
// NOTE order of shader object attaching is VERY IMPORTANT!!!
|
||||
if (features->calculatesAtmospherics)
|
||||
{
|
||||
if (features->hasWaterFog)
|
||||
{
|
||||
if (!shader->attachVertexObject("windlight/atmosphericsVarsWaterV.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else if (!shader->attachVertexObject("windlight/atmosphericsVarsV.glsl"))
|
||||
if (!shader->attachVertexObject("windlight/atmosphericsVarsV.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -201,14 +194,7 @@ BOOL LLShaderMgr::attachShaderFeatures(LLGLSLShader * shader)
|
|||
|
||||
if(features->calculatesAtmospherics || features->hasGamma || features->isDeferred)
|
||||
{
|
||||
if (features->hasWaterFog)
|
||||
{
|
||||
if (!shader->attachFragmentObject("windlight/atmosphericsVarsWaterF.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else if (!shader->attachFragmentObject("windlight/atmosphericsVarsF.glsl"))
|
||||
if (!shader->attachFragmentObject("windlight/atmosphericsVarsF.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
|
@ -300,7 +286,7 @@ BOOL LLShaderMgr::attachShaderFeatures(LLGLSLShader * shader)
|
|||
}
|
||||
|
||||
// NOTE order of shader object attaching is VERY IMPORTANT!!!
|
||||
if (features->hasWaterFog)
|
||||
if (features->hasAtmospherics)
|
||||
{
|
||||
if (!shader->attachFragmentObject("environment/waterFogF.glsl"))
|
||||
{
|
||||
|
|
@ -310,82 +296,40 @@ BOOL LLShaderMgr::attachShaderFeatures(LLGLSLShader * shader)
|
|||
|
||||
if (features->hasLighting)
|
||||
{
|
||||
if (features->hasWaterFog)
|
||||
if (features->disableTextureIndex)
|
||||
{
|
||||
if (features->disableTextureIndex)
|
||||
if (features->hasAlphaMask)
|
||||
{
|
||||
if (features->hasAlphaMask)
|
||||
if (!shader->attachFragmentObject("lighting/lightAlphaMaskNonIndexedF.glsl"))
|
||||
{
|
||||
if (!shader->attachFragmentObject("lighting/lightWaterAlphaMaskNonIndexedF.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!shader->attachFragmentObject("lighting/lightWaterNonIndexedF.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
if (features->hasAlphaMask)
|
||||
if (!shader->attachFragmentObject("lighting/lightNonIndexedF.glsl"))
|
||||
{
|
||||
if (!shader->attachFragmentObject("lighting/lightWaterAlphaMaskF.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!shader->attachFragmentObject("lighting/lightWaterF.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
shader->mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
else
|
||||
{
|
||||
if (features->disableTextureIndex)
|
||||
if (features->hasAlphaMask)
|
||||
{
|
||||
if (features->hasAlphaMask)
|
||||
if (!shader->attachFragmentObject("lighting/lightAlphaMaskF.glsl"))
|
||||
{
|
||||
if (!shader->attachFragmentObject("lighting/lightAlphaMaskNonIndexedF.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!shader->attachFragmentObject("lighting/lightNonIndexedF.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
if (features->hasAlphaMask)
|
||||
if (!shader->attachFragmentObject("lighting/lightF.glsl"))
|
||||
{
|
||||
if (!shader->attachFragmentObject("lighting/lightAlphaMaskF.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!shader->attachFragmentObject("lighting/lightF.glsl"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
shader->mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels-1, 1);
|
||||
}
|
||||
shader->mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -164,7 +164,8 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
|
|||
mHighlightColor(p.highlight_color()),
|
||||
mPreeditBgColor(p.preedit_bg_color()),
|
||||
mGLFont(p.font),
|
||||
mContextMenuHandle()
|
||||
mContextMenuHandle(),
|
||||
mShowContextMenu(true)
|
||||
{
|
||||
llassert( mMaxLengthBytes > 0 );
|
||||
|
||||
|
|
@ -825,7 +826,7 @@ BOOL LLLineEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
|
|||
BOOL LLLineEditor::handleRightMouseDown(S32 x, S32 y, MASK mask)
|
||||
{
|
||||
setFocus(TRUE);
|
||||
if (!LLUICtrl::handleRightMouseDown(x, y, mask))
|
||||
if (!LLUICtrl::handleRightMouseDown(x, y, mask) && getShowContextMenu())
|
||||
{
|
||||
showContextMenu(x, y);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -286,7 +286,10 @@ public:
|
|||
void setBgImage(LLPointer<LLUIImage> image) { mBgImage = image; }
|
||||
void setBgImageFocused(LLPointer<LLUIImage> image) { mBgImageFocused = image; }
|
||||
|
||||
private:
|
||||
void setShowContextMenu(bool show) { mShowContextMenu = show; }
|
||||
bool getShowContextMenu() const { return mShowContextMenu; }
|
||||
|
||||
private:
|
||||
// private helper methods
|
||||
|
||||
void pasteHelper(bool is_primary);
|
||||
|
|
@ -405,6 +408,8 @@ protected:
|
|||
|
||||
LLHandle<LLContextMenu> mContextMenuHandle;
|
||||
|
||||
bool mShowContextMenu;
|
||||
|
||||
private:
|
||||
// Instances that by default point to the statics but can be overidden in XML.
|
||||
LLPointer<LLUIImage> mBgImage;
|
||||
|
|
|
|||
|
|
@ -2149,14 +2149,19 @@ void LLTabContainer::commitHoveredButton(S32 x, S32 y)
|
|||
{
|
||||
if (!getTabsHidden() && hasMouseCapture())
|
||||
{
|
||||
for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
|
||||
for (tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter)
|
||||
{
|
||||
LLTabTuple* tuple = *iter;
|
||||
S32 local_x = x - tuple->mButton->getRect().mLeft;
|
||||
S32 local_y = y - tuple->mButton->getRect().mBottom;
|
||||
if (tuple->mButton->pointInView(local_x, local_y) && tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible())
|
||||
LLButton* button = (*iter)->mButton;
|
||||
LLPanel* panel = (*iter)->mTabPanel;
|
||||
if (button->getEnabled() && button->getVisible() && !panel->getVisible())
|
||||
{
|
||||
tuple->mButton->onCommit();
|
||||
S32 local_x = x - button->getRect().mLeft;
|
||||
S32 local_y = y - button->getRect().mBottom;
|
||||
if (button->pointInView(local_x, local_y))
|
||||
{
|
||||
button->onCommit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1550,7 +1550,13 @@ S32 LLTextBase::getLeftOffset(S32 width)
|
|||
case LLFontGL::HCENTER:
|
||||
return mHPad + llmax(0, (mVisibleTextRect.getWidth() - width - mHPad) / 2);
|
||||
case LLFontGL::RIGHT:
|
||||
return mVisibleTextRect.getWidth() - width;
|
||||
{
|
||||
// Font's rendering rounds string size, if value gets rounded
|
||||
// down last symbol might not have enough space to render,
|
||||
// compensate by adding an extra pixel as padding
|
||||
const S32 right_padding = 1;
|
||||
return llmax(mHPad, mVisibleTextRect.getWidth() - width - right_padding);
|
||||
}
|
||||
default:
|
||||
return mHPad;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@
|
|||
|
||||
#include "llavatarnamecache.h"
|
||||
#include "llcachename.h"
|
||||
#include "llkeyboard.h"
|
||||
#include "llregex.h"
|
||||
#include "llscrolllistctrl.h" // for LLUrlEntryKeybinding file parsing
|
||||
#include "lltrans.h"
|
||||
#include "lluicolortable.h"
|
||||
#include "message.h"
|
||||
|
|
@ -1609,3 +1611,122 @@ std::string LLUrlEntryIPv6::getUrl(const std::string &string) const
|
|||
{
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// LLUrlEntryKeybinding Displays currently assigned key
|
||||
//
|
||||
LLUrlEntryKeybinding::LLUrlEntryKeybinding()
|
||||
: LLUrlEntryBase()
|
||||
, pHandler(NULL)
|
||||
{
|
||||
mPattern = boost::regex(APP_HEADER_REGEX "/keybinding/\\w+(\\?mode=\\w+)?$",
|
||||
boost::regex::perl | boost::regex::icase);
|
||||
mMenuName = "menu_url_experience.xml";
|
||||
|
||||
initLocalization();
|
||||
}
|
||||
|
||||
std::string LLUrlEntryKeybinding::getLabel(const std::string& url, const LLUrlLabelCallback& cb)
|
||||
{
|
||||
std::string control = getControlName(url);
|
||||
|
||||
std::map<std::string, LLLocalizationData>::iterator iter = mLocalizations.find(control);
|
||||
|
||||
std::string keybind;
|
||||
if (pHandler)
|
||||
{
|
||||
keybind = pHandler->getKeyBindingAsString(getMode(url), control);
|
||||
}
|
||||
|
||||
if (iter != mLocalizations.end())
|
||||
{
|
||||
return iter->second.mLocalization + ": " + keybind;
|
||||
}
|
||||
|
||||
return control + ": " + keybind;
|
||||
}
|
||||
|
||||
std::string LLUrlEntryKeybinding::getTooltip(const std::string& url) const
|
||||
{
|
||||
std::string control = getControlName(url);
|
||||
|
||||
std::map<std::string, LLLocalizationData>::const_iterator iter = mLocalizations.find(control);
|
||||
if (iter != mLocalizations.end())
|
||||
{
|
||||
return iter->second.mTooltip;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
std::string LLUrlEntryKeybinding::getControlName(const std::string& url) const
|
||||
{
|
||||
std::string search = "/keybinding/";
|
||||
size_t pos_start = url.find(search);
|
||||
if (pos_start == std::string::npos)
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
pos_start += search.size();
|
||||
|
||||
size_t pos_end = url.find("?mode=");
|
||||
if (pos_end == std::string::npos)
|
||||
{
|
||||
pos_end = url.size();
|
||||
}
|
||||
return url.substr(pos_start, pos_end - pos_start);
|
||||
}
|
||||
|
||||
std::string LLUrlEntryKeybinding::getMode(const std::string& url) const
|
||||
{
|
||||
std::string search = "?mode=";
|
||||
size_t pos_start = url.find(search);
|
||||
if (pos_start == std::string::npos)
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
pos_start += search.size();
|
||||
return url.substr(pos_start, url.size() - pos_start);
|
||||
}
|
||||
|
||||
void LLUrlEntryKeybinding::initLocalization()
|
||||
{
|
||||
initLocalizationFromFile("control_table_contents_movement.xml");
|
||||
initLocalizationFromFile("control_table_contents_camera.xml");
|
||||
initLocalizationFromFile("control_table_contents_editing.xml");
|
||||
initLocalizationFromFile("control_table_contents_media.xml");
|
||||
}
|
||||
|
||||
void LLUrlEntryKeybinding::initLocalizationFromFile(const std::string& filename)
|
||||
{
|
||||
LLXMLNodePtr xmlNode;
|
||||
LLScrollListCtrl::Contents contents;
|
||||
if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode))
|
||||
{
|
||||
LL_WARNS() << "Failed to load " << filename << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
LLXUIParser parser;
|
||||
parser.readXUI(xmlNode, contents, filename);
|
||||
|
||||
if (!contents.validateBlock())
|
||||
{
|
||||
LL_WARNS() << "Failed to validate " << filename << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
|
||||
for (LLInitParam::ParamIterator<LLScrollListItem::Params>::const_iterator row_it = contents.rows.begin();
|
||||
row_it != contents.rows.end();
|
||||
++row_it)
|
||||
{
|
||||
std::string control = row_it->value.getValue().asString();
|
||||
if (!control.empty() && control != "menu_separator")
|
||||
{
|
||||
mLocalizations[control] =
|
||||
LLLocalizationData(
|
||||
row_it->columns.begin()->value.getValue().asString(),
|
||||
row_it->columns.begin()->tool_tip.getValue()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -550,4 +550,37 @@ public:
|
|||
std::string mHostPath;
|
||||
};
|
||||
|
||||
class LLKeyBindingToStringHandler;
|
||||
|
||||
///
|
||||
/// LLUrlEntryKeybinding A way to access keybindings and show currently used one in text.
|
||||
/// secondlife:///app/keybinding/control_name
|
||||
class LLUrlEntryKeybinding: public LLUrlEntryBase
|
||||
{
|
||||
public:
|
||||
LLUrlEntryKeybinding();
|
||||
/*virtual*/ std::string getLabel(const std::string& url, const LLUrlLabelCallback& cb);
|
||||
/*virtual*/ std::string getTooltip(const std::string& url) const;
|
||||
void setHandler(LLKeyBindingToStringHandler* handler) {pHandler = handler;}
|
||||
private:
|
||||
std::string getControlName(const std::string& url) const;
|
||||
std::string getMode(const std::string& url) const;
|
||||
void initLocalization();
|
||||
void initLocalizationFromFile(const std::string& filename);
|
||||
|
||||
struct LLLocalizationData
|
||||
{
|
||||
LLLocalizationData() {}
|
||||
LLLocalizationData(const std::string& localization, const std::string& tooltip)
|
||||
: mLocalization(localization)
|
||||
, mTooltip(tooltip)
|
||||
{}
|
||||
std::string mLocalization;
|
||||
std::string mTooltip;
|
||||
};
|
||||
|
||||
std::map<std::string, LLLocalizationData> mLocalizations;
|
||||
LLKeyBindingToStringHandler* pHandler;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ LLUrlRegistry::LLUrlRegistry()
|
|||
registerUrl(new LLUrlEntryPlace());
|
||||
registerUrl(new LLUrlEntryInventory());
|
||||
registerUrl(new LLUrlEntryExperienceProfile());
|
||||
mUrlEntryKeybinding = new LLUrlEntryKeybinding();
|
||||
registerUrl(mUrlEntryKeybinding);
|
||||
//LLUrlEntrySL and LLUrlEntrySLLabel have more common pattern,
|
||||
//so it should be registered in the end of list
|
||||
registerUrl(new LLUrlEntrySL());
|
||||
|
|
@ -307,3 +309,9 @@ bool LLUrlRegistry::isUrl(const LLWString &text)
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LLUrlRegistry::setKeybindingHandler(LLKeyBindingToStringHandler* handler)
|
||||
{
|
||||
LLUrlEntryKeybinding *entry = (LLUrlEntryKeybinding*)mUrlEntryKeybinding;
|
||||
entry->setHandler(handler);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class LLKeyBindingToStringHandler;
|
||||
|
||||
/// This default callback for findUrl() simply ignores any label updates
|
||||
void LLUrlRegistryNullCallback(const std::string &url,
|
||||
const std::string &label,
|
||||
|
|
@ -88,6 +90,9 @@ public:
|
|||
bool isUrl(const std::string &text);
|
||||
bool isUrl(const LLWString &text);
|
||||
|
||||
// Set handler for url registry to be capable of parsing and populating keybindings
|
||||
void setKeybindingHandler(LLKeyBindingToStringHandler* handler);
|
||||
|
||||
private:
|
||||
std::vector<LLUrlEntryBase *> mUrlEntry;
|
||||
LLUrlEntryBase* mUrlEntryTrusted;
|
||||
|
|
@ -96,6 +101,7 @@ private:
|
|||
LLUrlEntryBase* mUrlEntryHTTPLabel;
|
||||
LLUrlEntryBase* mUrlEntrySLLabel;
|
||||
LLUrlEntryBase* mUrlEntryNoLink;
|
||||
LLUrlEntryBase* mUrlEntryKeybinding;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -366,6 +366,45 @@ std::string LLKeyboard::stringFromKey(KEY key, bool translate)
|
|||
return res;
|
||||
}
|
||||
|
||||
//static
|
||||
std::string LLKeyboard::stringFromMouse(EMouseClickType click, bool translate)
|
||||
{
|
||||
std::string res;
|
||||
switch (click)
|
||||
{
|
||||
case CLICK_LEFT:
|
||||
res = "LMB";
|
||||
break;
|
||||
case CLICK_MIDDLE:
|
||||
res = "MMB";
|
||||
break;
|
||||
case CLICK_RIGHT:
|
||||
res = "RMB";
|
||||
break;
|
||||
case CLICK_BUTTON4:
|
||||
res = "MB4";
|
||||
break;
|
||||
case CLICK_BUTTON5:
|
||||
res = "MB5";
|
||||
break;
|
||||
case CLICK_DOUBLELEFT:
|
||||
res = "Double LMB";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (translate && !res.empty())
|
||||
{
|
||||
LLKeyStringTranslatorFunc* trans = gKeyboard->mStringTranslator;
|
||||
if (trans != NULL)
|
||||
{
|
||||
res = trans(res.c_str());
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
//static
|
||||
std::string LLKeyboard::stringFromAccelerator(MASK accel_mask)
|
||||
{
|
||||
|
|
@ -433,6 +472,18 @@ std::string LLKeyboard::stringFromAccelerator( MASK accel_mask, KEY key )
|
|||
return res;
|
||||
}
|
||||
|
||||
//static
|
||||
std::string LLKeyboard::stringFromAccelerator(MASK accel_mask, EMouseClickType click)
|
||||
{
|
||||
std::string res;
|
||||
if (CLICK_NONE == click)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
res.append(stringFromAccelerator(accel_mask));
|
||||
res.append(stringFromMouse(click));
|
||||
return res;
|
||||
}
|
||||
|
||||
//static
|
||||
BOOL LLKeyboard::maskFromString(const std::string& str, MASK *mask)
|
||||
|
|
|
|||
|
|
@ -96,8 +96,10 @@ public:
|
|||
static BOOL maskFromString(const std::string& str, MASK *mask); // False on failure
|
||||
static BOOL keyFromString(const std::string& str, KEY *key); // False on failure
|
||||
static std::string stringFromKey(KEY key, bool translate = true);
|
||||
static std::string stringFromMouse(EMouseClickType click, bool translate = true);
|
||||
static std::string stringFromAccelerator( MASK accel_mask ); // separated for convinience, returns with "+": "Shift+" or "Shift+Alt+"...
|
||||
static std::string stringFromAccelerator( MASK accel_mask, KEY key );
|
||||
static std::string stringFromAccelerator(MASK accel_mask, EMouseClickType click);
|
||||
|
||||
void setCallbacks(LLWindowCallbacks *cbs) { mCallbacks = cbs; }
|
||||
F32 getKeyElapsedTime( KEY key ); // Returns time in seconds since key was pressed.
|
||||
|
|
@ -130,6 +132,13 @@ protected:
|
|||
static std::map<std::string,KEY> sNamesToKeys;
|
||||
};
|
||||
|
||||
// Interface to get key from assigned command
|
||||
class LLKeyBindingToStringHandler
|
||||
{
|
||||
public:
|
||||
virtual std::string getKeyBindingAsString(const std::string& mode, const std::string& control) const = 0;
|
||||
};
|
||||
|
||||
extern LLKeyboard *gKeyboard;
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ LLWindowMacOSX::LLWindowMacOSX(LLWindowCallbacks* callbacks,
|
|||
return;
|
||||
}
|
||||
|
||||
//start with arrow cursor
|
||||
//start with arrow cursor
|
||||
initCursors();
|
||||
setCursor( UI_CURSOR_ARROW );
|
||||
|
||||
|
|
@ -637,6 +637,34 @@ BOOL LLWindowMacOSX::createContext(int x, int y, int width, int height, int bits
|
|||
mGLView = createOpenGLView(mWindow, mFSAASamples, enable_vsync);
|
||||
mContext = getCGLContextObj(mGLView);
|
||||
gGLManager.mVRAM = getVramSize(mGLView);
|
||||
|
||||
if(!mPixelFormat)
|
||||
{
|
||||
CGLPixelFormatAttribute attribs[] =
|
||||
{
|
||||
kCGLPFANoRecovery,
|
||||
kCGLPFADoubleBuffer,
|
||||
kCGLPFAClosestPolicy,
|
||||
kCGLPFAAccelerated,
|
||||
kCGLPFAMultisample,
|
||||
kCGLPFASampleBuffers, static_cast<CGLPixelFormatAttribute>((mFSAASamples > 0 ? 1 : 0)),
|
||||
kCGLPFASamples, static_cast<CGLPixelFormatAttribute>(mFSAASamples),
|
||||
kCGLPFAStencilSize, static_cast<CGLPixelFormatAttribute>(8),
|
||||
kCGLPFADepthSize, static_cast<CGLPixelFormatAttribute>(24),
|
||||
kCGLPFAAlphaSize, static_cast<CGLPixelFormatAttribute>(8),
|
||||
kCGLPFAColorSize, static_cast<CGLPixelFormatAttribute>(24),
|
||||
kCGLPFAOpenGLProfile, static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_GL4_Core),
|
||||
static_cast<CGLPixelFormatAttribute>(0)
|
||||
};
|
||||
|
||||
GLint numPixelFormats;
|
||||
CGLChoosePixelFormat (attribs, &mPixelFormat, &numPixelFormats);
|
||||
|
||||
if(mPixelFormat == NULL) {
|
||||
CGLChoosePixelFormat (attribs, &mPixelFormat, &numPixelFormats);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This sets up our view to recieve text from our non-inline text input window.
|
||||
|
|
@ -1935,6 +1963,11 @@ void* LLWindowMacOSX::createSharedContext()
|
|||
{
|
||||
sharedContext* sc = new sharedContext();
|
||||
CGLCreateContext(mPixelFormat, mContext, &(sc->mContext));
|
||||
|
||||
if (sUseMultGL)
|
||||
{
|
||||
CGLEnable(mContext, kCGLCEMPEngine);
|
||||
}
|
||||
|
||||
return (void *)sc;
|
||||
}
|
||||
|
|
@ -1942,6 +1975,25 @@ void* LLWindowMacOSX::createSharedContext()
|
|||
void LLWindowMacOSX::makeContextCurrent(void* context)
|
||||
{
|
||||
CGLSetCurrentContext(((sharedContext*)context)->mContext);
|
||||
|
||||
//enable multi-threaded OpenGL
|
||||
if (sUseMultGL)
|
||||
{
|
||||
CGLError cgl_err;
|
||||
CGLContextObj ctx = CGLGetCurrentContext();
|
||||
|
||||
cgl_err = CGLEnable( ctx, kCGLCEMPEngine);
|
||||
|
||||
if (cgl_err != kCGLNoError )
|
||||
{
|
||||
LL_INFOS("GLInit") << "Multi-threaded OpenGL not available." << LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_INFOS("GLInit") << "Multi-threaded OpenGL enabled." << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LLWindowMacOSX::destroySharedContext(void* context)
|
||||
|
|
|
|||
|
|
@ -1929,7 +1929,7 @@ void LLWindowWin32::toggleVSync(bool enable_vsync)
|
|||
LL_INFOS("Window") << "Disabling vertical sync" << LL_ENDL;
|
||||
wglSwapIntervalEXT(0);
|
||||
}
|
||||
else
|
||||
else if (wglSwapIntervalEXT)
|
||||
{
|
||||
LL_INFOS("Window") << "Enabling vertical sync" << LL_ENDL;
|
||||
wglSwapIntervalEXT(1);
|
||||
|
|
|
|||
|
|
@ -1699,6 +1699,7 @@ if (WINDOWS)
|
|||
LINK_FLAGS_DEBUG "/NODEFAULTLIB:\"LIBCMT;LIBCMTD;MSVCRT\" /INCREMENTAL:NO /LARGEADDRESSAWARE"
|
||||
LINK_FLAGS_RELEASE "/FORCE:MULTIPLE /MAP\"secondlife-bin.MAP\" /OPT:REF /LARGEADDRESSAWARE"
|
||||
)
|
||||
target_compile_options(${VIEWER_BINARY_NAME} PRIVATE /bigobj)
|
||||
|
||||
if(USE_PRECOMPILED_HEADERS)
|
||||
target_precompile_headers( ${VIEWER_BINARY_NAME} PRIVATE llviewerprecompiledheaders.h )
|
||||
|
|
@ -1755,7 +1756,7 @@ if (WINDOWS)
|
|||
if (TARGET ll::fmodstudio)
|
||||
list(APPEND COPY_INPUT_DEPENDENCIES
|
||||
${SHARED_LIB_STAGING_DIR}/fmod.dll
|
||||
${SHARED_LIB_STAGING_DIR}/Debug/fmodL.dll
|
||||
${SHARED_LIB_STAGING_DIR}/fmodL.dll
|
||||
)
|
||||
endif ()
|
||||
|
||||
|
|
@ -2166,20 +2167,6 @@ if (PACKAGE AND (RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) AND VIE
|
|||
)
|
||||
add_custom_target(dsym_generate DEPENDS "${VIEWER_APP_DSYM}")
|
||||
add_dependencies(dsym_generate ${VIEWER_BINARY_NAME})
|
||||
add_custom_command(OUTPUT "${VIEWER_SYMBOL_FILE}"
|
||||
# See above comments about "tar ...j"
|
||||
COMMAND "tar"
|
||||
ARGS
|
||||
"cjf"
|
||||
"${VIEWER_SYMBOL_FILE}"
|
||||
"-C"
|
||||
"${VIEWER_APP_DSYM}/.."
|
||||
"${product}.dSYM"
|
||||
DEPENDS "${VIEWER_APP_DSYM}"
|
||||
COMMENT "Packing dSYM into ${VIEWER_SYMBOL_FILE}"
|
||||
)
|
||||
add_custom_target(dsym_tarball DEPENDS "${VIEWER_SYMBOL_FILE}")
|
||||
add_dependencies(dsym_tarball dsym_generate)
|
||||
add_custom_command(OUTPUT "${VIEWER_APP_XCARCHIVE}"
|
||||
COMMAND "zip"
|
||||
ARGS
|
||||
|
|
@ -2197,24 +2184,22 @@ if (PACKAGE AND (RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) AND VIE
|
|||
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/dsym.stamp"
|
||||
COMMAND rm -rf "${VIEWER_APP_DSYM}"
|
||||
COMMAND touch "${CMAKE_CURRENT_BINARY_DIR}/dsym.stamp"
|
||||
DEPENDS "${VIEWER_SYMBOL_FILE}" "${VIEWER_APP_XCARCHIVE}"
|
||||
DEPENDS "${VIEWER_APP_XCARCHIVE}"
|
||||
COMMENT "Cleaning up dSYM"
|
||||
)
|
||||
add_custom_target(generate_symbols DEPENDS
|
||||
"${VIEWER_APP_DSYM}"
|
||||
"${VIEWER_SYMBOL_FILE}"
|
||||
"${VIEWER_APP_XCARCHIVE}"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/dsym.stamp"
|
||||
)
|
||||
add_dependencies(generate_symbols dsym_tarball dsym_xcarchive)
|
||||
add_dependencies(generate_symbols dsym_xcarchive)
|
||||
endif (DARWIN)
|
||||
if (LINUX)
|
||||
# TBD
|
||||
endif (LINUX)
|
||||
endif (USE_BUGSPLAT)
|
||||
|
||||
# for both Bugsplat and Breakpad
|
||||
add_dependencies(llpackage generate_symbols)
|
||||
add_dependencies(llpackage generate_symbols)
|
||||
endif (USE_BUGSPLAT)
|
||||
endif ()
|
||||
|
||||
if (LL_TESTS)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
7.0.0
|
||||
7.1.3
|
||||
|
|
|
|||
|
|
@ -533,6 +533,17 @@
|
|||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>RenderSnapshotNoPost</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Disable tone mapping and exposure correction when snapshot is being rendered</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>AutomaticFly</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
@ -9017,7 +9028,7 @@
|
|||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>RenderPerformanceTest</key>
|
||||
<map>
|
||||
|
|
@ -9543,7 +9554,7 @@
|
|||
<key>Type</key>
|
||||
<string>F32</string>
|
||||
<key>Value</key>
|
||||
<real>-0.004</real>
|
||||
<real>-0.002</real>
|
||||
</map>
|
||||
<key>RenderShadowOffset</key>
|
||||
<map>
|
||||
|
|
@ -10665,7 +10676,7 @@
|
|||
<key>Type</key>
|
||||
<string>F32</string>
|
||||
<key>Value</key>
|
||||
<real>2.0</real>
|
||||
<real>1.0</real>
|
||||
</map>
|
||||
<key>RendeSkyAutoAdjustBlueHorizonScale</key>
|
||||
<map>
|
||||
|
|
@ -10709,7 +10720,7 @@
|
|||
<key>Type</key>
|
||||
<string>F32</string>
|
||||
<key>Value</key>
|
||||
<real>0.001</real>
|
||||
<real>0.01</real>
|
||||
</map>
|
||||
<key>RenderSkySunlightScale</key>
|
||||
<map>
|
||||
|
|
@ -15722,7 +15733,7 @@
|
|||
<key>Type</key>
|
||||
<string>S32</string>
|
||||
<key>Value</key>
|
||||
<integer>2048</integer>
|
||||
<integer>1024</integer>
|
||||
</map>
|
||||
<key>max_texture_dimension_Y</key>
|
||||
<map>
|
||||
|
|
@ -15733,7 +15744,7 @@
|
|||
<key>Type</key>
|
||||
<string>S32</string>
|
||||
<key>Value</key>
|
||||
<integer>2048</integer>
|
||||
<integer>1024</integer>
|
||||
</map>
|
||||
<!-- End of back compatibility settings -->
|
||||
<key>teleport_offer_invitation_max_length</key>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,6 @@ void main()
|
|||
{
|
||||
// NOTE: when this shader is used, only alpha is being written to
|
||||
float a = diffuseLookup(vary_texcoord0.xy).a*vertex_color.a;
|
||||
frag_color = vec4(0, 0, 0, a);
|
||||
frag_color = max(vec4(0, 0, 0, a), vec4(0));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,21 +35,19 @@ in vec3 vary_position;
|
|||
in vec4 vertex_color;
|
||||
in vec2 vary_texcoord0;
|
||||
|
||||
#ifdef WATER_FOG
|
||||
vec4 applyWaterFogView(vec3 pos, vec4 color);
|
||||
#endif
|
||||
|
||||
vec3 srgb_to_linear(vec3 cs);
|
||||
vec3 linear_to_srgb(vec3 cl);
|
||||
vec3 atmosFragLighting(vec3 light, vec3 additive, vec3 atten);
|
||||
void calcAtmosphericVars(vec3 inPositionEye, vec3 light_dir, float ambFactor, out vec3 sunlit, out vec3 amblit, out vec3 additive, out vec3 atten);
|
||||
|
||||
#ifdef HAS_ALPHA_MASK
|
||||
uniform float minimum_alpha;
|
||||
#endif
|
||||
|
||||
#ifdef IS_ALPHA
|
||||
uniform vec4 waterPlane;
|
||||
void waterClip(vec3 pos);
|
||||
void calcAtmosphericVars(vec3 inPositionEye, vec3 light_dir, float ambFactor, out vec3 sunlit, out vec3 amblit, out vec3 additive,
|
||||
out vec3 atten);
|
||||
vec4 applySkyAndWaterFog(vec3 pos, vec3 additive, vec3 atten, vec4 color);
|
||||
#endif
|
||||
|
||||
void main()
|
||||
|
|
@ -78,26 +76,21 @@ void main()
|
|||
|
||||
vec3 pos = vary_position;
|
||||
|
||||
color.a = final_alpha;
|
||||
#ifndef IS_HUD
|
||||
color.rgb = srgb_to_linear(color.rgb);
|
||||
#ifdef IS_ALPHA
|
||||
|
||||
vec3 sunlit;
|
||||
vec3 amblit;
|
||||
vec3 additive;
|
||||
vec3 atten;
|
||||
calcAtmosphericVars(pos.xyz, vec3(0), 1.0, sunlit, amblit, additive, atten);
|
||||
#endif
|
||||
|
||||
#ifdef WATER_FOG
|
||||
color.rgb = applySkyAndWaterFog(pos, additive, atten, color).rgb;
|
||||
|
||||
vec4 fogged = applyWaterFogView(pos, vec4(color.rgb, final_alpha));
|
||||
color.rgb = fogged.rgb;
|
||||
color.a = fogged.a;
|
||||
#else
|
||||
color.a = final_alpha;
|
||||
#endif
|
||||
|
||||
#ifndef IS_HUD
|
||||
color.rgb = srgb_to_linear(color.rgb);
|
||||
color.rgb = atmosFragLighting(color.rgb, additive, atten);
|
||||
#endif
|
||||
|
||||
frag_color = max(color, vec4(0));
|
||||
|
|
|
|||
|
|
@ -57,5 +57,8 @@ void main()
|
|||
frag_data[1] = vec4(0.0);
|
||||
frag_data[2] = vec4(0.0, 0.0, 0.0, GBUFFER_FLAG_HAS_ATMOS);
|
||||
frag_data[3] = vec4(c.rgb, c.a);
|
||||
|
||||
// Added and commented out for a ground truth. Do not uncomment - Geenz
|
||||
//gl_FragDepth = 0.999985f;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ void main()
|
|||
vec4 pos = (modelview_projection_matrix * vert);
|
||||
|
||||
// smash to *almost* far clip plane -- stars are still behind
|
||||
pos.z = pos.w*0.999999;
|
||||
// SL-19283 - finagle the moon position to be between clouds and stars.
|
||||
pos.z = pos.w*0.999991;
|
||||
gl_Position = pos;
|
||||
|
||||
vary_texcoord0 = (texture_matrix0 * vec4(texcoord0,0,1)).xy;
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ vec3 toneMap(vec3 color)
|
|||
color *= exposure * exp_scale;
|
||||
|
||||
// mix ACES and Linear here as a compromise to avoid over-darkening legacy content
|
||||
color = mix(toneMapACES_Hill(color), color, 0.333);
|
||||
color = mix(toneMapACES_Hill(color), color, 0.3);
|
||||
#endif
|
||||
|
||||
return color;
|
||||
|
|
|
|||
|
|
@ -30,12 +30,14 @@ uniform vec4 waterFogColor;
|
|||
uniform float waterFogDensity;
|
||||
uniform float waterFogKS;
|
||||
|
||||
vec3 getPositionEye();
|
||||
|
||||
vec3 srgb_to_linear(vec3 col);
|
||||
vec3 linear_to_srgb(vec3 col);
|
||||
|
||||
vec4 applyWaterFogView(vec3 pos, vec4 color)
|
||||
vec3 atmosFragLighting(vec3 light, vec3 additive, vec3 atten);
|
||||
|
||||
// get a water fog color that will apply the appropriate haze to a color given
|
||||
// a blend function of (ONE, SOURCE_ALPHA)
|
||||
vec4 getWaterFogViewNoClip(vec3 pos)
|
||||
{
|
||||
vec3 view = normalize(pos);
|
||||
//normalize view vector
|
||||
|
|
@ -67,38 +69,76 @@ vec4 applyWaterFogView(vec3 pos, vec4 color)
|
|||
float L = min(t1/t2*t3, 1.0);
|
||||
|
||||
float D = pow(0.98, l*kd);
|
||||
|
||||
color.rgb = color.rgb * D + kc.rgb * L;
|
||||
|
||||
return color;
|
||||
return vec4(srgb_to_linear(kc.rgb*L), D);
|
||||
}
|
||||
|
||||
vec4 applyWaterFogViewLinearNoClip(vec3 pos, vec4 color, vec3 sunlit)
|
||||
vec4 getWaterFogView(vec3 pos)
|
||||
{
|
||||
color.rgb = linear_to_srgb(color.rgb);
|
||||
color = applyWaterFogView(pos, color);
|
||||
color.rgb = srgb_to_linear(color.rgb);
|
||||
if (dot(pos, waterPlane.xyz) + waterPlane.w > 0.0)
|
||||
{
|
||||
return vec4(0,0,0,1);
|
||||
}
|
||||
|
||||
return getWaterFogViewNoClip(pos);
|
||||
}
|
||||
|
||||
vec4 applyWaterFogView(vec3 pos, vec4 color)
|
||||
{
|
||||
vec4 fogged = getWaterFogView(pos);
|
||||
|
||||
color.rgb = color.rgb * fogged.a + fogged.rgb;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
vec4 applyWaterFogViewLinear(vec3 pos, vec4 color, vec3 sunlit)
|
||||
vec4 applyWaterFogViewLinearNoClip(vec3 pos, vec4 color)
|
||||
{
|
||||
vec4 fogged = getWaterFogViewNoClip(pos);
|
||||
color.rgb *= fogged.a;
|
||||
color.rgb += fogged.rgb;
|
||||
return color;
|
||||
}
|
||||
|
||||
vec4 applyWaterFogViewLinear(vec3 pos, vec4 color)
|
||||
{
|
||||
if (dot(pos, waterPlane.xyz) + waterPlane.w > 0.0)
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
return applyWaterFogViewLinearNoClip(pos, color, sunlit);
|
||||
return applyWaterFogViewLinearNoClip(pos, color);
|
||||
}
|
||||
|
||||
vec4 applyWaterFogViewLinear(vec3 pos, vec4 color)
|
||||
// for post deferred shaders, apply sky and water fog in a way that is consistent with
|
||||
// the deferred rendering haze post effects
|
||||
vec4 applySkyAndWaterFog(vec3 pos, vec3 additive, vec3 atten, vec4 color)
|
||||
{
|
||||
return applyWaterFogViewLinear(pos, color, vec3(1));
|
||||
}
|
||||
bool eye_above_water = dot(vec3(0), waterPlane.xyz) + waterPlane.w > 0.0;
|
||||
bool obj_above_water = dot(pos.xyz, waterPlane.xyz) + waterPlane.w > 0.0;
|
||||
|
||||
vec4 applyWaterFog(vec4 color)
|
||||
{
|
||||
//normalize view vector
|
||||
return applyWaterFogViewLinear(getPositionEye(), color);
|
||||
}
|
||||
if (eye_above_water)
|
||||
{
|
||||
if (!obj_above_water)
|
||||
{
|
||||
color.rgb = applyWaterFogViewLinearNoClip(pos, color).rgb;
|
||||
}
|
||||
else
|
||||
{
|
||||
color.rgb = atmosFragLighting(color.rgb, additive, atten);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (obj_above_water)
|
||||
{
|
||||
color.rgb = atmosFragLighting(color.rgb, additive, atten);
|
||||
}
|
||||
else
|
||||
{
|
||||
color.rgb = applyWaterFogViewLinearNoClip(pos, color).rgb;
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
* @file class1\lighting\lightWaterAlphaMaskF.glsl
|
||||
*
|
||||
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2011, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
uniform float minimum_alpha;
|
||||
|
||||
vec3 atmosLighting(vec3 light);
|
||||
vec4 applyWaterFog(vec4 color);
|
||||
|
||||
in vec4 vertex_color;
|
||||
in vec2 vary_texcoord0;
|
||||
|
||||
void default_lighting_water()
|
||||
{
|
||||
vec4 color = diffuseLookup(vary_texcoord0.xy);
|
||||
|
||||
if (color.a < minimum_alpha)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
color.rgb *= vertex_color.rgb;
|
||||
|
||||
color.rgb = atmosLighting(color.rgb);
|
||||
|
||||
frag_color = max(applyWaterFog(color), vec4(0));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue