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: on:
push: push:
branches: branches:
- master - gha-master-ci
# FIXME: Change target branch to "master" before merging into "master" branch.
env: env:
#APP_NAME: gpu-frame-player
APP_NAME: interface APP_NAME: interface
BUILD_TYPE: Release BUILD_TYPE: Release
BUCKET_NAME: hifi-gh-builds BUILD_NUMBER: ${{ github.run_number }}
CI_BUILD: Github 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 }} GIT_COMMIT: ${{ github.sha }}
HIFI_VCPKG_BOOTSTRAP: true # VCPKG did not build well on OSX disabling HIFI_VCPKG_BOOTSTRAP, which invokes a download to a working version of vcpkg
LAUNCHER_HMAC_SECRET: ${{ secrets.launcher_hmac_secret }} # HIFI_VCPKG_BOOTSTRAP: true
OCULUS_APP_ID: '${{ secrets.oculus_app_id }}'
RELEASE_TYPE: PRODUCTION RELEASE_TYPE: PRODUCTION
RELEASE_DYNAMODB_V2: ReleaseManager2-ReleaseQueue-prod RELEASE_NUMBER: ${{ github.run_number }}
STABLE_BUILD: 0 STABLE_BUILD: 0
UPLOAD_BUCKET: athena-public
# OSX-specific variables
# OSX specific variables
DEVELOPER_DIR: /Applications/Xcode_11.2.app/Contents/Developer DEVELOPER_DIR: /Applications/Xcode_11.2.app/Contents/Developer
MACOSX_DEPLOYMENT_TARGET: '10.11' MACOSX_DEPLOYMENT_TARGET: '10.11'
# WIN32 specific variables # WIN-specific variables
PreferredToolArchitecture: X64 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: 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: build:
strategy: strategy:
matrix: matrix:
os: [windows-latest, macOS-latest] os: [windows-latest, macOS-latest, ubuntu-18.04]
build_type: [full, client] # build_type: [full, client]
#os: [windows-latest, macOS-latest, ubuntu-latest] build_type: [full]
# exclude: include:
# - os: ubuntu-latest - os: ubuntu-18.04
# build_type: client build_type: full
apt-dependencies: mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0
fail-fast: false
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
needs: generate_build_number
steps: steps:
- name: Download build number - name: Report Build Number
uses: actions/download-artifact@v1 shell: bash
with: run: |
name: BUILD_NUMBER echo "Build number: $BUILD_NUMBER"
- name: Restore build number - name: Configure build environment 1
id: buildnumber
uses: highfidelity/build-number@v3
with:
output_name: RELEASE_NUMBER
- name: Configure Build Environment 1
shell: bash shell: bash
id: buildenv1 id: buildenv1
run: | run: |
echo ::set-env name=UPLOAD_PREFIX::master echo ::set-env name=UPLOAD_PREFIX::master
echo ::set-env name=GIT_COMMIT_SHORT::`echo $GIT_COMMIT | cut -c1-7` 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 # 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=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 fi
# Mac build variables # Mac build variables
if [ "${{ matrix.os }}" = "macOS-latest" ]; then if [ "${{ matrix.os }}" = "macOS-latest" ]; then
@ -118,8 +64,8 @@ jobs:
echo ::set-env name=ZIP_COMMAND::zip echo ::set-env name=ZIP_COMMAND::zip
echo ::set-env name=ZIP_ARGS::-r echo ::set-env name=ZIP_ARGS::-r
echo ::set-env name=INSTALLER_EXT::dmg echo ::set-env name=INSTALLER_EXT::dmg
echo ::set-env name=SYMBOL_REGEX::dSYM 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::${{ steps.buildnumber.outputs.build_number }}-${{ matrix.build_type }}-mac-symbols.zip" echo "::set-output name=symbols_archive::${BUILD_NUMBER}-${{ matrix.build_type }}-mac-symbols.zip"
fi fi
# Windows build variables # Windows build variables
if [ "${{ matrix.os }}" = "windows-latest" ]; then if [ "${{ matrix.os }}" = "windows-latest" ]; then
@ -127,40 +73,28 @@ jobs:
echo ::set-env name=ZIP_COMMAND::7z echo ::set-env name=ZIP_COMMAND::7z
echo ::set-env name=ZIP_ARGS::a echo ::set-env name=ZIP_ARGS::a
echo ::set-env name=INSTALLER_EXT::exe 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-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 fi
# Configuration is broken into two steps because you can't set an env var and also reference it in the same step # 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 shell: bash
run: | run: |
echo "${{ steps.buildenv1.outputs.symbols_archive }}" 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 # Build type variables
if [ "${{ matrix.build_type }}" = "full" ]; then if [ "${{ matrix.build_type }}" = "full" ]; then
echo ::set-env name=CLIENT_ONLY::FALSE 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 else
echo ::set-env name=CLIENT_ONLY::TRUE 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 fi
# Linux build variables - name: Clear working directory
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then if: startsWith(matrix.os, 'windows')
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'
shell: bash shell: bash
working-directory: ${{runner.workspace}} working-directory: ${{runner.workspace}}
run: rm -rf ./* run: rm -rf ./*
@ -168,89 +102,119 @@ jobs:
with: with:
submodules: true submodules: true
fetch-depth: 1 fetch-depth: 1
- name: Create Build Directory - name: Install dependencies
run: cmake -E make_directory ${{runner.workspace}}/build if: startsWith(matrix.os, 'ubuntu')
- name: Decrypt Signing Key (Windows)
if: matrix.os == 'windows-latest'
working-directory: ${{runner.workspace}}/build
shell: bash shell: bash
run: gpg --batch --yes -o codesign.pfx --passphrase "${{secrets.gpg_symmetric_key}}" --decrypt $GITHUB_WORKSPACE/tools/ci-scripts/codesign.pfx.gpg run: |
- name: Import Signing Key (Windows) echo "Installing Python Modules:"
if: matrix.os == 'windows-latest' pip3 install distro || exit 1
working-directory: ${{runner.workspace}}/build echo "Updating apt repository index"
shell: powershell sudo apt update || exit 1
run: | echo "Installing apt packages"
$mypwd=ConvertTo-SecureString -String ${{ secrets.pfx_key }} -Force -AsPlainText sudo apt install -y ${{ matrix.apt-dependencies }} || exit 1
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
- name: Install Python modules - name: Install Python modules
if: matrix.os != 'ubuntu-latest' if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
shell: bash shell: bash
run: $PYTHON_EXEC -m pip install boto3 PyGithub 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 - name: Configure CMake
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
shell: bash shell: bash
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCLIENT_ONLY:BOOLEAN=$CLIENT_ONLY $CMAKE_EXTRA 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 - name: Build application
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
shell: bash shell: bash
run: cmake --build . --config $BUILD_TYPE --target $APP_NAME run: cmake --build . --config $BUILD_TYPE --target $APP_NAME $CMAKE_BUILD_EXTRA
- name: Build Console - name: Build domain server
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
shell: bash shell: bash
run: cmake --build . --config $BUILD_TYPE --target packaged-server-console run: cmake --build . --config $BUILD_TYPE --target domain-server $CMAKE_BUILD_EXTRA
- name: Build Domain Server (FullBuild) - name: Build assignment client
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
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
shell: bash shell: bash
run: cmake --build . --config $BUILD_TYPE --target package run: cmake --build . --config $BUILD_TYPE --target assignment-client $CMAKE_BUILD_EXTRA
- name: Sign Installer (Windows) - name: Build console
if: matrix.os == 'windows-latest' working-directory: ${{runner.workspace}}/build
shell: powershell shell: bash
working-directory: C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64 run: cmake --build . --config $BUILD_TYPE --target packaged-server-console $CMAKE_BUILD_EXTRA
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: Build installer
- name: Upload Artifact working-directory: ${{runner.workspace}}/build
if: matrix.os != 'ubuntu-latest' 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 shell: bash
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
env: env:
AWS_ACCESS_KEY_ID: ${{ secrets.aws_access_key_id }} AWS_ACCESS_KEY_ID: ${{ secrets.aws_access_key_id }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.aws_secret_access_key }} AWS_SECRET_ACCESS_KEY: ${{ secrets.aws_secret_access_key }}
run: $PYTHON_EXEC $GITHUB_WORKSPACE/tools/ci-scripts/upload.py run: $PYTHON_EXEC $GITHUB_WORKSPACE/tools/ci-scripts/upload.py
- name: Archive Symbols #- name: Archive symbols
if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest') # if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
working-directory: ${{runner.workspace}} # working-directory: ${{runner.workspace}}
shell: bash # shell: bash
run: | # run: |
SYMBOLS_TEMP="symbols-temp" # SYMBOLS_TEMP="symbols-temp"
mkdir $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 \; # find "./build" \( -path '*/tools/gpu-frame-player/*' -or -path '*/interface/*' -or -path '*/plugins/*' \) -regex ".*\.$SYMBOL_REGEX" -exec cp -r {} $SYMBOLS_TEMP \;
cd $SYMBOLS_TEMP # cd $SYMBOLS_TEMP
$ZIP_COMMAND $ZIP_ARGS ../${{ steps.buildenv1.outputs.symbols_archive }} . # $ZIP_COMMAND $ZIP_ARGS ../${{ steps.buildenv1.outputs.symbols_archive }} .
- name: Upload Symbols #- name: Upload symbols
if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest') # if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
working-directory: ${{runner.workspace}} # working-directory: ${{runner.workspace}}
shell: bash # shell: bash
run: | # 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" # 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 #- name: Debug list symbols
# if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest') # if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
# working-directory: ${{runner.workspace}} # working-directory: ${{runner.workspace}}
# shell: bash # shell: bash
# run: | # run: |
# unzip -v "${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}" # unzip -v "${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}"
# - name: Debug Upload Symbols Artifact #- name: Upload debug list symbols
# if: (matrix.os == 'windows-latest') || (matrix.os == 'macOS-latest') # if: startsWith(matrix.os, 'windows') || startsWith(matrix.os, 'macOS')
# uses: actions/upload-artifact@v1 # uses: actions/upload-artifact@v1
# with: # with:
# name: symbols # name: symbols
# path: ${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }} # path: ${{runner.workspace}}/${{ steps.buildenv1.outputs.symbols_archive }}

View file

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

View file

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

View file

@ -72,7 +72,8 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen
auto nodeList = DependencyManager::set<LimitedNodeList>(listenPort); auto nodeList = DependencyManager::set<LimitedNodeList>(listenPort);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); 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)); adjustOSResources(std::max(_numAssignmentClientForks, _maxAssignmentClientForks));
// use QProcess to fork off a process for each of the child assignment clients // 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 // Queue all requests until the Asset Server is fully setup
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); 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 #ifdef Q_OS_WIN
updateConsumedCores(); updateConsumedCores();
@ -464,10 +465,14 @@ void AssetServer::completeSetup() {
qCDebug(asset_server) << "Overriding temporary queuing packet handler."; qCDebug(asset_server) << "Overriding temporary queuing packet handler.";
// We're fully setup, override the request queueing handler and replay all requests // We're fully setup, override the request queueing handler and replay all requests
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet"); packetReceiver.registerListener(PacketType::AssetGet,
packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo"); PacketReceiver::makeSourcedListenerReference<AssetServer>(this, &AssetServer::handleAssetGet));
packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload"); packetReceiver.registerListener(PacketType::AssetGetInfo,
packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation"); 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(); replayRequests();
} }

View file

@ -101,20 +101,23 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
PacketType::InjectorGainSet, PacketType::InjectorGainSet,
PacketType::AudioSoloRequest, PacketType::AudioSoloRequest,
PacketType::StopInjector }, PacketType::StopInjector },
this, "queueAudioPacket"); PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::queueAudioPacket)
);
// packets whose consequences are global should be processed on the main thread // packets whose consequences are global should be processed on the main thread
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); packetReceiver.registerListener(PacketType::MuteEnvironment,
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket"); PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::handleMuteEnvironmentPacket));
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); packetReceiver.registerListener(PacketType::NodeMuteRequest,
PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::handleNodeMuteRequestPacket));
packetReceiver.registerListener(PacketType::KillAvatar,
PacketReceiver::makeSourcedListenerReference<AudioMixer>(this, &AudioMixer::handleKillAvatarPacket));
packetReceiver.registerListenerForTypes({ packetReceiver.registerListenerForTypes({
PacketType::ReplicatedMicrophoneAudioNoEcho, PacketType::ReplicatedMicrophoneAudioNoEcho,
PacketType::ReplicatedMicrophoneAudioWithEcho, PacketType::ReplicatedMicrophoneAudioWithEcho,
PacketType::ReplicatedInjectAudio, PacketType::ReplicatedInjectAudio,
PacketType::ReplicatedSilentAudioFrame PacketType::ReplicatedSilentAudioFrame },
}, PacketReceiver::makeUnsourcedListenerReference<AudioMixer>(this, &AudioMixer::queueReplicatedAudioPacket)
this, "queueReplicatedAudioPacket"
); );
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled); 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); connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AvatarData, this, "queueIncomingPacket"); packetReceiver.registerListener(PacketType::AvatarData,
packetReceiver.registerListener(PacketType::AdjustAvatarSorting, this, "handleAdjustAvatarSorting"); PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::queueIncomingPacket));
packetReceiver.registerListener(PacketType::AvatarQuery, this, "handleAvatarQueryPacket"); packetReceiver.registerListener(PacketType::AdjustAvatarSorting,
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleAdjustAvatarSorting));
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); packetReceiver.registerListener(PacketType::AvatarQuery,
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleAvatarQueryPacket));
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::AvatarIdentity,
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleAvatarIdentityPacket));
packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket"); packetReceiver.registerListener(PacketType::KillAvatar,
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket"); 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 }, packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket"); PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::handleOctreePacket));
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "queueIncomingPacket"); packetReceiver.registerListener(PacketType::ChallengeOwnership,
PacketReceiver::makeSourcedListenerReference<AvatarMixer>(this, &AvatarMixer::queueIncomingPacket));
packetReceiver.registerListenerForTypes({ packetReceiver.registerListenerForTypes({
PacketType::ReplicatedAvatarIdentity, PacketType::ReplicatedAvatarIdentity,
PacketType::ReplicatedKillAvatar 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>(); auto nodeList = DependencyManager::get<NodeList>();
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch);

View file

@ -59,8 +59,7 @@ EntityServer::EntityServer(ReceivedMessage& message) :
PacketType::ChallengeOwnership, PacketType::ChallengeOwnership,
PacketType::ChallengeOwnershipRequest, PacketType::ChallengeOwnershipRequest,
PacketType::ChallengeOwnershipReply }, PacketType::ChallengeOwnershipReply },
this, PacketReceiver::makeSourcedListenerReference<EntityServer>(this, &EntityServer::handleEntityPacket));
"handleEntityPacket");
connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification); connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification);
_dynamicDomainVerificationTimer.setSingleShot(true); _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); connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &MessagesMixer::nodeKilled);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::MessagesData, this, "handleMessages"); packetReceiver.registerListener(PacketType::MessagesData,
packetReceiver.registerListener(PacketType::MessagesSubscribe, this, "handleMessagesSubscribe"); PacketReceiver::makeSourcedListenerReference<MessagesMixer>(this, &MessagesMixer::handleMessages));
packetReceiver.registerListener(PacketType::MessagesUnsubscribe, this, "handleMessagesUnsubscribe"); 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) { void MessagesMixer::nodeKilled(SharedNodePointer killedNode) {

View file

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

View file

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

View file

@ -202,9 +202,9 @@
; The Inner invocation has written an uninstaller binary for us. ; The Inner invocation has written an uninstaller binary for us.
; We need to sign it if it's a production or PR build. ; We need to sign it if it's a production or PR build.
!if @PRODUCTION_BUILD@ == 1 !if @PRODUCTION_BUILD@ == 1
!if @BYPASS_SIGNING@ == 1 !if @BYPASS_SIGNING@ == TRUE
!warning "BYPASS_SIGNING set - installer will not be signed" !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 !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
!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) { function getMetaverseUrl(callback) {
$.ajax('/api/metaverse_info', { $.ajax('/api/metaverse_info', {
success: function(data) { success: function(data) {

View file

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

View file

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

View file

@ -50,6 +50,7 @@ $(document).ready(function(){
prepareAccessTokenPrompt(function(accessToken) { prepareAccessTokenPrompt(function(accessToken) {
Metaverse.accessToken = accessToken; Metaverse.accessToken = accessToken;
saveAccessToken(); saveAccessToken();
promptToCreateDomainID();
}); });
}); });
@ -171,6 +172,45 @@ function setupWizardSteps() {
$(currentStep).show(); $(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) { function updatePlaceNameLink(address) {
if (address) { if (address) {
var url = URLs.PLACE_URL + '/' + address; var url = URLs.PLACE_URL + '/' + address;
@ -341,7 +381,7 @@ function saveAccessToken() {
$(this).blur(); $(this).blur();
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved // 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) { function getSettingDescriptionForKey(groupKey, settingKey) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -25,7 +25,8 @@ OctreePacketProcessor::OctreePacketProcessor():
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
const PacketReceiver::PacketTypeList octreePackets = const PacketReceiver::PacketTypeList octreePackets =
{ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase, PacketType::EntityQueryInitialResultsComplete }; { PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase, PacketType::EntityQueryInitialResultsComplete };
packetReceiver.registerDirectListenerForTypes(octreePackets, this, "handleOctreePacket"); packetReceiver.registerDirectListenerForTypes(octreePackets,
PacketReceiver::makeSourcedListenerReference<OctreePacketProcessor>(this, &OctreePacketProcessor::handleOctreePacket));
} }
OctreePacketProcessor::~OctreePacketProcessor() { } 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. // 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>(); auto nodeList = DependencyManager::get<NodeList>();
PacketReceiver& packetReceiver = nodeList->getPacketReceiver(); PacketReceiver& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::AvatarZonePresence, this, "processAvatarZonePresencePacketOnClient"); packetReceiver.registerListener(PacketType::AvatarZonePresence,
PacketReceiver::makeUnsourcedListenerReference<ScreenshareScriptingInterface>(this, &ScreenshareScriptingInterface::processAvatarZonePresencePacketOnClient));
}; };
ScreenshareScriptingInterface::~ScreenshareScriptingInterface() { ScreenshareScriptingInterface::~ScreenshareScriptingInterface() {

View file

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

View file

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

View file

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

View file

@ -123,10 +123,14 @@ AvatarHashMap::AvatarHashMap() {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver(); auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); packetReceiver.registerListener(PacketType::BulkAvatarData,
packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar"); PacketReceiver::makeSourcedListenerReference<AvatarHashMap>(this, &AvatarHashMap::processAvatarDataPacket));
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::KillAvatar,
packetReceiver.registerListener(PacketType::BulkAvatarTraits, this, "processBulkAvatarTraits"); 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); 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) { void ClientTraitsHandler::markTraitUpdated(AvatarTraits::TraitType updatedTrait) {

View file

@ -26,7 +26,8 @@
EntityEditPacketSender::EntityEditPacketSender() { EntityEditPacketSender::EntityEditPacketSender() {
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); 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) { void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {

View file

@ -14,7 +14,8 @@
EntityScriptServerLogClient::EntityScriptServerLogClient() { EntityScriptServerLogClient::EntityScriptServerLogClient() {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver(); 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::nodeActivated, this, &EntityScriptServerLogClient::nodeActivated);
QObject::connect(nodeList.data(), &NodeList::nodeKilled, this, &EntityScriptServerLogClient::nodeKilled); 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); connect(nodeList.data(), &NodeList::canGetAndSetPrivateUserDataChanged, this, &EntityScriptingInterface::canGetAndSetPrivateUserDataChanged);
auto& packetReceiver = nodeList->getPacketReceiver(); 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, void EntityScriptingInterface::queueEntityMessage(PacketType packetType,

View file

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

View file

@ -10,11 +10,13 @@
// //
#include "GLBackend.h" #include "GLBackend.h"
#include <mutex> #include <mutex>
#include <queue> #include <queue>
#include <list> #include <list>
#include <functional> #include <functional>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
#include "gl/Config.h"
#if defined(NSIGHT_FOUND) #if defined(NSIGHT_FOUND)
#include "nvToolsExt.h" #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 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_TEXTURE_IMAGE_UNITS{ 0 };
GLint GLBackend::MAX_UNIFORM_BUFFER_BINDINGS{ 0 }; GLint GLBackend::MAX_UNIFORM_BUFFER_BINDINGS{ 0 };
GLint GLBackend::MAX_COMBINED_UNIFORM_BLOCKS{ 0 }; GLint GLBackend::MAX_COMBINED_UNIFORM_BLOCKS{ 0 };
GLint GLBackend::MAX_COMBINED_TEXTURE_IMAGE_UNITS{ 0 }; GLint GLBackend::MAX_COMBINED_TEXTURE_IMAGE_UNITS{ 0 };
GLint GLBackend::MAX_UNIFORM_BLOCK_SIZE{ 0 }; GLint GLBackend::MAX_UNIFORM_BLOCK_SIZE{ 0 };
GLint GLBackend::UNIFORM_BUFFER_OFFSET_ALIGNMENT{ 1 }; 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() { void GLBackend::init() {
static std::once_flag once; static std::once_flag once;
@ -132,13 +148,59 @@ void GLBackend::init() {
GL_GET_INTEGER(MAX_UNIFORM_BLOCK_SIZE); GL_GET_INTEGER(MAX_UNIFORM_BLOCK_SIZE);
GL_GET_INTEGER(UNIFORM_BUFFER_OFFSET_ALIGNMENT); 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); LOG_GL_CONTEXT_INFO(gpugllogging, contextInfo);
GPUIdent* gpu = GPUIdent::getInstance(vendor, renderer);
// From here on, GPUIdent::getInstance()->getMumble() should efficiently give the same answers. // From here on, GPUIdent::getInstance()->getMumble() should efficiently give the same answers.
qCDebug(gpugllogging) << "GPU:"; qCDebug(gpugllogging) << "GPU:";
qCDebug(gpugllogging) << "\tcard:" << gpu->getName(); qCDebug(gpugllogging) << "\tcard:" << gpu->getName();
qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver(); 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) << "Limits:";
qCDebug(gpugllogging) << "\tmax textures:" << MAX_TEXTURE_IMAGE_UNITS; qCDebug(gpugllogging) << "\tmax textures:" << MAX_TEXTURE_IMAGE_UNITS;
qCDebug(gpugllogging) << "\tmax texture binding:" << MAX_COMBINED_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) { GLBackend::GLBackend(bool syncCache) {
_pipeline._cameraCorrectionBuffer._buffer->flush(); _pipeline._cameraCorrectionBuffer._buffer->flush();
initShaderBinaryCache(); initShaderBinaryCache();

View file

@ -67,6 +67,13 @@ protected:
GLBackend(); GLBackend();
public: public:
enum VideoCardType {
ATI,
NVIDIA,
MESA,
Unknown
};
#if defined(USE_GLES) #if defined(USE_GLES)
// https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glGet.xhtml // https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glGet.xhtml
static const GLint MIN_REQUIRED_TEXTURE_IMAGE_UNITS = 16; static const GLint MIN_REQUIRED_TEXTURE_IMAGE_UNITS = 16;
@ -89,6 +96,25 @@ public:
static GLint MAX_COMBINED_TEXTURE_IMAGE_UNITS; static GLint MAX_COMBINED_TEXTURE_IMAGE_UNITS;
static GLint MAX_UNIFORM_BLOCK_SIZE; static GLint MAX_UNIFORM_BLOCK_SIZE;
static GLint UNIFORM_BUFFER_OFFSET_ALIGNMENT; 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(); virtual ~GLBackend();

View file

@ -19,6 +19,8 @@
#define MAX_RESOURCE_TEXTURES_PER_FRAME 2 #define MAX_RESOURCE_TEXTURES_PER_FRAME 2
#define NO_BUFFER_WORK_SLEEP_TIME_MS 2 #define NO_BUFFER_WORK_SLEEP_TIME_MS 2
#define THREADED_TEXTURE_BUFFERING 1 #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); 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() { void GLTextureTransferEngineDefault::updateMemoryPressure() {
PROFILE_RANGE(render_gpu_gl, __FUNCTION__); PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
bool useAvailableGlMemory = false;
size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage(); size_t allowedMemoryAllocation = gpu::Texture::getAllowedGPUMemoryUsage();
if (0 == allowedMemoryAllocation) { 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) // Clear any defunct textures (weak pointers that no longer have a valid texture)
@ -205,7 +223,7 @@ void GLTextureTransferEngineDefault::updateMemoryPressure() {
idealMemoryAllocation += texture->evalTotalSize(); idealMemoryAllocation += texture->evalTotalSize();
// Track how much we're actually using // Track how much we're actually using
totalVariableMemoryAllocation += gltexture->size(); totalVariableMemoryAllocation += gltexture->size();
if (vartexture->canDemote()) { if (!gltexture->_gpuObject.getImportant() && vartexture->canDemote()) {
canDemote |= true; canDemote |= true;
} }
if (vartexture->canPromote()) { if (vartexture->canPromote()) {
@ -218,7 +236,22 @@ void GLTextureTransferEngineDefault::updateMemoryPressure() {
Backend::textureResourceIdealGPUMemSize.set(idealMemoryAllocation); Backend::textureResourceIdealGPUMemSize.set(idealMemoryAllocation);
size_t unallocated = idealMemoryAllocation - totalVariableMemoryAllocation; 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 we're oversubscribed we need to demote textures IMMEDIATELY
if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) { if (pressure > OVERSUBSCRIBED_PRESSURE_VALUE && canDemote) {
@ -470,7 +503,7 @@ void GLTextureTransferEngineDefault::processDemotes(size_t reliefRequired, const
for (const auto& texture : strongTextures) { for (const auto& texture : strongTextures) {
GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture); GLTexture* gltexture = Backend::getGPUObject<GLTexture>(*texture);
GLVariableAllocationSupport* vargltexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture); GLVariableAllocationSupport* vargltexture = dynamic_cast<GLVariableAllocationSupport*>(gltexture);
if (vargltexture->canDemote()) { if (!gltexture->_gpuObject.getImportant() && vargltexture->canDemote()) {
demoteQueue.push({ texture, (float)gltexture->size() }); demoteQueue.push({ texture, (float)gltexture->size() });
} }
} }

View file

@ -571,6 +571,9 @@ public:
void setExternalRecycler(const ExternalRecycler& recycler); void setExternalRecycler(const ExternalRecycler& recycler);
ExternalRecycler getExternalRecycler() const; ExternalRecycler getExternalRecycler() const;
bool getImportant() const { return _important; }
void setImportant(bool important) { _important = important; }
const GPUObjectPointer gpuObject {}; const GPUObjectPointer gpuObject {};
ExternalUpdates getUpdates() const; ExternalUpdates getUpdates() const;
@ -632,6 +635,7 @@ protected:
bool _autoGenerateMips = false; bool _autoGenerateMips = false;
bool _isIrradianceValid = false; bool _isIrradianceValid = false;
bool _defined = 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); 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 nodeList = DependencyManager::get<LimitedNodeList>();
auto& packetReceiver = nodeList->getPacketReceiver(); auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssetMappingOperationReply, this, "handleAssetMappingOperationReply"); packetReceiver.registerListener(PacketType::AssetMappingOperationReply,
packetReceiver.registerListener(PacketType::AssetGetInfoReply, this, "handleAssetGetInfoReply"); PacketReceiver::makeSourcedListenerReference<AssetClient>(this, &AssetClient::handleAssetMappingOperationReply));
packetReceiver.registerListener(PacketType::AssetGetReply, this, "handleAssetGetReply", true); packetReceiver.registerListener(PacketType::AssetGetInfoReply,
packetReceiver.registerListener(PacketType::AssetUploadReply, this, "handleAssetUploadReply"); 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::nodeKilled, this, &AssetClient::handleNodeKilled);
connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset, connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset,

View file

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

View file

@ -34,7 +34,8 @@ EntityScriptClient::EntityScriptClient() {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver(); 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::nodeKilled, this, &EntityScriptClient::handleNodeKilled);
connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset, connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset,

View file

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

View file

@ -28,7 +28,8 @@ MessagesClient::MessagesClient() {
}); });
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver(); 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); connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated);
} }

View file

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

View file

@ -12,7 +12,8 @@
#include "PacketReceiver.h" #include "PacketReceiver.h"
#include <QMutexLocker> #include <QtCore/QMetaObject>
#include <QtCore/QMutexLocker>
#include "DependencyManager.h" #include "DependencyManager.h"
#include "NetworkLogging.h" #include "NetworkLogging.h"
@ -25,85 +26,57 @@ PacketReceiver::PacketReceiver(QObject* parent) : QObject(parent) {
qRegisterMetaType<QSharedPointer<ReceivedMessage>>(); 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(!types.empty(), "PacketReceiver::registerListenerForTypes", "No types to register");
Q_ASSERT_X(listener, "PacketReceiver::registerListenerForTypes", "No object to register"); Q_ASSERT_X(listener, "PacketReceiver::registerListenerForTypes", "No listener to register");
Q_ASSERT_X(slot, "PacketReceiver::registerListenerForTypes", "No slot to register");
// Partition types based on whether they are sourced or not (non sourced in front) std::for_each(std::begin(types), std::end(types), [this, &listener](PacketType type) {
auto middle = std::partition(std::begin(types), std::end(types), [](PacketType type) { registerVerifiedListener(type, listener);
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);
}); });
return true; return true;
} }
void PacketReceiver::registerDirectListener(PacketType type, QObject* listener, const char* slot) { void PacketReceiver::registerDirectListener(PacketType type, const ListenerReferencePointer& listener) {
Q_ASSERT_X(listener, "PacketReceiver::registerDirectListener", "No object to register"); Q_ASSERT_X(listener, "PacketReceiver::registerDirectListener", "No listener to register");
Q_ASSERT_X(slot, "PacketReceiver::registerDirectListener", "No slot to register");
bool success = registerListener(type, listener, slot); bool success = registerListener(type, listener);
if (success) { if (success) {
QMutexLocker locker(&_directConnectSetMutex); QMutexLocker locker(&_directConnectSetMutex);
// if we successfully registered, add this object to the set of objects that are directly connected // 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, void PacketReceiver::registerDirectListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener) {
QObject* listener, const char* slot) { Q_ASSERT_X(listener, "PacketReceiver::registerDirectListenerForTypes", "No listener to register");
Q_ASSERT_X(listener, "PacketReceiver::registerDirectListenerForTypes", "No object to register");
Q_ASSERT_X(slot, "PacketReceiver::registerDirectListenerForTypes", "No slot to register");
// just call register listener for types to start // 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) { if (success) {
QMutexLocker locker(&_directConnectSetMutex); QMutexLocker locker(&_directConnectSetMutex);
// if we successfully registered, add this object to the set of objects that are directly connected // 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 PacketReceiver::registerListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending) {
bool deliverPending) { Q_ASSERT_X(listener, "PacketReceiver::registerListener", "No listener to register");
Q_ASSERT_X(listener, "PacketReceiver::registerListener", "No object to register");
Q_ASSERT_X(slot, "PacketReceiver::registerListener", "No slot 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; qCDebug(networking) << "Registering a packet listener for packet list type" << type;
registerVerifiedListener(type, listener, matchingMethod, deliverPending); registerVerifiedListener(type, listener, deliverPending);
return true; return true;
} else { } else {
qCWarning(networking) << "FAILED to Register a packet listener for packet list type" << type; 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 { bool PacketReceiver::matchingMethodForListener(PacketType type, const ListenerReferencePointer& listener) const {
Q_ASSERT_X(object, "PacketReceiver::matchingMethodForListener", "No object to call"); Q_ASSERT_X(listener, "PacketReceiver::matchingMethodForListener", "No listener to call");
Q_ASSERT_X(slot, "PacketReceiver::matchingMethodForListener", "No slot to call");
// normalize the slot with the expected parameters bool isSourced = listener->isSourced();
static const QString SIGNATURE_TEMPLATE("%1(%2)"); bool isNonSourcedPacket = PacketTypeEnum::getNonSourcedPackets().contains(type);
static const QString NON_SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer<ReceivedMessage>";
QSet<QString> possibleSignatures { assert(!isSourced || !isNonSourcedPacket);
SIGNATURE_TEMPLATE.arg(slot, NON_SOURCED_MESSAGE_LISTENER_PARAMETERS) if (isSourced && isNonSourcedPacket) {
}; qCDebug(networking) << "PacketReceiver::registerListener cannot support a sourced listener for type" << type;
return false;
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);
} }
int methodIndex = -1; return true;
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();
}
} }
void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object, const QMetaMethod& slot, bool deliverPending) { void PacketReceiver::registerVerifiedListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending) {
Q_ASSERT_X(object, "PacketReceiver::registerVerifiedListener", "No object to register"); Q_ASSERT_X(listener, "PacketReceiver::registerVerifiedListener", "No listener to register");
QMutexLocker locker(&_packetListenerLock); QMutexLocker locker(&_packetListenerLock);
if (_messageListenerMap.contains(type)) { if (_messageListenerMap.contains(type)) {
@ -175,7 +109,7 @@ void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object,
} }
// add the mapping // add the mapping
_messageListenerMap[type] = { QPointer<QObject>(object), slot, deliverPending }; _messageListenerMap[type] = { listener, deliverPending };
} }
void PacketReceiver::unregisterListener(QObject* listener) { void PacketReceiver::unregisterListener(QObject* listener) {
@ -188,7 +122,7 @@ void PacketReceiver::unregisterListener(QObject* listener) {
auto it = _messageListenerMap.begin(); auto it = _messageListenerMap.begin();
while (it != _messageListenerMap.end()) { while (it != _messageListenerMap.end()) {
if (it.value().object == listener) { if (it.value().listener->getObject() == listener) {
it = _messageListenerMap.erase(it); it = _messageListenerMap.erase(it);
} else { } else {
++it; ++it;
@ -261,7 +195,7 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer<ReceivedMessage> recei
QMutexLocker packetListenerLocker(&_packetListenerLock); QMutexLocker packetListenerLocker(&_packetListenerLock);
auto it = _messageListenerMap.find(receivedMessage->getType()); auto it = _messageListenerMap.find(receivedMessage->getType());
if (it != _messageListenerMap.end() && it->method.isValid()) { if (it != _messageListenerMap.end() && !it->listener.isNull()) {
auto listener = it.value(); auto listener = it.value();
@ -271,36 +205,19 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer<ReceivedMessage> recei
bool success = false; bool success = false;
Qt::ConnectionType connectionType; bool isDirectConnect = false;
// check if this is a directly connected listener // check if this is a directly connected listener
{ {
QMutexLocker directConnectLocker(&_directConnectSetMutex); 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 // one final check on the QPointer before we go to invoke
if (listener.object) { if (listener.listener->getObject()) {
if (metaMethod.parameterTypes().contains(SHARED_NODE_NORMALIZED)) { if (isDirectConnect) {
success = metaMethod.invoke(listener.object, success = listener.listener->invokeDirectly(receivedMessage, matchingNode);
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));
} else { } else {
success = metaMethod.invoke(listener.object, success = listener.listener->invokeWithQt(receivedMessage, matchingNode);
connectionType,
Q_ARG(QSharedPointer<ReceivedMessage>, receivedMessage));
} }
} else { } else {
qCDebug(networking).nospace() << "Listener for packet " << receivedMessage->getType() 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 // if it exists, remove the listener from _directlyConnectedObjects
{ {
QMutexLocker directConnectLocker(&_directConnectSetMutex); QMutexLocker directConnectLocker(&_directConnectSetMutex);
_directlyConnectedObjects.remove(listener.object); _directlyConnectedObjects.remove(listener.listener->getObject());
} }
} }
if (!success) { if (!success) {
qCDebug(networking).nospace() << "Error delivering packet " << receivedMessage->getType() << " to listener " qCDebug(networking).nospace() << "Error delivering packet " << receivedMessage->getType() << " to listener "
<< listener.object << "::" << qPrintable(listener.method.methodSignature()); << listener.listener->getObject();
} }
} else if (it == _messageListenerMap.end()) { } else if (it == _messageListenerMap.end()) {
qCWarning(networking) << "No listener found for packet type" << receivedMessage->getType(); qCWarning(networking) << "No listener found for packet type" << receivedMessage->getType();
// insert a dummy listener so we don't print this again // 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 <vector>
#include <unordered_map> #include <unordered_map>
#include <QtCore/QMap>
#include <QtCore/QMetaMethod>
#include <QtCore/QMutex> #include <QtCore/QMutex>
#include <QtCore/QObject> #include <QtCore/QObject>
#include <QtCore/QPointer> #include <QtCore/QPointer>
#include <QtCore/QSet> #include <QtCore/QSet>
#include <QtCore/QSharedPointer>
#include <QtCore/QEnableSharedFromThis>
#include "NLPacket.h" #include "NLPacket.h"
#include "NLPacketList.h" #include "NLPacketList.h"
@ -29,6 +29,7 @@
#include "udt/PacketHeaders.h" #include "udt/PacketHeaders.h"
class EntityEditPacketSender; class EntityEditPacketSender;
class Node;
class OctreePacketProcessor; class OctreePacketProcessor;
namespace std { namespace std {
@ -42,6 +43,22 @@ namespace std {
class PacketReceiver : public QObject { class PacketReceiver : public QObject {
Q_OBJECT 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: public:
using PacketTypeList = std::vector<PacketType>; 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 // 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 // been received. If deliverPending is true, ReceivedMessage will be delivered as soon as the first packet
// for the message is received. // for the message is received.
bool registerListener(PacketType type, QObject* listener, const char* slot, bool deliverPending = false); bool registerListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending = false);
bool registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot); bool registerListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener);
void unregisterListener(QObject* listener); void unregisterListener(QObject* listener);
void handleVerifiedPacket(std::unique_ptr<udt::Packet> packet); void handleVerifiedPacket(std::unique_ptr<udt::Packet> packet);
@ -64,9 +81,34 @@ public:
void handleMessageFailure(HifiSockAddr from, udt::Packet::MessageNumber messageNumber); void handleMessageFailure(HifiSockAddr from, udt::Packet::MessageNumber messageNumber);
private: 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 { struct Listener {
QPointer<QObject> object; ListenerReferencePointer listener;
QMetaMethod method;
bool deliverPending; bool deliverPending;
}; };
@ -74,11 +116,11 @@ private:
// these are brutal hacks for now - ideally GenericThread / ReceivedPacketProcessor // 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 // 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 registerDirectListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener);
void registerDirectListener(PacketType type, QObject* listener, const char* slot); void registerDirectListener(PacketType type, const ListenerReferencePointer& listener);
QMetaMethod matchingMethodForListener(PacketType type, QObject* object, const char* slot) const; bool matchingMethodForListener(PacketType type, const ListenerReferencePointer& listener) const;
void registerVerifiedListener(PacketType type, QObject* listener, const QMetaMethod& slot, bool deliverPending = false); void registerVerifiedListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending = false);
QMutex _packetListenerLock; QMutex _packetListenerLock;
QHash<PacketType, Listener> _messageListenerMap; QHash<PacketType, Listener> _messageListenerMap;
@ -93,4 +135,42 @@ private:
friend class OctreePacketProcessor; 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 #endif // hifi_PacketReceiver_h

View file

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

View file

@ -15,8 +15,14 @@
#include <DependencyManager.h> #include <DependencyManager.h>
#include <StatTracker.h> #include <StatTracker.h>
#include <QtCore/QDateTime>
#include <QtCore/QThread> #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() { void ResourceRequest::send() {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {

View file

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

View file

@ -64,7 +64,8 @@ void OctreePersistThread::start() {
cleanupOldReplacementBackups(); cleanupOldReplacementBackups();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver(); 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>(); auto nodeList = DependencyManager::get<NodeList>();
const DomainHandler& domainHandler = nodeList->getDomainHandler(); 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)); gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR));
_texture->setStoredMipFormat(formatMip); _texture->setStoredMipFormat(formatMip);
_texture->assignStoredMip(0, image.sizeInBytes(), image.constBits()); _texture->assignStoredMip(0, image.sizeInBytes(), image.constBits());
_texture->setImportant(true);
} }
void Font::setupGPU() { void Font::setupGPU() {

View file

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

View file

@ -60,7 +60,7 @@ private:
Mutex _containerLock; Mutex _containerLock;
QMap<QUrl, ScriptRequest> _activeScriptRequests; QMap<QUrl, ScriptRequest> _activeScriptRequests;
QHash<QUrl, QString> _scriptCache; QHash<QUrl, QVariantMap> _scriptCache;
QMultiMap<QUrl, ScriptUser*> _scriptUsers; 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. // 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 // Note that the first entry in the backtrace should be "print" and is somewhat useless to us
AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get(); AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get();
if (loggerInterface->showSourceDebugging()) { if (loggerInterface && loggerInterface->showSourceDebugging()) {
QScriptContext* userContext = context; QScriptContext* userContext = context;
while (userContext && QScriptContextInfo(userContext).functionType() == QScriptContextInfo::NativeFunction) { while (userContext && QScriptContextInfo(userContext).functionType() == QScriptContextInfo::NativeFunction) {
userContext = userContext->parentContext(); userContext = userContext->parentContext();

View file

@ -17,6 +17,10 @@
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <windows.h> #include <windows.h>
#endif #endif
#ifdef Q_OS_UNIX
#include <stdio.h>
#include <unistd.h>
#endif
#include <QtCore/QCoreApplication> #include <QtCore/QCoreApplication>
#include <QtCore/QDateTime> #include <QtCore/QDateTime>
@ -32,6 +36,40 @@ LogHandler& LogHandler::getInstance() {
return staticInstance; 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) { const char* stringForLogType(LogMsgType msgType) {
switch (msgType) { switch (msgType) {
case LogInfo: 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 // the following will produce 11/18 13:55:36
const QString DATE_STRING_FORMAT = "MM/dd hh:mm:ss"; 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 + " ")); 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 #ifdef Q_OS_WIN
// On windows, this will output log lines into the Visual Studio "output" tab // On windows, this will output log lines into the Visual Studio "output" tab
OutputDebugStringA(qPrintable(logMessage)); OutputDebugStringA(qPrintable(logMessage));

View file

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

View file

@ -172,31 +172,31 @@ public:
QMessageBox::StandardButtons buttons = QMessageBox::Ok, QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); 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 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 = 0); 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 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 = 0); 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 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 = 0); 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 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 = 0); 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 // 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 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 = 0); 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 // 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 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 = 0); 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 // 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 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 = 0); 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 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 = 0); 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 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()); 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 // Compatibility with QInputDialog::getText
static QString getText(void* ignored, const QString & title, const QString & label, static QString getText(void* ignored, const QString & title, const QString & label,
QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0, 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); return getText(OffscreenUi::ICON_NONE, title, label, text, ok);
} }
// Compatibility with QInputDialog::getItem // Compatibility with QInputDialog::getItem
static QString getItem(void *ignored, const QString & title, const QString & label, const QStringList & items, 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) { Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getItem(OffscreenUi::ICON_NONE, title, label, items, current, editable, ok); return getItem(OffscreenUi::ICON_NONE, title, label, items, current, editable, ok);
} }
@ -222,12 +222,12 @@ public:
// Compatibility with QInputDialog::getText // Compatibility with QInputDialog::getText
static ModalDialogListener* getTextAsync(void* ignored, const QString & title, const QString & label, static ModalDialogListener* getTextAsync(void* ignored, const QString & title, const QString & label,
QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0, 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); return getTextAsync(OffscreenUi::ICON_NONE, title, label, text);
} }
// Compatibility with QInputDialog::getItem // Compatibility with QInputDialog::getItem
static ModalDialogListener* getItemAsync(void *ignored, const QString & title, const QString & label, const QStringList & items, 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) { Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getItemAsync(OffscreenUi::ICON_NONE, title, label, items, current, editable); 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 // Created by Kasen IO on 2019.07.14 | realities.dev | kasenvr@gmail.com
// Copyright 2019 Kasen IO // Copyright 2019 Kasen IO
// //
// Authored by: Humbletim (humbletim@gmail.com)
//
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // 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 #ifndef EXAMPLE_SCRIPT_PLUGIN_H
#define 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 // Created by Kasen IO on 2019.07.14 | realities.dev | kasenvr@gmail.com
// Copyright 2019 Kasen IO // Copyright 2019 Kasen IO
// //
// Authored by: Humbletim (humbletim@gmail.com)
//
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // 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" #include "ExampleScriptPlugin.h"

View file

@ -1696,7 +1696,7 @@ function recursiveDelete(entities, childrenList, deletedIDs, entityHostType) {
} }
function unparentSelectedEntities() { function unparentSelectedEntities() {
if (SelectionManager.hasSelection()) { if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var selectedEntities = selectionManager.selections; var selectedEntities = selectionManager.selections;
var parentCheck = false; var parentCheck = false;
@ -1720,6 +1720,9 @@ function unparentSelectedEntities() {
} else { } else {
Window.notify("Entity unparented"); Window.notify("Entity unparented");
} }
//Refresh
entityListTool.sendUpdate();
selectionManager._update(false, this);
} else { } else {
audioFeedback.rejection(); audioFeedback.rejection();
if (selectedEntities.length > 1) { if (selectedEntities.length > 1) {
@ -1730,11 +1733,11 @@ function unparentSelectedEntities() {
} }
} else { } else {
audioFeedback.rejection(); audioFeedback.rejection();
Window.notifyEditError("You have nothing selected to unparent"); Window.notifyEditError("You have nothing selected or the selection has locked entities.");
} }
} }
function parentSelectedEntities() { function parentSelectedEntities() {
if (SelectionManager.hasSelection()) { if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var selectedEntities = selectionManager.selections; var selectedEntities = selectionManager.selections;
if (selectedEntities.length <= 1) { if (selectedEntities.length <= 1) {
audioFeedback.rejection(); audioFeedback.rejection();
@ -1756,17 +1759,20 @@ function parentSelectedEntities() {
if (parentCheck) { if (parentCheck) {
audioFeedback.confirmation(); audioFeedback.confirmation();
Window.notify("Entities parented"); Window.notify("Entities parented");
//Refresh
entityListTool.sendUpdate();
selectionManager._update(false, this);
} else { } else {
audioFeedback.rejection(); audioFeedback.rejection();
Window.notify("Entities are already parented to last"); Window.notify("Entities are already parented to last");
} }
} else { } else {
audioFeedback.rejection(); audioFeedback.rejection();
Window.notifyEditError("You have nothing selected to parent"); Window.notifyEditError("You have nothing selected or the selection has locked entities.");
} }
} }
function deleteSelectedEntities() { function deleteSelectedEntities() {
if (SelectionManager.hasSelection()) { if (SelectionManager.hasSelection() && SelectionManager.hasUnlockedSelection()) {
var deletedIDs = []; var deletedIDs = [];
SelectionManager.saveProperties(); SelectionManager.saveProperties();
@ -1797,6 +1803,9 @@ function deleteSelectedEntities() {
pushCommandForSelections([], savedProperties); pushCommandForSelections([], savedProperties);
entityListTool.deleteEntities(deletedIDs); 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; 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 }()); // END LOCAL_SCOPE

View file

@ -190,7 +190,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
PROFILE("getMultipleProperties", function () { PROFILE("getMultipleProperties", function () {
var multipleProperties = Entities.getMultipleEntityProperties(ids, ['position', 'name', 'type', 'locked', var multipleProperties = Entities.getMultipleEntityProperties(ids, ['position', 'name', 'type', 'locked',
'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script', 'certificateID', '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++) { for (var i = 0; i < multipleProperties.length; i++) {
var properties = multipleProperties[i]; var properties = multipleProperties[i];
@ -203,6 +203,17 @@ EntityListTool = function(shouldUseEditTabletApp) {
} else if (properties.type === "Image") { } else if (properties.type === "Image") {
url = properties.imageURL; 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({ entities.push({
id: ids[i], id: ids[i],
name: properties.name, name: properties.name,
@ -222,7 +233,10 @@ EntityListTool = function(shouldUseEditTabletApp) {
isBaked: entityIsBaked(properties), isBaked: entityIsBaked(properties),
drawCalls: (properties.renderInfo !== undefined ? drawCalls: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.drawCalls) : ""), 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) { function onFileSaveChanged(filename) {
Window.saveFileChanged.disconnect(onFileSaveChanged); Window.saveFileChanged.disconnect(onFileSaveChanged);
if (filename !== "") { if (filename !== "") {
@ -323,6 +353,24 @@ EntityListTool = function(shouldUseEditTabletApp) {
unparentSelectedEntities(); unparentSelectedEntities();
} else if (data.type === 'hmdMultiSelectMode') { } else if (data.type === 'hmdMultiSelectMode') {
hmdMultiSelectMode = data.value; 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> </div>
<button id="toggle-space-mode" class="hifi-edit-button space-mode-local">Local</button> <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="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="normal" id="selection" value="Selection..." />
<input type="button" class="blue vglyph" id="hmdcopy" value="B" style="display: none;" /> <input type="button" class="normal" id="actions" value="Actions..." />
<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="{" />
</div> </div>
<div id="entity-list"> <div id="entity-list">
<div id="filter-area"> <div id="filter-area">
@ -102,5 +91,142 @@
</div> </div>
</div> </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> </body>
</html> </html>

View file

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

View file

@ -19,6 +19,7 @@
const SPACE_LOCAL = "local"; const SPACE_LOCAL = "local";
const SPACE_WORLD = "world"; const SPACE_WORLD = "world";
const HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; const HIGHLIGHT_LIST_NAME = "editHandleHighlightList";
const MIN_DISTANCE_TO_REZ_FROM_AVATAR = 3;
Script.include([ Script.include([
"../../libraries/controllers.js", "../../libraries/controllers.js",
@ -26,7 +27,6 @@ Script.include([
"../../libraries/utils.js" "../../libraries/utils.js"
]); ]);
function deepCopy(v) { function deepCopy(v) {
return JSON.parse(JSON.stringify(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; return that;
})(); })();
@ -660,8 +795,10 @@ SelectionDisplay = (function() {
const COLOR_HOVER = { red: 255, green: 220, blue: 82 }; const COLOR_HOVER = { red: 255, green: 220, blue: 82 };
const COLOR_DUPLICATOR = { red: 162, green: 0, blue: 255 }; const COLOR_DUPLICATOR = { red: 162, green: 0, blue: 255 };
const COLOR_ROTATE_CURRENT_RING = { red: 255, green: 99, blue: 9 }; const COLOR_ROTATE_CURRENT_RING = { red: 255, green: 99, blue: 9 };
const COLOR_BOUNDING_EDGE = { red: 128, green: 128, blue: 128 }; const COLOR_BOUNDING_EDGE = { red: 160, green: 160, blue: 160 };
const COLOR_SCALE_CUBE = { 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 = { red: 255, green: 255, blue: 255 };
const COLOR_DEBUG_PICK_PLANE_HIT = { red: 255, green: 165, blue: 0 }; const COLOR_DEBUG_PICK_PLANE_HIT = { red: 255, green: 165, blue: 0 };
@ -1791,6 +1928,18 @@ SelectionDisplay = (function() {
var rotationZ = Quat.multiply(rotation, localRotationZ); var rotationZ = Quat.multiply(rotation, localRotationZ);
worldRotationZ = rotationZ; 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 = { var selectionBoxGeometry = {
position: position, position: position,
rotation: rotation, rotation: rotation,
@ -1912,6 +2061,7 @@ SelectionDisplay = (function() {
Entities.editEntity(handleBoundingBox, { Entities.editEntity(handleBoundingBox, {
position: position, position: position,
rotation: rotation, rotation: rotation,
color: handleBoundingBoxColor,
dimensions: dimensions dimensions: dimensions
}); });

View file

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

View file

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

View file

@ -415,6 +415,13 @@ input[type=button].glyph, button.hifi-edit-button.glyph {
padding: 0; 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 { input[type=button].vglyph, button.hifi-edit-button.vglyph {
font-family: Vircadia-Glyphs; font-family: Vircadia-Glyphs;
font-size: 20px; 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; 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 { div#grid-section, body#entity-list-body {
padding-bottom: 0; padding-bottom: 0;
margin: 16px; margin: 16px;
@ -1267,12 +1273,6 @@ div#grid-section, body#entity-list-body {
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
#delete {
float: right;
margin-right: 0;
background-color: #ff0000;
}
#entity-list { #entity-list {
position: relative; /* New positioning context. */ position: relative; /* New positioning context. */
} }
@ -1448,6 +1448,11 @@ input[type=button]#export {
font-size: 15px; font-size: 15px;
} }
#entity-table-scroll .vglyph {
font-family: Vircadia-Glyphs;
font-size: 15px;
}
#entity-table { #entity-table {
margin-top: -28px; margin-top: -28px;
margin-bottom: -18px; margin-bottom: -18px;
@ -1460,7 +1465,7 @@ input[type=button]#export {
background: none; background: none;
} }
#entity-table .glyph { #entity-table .glyph .vglyph {
margin: 0 -2px 0 -2px; margin: 0 -2px 0 -2px;
vertical-align: middle; vertical-align: middle;
} }
@ -1493,11 +1498,11 @@ input[type=button]#export {
outline: none; outline: none;
} }
#entity-table th .glyph { #entity-table th .glyph .vglyph {
position: relative; position: relative;
left: 4px; left: 4px;
} }
#entity-table th .glyph + .sort-order { #entity-table th .glyph .vglyph + .sort-order {
position: relative; position: relative;
left: 4px; left: 4px;
} }
@ -1524,7 +1529,7 @@ input[type=button]#export {
#entity-table td { #entity-table td {
box-sizing: border-box; box-sizing: border-box;
} }
#entity-table td.glyph { #entity-table td .glyph .vglyph {
text-align: center; text-align: center;
padding: 0; padding: 0;
} }
@ -1860,3 +1865,71 @@ div.multiZoneSelToolbar {
padding: 0px; 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 import glob
from github import Github from github import Github
def main(): def main():
bucket_name = os.environ['BUCKET_NAME'] bucket_name = os.environ['UPLOAD_BUCKET']
upload_prefix = os.environ['UPLOAD_PREFIX'] upload_prefix = os.environ['UPLOAD_PREFIX']
release_number = os.environ['RELEASE_NUMBER'] 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') S3 = boto3.client('s3')
path = os.path.join(os.getcwd(), os.environ['ARTIFACT_PATTERN']) path = os.path.join(os.getcwd(), os.environ['ARTIFACT_PATTERN'])
files = glob.glob(path, recursive=False) files = glob.glob(path, recursive=False)
for archiveFile in files: for archiveFile in files:
filePath, fileName = os.path.split(archiveFile) filePath, fileName = os.path.split(archiveFile)
S3.upload_file(os.path.join(filePath, fileName), bucket_name, full_prefix + '/' + fileName) 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") print("Finished")
main() main()