Merge branch 'master' into fix/interface-console

This commit is contained in:
David Rowe 2020-11-11 20:31:33 +13:00
commit b664fea758
69 changed files with 1616 additions and 565 deletions

View file

@ -3,114 +3,60 @@ name: Master CI Build
on:
push:
branches:
- master
- gha-master-ci
# FIXME: Change target branch to "master" before merging into "master" branch.
env:
#APP_NAME: gpu-frame-player
APP_NAME: interface
BUILD_TYPE: Release
BUCKET_NAME: hifi-gh-builds
BUILD_NUMBER: ${{ github.run_number }}
CI_BUILD: Github
CMAKE_BACKTRACE_URL: https://highfidelity.sp.backtrace.io:6098
CMAKE_BACKTRACE_TOKEN: ${{ secrets.backtrace_token }}
CMAKE_BACKTRACE_SYMBOLS_TOKEN: ${{ secrets.backtrace_symbols_token }}
GIT_COMMIT: ${{ github.sha }}
HIFI_VCPKG_BOOTSTRAP: true
LAUNCHER_HMAC_SECRET: ${{ secrets.launcher_hmac_secret }}
OCULUS_APP_ID: '${{ secrets.oculus_app_id }}'
# VCPKG did not build well on OSX disabling HIFI_VCPKG_BOOTSTRAP, which invokes a download to a working version of vcpkg
# HIFI_VCPKG_BOOTSTRAP: true
RELEASE_TYPE: PRODUCTION
RELEASE_DYNAMODB_V2: ReleaseManager2-ReleaseQueue-prod
RELEASE_NUMBER: ${{ github.run_number }}
STABLE_BUILD: 0
UPLOAD_BUCKET: athena-public
# OSX specific variables
# OSX-specific variables
DEVELOPER_DIR: /Applications/Xcode_11.2.app/Contents/Developer
MACOSX_DEPLOYMENT_TARGET: '10.11'
# WIN32 specific variables
# WIN-specific variables
PreferredToolArchitecture: X64
# Mac OS
#PLATFORM_CMAKE_GENERATOR=Xcode
#PLATFORM_BUILD_ARGUMENTS=--config Release --target package
#ARTIFACT_EXPRESSION=build/*.dmg,build/*.zip
# Windows
#PLATFORM_CMAKE_GENERATOR=Visual Studio 15 2017 Win64
#PLATFORM_BUILD_ARGUMENTS=--target package --config release
#ARTIFACT_EXPRESSION=build/*.exe,build/*.zip,*-symbols.zip
# Ubuntu
#PLATFORM_CMAKE_GENERATOR=Unix Makefiles
#PLATFORM_BUILD_ARGUMENTS=--target all -- -j4
#ARTIFACT_EXPRESSION=build/assignment-client/**,build/domain-server/**,build/ice-server/ice-server,build/tools/ice-client/ice-client,build/tools/ac-client/ac-client,build/tools/oven,build/ext/makefiles/nvtt/project/lib/**,build/ext/makefiles/quazip/project/lib/**
# Android
# branch: master
# GA_TRACKING_ID: ${{ secrets.ga_tracking_id }}
# ANDROID_OAUTH_CLIENT_SECRET=${MASKED_ANDROID_OAUTH_CLIENT_SECRET_NIGHTLY}
# ANDROID_OAUTH_CLIENT_ID=6c7d2349c0614640150db37457a1f75dce98a28ffe8f14d47f6cfae4de5b262a
# ANDROID_OAUTH_REDIRECT_URI=https://dev-android-interface.highfidelity.com/auth
# branch: !master
# GA_TRACKING_ID=UA-39558647-11
# ANDROID_OAUTH_CLIENT_SECRET=${MASKED_ANDROID_OAUTH_CLIENT_SECRET_RELEASE}
# ANDROID_OAUTH_CLIENT_ID= c1063ea5d0b0c405e0c9cd77351328e211a91496a3f25985a99e861f1661db1d
# ANDROID_OAUTH_REDIRECT_URI=https://android-interface.highfidelity.com/auth
# ARTIFACT_EXPRESSION=android/*.apk
# ANDROID_APK_NAME=HighFidelity-Beta-PR${RELEASE_NUMBER}-${GIT_COMMIT_SHORT}.apk
# ANDROID_BUILT_APK_NAME=interface-debug.apk
# ANDROID_APP=interface
# ANDROID_BUILD_DIR=debug
# ANDROID_BUILD_TARGET=assembleDebug
# STABLE_BUILD=0
jobs:
generate_build_number:
runs-on: ubuntu-latest
steps:
- name: Generate build number
id: buildnumber
uses: highfidelity/build-number@v3
with:
token: ${{secrets.github_token}}
- name: Upload build number
uses: actions/upload-artifact@v1
with:
name: BUILD_NUMBER
path: BUILD_NUMBER
build:
strategy:
matrix:
os: [windows-latest, macOS-latest]
build_type: [full, client]
#os: [windows-latest, macOS-latest, ubuntu-latest]
# exclude:
# - os: ubuntu-latest
# build_type: client
os: [windows-latest, macOS-latest, ubuntu-18.04]
# build_type: [full, client]
build_type: [full]
include:
- os: ubuntu-18.04
build_type: full
apt-dependencies: mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0
fail-fast: false
runs-on: ${{matrix.os}}
needs: generate_build_number
steps:
- name: Download build number
uses: actions/download-artifact@v1
with:
name: BUILD_NUMBER
- name: Restore build number
id: buildnumber
uses: highfidelity/build-number@v3
with:
output_name: RELEASE_NUMBER
- name: Configure Build Environment 1
- name: Report Build Number
shell: bash
run: |
echo "Build number: $BUILD_NUMBER"
- name: Configure build environment 1
shell: bash
id: buildenv1
run: |
echo ::set-env name=UPLOAD_PREFIX::master
echo ::set-env name=GIT_COMMIT_SHORT::`echo $GIT_COMMIT | cut -c1-7`
echo ::set-env name=JOB_NAME::"build (${{matrix.os}}, ${{matrix.build_type}})"
# Linux build variables
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
if [[ "${{ matrix.os }}" = "ubuntu-"* ]]; then
echo ::set-env name=PYTHON_EXEC::python3
echo ::set-env name=INSTALLER_EXT::tgz
echo ::set-env name=CMAKE_BUILD_EXTRA::"-- -j3"
echo ::set-env name=CMAKE_EXTRA::"-DBUILD_TOOLS:BOOLEAN=FALSE -DHIFI_PYTHON_EXEC:FILEPATH=$(which python3)"
fi
# Mac build variables
if [ "${{ matrix.os }}" = "macOS-latest" ]; then
@ -118,8 +64,8 @@ jobs:
echo ::set-env name=ZIP_COMMAND::zip
echo ::set-env name=ZIP_ARGS::-r
echo ::set-env name=INSTALLER_EXT::dmg
echo ::set-env name=SYMBOL_REGEX::dSYM
echo "::set-output name=symbols_archive::${{ steps.buildnumber.outputs.build_number }}-${{ matrix.build_type }}-mac-symbols.zip"
echo ::set-env name=CMAKE_EXTRA::"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode"
echo "::set-output name=symbols_archive::${BUILD_NUMBER}-${{ matrix.build_type }}-mac-symbols.zip"
fi
# Windows build variables
if [ "${{ matrix.os }}" = "windows-latest" ]; then
@ -127,40 +73,28 @@ jobs:
echo ::set-env name=ZIP_COMMAND::7z
echo ::set-env name=ZIP_ARGS::a
echo ::set-env name=INSTALLER_EXT::exe
echo ::set-env name=CMAKE_EXTRA::"-A x64"
echo "::set-env name=SYMBOL_REGEX::\(exe\|dll\|pdb\)"
echo "::set-output name=symbols_archive::${{ steps.buildnumber.outputs.build_number }}-${{ matrix.build_type }}-win-symbols.zip"
echo "::set-output name=symbols_archive::${BUILD_NUMBER}-${{ matrix.build_type }}-win-symbols.zip"
# echo ::set-env name=HF_PFX_PASSPHRASE::${{secrets.pfx_key}}
# echo "::set-env name=HF_PFX_FILE::${{runner.workspace}}\build\codesign.pfx"
fi
# Configuration is broken into two steps because you can't set an env var and also reference it in the same step
- name: Configure Build Environment 2
- name: Configure build environment 2
shell: bash
run: |
echo "${{ steps.buildenv1.outputs.symbols_archive }}"
echo ::set-env name=ARTIFACT_PATTERN::HighFidelity-Beta-*.$INSTALLER_EXT
echo ::set-env name=ARTIFACT_PATTERN::Vircadia-Alpha-*.$INSTALLER_EXT
# Build type variables
if [ "${{ matrix.build_type }}" = "full" ]; then
echo ::set-env name=CLIENT_ONLY::FALSE
echo ::set-env name=INSTALLER::HighFidelity-Beta-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
echo ::set-env name=INSTALLER::Vircadia-Alpha-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
else
echo ::set-env name=CLIENT_ONLY::TRUE
echo ::set-env name=INSTALLER::HighFidelity-Beta-Interface-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
echo ::set-env name=INSTALLER::Vircadia-Alpha-Interface-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
fi
# Linux build variables
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
echo ::set-env name=PYTHON_EXEC::python3
echo ::set-env name=CMAKE_EXTRA::""
fi
# Mac build variables
if [ "${{ matrix.os }}" = "macOS-latest" ]; then
echo ::set-env name=CMAKE_EXTRA::"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode"
fi
# Windows build variables
if [ "${{ matrix.os }}" = "windows-latest" ]; then
echo ::set-env name=CMAKE_EXTRA::"-A x64"
echo ::set-env name=HF_PFX_PASSPHRASE::${{secrets.pfx_key}}
echo "::set-env name=HF_PFX_FILE::${{runner.workspace}}\build\codesign.pfx"
fi
- name: Clear Working Directory
if: matrix.os == 'windows-latest'
- name: Clear working directory
if: startsWith(matrix.os, 'windows')
shell: bash
working-directory: ${{runner.workspace}}
run: rm -rf ./*
@ -168,89 +102,119 @@ jobs:
with:
submodules: true
fetch-depth: 1
- name: Create Build Directory
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Decrypt Signing Key (Windows)
if: matrix.os == 'windows-latest'
working-directory: ${{runner.workspace}}/build
- name: Install dependencies
if: startsWith(matrix.os, 'ubuntu')
shell: bash
run: gpg --batch --yes -o codesign.pfx --passphrase "${{secrets.gpg_symmetric_key}}" --decrypt $GITHUB_WORKSPACE/tools/ci-scripts/codesign.pfx.gpg
- name: Import Signing Key (Windows)
if: matrix.os == 'windows-latest'
working-directory: ${{runner.workspace}}/build
shell: powershell
run: |
$mypwd=ConvertTo-SecureString -String ${{ secrets.pfx_key }} -Force -AsPlainText
Import-PfxCertificate -Password $mypwd -CertStoreLocation Cert:\CurrentUser\My -FilePath ${{runner.workspace}}\build\codesign.pfx
Import-PfxCertificate -Password $mypwd -CertStoreLocation Cert:\LocalMachine\My -FilePath ${{runner.workspace}}\build\codesign.pfx
run: |
echo "Installing Python Modules:"
pip3 install distro || exit 1
echo "Updating apt repository index"
sudo apt update || exit 1
echo "Installing apt packages"
sudo apt install -y ${{ matrix.apt-dependencies }} || exit 1
- name: Install Python modules
if: matrix.os != 'ubuntu-latest'
if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
shell: bash
run: $PYTHON_EXEC -m pip install boto3 PyGithub
- name: Create build environment
shell: bash
run: cmake -E make_directory "${{runner.workspace}}/build"
- name: Configure CMake
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCLIENT_ONLY:BOOLEAN=$CLIENT_ONLY $CMAKE_EXTRA
- name: Build Application
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DVCPKG_BUILD_TYPE=release -DCLIENT_ONLY:BOOLEAN=$CLIENT_ONLY -DBYPASS_SIGNING:BOOLEAN=TRUE $CMAKE_EXTRA
- name: Build application
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target $APP_NAME
- name: Build Console
run: cmake --build . --config $BUILD_TYPE --target $APP_NAME $CMAKE_BUILD_EXTRA
- name: Build domain server
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target packaged-server-console
- name: Build Domain Server (FullBuild)
if: matrix.build_type == 'full'
shell: bash
working-directory: ${{runner.workspace}}/build
run: cmake --build . --config $BUILD_TYPE --target domain-server
- name: Build Assignment Client (FullBuild)
if: matrix.build_type == 'full'
shell: bash
working-directory: ${{runner.workspace}}/build
run: cmake --build . --config $BUILD_TYPE --target assignment-client
- name: Build Installer
run: cmake --build . --config $BUILD_TYPE --target domain-server $CMAKE_BUILD_EXTRA
- name: Build assignment client
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target package
- name: Sign Installer (Windows)
if: matrix.os == 'windows-latest'
shell: powershell
working-directory: C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64
run: .\signtool.exe sign /fd sha256 /f ${{runner.workspace}}\build\codesign.pfx /p ${{secrets.pfx_key}} /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp /td SHA256 ${{runner.workspace}}\build\${env:INSTALLER}
- name: Upload Artifact
if: matrix.os != 'ubuntu-latest'
run: cmake --build . --config $BUILD_TYPE --target assignment-client $CMAKE_BUILD_EXTRA
- name: Build console
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target packaged-server-console $CMAKE_BUILD_EXTRA
- name: Build installer
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
echo "Retry code from https://unix.stackexchange.com/a/137639"
function fail {
echo $1 >&2
exit 1
}
function retry {
local n=1
local max=5
local delay=15
while true; do
"$@" && break || {
if [[ $n -lt $max ]]; then
((n++))
echo "Command failed. Attempt $n/$max:"
sleep $delay;
else
fail "The command has failed after $n attempts."
fi
}
done
}
retry cmake --build . --config $BUILD_TYPE --target package $CMAKE_BUILD_EXTRA
#- name: Sign installer (Windows)
# if: startsWith(matrix.os, 'windows')
# shell: powershell
# working-directory: C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64
# run: .\signtool.exe sign /fd sha256 /f ${{runner.workspace}}\build\codesign.pfx /p ${{secrets.pfx_key}} /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp /td SHA256 ${{runner.workspace}}\build\${env:INSTALLER}
- name: Output system stats
if: ${{ always() }}
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
echo "Disk usage:"
df -h
- name: Output installer logs
if: failure() && startsWith(matrix.os, 'windows')
shell: bash
working-directory: ${{runner.workspace}}/build
run: cat ./_CPack_Packages/win64/NSIS/NSISOutput.log
- name: Upload artifact
if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
shell: bash
working-directory: ${{runner.workspace}}/build
env:
AWS_ACCESS_KEY_ID: ${{ secrets.aws_access_key_id }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.aws_secret_access_key }}
run: $PYTHON_EXEC $GITHUB_WORKSPACE/tools/ci-scripts/upload.py
- name: Archive Symbols
if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest')
working-directory: ${{runner.workspace}}
shell: bash
run: |
SYMBOLS_TEMP="symbols-temp"
mkdir $SYMBOLS_TEMP
find "./build" \( -path '*/tools/gpu-frame-player/*' -or -path '*/interface/*' -or -path '*/plugins/*' \) -regex ".*\.$SYMBOL_REGEX" -exec cp -r {} $SYMBOLS_TEMP \;
cd $SYMBOLS_TEMP
$ZIP_COMMAND $ZIP_ARGS ../${{ steps.buildenv1.outputs.symbols_archive }} .
- name: Upload Symbols
if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest')
working-directory: ${{runner.workspace}}
shell: bash
run: |
curl --data-binary @${{ steps.buildenv1.outputs.symbols_archive }} "$CMAKE_BACKTRACE_URL/post?format=symbols&token=$CMAKE_BACKTRACE_SYMBOLS_TOKEN&upload_file=${{steps.buildenv1.outputs.symbols_archive}}&tag=$RELEASE_NUMBER"
# - name: Debug List Symbols
# if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest')
# working-directory: ${{runner.workspace}}
# shell: bash
# run: |
# unzip -v "${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}"
# - name: Debug Upload Symbols Artifact
# if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest')
# uses: actions/upload-artifact@v1
# with:
# name: symbols
# path: ${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}
#- name: Archive symbols
# if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
# working-directory: ${{runner.workspace}}
# shell: bash
# run: |
# SYMBOLS_TEMP="symbols-temp"
# mkdir $SYMBOLS_TEMP
# find "./build" \( -path '*/tools/gpu-frame-player/*' -or -path '*/interface/*' -or -path '*/plugins/*' \) -regex ".*\.$SYMBOL_REGEX" -exec cp -r {} $SYMBOLS_TEMP \;
# cd $SYMBOLS_TEMP
# $ZIP_COMMAND $ZIP_ARGS ../${{ steps.buildenv1.outputs.symbols_archive }} .
#- name: Upload symbols
# if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
# working-directory: ${{runner.workspace}}
# shell: bash
# run: |
# curl --data-binary @${{ steps.buildenv1.outputs.symbols_archive }} "$CMAKE_BACKTRACE_URL/post?format=symbols&token=$CMAKE_BACKTRACE_SYMBOLS_TOKEN&upload_file=${{steps.buildenv1.outputs.symbols_archive}}&tag=$BUILD_NUMBER"
#- name: Debug list symbols
# if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
# working-directory: ${{runner.workspace}}
# shell: bash
# run: |
# unzip -v "${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}"
#- name: Upload debug list symbols
# if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
# uses: actions/upload-artifact@v1
# with:
# name: symbols
# path: ${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}

View file

@ -112,11 +112,12 @@ Agent::Agent(ReceivedMessage& message) :
packetReceiver.registerListenerForTypes(
{ PacketType::MixedAudio, PacketType::SilentAudioFrame },
this, "handleAudioPacket");
PacketReceiver::makeUnsourcedListenerReference<Agent>(this, &Agent::handleAudioPacket));
packetReceiver.registerListenerForTypes(
{ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket");
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
PacketReceiver::makeSourcedListenerReference<Agent>(this, &Agent::handleOctreePacket));
packetReceiver.registerListener(PacketType::SelectedAudioFormat,
PacketReceiver::makeUnsourcedListenerReference<Agent>(this, &Agent::handleSelectedAudioFormat));
// 100Hz timer for audio
const int TARGET_INTERVAL_MSEC = 10; // 10ms

View file

@ -118,8 +118,10 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
setUpStatusToMonitor();
}
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::CreateAssignment, this, "handleCreateAssignmentPacket");
packetReceiver.registerListener(PacketType::StopNode, this, "handleStopNodePacket");
packetReceiver.registerListener(PacketType::CreateAssignment,
PacketReceiver::makeUnsourcedListenerReference<AssignmentClient>(this, &AssignmentClient::handleCreateAssignmentPacket));
packetReceiver.registerListener(PacketType::StopNode,
PacketReceiver::makeUnsourcedListenerReference<AssignmentClient>(this, &AssignmentClient::handleStopNodePacket));
}
void AssignmentClient::stopAssignmentClient() {

View file

@ -72,7 +72,8 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen
auto nodeList = DependencyManager::set<LimitedNodeList>(listenPort);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssignmentClientStatus, this, "handleChildStatusPacket");
packetReceiver.registerListener(PacketType::AssignmentClientStatus,
PacketReceiver::makeUnsourcedListenerReference<AssignmentClientMonitor>(this, &AssignmentClientMonitor::handleChildStatusPacket));
adjustOSResources(std::max(_numAssignmentClientForks, _maxAssignmentClientForks));
// use QProcess to fork off a process for each of the child assignment clients

View file

@ -308,7 +308,8 @@ AssetServer::AssetServer(ReceivedMessage& message) :
// Queue all requests until the Asset Server is fully setup
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::AssetGet, PacketType::AssetGetInfo, PacketType::AssetUpload, PacketType::AssetMappingOperation }, this, "queueRequests");
packetReceiver.registerListenerForTypes({ PacketType::AssetGet, PacketType::AssetGetInfo, PacketType::AssetUpload, PacketType::AssetMappingOperation },
PacketReceiver::makeSourcedListenerReference<AssetServer>(this, &AssetServer::queueRequests));
#ifdef Q_OS_WIN
updateConsumedCores();
@ -464,10 +465,14 @@ void AssetServer::completeSetup() {
qCDebug(asset_server) << "Overriding temporary queuing packet handler.";
// We're fully setup, override the request queueing handler and replay all requests
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet");
packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo");
packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload");
packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation");
packetReceiver.registerListener(PacketType::AssetGet,
PacketReceiver::makeSourcedListenerReference<AssetServer>(this, &AssetServer::handleAssetGet));
packetReceiver.registerListener(PacketType::AssetGetInfo,
PacketReceiver::makeSourcedListenerReference<AssetServer>(this, &AssetServer::handleAssetGetInfo));
packetReceiver.registerListener(PacketType::AssetUpload,
PacketReceiver::makeSourcedListenerReference<AssetServer>(this, &AssetServer::handleAssetUpload));
packetReceiver.registerListener(PacketType::AssetMappingOperation,
PacketReceiver::makeSourcedListenerReference<AssetServer>(this, &AssetServer::handleAssetMappingOperation));
replayRequests();
}

View file

@ -101,20 +101,23 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
PacketType::InjectorGainSet,
PacketType::AudioSoloRequest,
PacketType::StopInjector },
this, "queueAudioPacket");
PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::queueAudioPacket)
);
// packets whose consequences are global should be processed on the main thread
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
packetReceiver.registerListener(PacketType::MuteEnvironment,
PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::handleMuteEnvironmentPacket));
packetReceiver.registerListener(PacketType::NodeMuteRequest,
PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::handleNodeMuteRequestPacket));
packetReceiver.registerListener(PacketType::KillAvatar,
PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::handleKillAvatarPacket));
packetReceiver.registerListenerForTypes({
PacketType::ReplicatedMicrophoneAudioNoEcho,
PacketType::ReplicatedMicrophoneAudioWithEcho,
PacketType::ReplicatedInjectAudio,
PacketType::ReplicatedSilentAudioFrame
},
this, "queueReplicatedAudioPacket"
PacketType::ReplicatedSilentAudioFrame },
PacketReceiver::makeUnsourcedListenerReference<AudioMixer>(this, &AudioMixer::queueReplicatedAudioPacket)
);
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);

View file

@ -71,26 +71,38 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AvatarData, this, "queueIncomingPacket");
packetReceiver.registerListener(PacketType::AdjustAvatarSorting, this, "handleAdjustAvatarSorting");
packetReceiver.registerListener(PacketType::AvatarQuery, this, "handleAvatarQueryPacket");
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket");
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
packetReceiver.registerListener(PacketType::AvatarData,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::queueIncomingPacket));
packetReceiver.registerListener(PacketType::AdjustAvatarSorting,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleAdjustAvatarSorting));
packetReceiver.registerListener(PacketType::AvatarQuery,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleAvatarQueryPacket));
packetReceiver.registerListener(PacketType::AvatarIdentity,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleAvatarIdentityPacket));
packetReceiver.registerListener(PacketType::KillAvatar,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleKillAvatarPacket));
packetReceiver.registerListener(PacketType::NodeIgnoreRequest,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleNodeIgnoreRequestPacket));
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleRadiusIgnoreRequestPacket));
packetReceiver.registerListener(PacketType::RequestsDomainListData,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleRequestsDomainListDataPacket));
packetReceiver.registerListener(PacketType::SetAvatarTraits,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::queueIncomingPacket));
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::queueIncomingPacket));
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket");
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "queueIncomingPacket");
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleOctreePacket));
packetReceiver.registerListener(PacketType::ChallengeOwnership,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::queueIncomingPacket));
packetReceiver.registerListenerForTypes({
PacketType::ReplicatedAvatarIdentity,
PacketType::ReplicatedKillAvatar
}, this, "handleReplicatedPacket");
}, PacketReceiver::makeUnsourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleReplicatedPacket));
packetReceiver.registerListener(PacketType::ReplicatedBulkAvatarData, this, "handleReplicatedBulkAvatarPacket");
packetReceiver.registerListener(PacketType::ReplicatedBulkAvatarData,
PacketReceiver::makeUnsourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleReplicatedBulkAvatarPacket));
auto nodeList = DependencyManager::get<NodeList>();
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch);

View file

@ -59,8 +59,7 @@ EntityServer::EntityServer(ReceivedMessage& message) :
PacketType::ChallengeOwnership,
PacketType::ChallengeOwnershipRequest,
PacketType::ChallengeOwnershipReply },
this,
"handleEntityPacket");
PacketReceiver::makeSourcedListenerReference<EntityServer>(this, &EntityServer::handleEntityPacket));
connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification);
_dynamicDomainVerificationTimer.setSingleShot(true);

View file

@ -25,9 +25,12 @@ MessagesMixer::MessagesMixer(ReceivedMessage& message) : ThreadedAssignment(mess
{
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &MessagesMixer::nodeKilled);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::MessagesData, this, "handleMessages");
packetReceiver.registerListener(PacketType::MessagesSubscribe, this, "handleMessagesSubscribe");
packetReceiver.registerListener(PacketType::MessagesUnsubscribe, this, "handleMessagesUnsubscribe");
packetReceiver.registerListener(PacketType::MessagesData,
PacketReceiver::makeSourcedListenerReference<MessagesMixer>(this, &MessagesMixer::handleMessages));
packetReceiver.registerListener(PacketType::MessagesSubscribe,
PacketReceiver::makeSourcedListenerReference<MessagesMixer>(this, &MessagesMixer::handleMessagesSubscribe));
packetReceiver.registerListener(PacketType::MessagesUnsubscribe,
PacketReceiver::makeSourcedListenerReference<MessagesMixer>(this, &MessagesMixer::handleMessagesUnsubscribe));
}
void MessagesMixer::nodeKilled(SharedNodePointer killedNode) {

View file

@ -1122,8 +1122,10 @@ void OctreeServer::run() {
void OctreeServer::domainSettingsRequestComplete() {
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket");
packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket");
packetReceiver.registerListener(PacketType::OctreeDataNack,
PacketReceiver::makeSourcedListenerReference<OctreeServer>(this, &OctreeServer::handleOctreeDataNackPacket));
packetReceiver.registerListener(getMyQueryMessageType(),
PacketReceiver::makeSourcedListenerReference<OctreeServer>(this, &OctreeServer::handleOctreeQueryPacket));
qDebug(octree_server) << "Received domain settings";

View file

@ -83,13 +83,18 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket");
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleOctreePacket));
packetReceiver.registerListener(PacketType::SelectedAudioFormat,
PacketReceiver::makeUnsourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleSelectedAudioFormat));
packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket");
packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket");
packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket");
packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket");
packetReceiver.registerListener(PacketType::ReloadEntityServerScript,
PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleReloadEntityServerScriptPacket));
packetReceiver.registerListener(PacketType::EntityScriptGetStatus,
PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityScriptGetStatusPacket));
packetReceiver.registerListener(PacketType::EntityServerScriptLog,
PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityServerScriptLogPacket));
packetReceiver.registerListener(PacketType::EntityScriptCallMethod,
PacketReceiver::makeSourcedListenerReference<EntityScriptServer>(this, &EntityScriptServer::handleEntityScriptCallMethodPacket));
static const int LOG_INTERVAL = MSECS_PER_SECOND / 10;
auto timer = new QTimer(this);

View file

@ -202,9 +202,9 @@
; The Inner invocation has written an uninstaller binary for us.
; We need to sign it if it's a production or PR build.
!if @PRODUCTION_BUILD@ == 1
!if @BYPASS_SIGNING@ == 1
!if @BYPASS_SIGNING@ == TRUE
!warning "BYPASS_SIGNING set - installer will not be signed"
!else
!else
!system '"@SIGNTOOL_EXECUTABLE@" sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://timestamp.comodoca.com?td=sha256 /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0
!endif
!endif

View file

@ -500,6 +500,31 @@ function prepareAccessTokenPrompt(callback) {
});
}
function createDomainIDPrompt(callback) {
swal({
title: 'Finish Registering Domain',
type: 'input',
text: 'Enter a label for this machine.</br></br>This will help you identify which domain ID belongs to which machine.</br></br>This is a required step for registration.</br></br>',
showCancelButton: true,
confirmButtonText: "Create",
closeOnConfirm: false,
html: true
}, function (inputValue) {
if (inputValue === false) {
return false;
}
if (inputValue === "") {
swal.showInputError("Please enter a valid label for your machine.");
return false;
}
if (callback) {
callback(inputValue);
}
});
}
function getMetaverseUrl(callback) {
$.ajax('/api/metaverse_info', {
success: function(data) {

View file

@ -40,6 +40,8 @@ $(document).ready(function(){
// call our method to setup the place names table
setupPlacesTable();
// hide the places table for now because we do not want that interacted with from the domain-server
$('#' + Settings.PLACES_TABLE_ID).hide();
setupDomainNetworkingSettings();
// setupDomainLabelSetting();
@ -363,7 +365,7 @@ $(document).ready(function(){
confirmButtonText: "Create",
closeOnConfirm: false,
html: true
}, function(inputValue){
}, function (inputValue) {
if (inputValue === false) {
swal.close();
@ -373,7 +375,7 @@ $(document).ready(function(){
}
} else {
// we're going to change the alert to a new one with a spinner while we create this domain
showSpinnerAlert('Creating domain ID');
// showSpinnerAlert('Creating domain ID');
createNewDomainID(inputValue, justConnected);
}
});
@ -385,7 +387,12 @@ $(document).ready(function(){
"label": label
}
$.post("/api/domains", domainJSON, function(data){
$.post("/api/domains", domainJSON, function(data) {
if (data.status === "failure") {
failedToCreateDomainID(data, justConnected);
return;
}
// we successfully created a domain ID, set it on that field
var domainID = data.domain.domainId;
console.log("Setting domain id to ", data, domainID);
@ -406,40 +413,50 @@ $(document).ready(function(){
text: successText,
html: true,
confirmButtonText: 'Save'
}, function(){
}, function () {
saveSettings();
});
}, 'json').fail(function(){
}, 'json').fail(function (data) {
failedToCreateDomainID(data, justConnected);
});
}
function failedToCreateDomainID(data, justConnected) {
var errorText = "There was a problem creating your new domain ID. Do you want to try again or";
var errorText = "There was a problem creating your new domain ID. Do you want to try again or";
if (data && data.status === "failure") {
errorText = "Error: " + data.error + "</br>Do you want to try again or";
console.log("Error: " + data.error);
} else {
console.log("Error: Failed to post to metaverse.");
}
if (justConnected) {
errorText += " just save your new access token?</br></br>You can always create a new domain ID later.";
if (justConnected) {
errorText += " just save your new access token?</br></br>You can always create a new domain ID later.";
} else {
errorText += " cancel?"
}
// we failed to create the new domain ID, show a sweet-alert that lets them try again or cancel
swal({
title: '',
type: 'error',
text: errorText,
html: true,
confirmButtonText: 'Try again',
showCancelButton: true,
closeOnConfirm: false
}, function (isConfirm) {
if (isConfirm) {
// they want to try creating a domain ID again
showDomainCreationAlert(justConnected);
} else {
errorText += " cancel?"
}
// we failed to create the new domain ID, show a sweet-alert that lets them try again or cancel
swal({
title: '',
type: 'error',
text: errorText,
html: true,
confirmButtonText: 'Try again',
showCancelButton: true,
closeOnConfirm: false
}, function(isConfirm){
if (isConfirm) {
// they want to try creating a domain ID again
showDomainCreationAlert(justConnected);
} else {
// they want to cancel
if (justConnected) {
// since they just connected we need to save the access token here
saveSettings();
}
// they want to cancel
if (justConnected) {
// since they just connected we need to save the access token here
saveSettings();
}
});
}
});
}
@ -711,8 +728,8 @@ $(document).ready(function(){
name: 'places',
label: 'Places',
html_id: Settings.PLACES_TABLE_ID,
help: "The following places currently point to this domain.</br>To point places to this domain, "
+ " go to the <a href='" + METAVERSE_URL + "/user/places'>My Places</a> "
help: "To point places to this domain, "
+ " go to the <a href='" + METAVERSE_URL + "/user/places'>Places</a> "
+ "page in your Metaverse account.",
read_only: true,
can_add_new_rows: false,
@ -745,9 +762,10 @@ $(document).ready(function(){
var errorEl = createDomainLoadingError("There was an error retrieving your places.");
$("#" + Settings.PLACES_TABLE_ID).after(errorEl);
var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name');
temporaryPlaceButton.hide();
$('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton);
// DISABLE TEMP PLACE NAME BUTTON...
// var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name');
// temporaryPlaceButton.hide();
// $('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton);
if (accessTokenIsSet()) {
appendAddButtonToPlacesTable();
}

View file

@ -19,7 +19,7 @@
</dl>
</div>
<div class="wizard-step col-md-8 col-centered" style="display: none;">
<!-- <div class="wizard-step col-md-8 col-centered" style="display: none;">
<h4 class="step-title"></h4>
<div class="row">
<div class="col-md-12">
@ -51,7 +51,7 @@
<button type="button" class="btn btn-md btn-block btn-primary next-button">Next</button>
</dd>
</dl>
</div>
</div> -->
<div class="wizard-step col-md-9 col-centered" style="display: none;">
<h4 class="step-title"></h4>
@ -204,7 +204,7 @@
</div>
<br />
<div id="automatic-threading-options-row" class="row">
<p class="col-md-2">
<p class="col-md-12">
<label>
<input id="enable-automatic-threading" name="threading-checkbox" type="checkbox" value="true"> Enable Automatic Threading
</label>

View file

@ -50,6 +50,7 @@ $(document).ready(function(){
prepareAccessTokenPrompt(function(accessToken) {
Metaverse.accessToken = accessToken;
saveAccessToken();
promptToCreateDomainID();
});
});
@ -171,6 +172,45 @@ function setupWizardSteps() {
$(currentStep).show();
}
function promptToCreateDomainID() {
setTimeout(function () {
createDomainIDPrompt(function (label) {
var domainJSON = {
"label": label
};
$.post("/api/domains", domainJSON, function (data) {
if (data.status === "failure") {
swal.showInputError("Error: " + data.error);
return;
}
swal.close();
// we successfully created a domain ID, set it on that field
var domainID = data.domain.domainId;
console.log("Setting domain ID to ", data, domainID);
var formJSON = {
"metaverse": {
"id": domainID
}
};
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
postSettings(formJSON, goToNextStep);
}, 'json').fail(function (data) {
if (data && data.status === "failure") {
swal.showInputError("Error: " + data.error);
} else {
swal.showInputError("Error: Failed to post to metaverse.");
}
console.log("Failed to create domain ID...");
});
});
}, 500); // Apparently swal needs time before opening another prompt.
}
function updatePlaceNameLink(address) {
if (address) {
var url = URLs.PLACE_URL + '/' + address;
@ -341,7 +381,7 @@ function saveAccessToken() {
$(this).blur();
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
postSettings(formJSON, goToNextStep);
postSettings(formJSON);
}
function getSettingDescriptionForKey(groupKey, settingKey) {

View file

@ -17,7 +17,8 @@
#include <openssl/x509.h>
#include <random>
#include <QDataStream>
#include <QtCore/QDataStream>
#include <QtCore/QMetaMethod>
#include <AccountManager.h>
#include <Assignment.h>

View file

@ -778,32 +778,51 @@ void DomainServer::setupNodeListAndAssignments() {
// register as the packet receiver for the types we want
PacketReceiver& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::RequestAssignment, this, "processRequestAssignmentPacket");
packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket");
packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket");
packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket");
packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket");
packetReceiver.registerListener(PacketType::AvatarZonePresence, this, "processAvatarZonePresencePacket");
packetReceiver.registerListener(PacketType::RequestAssignment,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processRequestAssignmentPacket));
packetReceiver.registerListener(PacketType::DomainListRequest,
PacketReceiver::makeSourcedListenerReference<DomainServer>(this, &DomainServer::processListRequestPacket));
packetReceiver.registerListener(PacketType::DomainServerPathQuery,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processPathQueryPacket));
packetReceiver.registerListener(PacketType::NodeJsonStats,
PacketReceiver::makeSourcedListenerReference<DomainServer>(this, &DomainServer::processNodeJSONStatsPacket));
packetReceiver.registerListener(PacketType::DomainDisconnectRequest,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processNodeDisconnectRequestPacket));
packetReceiver.registerListener(PacketType::AvatarZonePresence,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processAvatarZonePresencePacket));
// NodeList won't be available to the settings manager when it is created, so call registerListener here
packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket");
packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket");
packetReceiver.registerListener(PacketType::UsernameFromIDRequest, &_settingsManager, "processUsernameFromIDRequestPacket");
packetReceiver.registerListener(PacketType::DomainSettingsRequest,
PacketReceiver::makeUnsourcedListenerReference<DomainServerSettingsManager>(&_settingsManager, &DomainServerSettingsManager::processSettingsRequestPacket));
packetReceiver.registerListener(PacketType::NodeKickRequest,
PacketReceiver::makeSourcedListenerReference<DomainServerSettingsManager>(&_settingsManager, &DomainServerSettingsManager::processNodeKickRequestPacket));
packetReceiver.registerListener(PacketType::UsernameFromIDRequest,
PacketReceiver::makeSourcedListenerReference<DomainServerSettingsManager>(&_settingsManager, &DomainServerSettingsManager::processUsernameFromIDRequestPacket));
// register the gatekeeper for the packets it needs to receive
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket");
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket");
packetReceiver.registerListener(PacketType::DomainConnectRequest,
PacketReceiver::makeUnsourcedListenerReference<DomainGatekeeper>(&_gatekeeper, &DomainGatekeeper::processConnectRequestPacket));
packetReceiver.registerListener(PacketType::ICEPing,
PacketReceiver::makeUnsourcedListenerReference<DomainGatekeeper>(&_gatekeeper, &DomainGatekeeper::processICEPingPacket));
packetReceiver.registerListener(PacketType::ICEPingReply,
PacketReceiver::makeUnsourcedListenerReference<DomainGatekeeper>(&_gatekeeper, &DomainGatekeeper::processICEPingReplyPacket));
packetReceiver.registerListener(PacketType::ICEServerPeerInformation,
PacketReceiver::makeUnsourcedListenerReference<DomainGatekeeper>(&_gatekeeper, &DomainGatekeeper::processICEPeerInformationPacket));
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket");
packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK");
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processICEServerHeartbeatDenialPacket));
packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processICEServerHeartbeatACK));
packetReceiver.registerListener(PacketType::OctreeDataFileRequest, this, "processOctreeDataRequestMessage");
packetReceiver.registerListener(PacketType::OctreeDataPersist, this, "processOctreeDataPersistMessage");
packetReceiver.registerListener(PacketType::OctreeDataFileRequest,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processOctreeDataRequestMessage));
packetReceiver.registerListener(PacketType::OctreeDataPersist,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::processOctreeDataPersistMessage));
packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacementRequest");
packetReceiver.registerListener(PacketType::DomainContentReplacementFromUrl, this, "handleDomainContentReplacementFromURLRequest");
packetReceiver.registerListener(PacketType::OctreeFileReplacement,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::handleOctreeFileReplacementRequest));
packetReceiver.registerListener(PacketType::DomainContentReplacementFromUrl,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this, &DomainServer::handleDomainContentReplacementFromURLRequest));
// set a custom packetVersionMatch as the verify packet operator for the udt::Socket
nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified);

View file

@ -212,6 +212,8 @@ private:
/// guard read/write access from multiple threads to settings
QReadWriteLock _settingsLock { QReadWriteLock::Recursive };
friend class DomainServer;
};
#endif // hifi_DomainServerSettingsManager_h

View file

@ -333,6 +333,8 @@ Rectangle {
anchors.left: parent.left;
anchors.right: parent.right;
height: childrenRect.height;
// FIXME: Reuse or remove wallet-related code.
visible: false;
Rectangle {
id: walletHeaderContainer;

View file

@ -288,8 +288,10 @@ Wallet::Wallet() {
auto& packetReceiver = nodeList->getPacketReceiver();
_passphrase = new QString("");
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket");
packetReceiver.registerListener(PacketType::ChallengeOwnership,
PacketReceiver::makeSourcedListenerReference<Wallet>(this, &Wallet::handleChallengeOwnershipPacket));
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest,
PacketReceiver::makeSourcedListenerReference<Wallet>(this, &Wallet::handleChallengeOwnershipPacket));
connect(ledger.data(), &Ledger::accountResult, this, [](QJsonObject result) {
auto wallet = DependencyManager::get<Wallet>();

View file

@ -25,7 +25,8 @@ OctreePacketProcessor::OctreePacketProcessor():
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
const PacketReceiver::PacketTypeList octreePackets =
{ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase, PacketType::EntityQueryInitialResultsComplete };
packetReceiver.registerDirectListenerForTypes(octreePackets, this, "handleOctreePacket");
packetReceiver.registerDirectListenerForTypes(octreePackets,
PacketReceiver::makeSourcedListenerReference<OctreePacketProcessor>(this, &OctreePacketProcessor::handleOctreePacket));
}
OctreePacketProcessor::~OctreePacketProcessor() { }

View file

@ -44,7 +44,8 @@ ScreenshareScriptingInterface::ScreenshareScriptingInterface() {
// This packet listener handles the packet containing information about the latest zone ID in which we are allowed to share.
auto nodeList = DependencyManager::get<NodeList>();
PacketReceiver& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::AvatarZonePresence, this, "processAvatarZonePresencePacketOnClient");
packetReceiver.registerListener(PacketType::AvatarZonePresence,
PacketReceiver::makeUnsourcedListenerReference<ScreenshareScriptingInterface>(this, &ScreenshareScriptingInterface::processAvatarZonePresencePacketOnClient));
};
ScreenshareScriptingInterface::~ScreenshareScriptingInterface() {

View file

@ -9,7 +9,8 @@
//
#include "DomainConnectionModel.h"
#include <QLoggingCategory>
#include <QtCore/QLoggingCategory>
#include <QtCore/QMetaMethod>
#include <NodeList.h>
#include <NumericalConstants.h>

View file

@ -81,7 +81,8 @@ ContextOverlayInterface::ContextOverlayInterface() {
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, this, "handleChallengeOwnershipReplyPacket");
packetReceiver.registerListener(PacketType::ChallengeOwnershipReply,
PacketReceiver::makeSourcedListenerReference<ContextOverlayInterface>(this, &ContextOverlayInterface::handleChallengeOwnershipReplyPacket));
_challengeOwnershipTimeoutTimer.setSingleShot(true);
}

View file

@ -369,13 +369,20 @@ AudioClient::AudioClient() {
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::AudioStreamStats, &_stats, "processStreamStatsPacket");
packetReceiver.registerListener(PacketType::AudioEnvironment, this, "handleAudioEnvironmentDataPacket");
packetReceiver.registerListener(PacketType::SilentAudioFrame, this, "handleAudioDataPacket");
packetReceiver.registerListener(PacketType::MixedAudio, this, "handleAudioDataPacket");
packetReceiver.registerListener(PacketType::NoisyMute, this, "handleNoisyMutePacket");
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
packetReceiver.registerListener(PacketType::AudioStreamStats,
PacketReceiver::makeSourcedListenerReference<AudioIOStats>(&_stats, &AudioIOStats::processStreamStatsPacket));
packetReceiver.registerListener(PacketType::AudioEnvironment,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleAudioEnvironmentDataPacket));
packetReceiver.registerListener(PacketType::SilentAudioFrame,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleAudioDataPacket));
packetReceiver.registerListener(PacketType::MixedAudio,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleAudioDataPacket));
packetReceiver.registerListener(PacketType::NoisyMute,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleNoisyMutePacket));
packetReceiver.registerListener(PacketType::MuteEnvironment,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleMuteEnvironmentPacket));
packetReceiver.registerListener(PacketType::SelectedAudioFormat,
PacketReceiver::makeUnsourcedListenerReference<AudioClient>(this, &AudioClient::handleSelectedAudioFormat));
auto& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] {

View file

@ -123,10 +123,14 @@ AvatarHashMap::AvatarHashMap() {
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar");
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket");
packetReceiver.registerListener(PacketType::BulkAvatarTraits, this, "processBulkAvatarTraits");
packetReceiver.registerListener(PacketType::BulkAvatarData,
PacketReceiver::makeSourcedListenerReference<AvatarHashMap>(this, &AvatarHashMap::processAvatarDataPacket));
packetReceiver.registerListener(PacketType::KillAvatar,
PacketReceiver::makeSourcedListenerReference<AvatarHashMap>(this, &AvatarHashMap::processKillAvatar));
packetReceiver.registerListener(PacketType::AvatarIdentity,
PacketReceiver::makeSourcedListenerReference<AvatarHashMap>(this, &AvatarHashMap::processAvatarIdentityPacket));
packetReceiver.registerListener(PacketType::BulkAvatarTraits,
PacketReceiver::makeSourcedListenerReference<AvatarHashMap>(this, &AvatarHashMap::processBulkAvatarTraits));
connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged);

View file

@ -28,7 +28,8 @@ ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) :
}
});
nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, this, "processTraitOverride");
nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits,
PacketReceiver::makeSourcedListenerReference<ClientTraitsHandler>(this, &ClientTraitsHandler::processTraitOverride));
}
void ClientTraitsHandler::markTraitUpdated(AvatarTraits::TraitType updatedTrait) {

View file

@ -26,7 +26,8 @@
EntityEditPacketSender::EntityEditPacketSender() {
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerDirectListener(PacketType::EntityEditNack, this, "processEntityEditNackPacket");
packetReceiver.registerDirectListener(PacketType::EntityEditNack,
PacketReceiver::makeSourcedListenerReference<EntityEditPacketSender>(this, &EntityEditPacketSender::processEntityEditNackPacket));
}
void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {

View file

@ -14,7 +14,8 @@
EntityScriptServerLogClient::EntityScriptServerLogClient() {
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket");
packetReceiver.registerListener(PacketType::EntityServerScriptLog,
PacketReceiver::makeSourcedListenerReference<EntityScriptServerLogClient>(this, &EntityScriptServerLogClient::handleEntityServerScriptLogPacket));
QObject::connect(nodeList.data(), &NodeList::nodeActivated, this, &EntityScriptServerLogClient::nodeActivated);
QObject::connect(nodeList.data(), &NodeList::nodeKilled, this, &EntityScriptServerLogClient::nodeKilled);

View file

@ -59,7 +59,8 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership
connect(nodeList.data(), &NodeList::canGetAndSetPrivateUserDataChanged, this, &EntityScriptingInterface::canGetAndSetPrivateUserDataChanged);
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket");
packetReceiver.registerListener(PacketType::EntityScriptCallMethod,
PacketReceiver::makeSourcedListenerReference<EntityScriptingInterface>(this, &EntityScriptingInterface::handleEntityScriptCallMethodPacket));
}
void EntityScriptingInterface::queueEntityMessage(PacketType packetType,

View file

@ -74,6 +74,10 @@ static void* getGlProcessAddress(const char *namez) {
#else
typedef Bool (*PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC) (int attribute, unsigned int *value);
PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC QueryCurrentRendererIntegerMESA;
static void* getGlProcessAddress(const char *namez) {
return (void*)glXGetProcAddressARB((const GLubyte*)namez);
}
@ -92,6 +96,10 @@ void gl::initModuleGl() {
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)getGlProcessAddress("wglCreateContextAttribsARB");
#endif
#if defined(Q_OS_LINUX)
QueryCurrentRendererIntegerMESA = (PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC)getGlProcessAddress("glXQueryCurrentRendererIntegerMESA");
#endif
#if defined(USE_GLES)
gladLoadGLES2Loader(getGlProcessAddress);
#else
@ -124,3 +132,14 @@ void gl::setSwapInterval(int interval) {
Q_UNUSED(interval);
#endif
}
bool gl::queryCurrentRendererIntegerMESA(int attr, unsigned int *value) {
#if defined(Q_OS_LINUX)
if (QueryCurrentRendererIntegerMESA) {
return QueryCurrentRendererIntegerMESA(attr, value);
}
#endif
*value = 0;
return false;
}

View file

@ -52,6 +52,7 @@ namespace gl {
void initModuleGl();
int getSwapInterval();
void setSwapInterval(int swapInterval);
bool queryCurrentRendererIntegerMESA(int attr, unsigned int *value);
}
#endif // hifi_gpu_GPUConfig_h

View file

@ -10,11 +10,13 @@
//
#include "GLBackend.h"
#include <mutex>
#include <queue>
#include <list>
#include <functional>
#include <glm/gtc/type_ptr.hpp>
#include "gl/Config.h"
#if defined(NSIGHT_FOUND)
#include "nvToolsExt.h"
@ -105,13 +107,27 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
};
#define GL_GET_INTEGER(NAME) glGetIntegerv(GL_##NAME, &const_cast<GLint&>(NAME));
#define BYTES_PER_KIB 1024L
#define BYTES_PER_MIB (1024L * BYTES_PER_KIB)
GLint GLBackend::MAX_TEXTURE_IMAGE_UNITS{ 0 };
GLint GLBackend::MAX_UNIFORM_BUFFER_BINDINGS{ 0 };
GLint GLBackend::MAX_COMBINED_UNIFORM_BLOCKS{ 0 };
GLint GLBackend::MAX_COMBINED_TEXTURE_IMAGE_UNITS{ 0 };
GLint GLBackend::MAX_UNIFORM_BLOCK_SIZE{ 0 };
GLint GLBackend::UNIFORM_BUFFER_OFFSET_ALIGNMENT{ 1 };
GLint GLBackend::GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX{ 0 };
GLint GLBackend::GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX{ 0 };
GLint GLBackend::GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX{ 0 };
GLint GLBackend::TEXTURE_FREE_MEMORY_ATI{ 0 };
size_t GLBackend::_totalMemory{ 0 };
size_t GLBackend::_dedicatedMemory{ 0 };
GLBackend::VideoCardType GLBackend::_videoCard{ GLBackend::Unknown };
#define GLX_RENDERER_VIDEO_MEMORY_MESA 0x8187
void GLBackend::init() {
static std::once_flag once;
@ -132,13 +148,59 @@ void GLBackend::init() {
GL_GET_INTEGER(MAX_UNIFORM_BLOCK_SIZE);
GL_GET_INTEGER(UNIFORM_BUFFER_OFFSET_ALIGNMENT);
GPUIdent* gpu = GPUIdent::getInstance(vendor, renderer);
unsigned int mem;
if (vendor.contains("NVIDIA") ) {
qCDebug(gpugllogging) << "NVIDIA card detected";
GL_GET_INTEGER(GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX);
GL_GET_INTEGER(GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX);
GL_GET_INTEGER(GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX);
qCDebug(gpugllogging) << "GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX: " << GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX;
qCDebug(gpugllogging) << "GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX: " << GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX;
qCDebug(gpugllogging) << "GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX: " << GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX;
_totalMemory = GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX * BYTES_PER_KIB;
_dedicatedMemory = GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX * BYTES_PER_KIB;
_videoCard = NVIDIA;
} else if (vendor.contains("ATI")) {
qCDebug(gpugllogging) << "ATI card detected";
GL_GET_INTEGER(TEXTURE_FREE_MEMORY_ATI);
_totalMemory = TEXTURE_FREE_MEMORY_ATI * BYTES_PER_KIB;
_dedicatedMemory = _totalMemory;
_videoCard = ATI;
} else if ( ::gl::queryCurrentRendererIntegerMESA(GLX_RENDERER_VIDEO_MEMORY_MESA, &mem) ) {
// This works only on Linux. queryCurrentRendererIntegerMESA will return false if the
// function is not supported because we're not on Linux, or for any other reason.
qCDebug(gpugllogging) << "MESA card detected";
_totalMemory = mem * BYTES_PER_MIB;
_dedicatedMemory = _totalMemory;
_videoCard = MESA;
} else {
qCCritical(gpugllogging) << "Don't know how to get memory for OpenGL vendor " << vendor << "; renderer " << renderer << ", trying fallback";
_videoCard = Unknown;
_dedicatedMemory = gpu->getMemory();
_totalMemory = _dedicatedMemory;
}
qCDebug(gpugllogging) << "dedicated: " << _dedicatedMemory;
qCDebug(gpugllogging) << "total: " << _totalMemory;
LOG_GL_CONTEXT_INFO(gpugllogging, contextInfo);
GPUIdent* gpu = GPUIdent::getInstance(vendor, renderer);
// From here on, GPUIdent::getInstance()->getMumble() should efficiently give the same answers.
qCDebug(gpugllogging) << "GPU:";
qCDebug(gpugllogging) << "\tcard:" << gpu->getName();
qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver();
qCDebug(gpugllogging) << "\tdedicated memory:" << gpu->getMemory() << "MB";
qCDebug(gpugllogging) << "\ttotal memory:" << (_totalMemory / BYTES_PER_KIB) << "KB";
qCDebug(gpugllogging) << "\tdedicated memory:" << (_dedicatedMemory / BYTES_PER_KIB) << "KB";
qCDebug(gpugllogging) << "\tavailable memory:" << (getAvailableMemory() / BYTES_PER_KIB) << "KB";
qCDebug(gpugllogging) << "Limits:";
qCDebug(gpugllogging) << "\tmax textures:" << MAX_TEXTURE_IMAGE_UNITS;
qCDebug(gpugllogging) << "\tmax texture binding:" << MAX_COMBINED_TEXTURE_IMAGE_UNITS;
@ -152,6 +214,41 @@ void GLBackend::init() {
});
}
size_t GLBackend::getAvailableMemory() {
GLint mem;
switch( _videoCard ) {
case NVIDIA:
glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &mem);
return mem * BYTES_PER_KIB;
case ATI:
glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, &mem);
return mem * BYTES_PER_KIB;
case MESA:
return 0; // Don't know the current value
case Unknown:
break;
}
return 0;
}
bool GLBackend::availableMemoryKnown() {
switch( _videoCard ) {
case NVIDIA:
return true;
case ATI:
return true;
case MESA:
return false;
case Unknown:
return false;
}
return false;
}
GLBackend::GLBackend(bool syncCache) {
_pipeline._cameraCorrectionBuffer._buffer->flush();
initShaderBinaryCache();

View file

@ -67,6 +67,13 @@ protected:
GLBackend();
public:
enum VideoCardType {
ATI,
NVIDIA,
MESA,
Unknown
};
#if defined(USE_GLES)
// https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glGet.xhtml
static const GLint MIN_REQUIRED_TEXTURE_IMAGE_UNITS = 16;
@ -89,6 +96,25 @@ public:
static GLint MAX_COMBINED_TEXTURE_IMAGE_UNITS;
static GLint MAX_UNIFORM_BLOCK_SIZE;
static GLint UNIFORM_BUFFER_OFFSET_ALIGNMENT;
static GLint GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX;
static GLint GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX;
static GLint GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX;
static GLint TEXTURE_FREE_MEMORY_ATI;
static size_t _totalMemory;
static size_t _dedicatedMemory;
static VideoCardType _videoCard;
static size_t getTotalMemory() { return _totalMemory; }
static size_t getDedicatedMemory() { return _dedicatedMemory; }
static size_t getAvailableMemory();
static bool availableMemoryKnown();
virtual ~GLBackend();

View file

@ -19,6 +19,8 @@
#define MAX_RESOURCE_TEXTURES_PER_FRAME 2
#define NO_BUFFER_WORK_SLEEP_TIME_MS 2
#define THREADED_TEXTURE_BUFFERING 1
#define MAX_AUTO_FRACTION_OF_TOTAL_MEMORY 0.8f
#define AUTO_RESERVE_TEXTURE_MEMORY MB_TO_BYTES(64)
static const size_t DEFAULT_ALLOWED_TEXTURE_MEMORY = MB_TO_BYTES(DEFAULT_ALLOWED_TEXTURE_MEMORY_MB);
@ -183,9 +185,25 @@ void GLTextureTransferEngineDefault::manageMemory() {
void GLTextureTransferEngineDefault::updateMemoryPressure() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
bool useAvailableGlMemory = false;
size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage();
if (0 == allowedMemoryAllocation) {
allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY;
// Automatic allocation
if (GLBackend::availableMemoryKnown()) {
// If we know how much is free, then we use that
useAvailableGlMemory = true;
} else {
// We don't know how much is free, so leave some reasonable spare room
// and hope it works.
allowedMemoryAllocation = GLBackend::getTotalMemory() * MAX_AUTO_FRACTION_OF_TOTAL_MEMORY;
if (0 == allowedMemoryAllocation) {
// Last resort, if we failed to detect
allowedMemoryAllocation = DEFAULT_ALLOWED_TEXTURE_MEMORY;
}
}
}
// Clear any defunct textures (weak pointers that no longer have a valid texture)
@ -205,7 +223,7 @@ void GLTextureTransferEngineDefault::updateMemoryPressure() {
idealMemoryAllocation += texture->evalTotalSize();
// Track how much we're actually using
totalVariableMemoryAllocation += gltexture->size();
if (vartexture->canDemote()) {
if (!gltexture->_gpuObject.getImportant() && vartexture->canDemote()) {
canDemote |= true;
}
if (vartexture->canPromote()) {
@ -218,7 +236,22 @@ void GLTextureTransferEngineDefault::updateMemoryPressure() {
Backend::textureResourceIdealGPUMemSize.set(idealMemoryAllocation);
size_t unallocated = idealMemoryAllocation - totalVariableMemoryAllocation;
float pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation;
float pressure = 0;
if (useAvailableGlMemory) {
size_t totalMem = GLBackend::getTotalMemory();
size_t availMem = GLBackend::getAvailableMemory();
if (availMem >= AUTO_RESERVE_TEXTURE_MEMORY) {
availMem -= AUTO_RESERVE_TEXTURE_MEMORY;
} else {
availMem = 0;
}
pressure = ((float)totalMem - (float)availMem) / (float)totalMem;
} else {
pressure = (float)totalVariableMemoryAllocation / (float)allowedMemoryAllocation;
}
// If we're oversubscribed we need to demote textures IMMEDIATELY
if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
@ -470,7 +503,7 @@ void GLTextureTransferEngineDefault::processDemotes(size_t reliefRequired, const
for (const auto& texture : strongTextures) {
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vargltexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
if (vargltexture->canDemote()) {
if (!gltexture->_gpuObject.getImportant() && vargltexture->canDemote()) {
demoteQueue.push({ texture, (float)gltexture->size() });
}
}

View file

@ -571,6 +571,9 @@ public:
void setExternalRecycler(const ExternalRecycler& recycler);
ExternalRecycler getExternalRecycler() const;
bool getImportant() const { return _important; }
void setImportant(bool important) { _important = important; }
const GPUObjectPointer gpuObject {};
ExternalUpdates getUpdates() const;
@ -632,6 +635,7 @@ protected:
bool _autoGenerateMips = false;
bool _isIrradianceValid = false;
bool _defined = false;
bool _important = false;
static TexturePointer create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips, const Sampler& sampler);

View file

@ -43,10 +43,14 @@ AssetClient::AssetClient() {
auto nodeList = DependencyManager::get<LimitedNodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssetMappingOperationReply, this, "handleAssetMappingOperationReply");
packetReceiver.registerListener(PacketType::AssetGetInfoReply, this, "handleAssetGetInfoReply");
packetReceiver.registerListener(PacketType::AssetGetReply, this, "handleAssetGetReply", true);
packetReceiver.registerListener(PacketType::AssetUploadReply, this, "handleAssetUploadReply");
packetReceiver.registerListener(PacketType::AssetMappingOperationReply,
PacketReceiver::makeSourcedListenerReference<AssetClient>(this, &AssetClient::handleAssetMappingOperationReply));
packetReceiver.registerListener(PacketType::AssetGetInfoReply,
PacketReceiver::makeSourcedListenerReference<AssetClient>(this, &AssetClient::handleAssetGetInfoReply));
packetReceiver.registerListener(PacketType::AssetGetReply,
PacketReceiver::makeSourcedListenerReference<AssetClient>(this, &AssetClient::handleAssetGetReply), true);
packetReceiver.registerListener(PacketType::AssetUploadReply,
PacketReceiver::makeSourcedListenerReference<AssetClient>(this, &AssetClient::handleAssetUploadReply));
connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &AssetClient::handleNodeKilled);
connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset,

View file

@ -76,9 +76,9 @@ const quint16 DOMAIN_SERVER_EXPORTER_PORT =
const quint16 DOMAIN_SERVER_METADATA_EXPORTER_PORT =
QProcessEnvironment::systemEnvironment()
.contains("DOMAIN_SERVER_METADATA_EXPORTER_PORT")
.contains("VIRCADIA_DOMAIN_SERVER_METADATA_EXPORTER_PORT")
? QProcessEnvironment::systemEnvironment()
.value("DOMAIN_SERVER_METADATA_EXPORTER_PORT")
.value("VIRCADIA_DOMAIN_SERVER_METADATA_EXPORTER_PORT")
.toUInt()
: 9704;

View file

@ -34,7 +34,8 @@ EntityScriptClient::EntityScriptClient() {
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::EntityScriptGetStatusReply, this, "handleGetScriptStatusReply");
packetReceiver.registerListener(PacketType::EntityScriptGetStatusReply,
PacketReceiver::makeSourcedListenerReference<EntityScriptClient>(this, &EntityScriptClient::handleGetScriptStatusReply));
connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &EntityScriptClient::handleNodeKilled);
connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset,

View file

@ -13,6 +13,7 @@
#include <QtCore/QFile>
#include <QtCore/QFileSelector>
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <StatTracker.h>
@ -54,6 +55,7 @@ void FileResourceRequest::doSend() {
} else {
QFile file(filename);
if (file.exists()) {
setProperty("last-modified", toHttpDateString(QFileInfo(file).lastModified().toMSecsSinceEpoch()));
if (file.open(QFile::ReadOnly)) {
if (file.size() < _byteRange.fromInclusive || file.size() < _byteRange.toExclusive) {

View file

@ -28,7 +28,8 @@ MessagesClient::MessagesClient() {
});
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::MessagesData, this, "handleMessagesPacket");
packetReceiver.registerListener(PacketType::MessagesData,
PacketReceiver::makeSourcedListenerReference<MessagesClient>(this, &MessagesClient::handleMessagesPacket));
connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated);
}

View file

@ -139,20 +139,34 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
startSTUNPublicSocketUpdate();
auto& packetReceiver = getPacketReceiver();
packetReceiver.registerListener(PacketType::DomainList, this, "processDomainServerList");
packetReceiver.registerListener(PacketType::Ping, this, "processPingPacket");
packetReceiver.registerListener(PacketType::PingReply, this, "processPingReplyPacket");
packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket");
packetReceiver.registerListener(PacketType::DomainServerAddedNode, this, "processDomainServerAddedNode");
packetReceiver.registerListener(PacketType::DomainServerConnectionToken, this, "processDomainServerConnectionTokenPacket");
packetReceiver.registerListener(PacketType::DomainConnectionDenied, &_domainHandler, "processDomainServerConnectionDeniedPacket");
packetReceiver.registerListener(PacketType::DomainSettings, &_domainHandler, "processSettingsPacketList");
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_domainHandler, "processICEResponsePacket");
packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket");
packetReceiver.registerListener(PacketType::ICEPingReply, &_domainHandler, "processICEPingReplyPacket");
packetReceiver.registerListener(PacketType::DomainServerPathResponse, this, "processDomainServerPathResponse");
packetReceiver.registerListener(PacketType::DomainServerRemovedNode, this, "processDomainServerRemovedNode");
packetReceiver.registerListener(PacketType::UsernameFromIDReply, this, "processUsernameFromIDReply");
packetReceiver.registerListener(PacketType::DomainList,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerList));
packetReceiver.registerListener(PacketType::Ping,
PacketReceiver::makeSourcedListenerReference<NodeList>(this, &NodeList::processPingPacket));
packetReceiver.registerListener(PacketType::PingReply,
PacketReceiver::makeSourcedListenerReference<NodeList>(this, &NodeList::processPingReplyPacket));
packetReceiver.registerListener(PacketType::ICEPing,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processICEPingPacket));
packetReceiver.registerListener(PacketType::DomainServerAddedNode,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerAddedNode));
packetReceiver.registerListener(PacketType::DomainServerConnectionToken,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerConnectionTokenPacket));
packetReceiver.registerListener(PacketType::DomainConnectionDenied,
PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processDomainServerConnectionDeniedPacket));
packetReceiver.registerListener(PacketType::DomainSettings,
PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processSettingsPacketList));
packetReceiver.registerListener(PacketType::ICEServerPeerInformation,
PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processICEResponsePacket));
packetReceiver.registerListener(PacketType::DomainServerRequireDTLS,
PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processDTLSRequirementPacket));
packetReceiver.registerListener(PacketType::ICEPingReply,
PacketReceiver::makeUnsourcedListenerReference<DomainHandler>(&_domainHandler, &DomainHandler::processICEPingReplyPacket));
packetReceiver.registerListener(PacketType::DomainServerPathResponse,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerPathResponse));
packetReceiver.registerListener(PacketType::DomainServerRemovedNode,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerRemovedNode));
packetReceiver.registerListener(PacketType::UsernameFromIDReply,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processUsernameFromIDReply));
}
qint64 NodeList::sendStats(QJsonObject statsObject, HifiSockAddr destination) {

View file

@ -12,7 +12,8 @@
#include "PacketReceiver.h"
#include <QMutexLocker>
#include <QtCore/QMetaObject>
#include <QtCore/QMutexLocker>
#include "DependencyManager.h"
#include "NetworkLogging.h"
@ -25,85 +26,57 @@ PacketReceiver::PacketReceiver(QObject* parent) : QObject(parent) {
qRegisterMetaType<QSharedPointer<ReceivedMessage>>();
}
bool PacketReceiver::registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot) {
bool PacketReceiver::ListenerReference::invokeWithQt(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode) {
ListenerReferencePointer thisPointer = sharedFromThis();
return QMetaObject::invokeMethod(getObject(), [=]() {
thisPointer->invokeDirectly(receivedMessagePointer, sourceNode);
});
}
bool PacketReceiver::registerListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener) {
Q_ASSERT_X(!types.empty(), "PacketReceiver::registerListenerForTypes", "No types to register");
Q_ASSERT_X(listener, "PacketReceiver::registerListenerForTypes", "No object to register");
Q_ASSERT_X(slot, "PacketReceiver::registerListenerForTypes", "No slot to register");
Q_ASSERT_X(listener, "PacketReceiver::registerListenerForTypes", "No listener to register");
// Partition types based on whether they are sourced or not (non sourced in front)
auto middle = std::partition(std::begin(types), std::end(types), [](PacketType type) {
return PacketTypeEnum::getNonSourcedPackets().contains(type);
});
QMetaMethod nonSourcedMethod, sourcedMethod;
// Check we have a valid method for non sourced types if any
if (middle != std::begin(types)) {
nonSourcedMethod = matchingMethodForListener(*std::begin(types), listener, slot);
if (!nonSourcedMethod.isValid()) {
return false;
}
}
// Check we have a valid method for sourced types if any
if (middle != std::end(types)) {
sourcedMethod = matchingMethodForListener(*middle, listener, slot);
if (!sourcedMethod.isValid()) {
return false;
}
}
// Register non sourced types
std::for_each(std::begin(types), middle, [this, &listener, &nonSourcedMethod](PacketType type) {
registerVerifiedListener(type, listener, nonSourcedMethod);
});
// Register sourced types
std::for_each(middle, std::end(types), [this, &listener, &sourcedMethod](PacketType type) {
registerVerifiedListener(type, listener, sourcedMethod);
std::for_each(std::begin(types), std::end(types), [this, &listener](PacketType type) {
registerVerifiedListener(type, listener);
});
return true;
}
void PacketReceiver::registerDirectListener(PacketType type, QObject* listener, const char* slot) {
Q_ASSERT_X(listener, "PacketReceiver::registerDirectListener", "No object to register");
Q_ASSERT_X(slot, "PacketReceiver::registerDirectListener", "No slot to register");
void PacketReceiver::registerDirectListener(PacketType type, const ListenerReferencePointer& listener) {
Q_ASSERT_X(listener, "PacketReceiver::registerDirectListener", "No listener to register");
bool success = registerListener(type, listener, slot);
bool success = registerListener(type, listener);
if (success) {
QMutexLocker locker(&_directConnectSetMutex);
// if we successfully registered, add this object to the set of objects that are directly connected
_directlyConnectedObjects.insert(listener);
_directlyConnectedObjects.insert(listener->getObject());
}
}
void PacketReceiver::registerDirectListenerForTypes(PacketTypeList types,
QObject* listener, const char* slot) {
Q_ASSERT_X(listener, "PacketReceiver::registerDirectListenerForTypes", "No object to register");
Q_ASSERT_X(slot, "PacketReceiver::registerDirectListenerForTypes", "No slot to register");
void PacketReceiver::registerDirectListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener) {
Q_ASSERT_X(listener, "PacketReceiver::registerDirectListenerForTypes", "No listener to register");
// just call register listener for types to start
bool success = registerListenerForTypes(std::move(types), listener, slot);
bool success = registerListenerForTypes(std::move(types), listener);
if (success) {
QMutexLocker locker(&_directConnectSetMutex);
// if we successfully registered, add this object to the set of objects that are directly connected
_directlyConnectedObjects.insert(listener);
_directlyConnectedObjects.insert(listener->getObject());
}
}
bool PacketReceiver::registerListener(PacketType type, QObject* listener, const char* slot,
bool deliverPending) {
Q_ASSERT_X(listener, "PacketReceiver::registerListener", "No object to register");
Q_ASSERT_X(slot, "PacketReceiver::registerListener", "No slot to register");
bool PacketReceiver::registerListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending) {
Q_ASSERT_X(listener, "PacketReceiver::registerListener", "No listener to register");
QMetaMethod matchingMethod = matchingMethodForListener(type, listener, slot);
bool matchingMethod = matchingMethodForListener(type, listener);
if (matchingMethod.isValid()) {
if (matchingMethod) {
qCDebug(networking) << "Registering a packet listener for packet list type" << type;
registerVerifiedListener(type, listener, matchingMethod, deliverPending);
registerVerifiedListener(type, listener, deliverPending);
return true;
} else {
qCWarning(networking) << "FAILED to Register a packet listener for packet list type" << type;
@ -111,62 +84,23 @@ bool PacketReceiver::registerListener(PacketType type, QObject* listener, const
}
}
QMetaMethod PacketReceiver::matchingMethodForListener(PacketType type, QObject* object, const char* slot) const {
Q_ASSERT_X(object, "PacketReceiver::matchingMethodForListener", "No object to call");
Q_ASSERT_X(slot, "PacketReceiver::matchingMethodForListener", "No slot to call");
bool PacketReceiver::matchingMethodForListener(PacketType type, const ListenerReferencePointer& listener) const {
Q_ASSERT_X(listener, "PacketReceiver::matchingMethodForListener", "No listener to call");
// normalize the slot with the expected parameters
static const QString SIGNATURE_TEMPLATE("%1(%2)");
static const QString NON_SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer<ReceivedMessage>";
bool isSourced = listener->isSourced();
bool isNonSourcedPacket = PacketTypeEnum::getNonSourcedPackets().contains(type);
QSet<QString> possibleSignatures {
SIGNATURE_TEMPLATE.arg(slot, NON_SOURCED_MESSAGE_LISTENER_PARAMETERS)
};
if (!PacketTypeEnum::getNonSourcedPackets().contains(type)) {
static const QString SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer<ReceivedMessage>,QSharedPointer<Node>";
static const QString TYPEDEF_SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer<ReceivedMessage>,SharedNodePointer";
// a sourced packet must take the shared pointer to the ReceivedMessage but optionally could include
// a shared pointer to the node
possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, TYPEDEF_SOURCED_MESSAGE_LISTENER_PARAMETERS);
possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, SOURCED_MESSAGE_LISTENER_PARAMETERS);
assert(!isSourced || !isNonSourcedPacket);
if (isSourced && isNonSourcedPacket) {
qCDebug(networking) << "PacketReceiver::registerListener cannot support a sourced listener for type" << type;
return false;
}
int methodIndex = -1;
foreach(const QString& signature, possibleSignatures) {
QByteArray normalizedSlot =
QMetaObject::normalizedSignature(signature.toStdString().c_str());
// does the constructed normalized method exist?
methodIndex = object->metaObject()->indexOfSlot(normalizedSlot.toStdString().c_str());
if (methodIndex >= 0) {
break;
}
}
if (methodIndex < 0) {
qCDebug(networking) << "PacketReceiver::registerListener expected a slot with one of the following signatures:"
<< possibleSignatures.toList() << "- but such a slot was not found."
<< "Could not complete listener registration for type" << type;
}
Q_ASSERT(methodIndex >= 0);
// return the converted QMetaMethod
if (methodIndex >= 0) {
return object->metaObject()->method(methodIndex);
} else {
// if somehow (scripting?) something bad gets in here at runtime that doesn't hit the asserts above
// return a non-valid QMetaMethod
return QMetaMethod();
}
return true;
}
void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object, const QMetaMethod& slot, bool deliverPending) {
Q_ASSERT_X(object, "PacketReceiver::registerVerifiedListener", "No object to register");
void PacketReceiver::registerVerifiedListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending) {
Q_ASSERT_X(listener, "PacketReceiver::registerVerifiedListener", "No listener to register");
QMutexLocker locker(&_packetListenerLock);
if (_messageListenerMap.contains(type)) {
@ -175,7 +109,7 @@ void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object,
}
// add the mapping
_messageListenerMap[type] = { QPointer<QObject>(object), slot, deliverPending };
_messageListenerMap[type] = { listener, deliverPending };
}
void PacketReceiver::unregisterListener(QObject* listener) {
@ -188,7 +122,7 @@ void PacketReceiver::unregisterListener(QObject* listener) {
auto it = _messageListenerMap.begin();
while (it != _messageListenerMap.end()) {
if (it.value().object == listener) {
if (it.value().listener->getObject() == listener) {
it = _messageListenerMap.erase(it);
} else {
++it;
@ -261,7 +195,7 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer<ReceivedMessage> recei
QMutexLocker packetListenerLocker(&_packetListenerLock);
auto it = _messageListenerMap.find(receivedMessage->getType());
if (it != _messageListenerMap.end() && it->method.isValid()) {
if (it != _messageListenerMap.end() && !it->listener.isNull()) {
auto listener = it.value();
@ -271,36 +205,19 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer<ReceivedMessage> recei
bool success = false;
Qt::ConnectionType connectionType;
bool isDirectConnect = false;
// check if this is a directly connected listener
{
QMutexLocker directConnectLocker(&_directConnectSetMutex);
connectionType = _directlyConnectedObjects.contains(listener.object) ? Qt::DirectConnection : Qt::AutoConnection;
isDirectConnect = _directlyConnectedObjects.contains(listener.listener->getObject());
}
QMetaMethod metaMethod = listener.method;
static const QByteArray QSHAREDPOINTER_NODE_NORMALIZED = QMetaObject::normalizedType("QSharedPointer<Node>");
static const QByteArray SHARED_NODE_NORMALIZED = QMetaObject::normalizedType("SharedNodePointer");
// one final check on the QPointer before we go to invoke
if (listener.object) {
if (metaMethod.parameterTypes().contains(SHARED_NODE_NORMALIZED)) {
success = metaMethod.invoke(listener.object,
connectionType,
Q_ARG(QSharedPointer<ReceivedMessage>, receivedMessage),
Q_ARG(SharedNodePointer, matchingNode));
} else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) {
success = metaMethod.invoke(listener.object,
connectionType,
Q_ARG(QSharedPointer<ReceivedMessage>, receivedMessage),
Q_ARG(QSharedPointer<Node>, matchingNode));
if (listener.listener->getObject()) {
if (isDirectConnect) {
success = listener.listener->invokeDirectly(receivedMessage, matchingNode);
} else {
success = metaMethod.invoke(listener.object,
connectionType,
Q_ARG(QSharedPointer<ReceivedMessage>, receivedMessage));
success = listener.listener->invokeWithQt(receivedMessage, matchingNode);
}
} else {
qCDebug(networking).nospace() << "Listener for packet " << receivedMessage->getType()
@ -310,19 +227,19 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer<ReceivedMessage> recei
// if it exists, remove the listener from _directlyConnectedObjects
{
QMutexLocker directConnectLocker(&_directConnectSetMutex);
_directlyConnectedObjects.remove(listener.object);
_directlyConnectedObjects.remove(listener.listener->getObject());
}
}
if (!success) {
qCDebug(networking).nospace() << "Error delivering packet " << receivedMessage->getType() << " to listener "
<< listener.object << "::" << qPrintable(listener.method.methodSignature());
<< listener.listener->getObject();
}
} else if (it == _messageListenerMap.end()) {
qCWarning(networking) << "No listener found for packet type" << receivedMessage->getType();
// insert a dummy listener so we don't print this again
_messageListenerMap.insert(receivedMessage->getType(), { nullptr, QMetaMethod(), false });
_messageListenerMap.insert(receivedMessage->getType(), { ListenerReferencePointer(), false });
}
}

View file

@ -16,12 +16,12 @@
#include <vector>
#include <unordered_map>
#include <QtCore/QMap>
#include <QtCore/QMetaMethod>
#include <QtCore/QMutex>
#include <QtCore/QObject>
#include <QtCore/QPointer>
#include <QtCore/QSet>
#include <QtCore/QSharedPointer>
#include <QtCore/QEnableSharedFromThis>
#include "NLPacket.h"
#include "NLPacketList.h"
@ -29,6 +29,7 @@
#include "udt/PacketHeaders.h"
class EntityEditPacketSender;
class Node;
class OctreePacketProcessor;
namespace std {
@ -42,6 +43,22 @@ namespace std {
class PacketReceiver : public QObject {
Q_OBJECT
public:
class ListenerReference : public QEnableSharedFromThis<ListenerReference> {
public:
virtual bool invokeDirectly(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode) = 0;
bool invokeWithQt(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode);
virtual bool isSourced() const = 0;
virtual QObject* getObject() const = 0;
};
typedef QSharedPointer<ListenerReference> ListenerReferencePointer;
template<class T>
static ListenerReferencePointer makeUnsourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>));
template <class T>
static ListenerReferencePointer makeSourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>, QSharedPointer<Node>));
public:
using PacketTypeList = std::vector<PacketType>;
@ -55,8 +72,8 @@ public:
// If deliverPending is false, ReceivedMessage will only be delivered once all packets for the message have
// been received. If deliverPending is true, ReceivedMessage will be delivered as soon as the first packet
// for the message is received.
bool registerListener(PacketType type, QObject* listener, const char* slot, bool deliverPending = false);
bool registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot);
bool registerListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending = false);
bool registerListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener);
void unregisterListener(QObject* listener);
void handleVerifiedPacket(std::unique_ptr<udt::Packet> packet);
@ -64,9 +81,34 @@ public:
void handleMessageFailure(HifiSockAddr from, udt::Packet::MessageNumber messageNumber);
private:
template <class T>
class UnsourcedListenerReference : public ListenerReference {
public:
inline UnsourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>));
virtual bool invokeDirectly(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode);
virtual bool isSourced() const { return false; }
virtual QObject* getObject() const { return _target; }
private:
QPointer<T> _target;
void (T::*_slot)(QSharedPointer<ReceivedMessage>);
};
template <class T>
class SourcedListenerReference : public ListenerReference {
public:
inline SourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>, QSharedPointer<Node>));
virtual bool invokeDirectly(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode);
virtual bool isSourced() const { return true; }
virtual QObject* getObject() const { return _target; }
private:
QPointer<T> _target;
void (T::*_slot)(QSharedPointer<ReceivedMessage>, QSharedPointer<Node>);
};
struct Listener {
QPointer<QObject> object;
QMetaMethod method;
ListenerReferencePointer listener;
bool deliverPending;
};
@ -74,11 +116,11 @@ private:
// these are brutal hacks for now - ideally GenericThread / ReceivedPacketProcessor
// should be changed to have a true event loop and be able to handle our QMetaMethod::invoke
void registerDirectListenerForTypes(PacketTypeList types, QObject* listener, const char* slot);
void registerDirectListener(PacketType type, QObject* listener, const char* slot);
void registerDirectListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener);
void registerDirectListener(PacketType type, const ListenerReferencePointer& listener);
QMetaMethod matchingMethodForListener(PacketType type, QObject* object, const char* slot) const;
void registerVerifiedListener(PacketType type, QObject* listener, const QMetaMethod& slot, bool deliverPending = false);
bool matchingMethodForListener(PacketType type, const ListenerReferencePointer& listener) const;
void registerVerifiedListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending = false);
QMutex _packetListenerLock;
QHash<PacketType, Listener> _messageListenerMap;
@ -93,4 +135,42 @@ private:
friend class OctreePacketProcessor;
};
template <class T>
PacketReceiver::ListenerReferencePointer PacketReceiver::makeUnsourcedListenerReference(T* target, void (T::* slot)(QSharedPointer<ReceivedMessage>)) {
return QSharedPointer<UnsourcedListenerReference<T>>::create(target, slot);
}
template <class T>
PacketReceiver::ListenerReferencePointer PacketReceiver::makeSourcedListenerReference(T* target, void (T::* slot)(QSharedPointer<ReceivedMessage>, QSharedPointer<Node>)) {
return QSharedPointer<SourcedListenerReference<T>>::create(target, slot);
}
template <class T>
PacketReceiver::UnsourcedListenerReference<T>::UnsourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>)) :
_target(target),_slot(slot) {
}
template <class T>
bool PacketReceiver::UnsourcedListenerReference<T>::invokeDirectly(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>&) {
if (_target.isNull()) {
return false;
}
(_target->*_slot)(receivedMessagePointer);
return true;
}
template <class T>
PacketReceiver::SourcedListenerReference<T>::SourcedListenerReference(T* target, void (T::*slot)(QSharedPointer<ReceivedMessage>, QSharedPointer<Node>)) :
_target(target),_slot(slot) {
}
template <class T>
bool PacketReceiver::SourcedListenerReference<T>::invokeDirectly(const QSharedPointer<ReceivedMessage>& receivedMessagePointer, const QSharedPointer<Node>& sourceNode) {
if (_target.isNull()) {
return false;
}
(_target->*_slot)(receivedMessagePointer, sourceNode);
return true;
}
#endif // hifi_PacketReceiver_h

View file

@ -16,8 +16,9 @@
#include <cmath>
#include <assert.h>
#include <QThread>
#include <QTimer>
#include <QtCore/QMetaMethod>
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <SharedUtil.h>
#include <shared/QtHelpers.h>

View file

@ -15,8 +15,14 @@
#include <DependencyManager.h>
#include <StatTracker.h>
#include <QtCore/QDateTime>
#include <QtCore/QThread>
QString ResourceRequest::toHttpDateString(uint64_t msecsSinceEpoch) {
return QDateTime::fromMSecsSinceEpoch(msecsSinceEpoch)
.toString("ddd, dd MMM yyyy hh:mm:ss 'GMT'")
.toLatin1();
}
void ResourceRequest::send() {
if (QThread::currentThread() != thread()) {

View file

@ -90,6 +90,7 @@ public:
void setCacheEnabled(bool value) { _cacheEnabled = value; }
void setByteRange(ByteRange byteRange) { _byteRange = byteRange; }
static QString toHttpDateString(uint64_t msecsSinceEpoch);
public slots:
void send();

View file

@ -64,7 +64,8 @@ void OctreePersistThread::start() {
cleanupOldReplacementBackups();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::OctreeDataFileReply, this, "handleOctreeDataFileReply");
packetReceiver.registerListener(PacketType::OctreeDataFileReply,
PacketReceiver::makeUnsourcedListenerReference<OctreePersistThread>(this, &OctreePersistThread::handleOctreeDataFileReply));
auto nodeList = DependencyManager::get<NodeList>();
const DomainHandler& domainHandler = nodeList->getDomainHandler();

View file

@ -260,6 +260,7 @@ void Font::read(QIODevice& in) {
gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR));
_texture->setStoredMipFormat(formatMip);
_texture->assignStoredMip(0, image.sizeInBytes(), image.constBits());
_texture->setImportant(true);
}
void Font::setupGPU() {

View file

@ -85,11 +85,25 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
Lock lock(_containerLock);
if (_scriptCache.contains(url) && !forceDownload) {
auto scriptContent = _scriptCache[url];
lock.unlock();
qCDebug(scriptengine) << "Found script in cache:" << url.fileName();
contentAvailable(url.toString(), scriptContent, true, true, STATUS_CACHED);
} else {
auto entry = _scriptCache[url];
if (url.isLocalFile() || url.scheme().isEmpty()) {
auto modifiedTime = QFileInfo(url.toLocalFile()).lastModified();
QString localTime = ResourceRequest::toHttpDateString(modifiedTime.toMSecsSinceEpoch());
QString cachedTime = entry["last-modified"].toString();
if (cachedTime != localTime) {
forceDownload = true;
qCDebug(scriptengine) << "Found script in cache, but local file modified; reloading:" << url.fileName()
<< "(memory:" << cachedTime << "disk:" << localTime << ")";
}
}
if (!forceDownload) {
lock.unlock();
qCDebug(scriptengine) << "Found script in cache:" << url.fileName();
contentAvailable(url.toString(), entry["data"].toString(), true, true, STATUS_CACHED);
return;
}
}
{
auto& scriptRequest = _activeScriptRequests[url];
bool alreadyWaiting = scriptRequest.scriptUsers.size() > 0;
scriptRequest.scriptUsers.push_back(contentAvailable);
@ -140,7 +154,10 @@ void ScriptCache::scriptContentAvailable(int maxRetries) {
_activeScriptRequests.remove(url);
_scriptCache[url] = scriptContent = req->getData();
_scriptCache[url] = {
{ "data", scriptContent = req->getData() },
{ "last-modified", req->property("last-modified") },
};
} else {
auto result = req->getResult();
bool irrecoverable =
@ -177,7 +194,7 @@ void ScriptCache::scriptContentAvailable(int maxRetries) {
allCallbacks = scriptRequest.scriptUsers;
if (_scriptCache.contains(url)) {
scriptContent = _scriptCache[url];
scriptContent = _scriptCache[url]["data"].toString();
}
_activeScriptRequests.remove(url);
qCWarning(scriptengine) << "Error loading script from URL (" << status <<")";

View file

@ -60,7 +60,7 @@ private:
Mutex _containerLock;
QMap<QUrl, ScriptRequest> _activeScriptRequests;
QHash<QUrl, QString> _scriptCache;
QHash<QUrl, QVariantMap> _scriptCache;
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
};

View file

@ -130,7 +130,7 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) {
// This message was sent by one of our script engines, let's try to see if we can find the source.
// Note that the first entry in the backtrace should be "print" and is somewhat useless to us
AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get();
if (loggerInterface->showSourceDebugging()) {
if (loggerInterface && loggerInterface->showSourceDebugging()) {
QScriptContext* userContext = context;
while (userContext && QScriptContextInfo(userContext).functionType() == QScriptContextInfo::NativeFunction) {
userContext = userContext->parentContext();

View file

@ -17,6 +17,10 @@
#ifdef Q_OS_WIN
#include <windows.h>
#endif
#ifdef Q_OS_UNIX
#include <stdio.h>
#include <unistd.h>
#endif
#include <QtCore/QCoreApplication>
#include <QtCore/QDateTime>
@ -32,6 +36,40 @@ LogHandler& LogHandler::getInstance() {
return staticInstance;
}
LogHandler::LogHandler() {
QString logOptions = qgetenv("VIRCADIA_LOG_OPTIONS").toLower();
#ifdef Q_OS_UNIX
// Enable color by default if we're on Unix, and output is a tty (so we're not being piped into something)
//
// On Windows the situation is more complex, and color is supported or not depending on version and
// registry settings, so for now it's off by default and it's up to the user to do what's required.
if (isatty(fileno(stdout))) {
_useColor = true;
}
#endif
auto optionList = logOptions.split(",");
for (auto option : optionList) {
option = option.trimmed();
if (option == "color") {
_useColor = true;
} else if (option == "nocolor") {
_useColor = false;
} else if (option == "process_id") {
_shouldOutputProcessID = true;
} else if (option == "thread_id") {
_shouldOutputThreadID = true;
} else if (option == "milliseconds") {
_shouldDisplayMilliseconds = true;
} else if (option != "") {
fprintf(stdout, "Unrecognized option in VIRCADIA_LOG_OPTIONS: '%s'\n", option.toUtf8().constData());
}
}
}
const char* stringForLogType(LogMsgType msgType) {
switch (msgType) {
case LogInfo:
@ -51,6 +89,29 @@ const char* stringForLogType(LogMsgType msgType) {
}
}
const char* colorForLogType(LogMsgType msgType) {
switch (msgType) {
case LogInfo:
return "\u001b[37;1m"; // Bold white
case LogDebug:
return "";
case LogWarning:
return "\u001b[35;1m"; // Bright magenta
case LogCritical:
return "\u001b[31;1m"; // Bright red
case LogFatal:
return "\u001b[31;1m"; // Bright red
case LogSuppressed:
return "";
default:
return "";
}
}
const char* colorReset() {
return "\u001b[0m";
}
// the following will produce 11/18 13:55:36
const QString DATE_STRING_FORMAT = "MM/dd hh:mm:ss";
@ -133,7 +194,15 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont
QString logMessage = QString("%1 %2\n").arg(prefixString, message.split('\n').join('\n' + prefixString + " "));
fprintf(stdout, "%s", qPrintable(logMessage));
const char* color = "";
const char* resetColor = "";
if (_useColor) {
color = colorForLogType(type);
resetColor = colorReset();
}
fprintf(stdout, "%s%s%s", color, qPrintable(logMessage), resetColor);
#ifdef Q_OS_WIN
// On windows, this will output log lines into the Visual Studio "output" tab
OutputDebugStringA(qPrintable(logMessage));

View file

@ -57,7 +57,7 @@ public:
void setupRepeatedMessageFlusher();
private:
LogHandler() = default;
LogHandler();
~LogHandler() = default;
void flushRepeatedMessages();
@ -66,6 +66,7 @@ private:
bool _shouldOutputProcessID { false };
bool _shouldOutputThreadID { false };
bool _shouldDisplayMilliseconds { false };
bool _useColor { false };
int _currentMessageID { 0 };
struct RepeatedMessageRecord {

View file

@ -172,31 +172,31 @@ public:
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
Q_INVOKABLE QString fileOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* fileOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString fileOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE ModalDialogListener* fileOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE QString fileSaveDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* fileSaveDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString fileSaveDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE ModalDialogListener* fileSaveDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE QString existingDirectoryDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* existingDirectoryDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString existingDirectoryDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE ModalDialogListener* existingDirectoryDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE QString assetOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* assetOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString assetOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE ModalDialogListener* assetOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
// Compatibility with QFileDialog::getOpenFileName
static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getOpenFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
static ModalDialogListener* getOpenFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
// Compatibility with QFileDialog::getSaveFileName
static QString getSaveFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getSaveFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static QString getSaveFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
static ModalDialogListener* getSaveFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
// Compatibility with QFileDialog::getExistingDirectory
static QString getExistingDirectory(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getExistingDirectoryAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static QString getExistingDirectory(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
static ModalDialogListener* getExistingDirectoryAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
static QString getOpenAssetName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getOpenAssetNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static QString getOpenAssetName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
static ModalDialogListener* getOpenAssetNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = QFileDialog::Options());
Q_INVOKABLE QVariant inputDialog(const Icon icon, const QString& title, const QString& label = QString(), const QVariant& current = QVariant());
Q_INVOKABLE ModalDialogListener* inputDialogAsync(const Icon icon, const QString& title, const QString& label = QString(), const QVariant& current = QVariant());
@ -209,12 +209,12 @@ public:
// Compatibility with QInputDialog::getText
static QString getText(void* ignored, const QString & title, const QString & label,
QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0,
Qt::WindowFlags flags = 0, Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
Qt::WindowFlags flags = Qt::WindowFlags(), Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getText(OffscreenUi::ICON_NONE, title, label, text, ok);
}
// Compatibility with QInputDialog::getItem
static QString getItem(void *ignored, const QString & title, const QString & label, const QStringList & items,
int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = 0,
int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = Qt::WindowFlags(),
Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getItem(OffscreenUi::ICON_NONE, title, label, items, current, editable, ok);
}
@ -222,12 +222,12 @@ public:
// Compatibility with QInputDialog::getText
static ModalDialogListener* getTextAsync(void* ignored, const QString & title, const QString & label,
QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0,
Qt::WindowFlags flags = 0, Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
Qt::WindowFlags flags = Qt::WindowFlags(), Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getTextAsync(OffscreenUi::ICON_NONE, title, label, text);
}
// Compatibility with QInputDialog::getItem
static ModalDialogListener* getItemAsync(void *ignored, const QString & title, const QString & label, const QStringList & items,
int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = 0,
int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = Qt::WindowFlags(),
Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getItemAsync(OffscreenUi::ICON_NONE, title, label, items, current, editable);
}

View file

@ -5,10 +5,12 @@
// Created by Kasen IO on 2019.07.14 | realities.dev | kasenvr@gmail.com
// Copyright 2019 Kasen IO
//
// Authored by: Humbletim (humbletim@gmail.com)
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Supporting file containing all QtScript specific integration.
// Supporting file containing all QtScript specific integration.
#ifndef EXAMPLE_SCRIPT_PLUGIN_H
#define EXAMPLE_SCRIPT_PLUGIN_H

View file

@ -5,10 +5,12 @@
// Created by Kasen IO on 2019.07.14 | realities.dev | kasenvr@gmail.com
// Copyright 2019 Kasen IO
//
// Authored by: Humbletim (humbletim@gmail.com)
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Example of prototyping new JS APIs by leveraging the existing plugin system.
// Example of prototyping new JS APIs by leveraging the existing plugin system.
#include "ExampleScriptPlugin.h"

View file

@ -1696,7 +1696,7 @@ function recursiveDelete(entities, childrenList, deletedIDs, entityHostType) {
}
function unparentSelectedEntities() {
if (SelectionManager.hasSelection()) {
if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var selectedEntities = selectionManager.selections;
var parentCheck = false;
@ -1720,6 +1720,9 @@ function unparentSelectedEntities() {
} else {
Window.notify("Entity unparented");
}
//Refresh
entityListTool.sendUpdate();
selectionManager._update(false, this);
} else {
audioFeedback.rejection();
if (selectedEntities.length > 1) {
@ -1730,11 +1733,11 @@ function unparentSelectedEntities() {
}
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected to unparent");
Window.notifyEditError("You have nothing selected or the selection has locked entities.");
}
}
function parentSelectedEntities() {
if (SelectionManager.hasSelection()) {
if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var selectedEntities = selectionManager.selections;
if (selectedEntities.length <= 1) {
audioFeedback.rejection();
@ -1756,17 +1759,20 @@ function parentSelectedEntities() {
if (parentCheck) {
audioFeedback.confirmation();
Window.notify("Entities parented");
//Refresh
entityListTool.sendUpdate();
selectionManager._update(false, this);
} else {
audioFeedback.rejection();
Window.notify("Entities are already parented to last");
}
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected to parent");
Window.notifyEditError("You have nothing selected or the selection has locked entities.");
}
}
function deleteSelectedEntities() {
if (SelectionManager.hasSelection()) {
if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var deletedIDs = [];
SelectionManager.saveProperties();
@ -1797,6 +1803,9 @@ function deleteSelectedEntities() {
pushCommandForSelections([], savedProperties);
entityListTool.deleteEntities(deletedIDs);
}
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected or the selection has locked entities.");
}
}
@ -2963,4 +2972,22 @@ function zoneSortOrder(a, b) {
return 0;
}
function getParentState(id) {
var state = "NONE";
var properties = Entities.getEntityProperties(id, ["parentID"]);
var children = Entities.getChildrenIDs(id);
if (properties.parentID !== Uuid.NULL) {
if (children.length > 0) {
state = "PARENT_CHILDREN";
} else {
state = "CHILDREN";
}
} else {
if (children.length > 0) {
state = "PARENT";
}
}
return state;
}
}()); // END LOCAL_SCOPE

View file

@ -190,7 +190,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
PROFILE("getMultipleProperties", function () {
var multipleProperties = Entities.getMultipleEntityProperties(ids, ['position', 'name', 'type', 'locked',
'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script', 'certificateID',
'skybox.url', 'ambientLight.url']);
'skybox.url', 'ambientLight.url', 'created', 'lastEdited']);
for (var i = 0; i < multipleProperties.length; i++) {
var properties = multipleProperties[i];
@ -203,6 +203,17 @@ EntityListTool = function(shouldUseEditTabletApp) {
} else if (properties.type === "Image") {
url = properties.imageURL;
}
var parentStatus = getParentState(ids[i]);
var parentState = "";
if (parentStatus === "PARENT") {
parentState = "A";
} else if (parentStatus === "CHILDREN") {
parentState = "C";
} else if (parentStatus === "PARENT_CHILDREN") {
parentState = "B";
}
entities.push({
id: ids[i],
name: properties.name,
@ -222,7 +233,10 @@ EntityListTool = function(shouldUseEditTabletApp) {
isBaked: entityIsBaked(properties),
drawCalls: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.drawCalls) : ""),
hasScript: properties.script !== ""
hasScript: properties.script !== "",
parentState: parentState,
created: formatToStringDateTime(properties.created),
lastEdited: formatToStringDateTime(properties.lastEdited)
});
}
}
@ -242,6 +256,22 @@ EntityListTool = function(shouldUseEditTabletApp) {
});
};
function formatToStringDateTime(timestamp) {
var d = new Date(Math.floor(timestamp/1000));
var dateTime = d.getUTCFullYear() + "-" + zeroPad((d.getUTCMonth() + 1), 2) + "-" + zeroPad(d.getUTCDate(), 2);
dateTime = dateTime + " " + zeroPad(d.getUTCHours(), 2) + ":" + zeroPad(d.getUTCMinutes(), 2) + ":" + zeroPad(d.getUTCSeconds(), 2);
dateTime = dateTime + "." + zeroPad(d.getUTCMilliseconds(), 3);
return dateTime;
}
function zeroPad(num, size) {
num = num.toString();
while (num.length < size) {
num = "0" + num;
}
return num;
}
function onFileSaveChanged(filename) {
Window.saveFileChanged.disconnect(onFileSaveChanged);
if (filename !== "") {
@ -323,6 +353,24 @@ EntityListTool = function(shouldUseEditTabletApp) {
unparentSelectedEntities();
} else if (data.type === 'hmdMultiSelectMode') {
hmdMultiSelectMode = data.value;
} else if (data.type === 'selectAllInBox') {
selectAllEntitiesInCurrentSelectionBox(false);
} else if (data.type === 'selectAllTouchingBox') {
selectAllEntitiesInCurrentSelectionBox(true);
} else if (data.type === 'selectParent') {
SelectionManager.selectParent();
} else if (data.type === 'selectTopParent') {
SelectionManager.selectTopParent();
} else if (data.type === 'addChildrenToSelection') {
SelectionManager.addChildrenToSelection();
} else if (data.type === 'selectFamily') {
SelectionManager.selectFamily();
} else if (data.type === 'selectTopFamily') {
SelectionManager.selectTopFamily();
} else if (data.type === 'teleportToEntity') {
SelectionManager.teleportToEntity();
} else if (data.type === 'moveEntitySelectionToAvatar') {
SelectionManager.moveEntitiesSelectionToAvatar();
}
};

View file

@ -32,19 +32,8 @@
</div>
<button id="toggle-space-mode" class="hifi-edit-button space-mode-local">Local</button>
<input type="button" class="vglyph" id="hmdmultiselect" value="I" style="display: none;" />
<input type="button" class="blue vglyph" id="hmdcut" value="D" style="display: none;" />
<input type="button" class="blue vglyph" id="hmdcopy" value="B" style="display: none;" />
<input type="button" class="blue vglyph" id="hmdpaste" value="E" style="display: none;" />
<input type="button" class="green vglyph" id="hmdduplicate" value="A" style="display: none;" />
<div>
<input type="button" class="vglyph" id="parent" value="K" />
<input type="button" class="vglyph" id="unparent" value="L" />
</div>
<div>
<input type="button" class="orange vglyph" id="undo" value="F" />
<input type="button" class="orange vglyph" id="redo" value="G" />
</div>
<input type="button" class="red glyph" id="delete" value="{" />
<input type="button" class="normal" id="selection" value="Selection..." />
<input type="button" class="normal" id="actions" value="Actions..." />
</div>
<div id="entity-list">
<div id="filter-area">
@ -102,5 +91,142 @@
</div>
</div>
</div>
<div class="entity-list-menu" id="actions-menu" >
<button class="menu-button" id="undo" >
<div class = "menu-item">
<div class = "menu-item-caption">Undo</div>
<div class = "menu-item-shortcut">Ctrl-Z</div>
</div>
</button>
<button class="menu-button" id="redo" >
<div class = "menu-item">
<div class = "menu-item-caption">Redo</div>
<div class = "menu-item-shortcut">Ctrl-Y</div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="hmdcut" >
<div class = "menu-item">
<div class = "menu-item-caption">Cut</div>
<div class = "menu-item-shortcut">Ctrl-X</div>
</div>
</button>
<button class="menu-button" id="hmdcopy" >
<div class = "menu-item">
<div class = "menu-item-caption">Copy</div>
<div class = "menu-item-shortcut">Ctrl-C</div>
</div>
</button>
<button class="menu-button" id="hmdpaste" >
<div class = "menu-item">
<div class = "menu-item-caption">Paste</div>
<div class = "menu-item-shortcut">Ctrl-V</div>
</div>
</button>
<button class="menu-button" id="hmdduplicate" >
<div class = "menu-item">
<div class = "menu-item-caption">Duplicate</div>
<div class = "menu-item-shortcut">Ctrl-D</div>
</div>
</button>
<button class="menu-button" id="delete" >
<div class = "menu-item">
<div class = "menu-item-caption">Delete</div>
<div class = "menu-item-shortcut">Del</div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="parent" >
<div class = "menu-item">
<div class = "menu-item-caption">Parent Entities to the Last Selected</div>
<div class = "menu-item-shortcut">Ctrl-P</div>
</div>
</button>
<button class="menu-button" id="unparent" >
<div class = "menu-item">
<div class = "menu-item-caption">Unparent Entity</div>
<div class = "menu-item-shortcut">Ctrl-Shift-P</div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="moveEntitySelectionToAvatar" >
<div class = "menu-item">
<div class = "menu-item-caption">Move Selected Entities to Avatar</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
</div>
<div class="entity-list-menu" id="selection-menu" >
<button class="menu-button" id="selectall" >
<div class = "menu-item">
<div class = "menu-item-caption">Select All</div>
<div class = "menu-item-shortcut">Ctrl-A</div>
</div>
</button>
<button class="menu-button" id="selectnone" >
<div class = "menu-item">
<div class = "menu-item-caption">Select None</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="selectinverse" >
<div class = "menu-item">
<div class = "menu-item-caption">Inverse Selection</div>
<div class = "menu-item-shortcut">Ctrl-I</div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="selectallinbox" >
<div class = "menu-item">
<div class = "menu-item-caption">Select All Entities In Box</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="selectalltouchingbox" >
<div class = "menu-item">
<div class = "menu-item-caption">Select All Entities Touching Box</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="selectparent" >
<div class = "menu-item">
<div class = "menu-item-caption">Select Parent</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="selecttopparent" >
<div class = "menu-item">
<div class = "menu-item-caption">Select Top Parent</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="addchildrentoselection" >
<div class = "menu-item">
<div class = "menu-item-caption">Add Children To Selection</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="selectfamily" >
<div class = "menu-item">
<div class = "menu-item-caption">Select Family</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<button class="menu-button" id="selecttopfamily" >
<div class = "menu-item">
<div class = "menu-item-caption">Select Top Family</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
<div class="menu-separator"></div>
<button class="menu-button" id="teleport-to-entity" >
<div class = "menu-item">
<div class = "menu-item-caption">Teleport To Selected Entities</div>
<div class = "menu-item-shortcut"></div>
</div>
</button>
</div>
<div id="menuBackgroundOverlay" ></div>
</body>
</html>

View file

@ -47,6 +47,14 @@ const COLUMNS = {
alwaysShown: true,
defaultSortOrder: ASCENDING_SORT,
},
parentState: {
columnHeader: "A",
vglyph: true,
dropdownLabel: "Hierarchy",
propertyID: "parentState",
initialWidth: 0.08,
defaultSortOrder: DESCENDING_SORT,
},
name: {
columnHeader: "Name",
propertyID: "name",
@ -134,6 +142,20 @@ const COLUMNS = {
initialWidth: 0.06,
defaultSortOrder: DESCENDING_SORT,
},
created: {
columnHeader: "Created (UTC)",
dropdownLabel: "Creation Date",
propertyID: "created",
initialWidth: 0.38,
defaultSortOrder: DESCENDING_SORT,
},
lastEdited: {
columnHeader: "Modified (UTC)",
dropdownLabel: "Modification Date",
propertyID: "lastEdited",
initialWidth: 0.38,
defaultSortOrder: DESCENDING_SORT,
},
};
const FILTER_TYPES = [
@ -200,7 +222,10 @@ let elEntityTable,
elRefresh,
elToggleLocked,
elToggleVisible,
elHmdMultiSelect,
elActionsMenu,
elSelectionMenu,
elMenuBackgroundOverlay,
elHmdMultiSelect,
elHmdCopy,
elHmdCut,
elHmdPaste,
@ -210,6 +235,18 @@ let elEntityTable,
elParent,
elUnparent,
elDelete,
elMoveEntitySelectionToAvatar,
elSelectAll,
elSelectInverse,
elSelectNone,
elSelectAllInBox,
elSelectAllTouchingBox,
elSelectParent,
elSelectTopParent,
elAddChildrenToSelection,
elSelectFamily,
elSelectTopFamily,
elTeleportToEntity,
elFilterTypeMultiselectBox,
elFilterTypeText,
elFilterTypeOptions,
@ -252,8 +289,11 @@ function loaded() {
elEntityTableScroll = document.getElementById("entity-table-scroll");
elRefresh = document.getElementById("refresh");
elToggleLocked = document.getElementById("locked");
elToggleVisible = document.getElementById("visible");
elToggleVisible = document.getElementById("visible");
elHmdMultiSelect = document.getElementById("hmdmultiselect");
elActionsMenu = document.getElementById("actions");
elSelectionMenu = document.getElementById("selection");
elMenuBackgroundOverlay = document.getElementById("menuBackgroundOverlay");
elHmdCopy = document.getElementById("hmdcopy");
elHmdCut = document.getElementById("hmdcut");
elHmdPaste = document.getElementById("hmdpaste");
@ -263,6 +303,18 @@ function loaded() {
elParent = document.getElementById("parent");
elUnparent = document.getElementById("unparent");
elDelete = document.getElementById("delete");
elMoveEntitySelectionToAvatar = document.getElementById("moveEntitySelectionToAvatar");
elSelectAll = document.getElementById("selectall");
elSelectInverse = document.getElementById("selectinverse");
elSelectNone = document.getElementById("selectnone");
elSelectAllInBox = document.getElementById("selectallinbox");
elSelectAllTouchingBox = document.getElementById("selectalltouchingbox");
elSelectParent = document.getElementById("selectparent");
elSelectTopParent = document.getElementById("selecttopparent");
elAddChildrenToSelection = document.getElementById("addchildrentoselection");
elSelectFamily = document.getElementById("selectfamily");
elSelectTopFamily = document.getElementById("selecttopfamily");
elTeleportToEntity = document.getElementById("teleport-to-entity");
elFilterTypeMultiselectBox = document.getElementById("filter-type-multiselect-box");
elFilterTypeText = document.getElementById("filter-type-text");
elFilterTypeOptions = document.getElementById("filter-type-options");
@ -300,32 +352,146 @@ function loaded() {
}
EventBridge.emitWebEvent(JSON.stringify({ type: 'hmdMultiSelectMode', value: hmdMultiSelectMode }));
};
elActionsMenu.onclick = function() {
document.getElementById("menuBackgroundOverlay").style.display = "block";
document.getElementById("actions-menu").style.display = "block";
};
elSelectionMenu.onclick = function() {
document.getElementById("menuBackgroundOverlay").style.display = "block";
document.getElementById("selection-menu").style.display = "block";
};
elMenuBackgroundOverlay.onclick = function() {
closeAllEntityListMenu();
};
elHmdCopy.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' }));
closeAllEntityListMenu();
};
elHmdCut.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'cut' }));
closeAllEntityListMenu();
};
elHmdPaste.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'paste' }));
closeAllEntityListMenu();
};
elHmdDuplicate.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'duplicate' }));
};
closeAllEntityListMenu();
};
elParent.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' }));
closeAllEntityListMenu();
};
elUnparent.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' }));
closeAllEntityListMenu();
};
elUndo.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'undo' }));
closeAllEntityListMenu();
};
elRedo.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'redo' }));
closeAllEntityListMenu();
};
elDelete.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
closeAllEntityListMenu();
};
elMoveEntitySelectionToAvatar.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'moveEntitySelectionToAvatar' }));
closeAllEntityListMenu();
};
elSelectAll.onclick = function() {
let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id);
let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => {
return selectedEntities.includes(visibleEntityID);
});
let selection = [];
if (!selectionIncludesAllVisibleEntityIDs) {
selection = visibleEntityIDs;
}
updateSelectedEntities(selection, false);
EventBridge.emitWebEvent(JSON.stringify({
"type": "selectionUpdate",
"focus": false,
"entityIds": selection
}));
closeAllEntityListMenu();
};
elSelectInverse.onclick = function() {
let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id);
let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => {
return selectedEntities.includes(visibleEntityID);
});
let selection = [];
if (!selectionIncludesAllVisibleEntityIDs) {
visibleEntityIDs.forEach(function(id) {
if (!selectedEntities.includes(id)) {
selection.push(id);
}
});
}
updateSelectedEntities(selection, false);
EventBridge.emitWebEvent(JSON.stringify({
"type": "selectionUpdate",
"focus": false,
"entityIds": selection
}));
closeAllEntityListMenu();
};
elSelectNone.onclick = function() {
updateSelectedEntities([], false);
EventBridge.emitWebEvent(JSON.stringify({
"type": "selectionUpdate",
"focus": false,
"entityIds": []
}));
closeAllEntityListMenu();
};
elSelectAllInBox.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectAllInBox' }));
closeAllEntityListMenu();
};
elSelectAllTouchingBox.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectAllTouchingBox' }));
closeAllEntityListMenu();
};
elSelectParent.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectParent' }));
closeAllEntityListMenu();
};
elSelectTopParent.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectTopParent' }));
closeAllEntityListMenu();
};
elAddChildrenToSelection.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'addChildrenToSelection' }));
closeAllEntityListMenu();
};
elSelectFamily.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectFamily' }));
closeAllEntityListMenu();
};
elSelectTopFamily.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'selectTopFamily' }));
closeAllEntityListMenu();
};
elTeleportToEntity.onclick = function () {
EventBridge.emitWebEvent(JSON.stringify({ type: "teleportToEntity" }));
closeAllEntityListMenu();
};
elToggleSpaceMode.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleSpaceMode' }));
@ -384,9 +550,12 @@ function loaded() {
elTh.setAttribute("id", thID);
elTh.setAttribute("columnIndex", columnIndex);
elTh.setAttribute("columnID", columnID);
if (columnData.glyph) {
if (columnData.glyph || columnData.vglyph) {
let elGlyph = document.createElement("span");
elGlyph.className = "glyph";
if (columnData.vglyph) {
elGlyph.className = "vglyph";
}
elGlyph.innerHTML = columnData.columnHeader;
elTh.appendChild(elGlyph);
} else {
@ -686,10 +855,13 @@ function loaded() {
isBaked: entity.isBaked,
drawCalls: displayIfNonZero(entity.drawCalls),
hasScript: entity.hasScript,
parentState: entity.parentState,
created: entity.created,
lastEdited: entity.lastEdited,
elRow: null, // if this entity has a visible row element assigned to it
selected: false // if this entity is selected for edit regardless of having a visible row
};
entities.push(entityData);
entitiesByID[entityData.id] = entityData;
});
@ -871,7 +1043,7 @@ function loaded() {
function updateSelectedEntities(selectedIDs, autoScroll) {
let notFound = false;
// reset all currently selected entities and their rows first
selectedEntities.forEach(function(id) {
let entity = entitiesByID[id];
@ -882,7 +1054,7 @@ function loaded() {
}
}
});
// then reset selected entities list with newly selected entities and set them selected
selectedEntities = [];
selectedIDs.forEach(function(id) {
@ -945,6 +1117,8 @@ function loaded() {
let elCell = elRow.childNodes[i];
if (column.data.glyph) {
elCell.innerHTML = itemData[column.data.propertyID] ? column.data.columnHeader : null;
} else if (column.data.vglyph) {
elCell.innerHTML = itemData[column.data.propertyID];
} else {
let value = itemData[column.data.propertyID];
if (column.data.format) {
@ -1032,6 +1206,9 @@ function loaded() {
let column = columnsByID[columnID];
let visible = column.elTh.style.visibility !== "hidden";
let className = column.data.glyph ? "glyph" : "";
if (column.data.vglyph) {
className = "vglyph";
}
className += visible ? "" : " hidden";
return className;
}
@ -1381,7 +1558,7 @@ function loaded() {
break;
}
if (controlKey && keyCodeString === "A") {
if (controlKey && !shiftKey && !altKey && keyCodeString === "A") {
let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id);
let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => {
return selectedEntities.includes(visibleEntityID);
@ -1404,6 +1581,32 @@ function loaded() {
return;
}
if (controlKey && !shiftKey && !altKey && keyCodeString === "I") {
let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id);
let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => {
return selectedEntities.includes(visibleEntityID);
});
let selection = [];
if (!selectionIncludesAllVisibleEntityIDs) {
visibleEntityIDs.forEach(function(id) {
if (!selectedEntities.includes(id)) {
selection.push(id);
}
});
}
updateSelectedEntities(selection);
EventBridge.emitWebEvent(JSON.stringify({
"type": "selectionUpdate",
"focus": false,
"entityIds": selection
}));
return;
}
EventBridge.emitWebEvent(JSON.stringify({
type: 'keyUpEvent',
@ -1454,16 +1657,8 @@ function loaded() {
} else if (data.type === "confirmHMDstate") {
if (data.isHmd) {
document.getElementById("hmdmultiselect").style.display = "inline";
document.getElementById("hmdcopy").style.display = "inline";
document.getElementById("hmdcut").style.display = "inline";
document.getElementById("hmdpaste").style.display = "inline";
document.getElementById("hmdduplicate").style.display = "inline";
} else {
document.getElementById("hmdmultiselect").style.display = "none";
document.getElementById("hmdcopy").style.display = "none";
document.getElementById("hmdcut").style.display = "none";
document.getElementById("hmdpaste").style.display = "none";
document.getElementById("hmdduplicate").style.display = "none";
document.getElementById("hmdmultiselect").style.display = "none";
}
}
});
@ -1489,4 +1684,11 @@ function loaded() {
$(window).blur(function() {
entityListContextMenu.close();
});
function closeAllEntityListMenu() {
document.getElementById("menuBackgroundOverlay").style.display = "none";
document.getElementById("selection-menu").style.display = "none";
document.getElementById("actions-menu").style.display = "none";
}
}

View file

@ -1618,7 +1618,8 @@ const GROUPS = [
type: "vec3",
vec3Type: "pyr",
multiplier: DEGREES_TO_RADIANS,
decimals: 4,
decimals: 6,
step: 1,
subLabels: [ "x", "y", "z" ],
unit: "deg/s",
propertyID: "localAngularVelocity",

View file

@ -19,6 +19,7 @@
const SPACE_LOCAL = "local";
const SPACE_WORLD = "world";
const HIGHLIGHT_LIST_NAME = "editHandleHighlightList";
const MIN_DISTANCE_TO_REZ_FROM_AVATAR = 3;
Script.include([
"../../libraries/controllers.js",
@ -26,7 +27,6 @@ Script.include([
"../../libraries/utils.js"
]);
function deepCopy(v) {
return JSON.parse(JSON.stringify(v));
}
@ -636,6 +636,141 @@ SelectionManager = (function() {
}
};
that.teleportToEntity = function() {
if (that.hasSelection()) {
var distanceFromTarget = MIN_DISTANCE_TO_REZ_FROM_AVATAR + Math.max(Math.max(that.worldDimensions.x, that.worldDimensions.y), that.worldDimensions.z);
var teleportTargetPosition = Vec3.sum(that.worldPosition, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: distanceFromTarget }));
MyAvatar.goToLocation(teleportTargetPosition, false);
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected.");
}
};
that.moveEntitiesSelectionToAvatar = function() {
if (that.hasSelection() && that.hasUnlockedSelection()) {
that.saveProperties();
var distanceFromTarget = MIN_DISTANCE_TO_REZ_FROM_AVATAR + Math.max(Math.max(that.worldDimensions.x, that.worldDimensions.y), that.worldDimensions.z);
var targetPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -distanceFromTarget }));
// editing a parent will cause all the children to automatically follow along, so don't
// edit any entity who has an ancestor in that.selections
var toMove = that.selections.filter(function (selection) {
if (that.selections.indexOf(that.savedProperties[selection].parentID) >= 0) {
return false; // a parent is also being moved, so don't issue an edit for this entity
} else {
return true;
}
});
for (var i = 0; i < toMove.length; i++) {
var id = toMove[i];
var properties = that.savedProperties[id];
var relativePosition = Vec3.subtract(properties.position, that.worldPosition);
var newPosition = Vec3.sum(relativePosition, targetPosition);
Entities.editEntity(id, { "position": newPosition });
}
that._update(false, this);
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected or the selection has locked entities.");
}
};
that.selectParent = function() {
if (that.hasSelection()) {
var currentSelection = that.selections;
that.selections = [];
for (var i = 0; i < currentSelection.length; i++) {
var properties = Entities.getEntityProperties(currentSelection[i], ['parentID']);
if (properties.parentID !== Uuid.NULL) {
that.selections.push(properties.parentID);
}
}
that._update(true, this);
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected.");
}
};
that.selectTopParent = function() {
if (that.hasSelection()) {
var currentSelection = that.selections;
that.selections = [];
for (var i = 0; i < currentSelection.length; i++) {
var topParentId = getTopParent(currentSelection[i]);
if (topParentId !== Uuid.NULL) {
that.selections.push(topParentId);
}
}
that._update(true, this);
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected.");
}
};
function getTopParent(id) {
var topParentId = Uuid.NULL;
var properties = Entities.getEntityProperties(id, ['parentID']);
if (properties.parentID === Uuid.NULL) {
topParentId = id;
} else {
topParentId = getTopParent(properties.parentID);
}
return topParentId;
}
that.addChildrenToSelection = function() {
if (that.hasSelection()) {
for (var i = 0; i < that.selections.length; i++) {
var childrenIDs = Entities.getChildrenIDs(that.selections[i]);
var collectNewChildren;
var j;
var k = 0;
do {
collectNewChildren = Entities.getChildrenIDs(childrenIDs[k]);
if (collectNewChildren.length > 0) {
for (j = 0; j < collectNewChildren.length; j++) {
childrenIDs.push(collectNewChildren[j]);
}
}
k++;
} while (k < childrenIDs.length);
if (childrenIDs.length > 0) {
for (j = 0; j < childrenIDs.length; j++) {
that.selections.push(childrenIDs[j]);
}
}
}
that._update(true, this);
} else {
audioFeedback.rejection();
Window.notifyEditError("You have nothing selected.");
}
};
that.hasUnlockedSelection = function() {
var selectionNotLocked = true;
for (var i = 0; i < that.selections.length; i++) {
var properties = Entities.getEntityProperties(that.selections[i], ['locked']);
if (properties.locked) {
selectionNotLocked = false;
break;
}
}
return selectionNotLocked;
};
that.selectFamily = function() {
that.selectParent();
that.addChildrenToSelection();
};
that.selectTopFamily = function() {
that.selectTopParent();
that.addChildrenToSelection();
};
return that;
})();
@ -660,8 +795,10 @@ SelectionDisplay = (function() {
const COLOR_HOVER = { red: 255, green: 220, blue: 82 };
const COLOR_DUPLICATOR = { red: 162, green: 0, blue: 255 };
const COLOR_ROTATE_CURRENT_RING = { red: 255, green: 99, blue: 9 };
const COLOR_BOUNDING_EDGE = { red: 128, green: 128, blue: 128 };
const COLOR_SCALE_CUBE = { red: 160, green: 160, blue: 160 };
const COLOR_BOUNDING_EDGE = { red: 160, green: 160, blue: 160 };
const COLOR_BOUNDING_EDGE_PARENT = { red: 194, green: 123, blue: 0 };
const COLOR_BOUNDING_EDGE_CHILDREN = { red: 0, green: 168, blue: 214 };
const COLOR_SCALE_CUBE = { red: 192, green: 192, blue: 192 };
const COLOR_DEBUG_PICK_PLANE = { red: 255, green: 255, blue: 255 };
const COLOR_DEBUG_PICK_PLANE_HIT = { red: 255, green: 165, blue: 0 };
@ -1791,6 +1928,18 @@ SelectionDisplay = (function() {
var rotationZ = Quat.multiply(rotation, localRotationZ);
worldRotationZ = rotationZ;
var handleBoundingBoxColor = COLOR_BOUNDING_EDGE;
if (SelectionManager.selections.length === 1) {
var parentState = getParentState(SelectionManager.selections[0]);
if (parentState === "CHILDREN") {
handleBoundingBoxColor = COLOR_BOUNDING_EDGE_CHILDREN;
} else {
if (parentState === "PARENT" || parentState === "PARENT_CHILDREN") {
handleBoundingBoxColor = COLOR_BOUNDING_EDGE_PARENT;
}
}
}
var selectionBoxGeometry = {
position: position,
rotation: rotation,
@ -1912,6 +2061,7 @@ SelectionDisplay = (function() {
Entities.editEntity(handleBoundingBox, {
position: position,
rotation: rotation,
color: handleBoundingBoxColor,
dimensions: dimensions
});

View file

@ -55,7 +55,7 @@ Rectangle {
Text {
id: text1
text: qsTr("Material URL (Optional)")
text: qsTr("Material URL <i>(Optional)</i>")
color: "#ffffff"
font.pixelSize: 12
}

View file

@ -55,7 +55,7 @@ Rectangle {
Text {
id: text1
text: qsTr("Model URL")
text: qsTr("Model URL <i>(.fbx, .fst, .glb, .gltf, .obj, .gz)</i>")
color: "#ffffff"
font.pixelSize: 12
}

View file

@ -415,6 +415,13 @@ input[type=button].glyph, button.hifi-edit-button.glyph {
padding: 0;
}
input[type=button].normal, button.hifi-edit-button.normal {
font-family: FiraSans-SemiBold;
font-size: 15px;
text-transform: none;
padding: 0;
}
input[type=button].vglyph, button.hifi-edit-button.vglyph {
font-family: Vircadia-Glyphs;
font-size: 20px;
@ -1236,7 +1243,6 @@ textarea:enabled[scrolling="true"]::-webkit-resizer {
background: #2e2e2e url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACKSURBVChTjdAxDsMgDAXQT4UYuQIzCwsSKxsSJ4YDoByDY7AwUOG2aZMQqX+xhd9gzIwxA3/k8a7LCCFgraX+Fk4UY4RSCoyxNfwgzjlyzhhjXOEvSimhtUbvB3hGUkp472m2wxUKIaD3TnOCd6jWim3bvlBrfdjJOUeolEJoZj/4PMH83bl/BXgCWSs2Z09IjgoAAAAASUVORK5CYII=) no-repeat bottom right;
}
div#grid-section, body#entity-list-body {
padding-bottom: 0;
margin: 16px;
@ -1267,12 +1273,6 @@ div#grid-section, body#entity-list-body {
border-bottom-left-radius: 0;
}
#delete {
float: right;
margin-right: 0;
background-color: #ff0000;
}
#entity-list {
position: relative; /* New positioning context. */
}
@ -1448,6 +1448,11 @@ input[type=button]#export {
font-size: 15px;
}
#entity-table-scroll .vglyph {
font-family: Vircadia-Glyphs;
font-size: 15px;
}
#entity-table {
margin-top: -28px;
margin-bottom: -18px;
@ -1460,7 +1465,7 @@ input[type=button]#export {
background: none;
}
#entity-table .glyph {
#entity-table .glyph .vglyph {
margin: 0 -2px 0 -2px;
vertical-align: middle;
}
@ -1493,11 +1498,11 @@ input[type=button]#export {
outline: none;
}
#entity-table th .glyph {
#entity-table th .glyph .vglyph {
position: relative;
left: 4px;
}
#entity-table th .glyph + .sort-order {
#entity-table th .glyph .vglyph + .sort-order {
position: relative;
left: 4px;
}
@ -1524,7 +1529,7 @@ input[type=button]#export {
#entity-table td {
box-sizing: border-box;
}
#entity-table td.glyph {
#entity-table td .glyph .vglyph {
text-align: center;
padding: 0;
}
@ -1860,3 +1865,71 @@ div.multiZoneSelToolbar {
padding: 0px;
}
#menuBackgroundOverlay{
background-color:transparent;
position:fixed;
width: 100%;
height: 100%;
top:0;
left:0;
right:0;
bottom:0;
display:none;
}
div.entity-list-menu {
position: fixed;
display: none;
width: 70%;
height: 30px;
top: 42px;
left: 150px;
right: 0;
bottom: 0;
border-style: solid;
border-color: #505050;
border-width: 1px;
background-color: #c0c0c0;
z-index: 2;
cursor: pointer;
}
div.menu-separator{
width: 90%;
height: 2px;
background-color: #505050;
}
button.menu-button {
font-family: FiraSans-SemiBold;
font-size: 15px;
width: 100%;
height: auto;
border-radius: 0;
padding: 6px;
text-align: left;
background-color: #c0c0c0;
border: none;
}
button.menu-button:hover {
background-color: #00B4EF;
border: none;
}
button.menu-button:active {
background-color: #00B4EF;
border: none;
}
div.menu-item {
width: 100%;
}
div.menu-item-caption {
float: left;
}
div.menu-item-shortcut {
float: right;
}

View file

@ -6,19 +6,18 @@ import boto3
import glob
from github import Github
def main():
bucket_name = os.environ['BUCKET_NAME']
bucket_name = os.environ['UPLOAD_BUCKET']
upload_prefix = os.environ['UPLOAD_PREFIX']
release_number = os.environ['RELEASE_NUMBER']
full_prefix = upload_prefix + '/' + release_number[0:-2] + '/' + release_number
full_prefix = upload_prefix + '/' + release_number
S3 = boto3.client('s3')
path = os.path.join(os.getcwd(), os.environ['ARTIFACT_PATTERN'])
files = glob.glob(path, recursive=False)
for archiveFile in files:
filePath, fileName = os.path.split(archiveFile)
S3.upload_file(os.path.join(filePath, fileName), bucket_name, full_prefix + '/' + fileName)
print("Uploaded Artifact to S3: https://{}.s3-us-west-2.amazonaws.com/{}/{}".format(bucket_name, full_prefix, fileName))
print("Uploaded Artifact to S3: https://{}.s3-eu-west-3.amazonaws.com/{}/{}".format(bucket_name, full_prefix, fileName))
print("Finished")
main()