Merge branch 'release/materials_featurette' into DRTVWR-592

master
RunitaiLinden 2024-01-19 11:36:56 -06:00 committed by GitHub
commit 6dd260c369
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
271 changed files with 10178 additions and 12780 deletions

18
.github/release.yaml vendored Normal file
View File

@ -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:
- '*'

View File

@ -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

View File

@ -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

5
.gitignore vendored
View File

@ -86,4 +86,7 @@ tarfile_tmp
trivial_change_force_build
web/config.*
web/locale.*
web/secondlife.com.*
web/secondlife.com.*
.env
.vscode

File diff suppressed because it is too large Load Diff

173
build.sh
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
)

View File

@ -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

View File

@ -62,6 +62,7 @@ elseif (WINDOWS)
user32
ole32
dbghelp
rpcrt4.lib
legacy_stdio_definitions
)
else()

View File

@ -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)

View File

@ -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) )

View File

@ -30,6 +30,7 @@
#include <list>
#include <map>
#include <array>
#include "v3math.h"
#include "v3dmath.h"

View File

@ -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;

View File

@ -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}")

View File

@ -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) */

29
indra/llcommon/apply.cpp Normal file
View File

@ -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))")));
}
}

View File

@ -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) */

View File

@ -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) */

View File

@ -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);
}

View File

@ -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) */

View File

@ -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)

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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()
{
}

View File

@ -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;
};

View File

@ -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

View File

@ -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
*****************************************************************************/

View File

@ -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.

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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) */

View File

@ -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);
}

View File

@ -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>

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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.)

View File

@ -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

View File

@ -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*>(&registrar)))
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

View File

@ -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

View File

@ -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();
}

View File

@ -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<>

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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]

View File

@ -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;

View File

@ -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

View File

@ -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;
}
///----------------------------------------------------------------------------

View File

@ -31,6 +31,7 @@
#include <algorithm>
#include "llsdserialize.h"
#include <boost/bind.hpp>
//=========================================================================
namespace

View File

@ -31,6 +31,7 @@
#include "lltrace.h"
#include "llfasttimer.h"
#include "v3colorutil.h"
#include <boost/bind.hpp>
//=========================================================================

View File

@ -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");

View File

@ -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

View File

@ -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];
}

View File

@ -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());

View File

@ -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
}

View File

@ -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);

View File

@ -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)

View File

@ -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)
{

View File

@ -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;

View File

@ -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);

View File

@ -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;
};

View File

@ -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;

View File

@ -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() ;
}

View File

@ -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 ,

View File

@ -44,6 +44,8 @@
#include "llmatrix4a.h"
#include "glh/glh_linear.h"
#include <array>
class LLVertexBuffer;
class LLCubeMap;
class LLImageGL;

View File

@ -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 );
}
//=============================================================================

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
}
}
}
}

View File

@ -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;
}

View File

@ -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()
);
}
}
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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)

View File

@ -1 +1 @@
7.0.0
7.1.3

View File

@ -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>

View File

@ -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));
}

View File

@ -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));

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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