mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-23 14:14:28 +02:00
Merge remote-tracking branch 'upstream/master' into update
This commit is contained in:
commit
2e780c34d1
40 changed files with 822 additions and 580 deletions
56
.github/workflows/master_build.yml
vendored
56
.github/workflows/master_build.yml
vendored
|
@ -3,8 +3,7 @@ name: Master CI Build
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- gha-master-ci
|
- master
|
||||||
# FIXME: Change target branch to "master" before merging into "master" branch.
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
APP_NAME: interface
|
APP_NAME: interface
|
||||||
|
@ -48,50 +47,51 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
id: buildenv1
|
id: buildenv1
|
||||||
run: |
|
run: |
|
||||||
echo ::set-env name=UPLOAD_PREFIX::master
|
echo "UPLOAD_PREFIX=master" >> $GITHUB_ENV
|
||||||
echo ::set-env name=GIT_COMMIT_SHORT::`echo $GIT_COMMIT | cut -c1-7`
|
echo ::set-output name=github_sha_short::`echo $GIT_COMMIT | cut -c1-7`
|
||||||
echo ::set-env name=JOB_NAME::"build (${{matrix.os}}, ${{matrix.build_type}})"
|
echo "JOB_NAME=build (${{matrix.os}}, ${{matrix.build_type}})" >> $GITHUB_ENV
|
||||||
# Linux build variables
|
# Linux build variables
|
||||||
if [[ "${{ matrix.os }}" = "ubuntu-"* ]]; then
|
if [[ "${{ matrix.os }}" = "ubuntu-"* ]]; then
|
||||||
echo ::set-env name=PYTHON_EXEC::python3
|
echo "PYTHON_EXEC=python3" >> $GITHUB_ENV
|
||||||
echo ::set-env name=INSTALLER_EXT::tgz
|
echo "INSTALLER_EXT=tgz" >> $GITHUB_ENV
|
||||||
echo ::set-env name=CMAKE_BUILD_EXTRA::"-- -j3"
|
echo "CMAKE_BUILD_EXTRA=-- -j3" >> $GITHUB_ENV
|
||||||
echo ::set-env name=CMAKE_EXTRA::"-DBUILD_TOOLS:BOOLEAN=FALSE -DHIFI_PYTHON_EXEC:FILEPATH=$(which python3)"
|
echo "CMAKE_EXTRA=-DBUILD_TOOLS:BOOLEAN=FALSE -DHIFI_PYTHON_EXEC:FILEPATH=$(which python3)" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
# Mac build variables
|
# Mac build variables
|
||||||
if [ "${{ matrix.os }}" = "macOS-latest" ]; then
|
if [ "${{ matrix.os }}" = "macOS-latest" ]; then
|
||||||
echo ::set-env name=PYTHON_EXEC::python3
|
echo "PYTHON_EXEC=python3" >> $GITHUB_ENV
|
||||||
echo ::set-env name=ZIP_COMMAND::zip
|
echo "ZIP_COMMAND=zip" >> $GITHUB_ENV
|
||||||
echo ::set-env name=ZIP_ARGS::-r
|
echo "ZIP_ARGS=-r" >> $GITHUB_ENV
|
||||||
echo ::set-env name=INSTALLER_EXT::dmg
|
echo "INSTALLER_EXT=dmg" >> $GITHUB_ENV
|
||||||
echo ::set-env name=CMAKE_EXTRA::"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode"
|
echo "CMAKE_EXTRA=-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode" >> $GITHUB_ENV
|
||||||
echo "::set-output name=symbols_archive::${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
|
||||||
echo ::set-env name=PYTHON_EXEC::python
|
echo "PYTHON_EXEC=python" >> $GITHUB_ENV
|
||||||
echo ::set-env name=ZIP_COMMAND::7z
|
echo "ZIP_COMMAND=7z" >> $GITHUB_ENV
|
||||||
echo ::set-env name=ZIP_ARGS::a
|
echo "ZIP_ARGS=a" >> $GITHUB_ENV
|
||||||
echo ::set-env name=INSTALLER_EXT::exe
|
echo "INSTALLER_EXT=exe" >> $GITHUB_ENV
|
||||||
echo ::set-env name=CMAKE_EXTRA::"-A x64"
|
echo "CMAKE_EXTRA=-A x64" >> $GITHUB_ENV
|
||||||
echo "::set-env name=SYMBOL_REGEX::\(exe\|dll\|pdb\)"
|
echo "SYMBOL_REGEX=\(exe\|dll\|pdb\)" >> $GITHUB_ENV
|
||||||
echo "::set-output name=symbols_archive::${BUILD_NUMBER}-${{ matrix.build_type }}-win-symbols.zip"
|
echo "symbols_archive=${BUILD_NUMBER}-${{ matrix.build_type }}-win-symbols.zip" >> $GITHUB_ENV
|
||||||
# echo ::set-env name=HF_PFX_PASSPHRASE::${{secrets.pfx_key}}
|
# echo "HF_PFX_PASSPHRASE=${{secrets.pfx_key}}" >> $GITHUB_ENV
|
||||||
# echo "::set-env name=HF_PFX_FILE::${{runner.workspace}}\build\codesign.pfx"
|
# echo "HF_PFX_FILE=${{runner.workspace}}\build\codesign.pfx" >> $GITHUB_ENV
|
||||||
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::Vircadia-Alpha-*.$INSTALLER_EXT
|
echo "ARTIFACT_PATTERN=Vircadia-Alpha-*.$INSTALLER_EXT" >> $GITHUB_ENV
|
||||||
# Build type variables
|
# Build type variables
|
||||||
|
echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV
|
||||||
if [ "${{ matrix.build_type }}" = "full" ]; then
|
if [ "${{ matrix.build_type }}" = "full" ]; then
|
||||||
echo ::set-env name=CLIENT_ONLY::FALSE
|
echo "CLIENT_ONLY=FALSE" >> $GITHUB_ENV
|
||||||
echo ::set-env name=INSTALLER::Vircadia-Alpha-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
|
echo "INSTALLER=Vircadia-Alpha-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo ::set-env name=CLIENT_ONLY::TRUE
|
echo "CLIENT_ONLY=TRUE" >> $GITHUB_ENV
|
||||||
echo ::set-env name=INSTALLER::Vircadia-Alpha-Interface-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
|
echo "INSTALLER=Vircadia-Alpha-Interface-$BUILD_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
- name: Clear working directory
|
- name: Clear working directory
|
||||||
if: startsWith(matrix.os, 'windows')
|
if: startsWith(matrix.os, 'windows')
|
||||||
|
|
74
.github/workflows/pr_build.yml
vendored
74
.github/workflows/pr_build.yml
vendored
|
@ -22,19 +22,24 @@ env:
|
||||||
|
|
||||||
# WIN32 specific variables
|
# WIN32 specific variables
|
||||||
PreferredToolArchitecture: X64
|
PreferredToolArchitecture: X64
|
||||||
GIT_COMMIT_SHORT: ${{ github.sha }}
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
name: "build (${{matrix.os}}, ${{matrix.build_type}})"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest, macOS-latest, ubuntu-18.04]
|
|
||||||
build_type: [full]
|
|
||||||
include:
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
build_type: full
|
||||||
|
- os: macOS-latest
|
||||||
|
build_type: full
|
||||||
- os: ubuntu-18.04
|
- os: ubuntu-18.04
|
||||||
build_type: full
|
build_type: full
|
||||||
apt-dependencies: mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0
|
apt-dependencies: mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0
|
||||||
|
- os: ubuntu-18.04
|
||||||
|
build_type: android
|
||||||
|
apt-dependencies: mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
if: github.event.action != 'labeled' || github.event.label.name == 'rebuild'
|
if: github.event.action != 'labeled' || github.event.label.name == 'rebuild'
|
||||||
|
@ -43,36 +48,48 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
id: buildenv1
|
id: buildenv1
|
||||||
run: |
|
run: |
|
||||||
echo ::set-env name=GIT_COMMIT_SHORT::`echo $GIT_COMMIT | cut -c1-7`
|
echo ::set-output name=github_sha_short::`echo $GIT_COMMIT | cut -c1-7`
|
||||||
echo ::set-env name=JOB_NAME::"build (${{matrix.os}}, ${{matrix.build_type}})"
|
echo "JOB_NAME=build (${{matrix.os}}, ${{matrix.build_type}})" >> $GITHUB_ENV
|
||||||
|
|
||||||
# Linux build variables
|
# Linux build variables
|
||||||
if [[ "${{ matrix.os }}" = "ubuntu-"* ]]; then
|
if [[ "${{ matrix.os }}" = "ubuntu-"* ]]; then
|
||||||
echo ::set-env name=PYTHON_EXEC::python3
|
echo "PYTHON_EXEC=python3" >> $GITHUB_ENV
|
||||||
echo ::set-env name=INSTALLER_EXT::*
|
echo "INSTALLER_EXT=*" >> $GITHUB_ENV
|
||||||
echo ::set-env name=CMAKE_BUILD_EXTRA::"-- -j3"
|
echo "CMAKE_BUILD_EXTRA=-- -j3" >> $GITHUB_ENV
|
||||||
echo ::set-env name=CMAKE_EXTRA::"-DBUILD_TOOLS:BOOLEAN=FALSE -DHIFI_PYTHON_EXEC:FILEPATH=$(which python3)"
|
echo "CMAKE_EXTRA=-DBUILD_TOOLS:BOOLEAN=FALSE -DHIFI_PYTHON_EXEC:FILEPATH=$(which python3)" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
# Mac build variables
|
# Mac build variables
|
||||||
if [ "${{ matrix.os }}" = "macOS-latest" ]; then
|
if [ "${{ matrix.os }}" = "macOS-latest" ]; then
|
||||||
echo ::set-env name=PYTHON_EXEC::python3
|
echo "PYTHON_EXEC=python3" >> $GITHUB_ENV
|
||||||
echo ::set-env name=INSTALLER_EXT::dmg
|
echo "INSTALLER_EXT=dmg" >> $GITHUB_ENV
|
||||||
echo ::set-env name=CMAKE_EXTRA::"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode"
|
echo "CMAKE_EXTRA=-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=OFF -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Xcode" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
# Windows build variables
|
# Windows build variables
|
||||||
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
||||||
echo ::set-env name=PYTHON_EXEC::python
|
echo "PYTHON_EXEC=python" >> $GITHUB_ENV
|
||||||
echo ::set-env name=INSTALLER_EXT::exe
|
echo "INSTALLER_EXT=exe" >> $GITHUB_ENV
|
||||||
echo ::set-env name=CMAKE_EXTRA::"-A x64"
|
echo "CMAKE_EXTRA=-A x64" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
# Android + Quest build variables
|
||||||
|
if [[ "${{ matrix.build_type }}" == "android" ]]; then
|
||||||
|
HIFI_ANDROID_PRECOMPILED="${{runner.workspace}}/dependencies"
|
||||||
|
echo "HIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED" >> $GITHUB_ENV
|
||||||
|
mkdir $HIFI_ANDROID_PRECOMPILED
|
||||||
|
echo "INSTALLER_EXT=apk" >> $GITHUB_ENV
|
||||||
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::Vircadia-Alpha-PR${{ github.event.number }}-*.$INSTALLER_EXT
|
echo "GIT_COMMIT_SHORT=${{ steps.buildenv1.outputs.github_sha_short }}" >> $GITHUB_ENV
|
||||||
|
if [[ "${{ matrix.build_type }}" != "android" ]]; then
|
||||||
|
echo "ARTIFACT_PATTERN=Vircadia-Alpha-PR${{ github.event.number }}-*.$INSTALLER_EXT" >> $GITHUB_ENV
|
||||||
# Build type variables
|
# Build type variables
|
||||||
echo ::set-env name=INSTALLER::Vircadia-Alpha-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT
|
echo "INSTALLER=Vircadia-Alpha-$RELEASE_NUMBER-$GIT_COMMIT_SHORT.$INSTALLER_EXT" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "ARTIFACT_PATTERN=*.$INSTALLER_EXT" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
- name: Clear Working Directory
|
- name: Clear Working Directory
|
||||||
if: startsWith(matrix.os, 'windows')
|
if: startsWith(matrix.os, 'windows')
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -102,22 +119,27 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DVCPKG_BUILD_TYPE=release $CMAKE_EXTRA
|
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DVCPKG_BUILD_TYPE=release $CMAKE_EXTRA
|
||||||
- name: Build Application
|
- name: Build Application
|
||||||
|
if: matrix.build_type == 'full' || matrix.build_type == 'client'
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cmake --build . --config $BUILD_TYPE --target $APP_NAME $CMAKE_BUILD_EXTRA
|
run: cmake --build . --config $BUILD_TYPE --target $APP_NAME $CMAKE_BUILD_EXTRA
|
||||||
- name: Build Domain Server
|
- name: Build Domain Server
|
||||||
|
if: matrix.build_type == 'full'
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cmake --build . --config $BUILD_TYPE --target domain-server $CMAKE_BUILD_EXTRA
|
run: cmake --build . --config $BUILD_TYPE --target domain-server $CMAKE_BUILD_EXTRA
|
||||||
- name: Build Assignment Client
|
- name: Build Assignment Client
|
||||||
|
if: matrix.build_type == 'full'
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cmake --build . --config $BUILD_TYPE --target assignment-client $CMAKE_BUILD_EXTRA
|
run: cmake --build . --config $BUILD_TYPE --target assignment-client $CMAKE_BUILD_EXTRA
|
||||||
- name: Build Console
|
- name: Build Console
|
||||||
|
if: matrix.build_type == 'full'
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cmake --build . --config $BUILD_TYPE --target packaged-server-console $CMAKE_BUILD_EXTRA
|
run: cmake --build . --config $BUILD_TYPE --target packaged-server-console $CMAKE_BUILD_EXTRA
|
||||||
- name: Build Installer
|
- name: Build Installer
|
||||||
|
if: matrix.build_type != 'android'
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -144,6 +166,18 @@ jobs:
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
retry cmake --build . --config $BUILD_TYPE --target package $CMAKE_BUILD_EXTRA
|
retry cmake --build . --config $BUILD_TYPE --target package $CMAKE_BUILD_EXTRA
|
||||||
|
- name: Build for Android + Quest
|
||||||
|
if: matrix.build_type == 'android'
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{runner.workspace}}/project-athena
|
||||||
|
run: |
|
||||||
|
echo "Pre-cache the vcpkg managed dependencies"
|
||||||
|
$PYTHON_EXEC prebuild.py --build-root ${{runner.workspace}}/build --android interface
|
||||||
|
cd android
|
||||||
|
# Pre-cache the gradle dependencies
|
||||||
|
./gradlew -m tasks -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED
|
||||||
|
# Build!
|
||||||
|
./build_android.sh
|
||||||
- name: Output system stats
|
- name: Output system stats
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
@ -161,4 +195,8 @@ jobs:
|
||||||
working-directory: ${{runner.workspace}}/build
|
working-directory: ${{runner.workspace}}/build
|
||||||
env:
|
env:
|
||||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||||
run: $PYTHON_EXEC "$GITHUB_WORKSPACE/tools/ci-scripts/upload_to_publish_server.py"
|
run: |
|
||||||
|
if [[ "${{ matrix.build_type }}" == "android" ]]; then
|
||||||
|
cd $GITHUB_WORKSPACE/android
|
||||||
|
fi
|
||||||
|
$PYTHON_EXEC "$GITHUB_WORKSPACE/tools/ci-scripts/upload_to_publish_server.py"
|
|
@ -5,11 +5,11 @@ ANDROID_BUILD_TYPE=release
|
||||||
ANDROID_BUILD_TARGET=assembleRelease
|
ANDROID_BUILD_TARGET=assembleRelease
|
||||||
|
|
||||||
if [[ "$RELEASE_TYPE" == "PR" ]]; then
|
if [[ "$RELEASE_TYPE" == "PR" ]]; then
|
||||||
ANDROID_APK_SUFFIX=PR${RELEASE_NUMBER}-${SHA7}.apk ;
|
ANDROID_APK_SUFFIX=PR${RELEASE_NUMBER}-${GIT_COMMIT_SHORT}.apk ;
|
||||||
elif [[ "${STABLE_BUILD}" == "1" ]]; then
|
elif [[ "${STABLE_BUILD}" == "1" ]]; then
|
||||||
ANDROID_APK_SUFFIX=${RELEASE_NUMBER}.apk ;
|
ANDROID_APK_SUFFIX=${RELEASE_NUMBER}.apk ;
|
||||||
else
|
else
|
||||||
ANDROID_APK_SUFFIX=${RELEASE_NUMBER}-${SHA7}.apk ;
|
ANDROID_APK_SUFFIX=${RELEASE_NUMBER}-${GIT_COMMIT_SHORT}.apk ;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ fi
|
||||||
ANDROID_APP=interface
|
ANDROID_APP=interface
|
||||||
ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE}
|
ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE}
|
||||||
ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk
|
ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk
|
||||||
ANDROID_APK_NAME=HighFidelity-Beta-${ANDROID_APK_SUFFIX}
|
ANDROID_APK_NAME=Vircadia-Alpha-${ANDROID_APK_SUFFIX}
|
||||||
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET}
|
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET}
|
||||||
cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME}
|
cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME}
|
||||||
|
|
||||||
|
@ -25,9 +25,9 @@ cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME}
|
||||||
ANDROID_APP=questInterface
|
ANDROID_APP=questInterface
|
||||||
ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE}
|
ANDROID_OUTPUT_DIR=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_TYPE}
|
||||||
ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk
|
ANDROID_OUTPUT_FILE=${ANDROID_APP}-${ANDROID_BUILD_TYPE}.apk
|
||||||
ANDROID_APK_NAME=HighFidelity-Quest-Beta-${ANDROID_APK_SUFFIX}
|
ANDROID_APK_NAME=Vircadia-Quest-Alpha-${ANDROID_APK_SUFFIX}
|
||||||
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} || true
|
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} || true
|
||||||
cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME} || true
|
cp ${ANDROID_OUTPUT_DIR}/${ANDROID_OUTPUT_FILE} ./${ANDROID_APK_NAME}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
|
|
||||||
DOCKER_IMAGE_NAME="hifi_androidbuild"
|
DOCKER_IMAGE_NAME="vircadia_androidbuild"
|
||||||
|
|
||||||
docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f docker/Dockerfile docker
|
docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f ./android/docker/Dockerfile ./android/docker
|
||||||
|
|
||||||
# The Jenkins PR builds use VERSION_CODE, but the release builds use VERSION
|
# The Jenkins PR builds use VERSION_CODE, but the release builds use VERSION
|
||||||
# So make sure we use VERSION_CODE consistently
|
# So make sure we use VERSION_CODE consistently
|
||||||
|
@ -17,7 +17,7 @@ test -z "$STABLE_BUILD" && export STABLE_BUILD=0
|
||||||
docker run \
|
docker run \
|
||||||
--rm \
|
--rm \
|
||||||
--security-opt seccomp:unconfined \
|
--security-opt seccomp:unconfined \
|
||||||
-v "${WORKSPACE}":/home/jenkins/hifi \
|
-v "${WORKSPACE}":/home/gha/project-athena \
|
||||||
-e RELEASE_NUMBER \
|
-e RELEASE_NUMBER \
|
||||||
-e RELEASE_TYPE \
|
-e RELEASE_TYPE \
|
||||||
-e ANDROID_APP \
|
-e ANDROID_APP \
|
||||||
|
@ -33,7 +33,7 @@ docker run \
|
||||||
-e OAUTH_CLIENT_SECRET \
|
-e OAUTH_CLIENT_SECRET \
|
||||||
-e OAUTH_CLIENT_ID \
|
-e OAUTH_CLIENT_ID \
|
||||||
-e OAUTH_REDIRECT_URI \
|
-e OAUTH_REDIRECT_URI \
|
||||||
-e SHA7 \
|
-e GIT_COMMIT_SHORT \
|
||||||
-e STABLE_BUILD \
|
-e STABLE_BUILD \
|
||||||
-e VERSION_CODE \
|
-e VERSION_CODE \
|
||||||
"${DOCKER_IMAGE_NAME}" \
|
"${DOCKER_IMAGE_NAME}" \
|
||||||
|
|
|
@ -57,13 +57,13 @@ RUN apt-get -y install \
|
||||||
|
|
||||||
# --- Gradle
|
# --- Gradle
|
||||||
ARG BUILD_UID=1001
|
ARG BUILD_UID=1001
|
||||||
RUN useradd -ms /bin/bash -u $BUILD_UID jenkins
|
RUN useradd -ms /bin/bash -u $BUILD_UID gha
|
||||||
RUN echo "jenkins ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
RUN echo "gha ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
||||||
USER jenkins
|
USER gha
|
||||||
WORKDIR /home/jenkins
|
WORKDIR /home/gha
|
||||||
|
|
||||||
# Hifi dependencies
|
# Vircadia dependencies
|
||||||
ENV HIFI_BASE="/home/jenkins/hifi_android"
|
ENV HIFI_BASE="/home/gha/vircadia_android"
|
||||||
ENV HIFI_ANDROID_PRECOMPILED="$HIFI_BASE/dependencies"
|
ENV HIFI_ANDROID_PRECOMPILED="$HIFI_BASE/dependencies"
|
||||||
ENV HIFI_VCPKG_BASE="$HIFI_BASE/vcpkg"
|
ENV HIFI_VCPKG_BASE="$HIFI_BASE/vcpkg"
|
||||||
|
|
||||||
|
@ -71,24 +71,18 @@ RUN mkdir "$HIFI_BASE" && \
|
||||||
mkdir "$HIFI_VCPKG_BASE" && \
|
mkdir "$HIFI_VCPKG_BASE" && \
|
||||||
mkdir "$HIFI_ANDROID_PRECOMPILED"
|
mkdir "$HIFI_ANDROID_PRECOMPILED"
|
||||||
|
|
||||||
# Checkout a relatively recent commit from the main repository and use it to cache the
|
# Download the repo
|
||||||
# gradle and vcpkg dependencies
|
RUN git clone https://github.com/kasenvr/project-athena.git
|
||||||
# This commit ID should be updated whenever someone changes the dependency list
|
|
||||||
# in cmake/ports
|
|
||||||
RUN git clone https://github.com/highfidelity/hifi.git && \
|
|
||||||
cd ~/hifi && \
|
|
||||||
git checkout 796bfb5d6715ff14c2e60f3ee8fac1465b7578c6
|
|
||||||
|
|
||||||
WORKDIR /home/jenkins/hifi
|
WORKDIR /home/gha/project-athena
|
||||||
|
|
||||||
RUN mkdir build
|
RUN mkdir build
|
||||||
|
|
||||||
# Pre-cache the vcpkg managed dependencies
|
# Pre-cache the vcpkg managed dependencies
|
||||||
WORKDIR /home/jenkins/hifi/build
|
WORKDIR /home/gha/project-athena/build
|
||||||
RUN python3 ../prebuild.py --build-root `pwd` --android interface
|
RUN python3 ../prebuild.py --build-root `pwd` --android interface
|
||||||
|
|
||||||
# Pre-cache the gradle dependencies
|
# Pre-cache the gradle dependencies
|
||||||
WORKDIR /home/jenkins/hifi/android
|
WORKDIR /home/gha/project-athena/android
|
||||||
RUN ./gradlew -m tasks -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED
|
RUN ./gradlew -m tasks -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED
|
||||||
#RUN ./gradlew extractDependencies -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED
|
#RUN ./gradlew extractDependencies -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED
|
||||||
|
|
||||||
|
|
|
@ -470,7 +470,9 @@ void EntityScriptServer::resetEntitiesScriptEngine() {
|
||||||
scriptEngines->runScriptInitializers(newEngine);
|
scriptEngines->runScriptInitializers(newEngine);
|
||||||
newEngine->runInThread();
|
newEngine->runInThread();
|
||||||
auto newEngineSP = qSharedPointerCast<EntitiesScriptEngineProvider>(newEngine);
|
auto newEngineSP = qSharedPointerCast<EntitiesScriptEngineProvider>(newEngine);
|
||||||
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(newEngineSP);
|
// On the entity script server, these are the same
|
||||||
|
DependencyManager::get<EntityScriptingInterface>()->setPersistentEntitiesScriptEngine(newEngineSP);
|
||||||
|
DependencyManager::get<EntityScriptingInterface>()->setNonPersistentEntitiesScriptEngine(newEngineSP);
|
||||||
|
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated,
|
disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated,
|
||||||
|
|
|
@ -1238,7 +1238,7 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString&
|
||||||
|
|
||||||
// Get data pertaining to "me", the user who generated the access token.
|
// Get data pertaining to "me", the user who generated the access token.
|
||||||
const QString WORDPRESS_USER_ROUTE = "wp/v2/users/me";
|
const QString WORDPRESS_USER_ROUTE = "wp/v2/users/me";
|
||||||
const QString WORDPRESS_USER_QUERY = "_fields=username,roles";
|
const QString WORDPRESS_USER_QUERY = "_fields=username,email,roles";
|
||||||
QUrl domainUserURL = apiBase + WORDPRESS_USER_ROUTE + (apiBase.contains("?") ? "&" : "?") + WORDPRESS_USER_QUERY;
|
QUrl domainUserURL = apiBase + WORDPRESS_USER_ROUTE + (apiBase.contains("?") ? "&" : "?") + WORDPRESS_USER_QUERY;
|
||||||
|
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
|
@ -1270,8 +1270,13 @@ void DomainGatekeeper::requestDomainUserFinished() {
|
||||||
if (200 <= httpStatus && httpStatus < 300) {
|
if (200 <= httpStatus && httpStatus < 300) {
|
||||||
|
|
||||||
QString username = rootObject.value("username").toString().toLower();
|
QString username = rootObject.value("username").toString().toLower();
|
||||||
if (_inFlightDomainUserIdentityRequests.contains(username)) {
|
QString email = rootObject.value("email").toString().toLower();
|
||||||
|
|
||||||
|
if (_inFlightDomainUserIdentityRequests.contains(username) || _inFlightDomainUserIdentityRequests.contains(email)) {
|
||||||
// Success! Verified user.
|
// Success! Verified user.
|
||||||
|
if (!_inFlightDomainUserIdentityRequests.contains(username)) {
|
||||||
|
username = email;
|
||||||
|
}
|
||||||
_verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username));
|
_verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username));
|
||||||
_inFlightDomainUserIdentityRequests.remove(username);
|
_inFlightDomainUserIdentityRequests.remove(username);
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ Item {
|
||||||
displayNameField.placeholderText = "Display Name (optional)";
|
displayNameField.placeholderText = "Display Name (optional)";
|
||||||
var savedDisplayName = Settings.getValue("Avatar/displayName", "");
|
var savedDisplayName = Settings.getValue("Avatar/displayName", "");
|
||||||
displayNameField.text = savedDisplayName;
|
displayNameField.text = savedDisplayName;
|
||||||
emailField.placeholderText = (!isLoggingInToDomain) ? "Username or Email" : "Username";
|
emailField.placeholderText = "Username or Email";
|
||||||
if (!isLoggingInToDomain) {
|
if (!isLoggingInToDomain) {
|
||||||
var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", "");
|
var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", "");
|
||||||
emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : "";
|
emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : "";
|
||||||
|
|
|
@ -312,9 +312,9 @@ Rectangle {
|
||||||
parent.color = hifi.colors.blueHighlight;
|
parent.color = hifi.colors.blueHighlight;
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
lightboxPopup.titleText = "Script Plugin Infrastructure by Kasen";
|
lightboxPopup.titleText = "Script Plugin Infrastructure";
|
||||||
lightboxPopup.bodyText = "Toggles the activation of scripting plugins in the 'plugins/scripting' folder. \n\n"
|
lightboxPopup.bodyText = "Toggles the activation of scripting plugins in the 'plugins/scripting' folder. \n\n"
|
||||||
+ "Created by https://kasen.io/";
|
+ "Created by:\n humbletim@gmail.com\n kasenvr@gmail.com";
|
||||||
lightboxPopup.button1text = "OK";
|
lightboxPopup.button1text = "OK";
|
||||||
lightboxPopup.button1method = function() {
|
lightboxPopup.button1method = function() {
|
||||||
lightboxPopup.visible = false;
|
lightboxPopup.visible = false;
|
||||||
|
|
|
@ -355,6 +355,7 @@ static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop";
|
||||||
static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin";
|
static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin";
|
||||||
static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
|
static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
|
||||||
static const QString KEEP_ME_LOGGED_IN_SETTING_NAME = "keepMeLoggedIn";
|
static const QString KEEP_ME_LOGGED_IN_SETTING_NAME = "keepMeLoggedIn";
|
||||||
|
static const QString CACHEBUST_SCRIPT_REQUIRE_SETTING_NAME = "cachebustScriptRequire";
|
||||||
|
|
||||||
static const float FOCUS_HIGHLIGHT_EXPANSION_FACTOR = 1.05f;
|
static const float FOCUS_HIGHLIGHT_EXPANSION_FACTOR = 1.05f;
|
||||||
|
|
||||||
|
@ -1967,6 +1968,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
|
|
||||||
updateVerboseLogging();
|
updateVerboseLogging();
|
||||||
|
|
||||||
|
setCachebustRequire();
|
||||||
|
|
||||||
// Make sure we don't time out during slow operations at startup
|
// Make sure we don't time out during slow operations at startup
|
||||||
updateHeartbeat();
|
updateHeartbeat();
|
||||||
QTimer* settingsTimer = new QTimer();
|
QTimer* settingsTimer = new QTimer();
|
||||||
|
@ -2600,6 +2603,16 @@ void Application::updateVerboseLogging() {
|
||||||
QLoggingCategory::setFilterRules(rules);
|
QLoggingCategory::setFilterRules(rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::setCachebustRequire() {
|
||||||
|
auto menu = Menu::getInstance();
|
||||||
|
if (!menu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool enable = menu->isOptionChecked(MenuOption::CachebustRequire);
|
||||||
|
|
||||||
|
Setting::Handle<bool>{ CACHEBUST_SCRIPT_REQUIRE_SETTING_NAME, false }.set(enable);
|
||||||
|
}
|
||||||
|
|
||||||
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
|
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
|
||||||
DomainHandler::ConnectionRefusedReason reasonCode = static_cast<DomainHandler::ConnectionRefusedReason>(reasonCodeInt);
|
DomainHandler::ConnectionRefusedReason reasonCode = static_cast<DomainHandler::ConnectionRefusedReason>(reasonCodeInt);
|
||||||
|
|
||||||
|
|
|
@ -473,6 +473,8 @@ public slots:
|
||||||
|
|
||||||
void updateVerboseLogging();
|
void updateVerboseLogging();
|
||||||
|
|
||||||
|
void setCachebustRequire();
|
||||||
|
|
||||||
void changeViewAsNeeded(float boomLength);
|
void changeViewAsNeeded(float boomLength);
|
||||||
|
|
||||||
QString getGraphicsCardType();
|
QString getGraphicsCardType();
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
//
|
//
|
||||||
// Created by Stephen Birarda on 8/12/13.
|
// Created by Stephen Birarda on 8/12/13.
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
// Copyright 2013 High Fidelity, Inc.
|
||||||
|
// Copyright 2020 Vircadia contributors.
|
||||||
//
|
//
|
||||||
// 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
|
||||||
//
|
//
|
||||||
|
|
||||||
// For happ(ier) development of QML, use these two things:
|
// For happ(ier) development of QML, use these two things:
|
||||||
// This forces QML files to be pulled from the source as you edit it: set environment variable HIFI_USE_SOURCE_TREE_RESOURCES=1
|
// This forces QML files to be pulled from the source as you edit it: set environment variable HIFI_USE_SOURCE_TREE_RESOURCES=1
|
||||||
// Use this to live reload: DependencyManager::get<OffscreenUi>()->clearCache();
|
// Use this to live reload: DependencyManager::get<OffscreenUi>()->clearCache();
|
||||||
|
@ -366,6 +366,10 @@ Menu::Menu() {
|
||||||
addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::VerboseLogging, 0, false,
|
addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::VerboseLogging, 0, false,
|
||||||
qApp, SLOT(updateVerboseLogging()));
|
qApp, SLOT(updateVerboseLogging()));
|
||||||
|
|
||||||
|
// Developer > Scripting > Enable Cachebusting of Script.require
|
||||||
|
addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::CachebustRequire, 0, false,
|
||||||
|
qApp, SLOT(setCachebustRequire()));
|
||||||
|
|
||||||
// Developer > Scripting > Enable Speech Control API
|
// Developer > Scripting > Enable Speech Control API
|
||||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||||
auto speechRecognizer = DependencyManager::get<SpeechRecognizer>();
|
auto speechRecognizer = DependencyManager::get<SpeechRecognizer>();
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
//
|
//
|
||||||
// Created by Stephen Birarda on 8/12/13.
|
// Created by Stephen Birarda on 8/12/13.
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
// Copyright 2013 High Fidelity, Inc.
|
||||||
|
// Copyright 2020 Vircadia contributors.
|
||||||
//
|
//
|
||||||
// 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
|
||||||
|
@ -54,6 +55,7 @@ namespace MenuOption {
|
||||||
const QString BookmarkAvatarEntities = "Bookmark Avatar Entities";
|
const QString BookmarkAvatarEntities = "Bookmark Avatar Entities";
|
||||||
const QString BookmarkLocation = "Bookmark Location";
|
const QString BookmarkLocation = "Bookmark Location";
|
||||||
const QString CalibrateCamera = "Calibrate Camera";
|
const QString CalibrateCamera = "Calibrate Camera";
|
||||||
|
const QString CachebustRequire = "Enable Cachebusting of Script.require";
|
||||||
const QString CenterPlayerInView = "Center Player In View";
|
const QString CenterPlayerInView = "Center Player In View";
|
||||||
const QString Chat = "Chat...";
|
const QString Chat = "Chat...";
|
||||||
const QString ClearDiskCaches = "Clear Disk Caches (requires restart)";
|
const QString ClearDiskCaches = "Clear Disk Caches (requires restart)";
|
||||||
|
|
|
@ -158,79 +158,98 @@ render::ItemID EntityTreeRenderer::renderableIdForEntityId(const EntityItemID& i
|
||||||
|
|
||||||
int EntityTreeRenderer::_entitiesScriptEngineCount = 0;
|
int EntityTreeRenderer::_entitiesScriptEngineCount = 0;
|
||||||
|
|
||||||
void EntityTreeRenderer::resetEntitiesScriptEngine() {
|
void EntityTreeRenderer::setupEntityScriptEngineSignals(const ScriptEnginePointer& scriptEngine) {
|
||||||
_entitiesScriptEngine = scriptEngineFactory(ScriptEngine::ENTITY_CLIENT_SCRIPT, NO_SCRIPT,
|
|
||||||
QString("about:Entities %1").arg(++_entitiesScriptEngineCount));
|
|
||||||
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_entitiesScriptEngine);
|
|
||||||
_entitiesScriptEngine->runInThread();
|
|
||||||
auto entitiesScriptEngineProvider = qSharedPointerCast<EntitiesScriptEngineProvider>(_entitiesScriptEngine);
|
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
entityScriptingInterface->setEntitiesScriptEngine(entitiesScriptEngineProvider);
|
|
||||||
|
|
||||||
// Connect mouse events to entity script callbacks
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
||||||
if (!_mouseAndPreloadSignalHandlersConnected) {
|
scriptEngine->callEntityScriptMethod(entityID, "mousePressOnEntity", event);
|
||||||
|
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "mousePressOnEntity", event);
|
|
||||||
});
|
});
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseDoublePressOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseDoublePressOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "mouseDoublePressOnEntity", event);
|
scriptEngine->callEntityScriptMethod(entityID, "mouseDoublePressOnEntity", event);
|
||||||
});
|
});
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "mouseMoveOnEntity", event);
|
scriptEngine->callEntityScriptMethod(entityID, "mouseMoveOnEntity", event);
|
||||||
// FIXME: this is a duplicate of mouseMoveOnEntity, but it seems like some scripts might use this naming
|
// FIXME: this is a duplicate of mouseMoveOnEntity, but it seems like some scripts might use this naming
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "mouseMoveEvent", event);
|
scriptEngine->callEntityScriptMethod(entityID, "mouseMoveEvent", event);
|
||||||
});
|
});
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "mouseReleaseOnEntity", event);
|
scriptEngine->callEntityScriptMethod(entityID, "mouseReleaseOnEntity", event);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "clickDownOnEntity", event);
|
scriptEngine->callEntityScriptMethod(entityID, "clickDownOnEntity", event);
|
||||||
});
|
});
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::holdingClickOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::holdingClickOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "holdingClickOnEntity", event);
|
scriptEngine->callEntityScriptMethod(entityID, "holdingClickOnEntity", event);
|
||||||
});
|
});
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickReleaseOnEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickReleaseOnEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "clickReleaseOnEntity", event);
|
scriptEngine->callEntityScriptMethod(entityID, "clickReleaseOnEntity", event);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "hoverEnterEntity", event);
|
scriptEngine->callEntityScriptMethod(entityID, "hoverEnterEntity", event);
|
||||||
});
|
});
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "hoverOverEntity", event);
|
scriptEngine->callEntityScriptMethod(entityID, "hoverOverEntity", event);
|
||||||
});
|
});
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, _entitiesScriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, scriptEngine.data(), [&](const EntityItemID& entityID, const PointerEvent& event) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "hoverLeaveEntity", event);
|
scriptEngine->callEntityScriptMethod(entityID, "hoverLeaveEntity", event);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptPreloadFinished, [&](const EntityItemID& entityID) {
|
connect(scriptEngine.data(), &ScriptEngine::entityScriptPreloadFinished, [&](const EntityItemID& entityID) {
|
||||||
EntityItemPointer entity = getTree()->findEntityByID(entityID);
|
EntityItemPointer entity = getTree()->findEntityByID(entityID);
|
||||||
if (entity) {
|
if (entity) {
|
||||||
entity->setScriptHasFinishedPreload(true);
|
entity->setScriptHasFinishedPreload(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_mouseAndPreloadSignalHandlersConnected = true;
|
void EntityTreeRenderer::resetPersistentEntitiesScriptEngine() {
|
||||||
|
if (_persistentEntitiesScriptEngine) {
|
||||||
|
_persistentEntitiesScriptEngine->unloadAllEntityScripts(true);
|
||||||
|
_persistentEntitiesScriptEngine->stop();
|
||||||
|
_persistentEntitiesScriptEngine->waitTillDoneRunning();
|
||||||
|
_persistentEntitiesScriptEngine->disconnectNonEssentialSignals();
|
||||||
}
|
}
|
||||||
|
_persistentEntitiesScriptEngine = scriptEngineFactory(ScriptEngine::ENTITY_CLIENT_SCRIPT, NO_SCRIPT,
|
||||||
|
QString("about:Entities %1").arg(++_entitiesScriptEngineCount));
|
||||||
|
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_persistentEntitiesScriptEngine);
|
||||||
|
_persistentEntitiesScriptEngine->runInThread();
|
||||||
|
auto entitiesScriptEngineProvider = qSharedPointerCast<EntitiesScriptEngineProvider>(_persistentEntitiesScriptEngine);
|
||||||
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
|
entityScriptingInterface->setPersistentEntitiesScriptEngine(entitiesScriptEngineProvider);
|
||||||
|
|
||||||
|
setupEntityScriptEngineSignals(_persistentEntitiesScriptEngine);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityTreeRenderer::resetNonPersistentEntitiesScriptEngine() {
|
||||||
|
if (_nonPersistentEntitiesScriptEngine) {
|
||||||
|
_nonPersistentEntitiesScriptEngine->unloadAllEntityScripts(true);
|
||||||
|
_nonPersistentEntitiesScriptEngine->stop();
|
||||||
|
_nonPersistentEntitiesScriptEngine->waitTillDoneRunning();
|
||||||
|
_nonPersistentEntitiesScriptEngine->disconnectNonEssentialSignals();
|
||||||
|
}
|
||||||
|
_nonPersistentEntitiesScriptEngine = scriptEngineFactory(ScriptEngine::ENTITY_CLIENT_SCRIPT, NO_SCRIPT,
|
||||||
|
QString("about:Entities %1").arg(++_entitiesScriptEngineCount));
|
||||||
|
DependencyManager::get<ScriptEngines>()->runScriptInitializers(_nonPersistentEntitiesScriptEngine);
|
||||||
|
_nonPersistentEntitiesScriptEngine->runInThread();
|
||||||
|
auto entitiesScriptEngineProvider = qSharedPointerCast<EntitiesScriptEngineProvider>(_nonPersistentEntitiesScriptEngine);
|
||||||
|
DependencyManager::get<EntityScriptingInterface>()->setNonPersistentEntitiesScriptEngine(entitiesScriptEngineProvider);
|
||||||
|
|
||||||
|
setupEntityScriptEngineSignals(_nonPersistentEntitiesScriptEngine);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::stopDomainAndNonOwnedEntities() {
|
void EntityTreeRenderer::stopDomainAndNonOwnedEntities() {
|
||||||
leaveDomainAndNonOwnedEntities();
|
leaveDomainAndNonOwnedEntities();
|
||||||
// unload and stop the engine
|
// unload and stop the engine
|
||||||
if (_entitiesScriptEngine) {
|
if (_nonPersistentEntitiesScriptEngine) {
|
||||||
QList<EntityItemID> entitiesWithEntityScripts = _entitiesScriptEngine->getListOfEntityScriptIDs();
|
QList<EntityItemID> entitiesWithEntityScripts = _nonPersistentEntitiesScriptEngine->getListOfEntityScriptIDs();
|
||||||
|
|
||||||
foreach (const EntityItemID& entityID, entitiesWithEntityScripts) {
|
foreach (const EntityItemID& entityID, entitiesWithEntityScripts) {
|
||||||
EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID);
|
EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID);
|
||||||
|
|
||||||
if (entityItem && !entityItem->getScript().isEmpty()) {
|
if (entityItem && !entityItem->getScript().isEmpty()) {
|
||||||
if (!(entityItem->isLocalEntity() || entityItem->isMyAvatarEntity())) {
|
if (!(entityItem->isLocalEntity() || entityItem->isMyAvatarEntity())) {
|
||||||
if (_currentEntitiesInside.contains(entityID)) {
|
_nonPersistentEntitiesScriptEngine->unloadEntityScript(entityID, true);
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
|
||||||
}
|
|
||||||
_entitiesScriptEngine->unloadEntityScript(entityID, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,6 +259,10 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() {
|
||||||
void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
|
void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
|
||||||
stopDomainAndNonOwnedEntities();
|
stopDomainAndNonOwnedEntities();
|
||||||
|
|
||||||
|
if (!_shuttingDown && _wantScripts) {
|
||||||
|
resetNonPersistentEntitiesScriptEngine();
|
||||||
|
}
|
||||||
|
|
||||||
std::unordered_map<EntityItemID, EntityRendererPointer> savedEntities;
|
std::unordered_map<EntityItemID, EntityRendererPointer> savedEntities;
|
||||||
std::unordered_set<EntityRendererPointer> savedRenderables;
|
std::unordered_set<EntityRendererPointer> savedRenderables;
|
||||||
// remove all entities from the scene
|
// remove all entities from the scene
|
||||||
|
@ -269,16 +292,22 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
|
||||||
|
|
||||||
void EntityTreeRenderer::clear() {
|
void EntityTreeRenderer::clear() {
|
||||||
leaveAllEntities();
|
leaveAllEntities();
|
||||||
// unload and stop the engine
|
|
||||||
if (_entitiesScriptEngine) {
|
|
||||||
// do this here (instead of in deleter) to avoid marshalling unload signals back to this thread
|
|
||||||
_entitiesScriptEngine->unloadAllEntityScripts(true);
|
|
||||||
_entitiesScriptEngine->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset the engine
|
// reset the engine
|
||||||
auto scene = _viewState->getMain3DScene();
|
auto scene = _viewState->getMain3DScene();
|
||||||
if (_shuttingDown) {
|
if (_shuttingDown) {
|
||||||
|
// unload and stop the engines
|
||||||
|
if (_nonPersistentEntitiesScriptEngine) {
|
||||||
|
// do this here (instead of in deleter) to avoid marshalling unload signals back to this thread
|
||||||
|
_nonPersistentEntitiesScriptEngine->unloadAllEntityScripts(true);
|
||||||
|
_nonPersistentEntitiesScriptEngine->stop();
|
||||||
|
}
|
||||||
|
if (_persistentEntitiesScriptEngine) {
|
||||||
|
// do this here (instead of in deleter) to avoid marshalling unload signals back to this thread
|
||||||
|
_persistentEntitiesScriptEngine->unloadAllEntityScripts(true);
|
||||||
|
_persistentEntitiesScriptEngine->stop();
|
||||||
|
}
|
||||||
|
|
||||||
if (scene) {
|
if (scene) {
|
||||||
render::Transaction transaction;
|
render::Transaction transaction;
|
||||||
for (const auto& entry : _entitiesInScene) {
|
for (const auto& entry : _entitiesInScene) {
|
||||||
|
@ -289,7 +318,8 @@ void EntityTreeRenderer::clear() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_wantScripts) {
|
if (_wantScripts) {
|
||||||
resetEntitiesScriptEngine();
|
resetPersistentEntitiesScriptEngine();
|
||||||
|
resetNonPersistentEntitiesScriptEngine();
|
||||||
}
|
}
|
||||||
if (scene) {
|
if (scene) {
|
||||||
for (const auto& entry : _entitiesInScene) {
|
for (const auto& entry : _entitiesInScene) {
|
||||||
|
@ -313,13 +343,17 @@ void EntityTreeRenderer::clear() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::reloadEntityScripts() {
|
void EntityTreeRenderer::reloadEntityScripts() {
|
||||||
_entitiesScriptEngine->unloadAllEntityScripts();
|
_persistentEntitiesScriptEngine->unloadAllEntityScripts();
|
||||||
_entitiesScriptEngine->resetModuleCache();
|
_persistentEntitiesScriptEngine->resetModuleCache();
|
||||||
|
_nonPersistentEntitiesScriptEngine->unloadAllEntityScripts();
|
||||||
|
_nonPersistentEntitiesScriptEngine->resetModuleCache();
|
||||||
|
|
||||||
for (const auto& entry : _entitiesInScene) {
|
for (const auto& entry : _entitiesInScene) {
|
||||||
const auto& renderer = entry.second;
|
const auto& renderer = entry.second;
|
||||||
const auto& entity = renderer->getEntity();
|
const auto& entity = renderer->getEntity();
|
||||||
if (!entity->getScript().isEmpty()) {
|
if (entity && !entity->getScript().isEmpty()) {
|
||||||
_entitiesScriptEngine->loadEntityScript(entity->getEntityItemID(), resolveScriptURL(entity->getScript()), true);
|
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
|
||||||
|
scriptEngine->loadEntityScript(entity->getEntityItemID(), resolveScriptURL(entity->getScript()), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,7 +363,8 @@ void EntityTreeRenderer::init() {
|
||||||
EntityTreePointer entityTree = std::static_pointer_cast<EntityTree>(_tree);
|
EntityTreePointer entityTree = std::static_pointer_cast<EntityTree>(_tree);
|
||||||
|
|
||||||
if (_wantScripts) {
|
if (_wantScripts) {
|
||||||
resetEntitiesScriptEngine();
|
resetPersistentEntitiesScriptEngine();
|
||||||
|
resetNonPersistentEntitiesScriptEngine();
|
||||||
}
|
}
|
||||||
|
|
||||||
forceRecheckEntities(); // setup our state to force checking our inside/outsideness of entities
|
forceRecheckEntities(); // setup our state to force checking our inside/outsideness of entities
|
||||||
|
@ -341,8 +376,11 @@ void EntityTreeRenderer::init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTreeRenderer::shutdown() {
|
void EntityTreeRenderer::shutdown() {
|
||||||
if (_entitiesScriptEngine) {
|
if (_persistentEntitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential
|
_persistentEntitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential
|
||||||
|
}
|
||||||
|
if (_nonPersistentEntitiesScriptEngine) {
|
||||||
|
_nonPersistentEntitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential
|
||||||
}
|
}
|
||||||
_shuttingDown = true;
|
_shuttingDown = true;
|
||||||
|
|
||||||
|
@ -658,12 +696,16 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||||
// EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts
|
// EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts
|
||||||
// for entity IDs that no longer exist.
|
// for entity IDs that no longer exist.
|
||||||
|
|
||||||
if (_entitiesScriptEngine) {
|
if (_persistentEntitiesScriptEngine && _nonPersistentEntitiesScriptEngine) {
|
||||||
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||||
if (!entitiesContainingAvatar.contains(entityID)) {
|
if (!entitiesContainingAvatar.contains(entityID)) {
|
||||||
emit leaveEntity(entityID);
|
emit leaveEntity(entityID);
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
auto entity = getTree()->findEntityByEntityItemID(entityID);
|
||||||
|
if (entity) {
|
||||||
|
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
|
||||||
|
scriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -671,7 +713,11 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||||
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
|
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
|
||||||
if (!_currentEntitiesInside.contains(entityID)) {
|
if (!_currentEntitiesInside.contains(entityID)) {
|
||||||
emit enterEntity(entityID);
|
emit enterEntity(entityID);
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity");
|
auto entity = getTree()->findEntityByEntityItemID(entityID);
|
||||||
|
if (entity) {
|
||||||
|
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
|
||||||
|
scriptEngine->callEntityScriptMethod(entityID, "enterEntity");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_currentEntitiesInside = entitiesContainingAvatar;
|
_currentEntitiesInside = entitiesContainingAvatar;
|
||||||
|
@ -687,8 +733,8 @@ void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() {
|
||||||
EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID);
|
EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID);
|
||||||
if (entityItem && !(entityItem->isLocalEntity() || entityItem->isMyAvatarEntity())) {
|
if (entityItem && !(entityItem->isLocalEntity() || entityItem->isMyAvatarEntity())) {
|
||||||
emit leaveEntity(entityID);
|
emit leaveEntity(entityID);
|
||||||
if (_entitiesScriptEngine) {
|
if (_nonPersistentEntitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
_nonPersistentEntitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
currentEntitiesInsideToSave.insert(entityID);
|
currentEntitiesInsideToSave.insert(entityID);
|
||||||
|
@ -706,8 +752,12 @@ void EntityTreeRenderer::leaveAllEntities() {
|
||||||
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||||
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
|
||||||
emit leaveEntity(entityID);
|
emit leaveEntity(entityID);
|
||||||
if (_entitiesScriptEngine) {
|
EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID);
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
if (entityItem) {
|
||||||
|
auto& scriptEngine = (entityItem->isLocalEntity() || entityItem->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
|
||||||
|
if (scriptEngine) {
|
||||||
|
scriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_currentEntitiesInside.clear();
|
_currentEntitiesInside.clear();
|
||||||
|
@ -1003,11 +1053,12 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_tree && !_shuttingDown && _entitiesScriptEngine && !itr->second->getEntity()->getScript().isEmpty()) {
|
auto& scriptEngine = (itr->second->getEntity()->isLocalEntity() || itr->second->getEntity()->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
|
||||||
|
if (_tree && !_shuttingDown && scriptEngine && !itr->second->getEntity()->getScript().isEmpty()) {
|
||||||
if (_currentEntitiesInside.contains(entityID)) {
|
if (_currentEntitiesInside.contains(entityID)) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
scriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||||
}
|
}
|
||||||
_entitiesScriptEngine->unloadEntityScript(entityID, true);
|
scriptEngine->unloadEntityScript(entityID, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto scene = _viewState->getMain3DScene();
|
auto scene = _viewState->getMain3DScene();
|
||||||
|
@ -1052,20 +1103,21 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bool shouldLoad = entity->shouldPreloadScript() && _entitiesScriptEngine;
|
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
|
||||||
|
bool shouldLoad = entity->shouldPreloadScript() && scriptEngine;
|
||||||
QString scriptUrl = entity->getScript();
|
QString scriptUrl = entity->getScript();
|
||||||
if ((shouldLoad && unloadFirst) || scriptUrl.isEmpty()) {
|
if ((shouldLoad && unloadFirst) || scriptUrl.isEmpty()) {
|
||||||
if (_entitiesScriptEngine) {
|
if (scriptEngine) {
|
||||||
if (_currentEntitiesInside.contains(entityID)) {
|
if (_currentEntitiesInside.contains(entityID)) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
scriptEngine->callEntityScriptMethod(entityID, "leaveEntity");
|
||||||
}
|
}
|
||||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
scriptEngine->unloadEntityScript(entityID);
|
||||||
}
|
}
|
||||||
entity->scriptHasUnloaded();
|
entity->scriptHasUnloaded();
|
||||||
}
|
}
|
||||||
if (shouldLoad) {
|
if (shouldLoad) {
|
||||||
entity->setScriptHasFinishedPreload(false);
|
entity->setScriptHasFinishedPreload(false);
|
||||||
_entitiesScriptEngine->loadEntityScript(entityID, resolveScriptURL(scriptUrl), reload);
|
scriptEngine->loadEntityScript(entityID, resolveScriptURL(scriptUrl), reload);
|
||||||
entity->scriptHasPreloaded();
|
entity->scriptHasPreloaded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1172,8 +1224,9 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
|
||||||
if ((myNodeID == entityASimulatorID && entityAIsDynamic) || (myNodeID == entityBSimulatorID && (!entityAIsDynamic || entityASimulatorID.isNull()))) {
|
if ((myNodeID == entityASimulatorID && entityAIsDynamic) || (myNodeID == entityBSimulatorID && (!entityAIsDynamic || entityASimulatorID.isNull()))) {
|
||||||
playEntityCollisionSound(entityA, collision);
|
playEntityCollisionSound(entityA, collision);
|
||||||
emit collisionWithEntity(idA, idB, collision);
|
emit collisionWithEntity(idA, idB, collision);
|
||||||
if (_entitiesScriptEngine) {
|
auto& scriptEngine = (entityA->isLocalEntity() || entityA->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision);
|
if (scriptEngine) {
|
||||||
|
scriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1183,8 +1236,9 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
|
||||||
Collision invertedCollision(collision);
|
Collision invertedCollision(collision);
|
||||||
invertedCollision.invert();
|
invertedCollision.invert();
|
||||||
emit collisionWithEntity(idB, idA, invertedCollision);
|
emit collisionWithEntity(idB, idA, invertedCollision);
|
||||||
if (_entitiesScriptEngine) {
|
auto& scriptEngine = (entityB->isLocalEntity() || entityB->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, invertedCollision);
|
if (scriptEngine) {
|
||||||
|
scriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, invertedCollision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,7 +173,9 @@ private:
|
||||||
EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); }
|
EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); }
|
||||||
render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); }
|
render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); }
|
||||||
|
|
||||||
void resetEntitiesScriptEngine();
|
void resetPersistentEntitiesScriptEngine();
|
||||||
|
void resetNonPersistentEntitiesScriptEngine();
|
||||||
|
void setupEntityScriptEngineSignals(const ScriptEnginePointer& scriptEngine);
|
||||||
|
|
||||||
void findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar);
|
void findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar);
|
||||||
|
|
||||||
|
@ -196,7 +198,8 @@ private:
|
||||||
QSet<EntityItemID> _currentEntitiesInside;
|
QSet<EntityItemID> _currentEntitiesInside;
|
||||||
|
|
||||||
bool _wantScripts;
|
bool _wantScripts;
|
||||||
ScriptEnginePointer _entitiesScriptEngine;
|
ScriptEnginePointer _nonPersistentEntitiesScriptEngine; // used for domain + non-owned avatar entities, cleared on domain switch
|
||||||
|
ScriptEnginePointer _persistentEntitiesScriptEngine; // used for local + owned avatar entities, persists on domain switch, cleared on reload content
|
||||||
|
|
||||||
void playEntityCollisionSound(const EntityItemPointer& entity, const Collision& collision);
|
void playEntityCollisionSound(const EntityItemPointer& entity, const Collision& collision);
|
||||||
|
|
||||||
|
@ -214,8 +217,6 @@ private:
|
||||||
std::function<RayToEntityIntersectionResult(unsigned int)> _getPrevRayPickResultOperator;
|
std::function<RayToEntityIntersectionResult(unsigned int)> _getPrevRayPickResultOperator;
|
||||||
std::function<void(unsigned int, bool)> _setPrecisionPickingOperator;
|
std::function<void(unsigned int, bool)> _setPrecisionPickingOperator;
|
||||||
|
|
||||||
bool _mouseAndPreloadSignalHandlersConnected { false };
|
|
||||||
|
|
||||||
class LayeredZone {
|
class LayeredZone {
|
||||||
public:
|
public:
|
||||||
LayeredZone(std::shared_ptr<ZoneEntityItem> zone) : zone(zone), id(zone->getID()), volume(zone->getVolumeEstimate()) {}
|
LayeredZone(std::shared_ptr<ZoneEntityItem> zone) : zone(zone), id(zone->getID()), volume(zone->getVolumeEstimate()) {}
|
||||||
|
|
|
@ -1046,18 +1046,26 @@ QSizeF EntityScriptingInterface::textSize(const QUuid& id, const QString& text)
|
||||||
return EntityTree::textSize(id, text);
|
return EntityTree::textSize(id, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityScriptingInterface::setEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine) {
|
void EntityScriptingInterface::setPersistentEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine) {
|
||||||
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
|
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
|
||||||
_entitiesScriptEngine = engine;
|
_persistentEntitiesScriptEngine = engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::setNonPersistentEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine) {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
|
||||||
|
_nonPersistentEntitiesScriptEngine = engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityScriptingInterface::callEntityMethod(const QUuid& id, const QString& method, const QStringList& params) {
|
void EntityScriptingInterface::callEntityMethod(const QUuid& id, const QString& method, const QStringList& params) {
|
||||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||||
|
|
||||||
|
auto entity = getEntityTree()->findEntityByEntityItemID(id);
|
||||||
|
if (entity) {
|
||||||
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
|
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
|
||||||
if (_entitiesScriptEngine) {
|
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
|
||||||
EntityItemID entityID{ id };
|
if (scriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, method, params);
|
scriptEngine->callEntityScriptMethod(id, method, params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1099,9 +1107,13 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer
|
||||||
params << paramString;
|
params << paramString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto entity = getEntityTree()->findEntityByEntityItemID(entityID);
|
||||||
|
if (entity) {
|
||||||
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
|
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
|
||||||
if (_entitiesScriptEngine) {
|
auto& scriptEngine = (entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine;
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(entityID, method, params, senderNode->getUUID());
|
if (scriptEngine) {
|
||||||
|
scriptEngine->callEntityScriptMethod(entityID, method, params, senderNode->getUUID());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1332,7 +1344,7 @@ bool EntityPropertyMetadataRequest::script(EntityItemID entityID, QScriptValue h
|
||||||
if (entitiesScriptEngine) {
|
if (entitiesScriptEngine) {
|
||||||
request->setFuture(entitiesScriptEngine->getLocalEntityScriptDetails(entityID));
|
request->setFuture(entitiesScriptEngine->getLocalEntityScriptDetails(entityID));
|
||||||
}
|
}
|
||||||
});
|
}, entityID);
|
||||||
if (!request->isStarted()) {
|
if (!request->isStarted()) {
|
||||||
request->deleteLater();
|
request->deleteLater();
|
||||||
callScopedHandlerObject(handler, _engine->makeError("Entities Scripting Provider unavailable", "InternalError"), QScriptValue());
|
callScopedHandlerObject(handler, _engine->makeError("Entities Scripting Provider unavailable", "InternalError"), QScriptValue());
|
||||||
|
|
|
@ -181,7 +181,8 @@ public:
|
||||||
|
|
||||||
void setEntityTree(EntityTreePointer modelTree);
|
void setEntityTree(EntityTreePointer modelTree);
|
||||||
EntityTreePointer getEntityTree() { return _entityTree; }
|
EntityTreePointer getEntityTree() { return _entityTree; }
|
||||||
void setEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine);
|
void setPersistentEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine);
|
||||||
|
void setNonPersistentEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine);
|
||||||
|
|
||||||
void resetActivityTracking();
|
void resetActivityTracking();
|
||||||
ActivityTracking getActivityTracking() const { return _activityTracking; }
|
ActivityTracking getActivityTracking() const { return _activityTracking; }
|
||||||
|
@ -2510,9 +2511,12 @@ signals:
|
||||||
void webEventReceived(const EntityItemID& entityItemID, const QVariant& message);
|
void webEventReceived(const EntityItemID& entityItemID, const QVariant& message);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void withEntitiesScriptEngine(std::function<void(QSharedPointer<EntitiesScriptEngineProvider>)> function) {
|
void withEntitiesScriptEngine(std::function<void(QSharedPointer<EntitiesScriptEngineProvider>)> function, const EntityItemID& id) {
|
||||||
|
auto entity = getEntityTree()->findEntityByEntityItemID(id);
|
||||||
|
if (entity) {
|
||||||
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
|
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
|
||||||
function(_entitiesScriptEngine);
|
function((entity->isLocalEntity() || entity->isMyAvatarEntity()) ? _persistentEntitiesScriptEngine : _nonPersistentEntitiesScriptEngine);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
@ -2542,7 +2546,8 @@ private:
|
||||||
EntityTreePointer _entityTree;
|
EntityTreePointer _entityTree;
|
||||||
|
|
||||||
std::recursive_mutex _entitiesScriptEngineLock;
|
std::recursive_mutex _entitiesScriptEngineLock;
|
||||||
QSharedPointer<EntitiesScriptEngineProvider> _entitiesScriptEngine;
|
QSharedPointer<EntitiesScriptEngineProvider> _persistentEntitiesScriptEngine;
|
||||||
|
QSharedPointer<EntitiesScriptEngineProvider> _nonPersistentEntitiesScriptEngine;
|
||||||
|
|
||||||
bool _bidOnSimulationOwnership { false };
|
bool _bidOnSimulationOwnership { false };
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ void gl::initModuleGl() {
|
||||||
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)getGlProcessAddress("wglCreateContextAttribsARB");
|
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)getGlProcessAddress("wglCreateContextAttribsARB");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX)
|
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||||
QueryCurrentRendererIntegerMESA = (PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC)getGlProcessAddress("glXQueryCurrentRendererIntegerMESA");
|
QueryCurrentRendererIntegerMESA = (PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC)getGlProcessAddress("glXQueryCurrentRendererIntegerMESA");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ void gl::setSwapInterval(int interval) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool gl::queryCurrentRendererIntegerMESA(int attr, unsigned int *value) {
|
bool gl::queryCurrentRendererIntegerMESA(int attr, unsigned int *value) {
|
||||||
#if defined(Q_OS_LINUX)
|
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||||
if (QueryCurrentRendererIntegerMESA) {
|
if (QueryCurrentRendererIntegerMESA) {
|
||||||
return QueryCurrentRendererIntegerMESA(attr, value);
|
return QueryCurrentRendererIntegerMESA(attr, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,9 +153,11 @@ void GLBackend::init() {
|
||||||
|
|
||||||
if (vendor.contains("NVIDIA") ) {
|
if (vendor.contains("NVIDIA") ) {
|
||||||
qCDebug(gpugllogging) << "NVIDIA card detected";
|
qCDebug(gpugllogging) << "NVIDIA card detected";
|
||||||
|
#if !defined(Q_OS_ANDROID)
|
||||||
GL_GET_INTEGER(GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX);
|
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_TOTAL_AVAILABLE_MEMORY_NVX);
|
||||||
GL_GET_INTEGER(GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX);
|
GL_GET_INTEGER(GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX);
|
||||||
|
#endif
|
||||||
|
|
||||||
qCDebug(gpugllogging) << "GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX: " << GPU_MEMORY_INFO_DEDICATED_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_TOTAL_AVAILABLE_MEMORY_NVX: " << GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX;
|
||||||
|
@ -168,7 +170,9 @@ void GLBackend::init() {
|
||||||
|
|
||||||
} else if (vendor.contains("ATI")) {
|
} else if (vendor.contains("ATI")) {
|
||||||
qCDebug(gpugllogging) << "ATI card detected";
|
qCDebug(gpugllogging) << "ATI card detected";
|
||||||
|
#if !defined(Q_OS_ANDROID)
|
||||||
GL_GET_INTEGER(TEXTURE_FREE_MEMORY_ATI);
|
GL_GET_INTEGER(TEXTURE_FREE_MEMORY_ATI);
|
||||||
|
#endif
|
||||||
|
|
||||||
_totalMemory = TEXTURE_FREE_MEMORY_ATI * BYTES_PER_KIB;
|
_totalMemory = TEXTURE_FREE_MEMORY_ATI * BYTES_PER_KIB;
|
||||||
_dedicatedMemory = _totalMemory;
|
_dedicatedMemory = _totalMemory;
|
||||||
|
@ -219,10 +223,14 @@ size_t GLBackend::getAvailableMemory() {
|
||||||
|
|
||||||
switch( _videoCard ) {
|
switch( _videoCard ) {
|
||||||
case NVIDIA:
|
case NVIDIA:
|
||||||
|
#if !defined(Q_OS_ANDROID)
|
||||||
glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &mem);
|
glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &mem);
|
||||||
|
#endif
|
||||||
return mem * BYTES_PER_KIB;
|
return mem * BYTES_PER_KIB;
|
||||||
case ATI:
|
case ATI:
|
||||||
|
#if !defined(Q_OS_ANDROID)
|
||||||
glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, &mem);
|
glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, &mem);
|
||||||
|
#endif
|
||||||
return mem * BYTES_PER_KIB;
|
return mem * BYTES_PER_KIB;
|
||||||
case MESA:
|
case MESA:
|
||||||
return 0; // Don't know the current value
|
return 0; // Don't know the current value
|
||||||
|
|
|
@ -451,7 +451,7 @@ void ScriptEngine::executeOnScriptThread(std::function<void()> function, const Q
|
||||||
function();
|
function();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::waitTillDoneRunning() {
|
void ScriptEngine::waitTillDoneRunning(bool shutdown) {
|
||||||
// Engine should be stopped already, but be defensive
|
// Engine should be stopped already, but be defensive
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
|
@ -520,12 +520,14 @@ void ScriptEngine::waitTillDoneRunning() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shutdown) {
|
||||||
// NOTE: This will be called on the main application thread (among other threads) from stopAllScripts.
|
// NOTE: This will be called on the main application thread (among other threads) from stopAllScripts.
|
||||||
// The thread will need to continue to process events, because
|
// The thread will need to continue to process events, because
|
||||||
// the scripts will likely need to marshall messages across to the main thread, e.g.
|
// the scripts will likely need to marshall messages across to the main thread, e.g.
|
||||||
// if they access Settings or Menu in any of their shutdown code. So:
|
// if they access Settings or Menu in any of their shutdown code. So:
|
||||||
// Process events for this thread, allowing invokeMethod calls to pass between threads.
|
// Process events for this thread, allowing invokeMethod calls to pass between threads.
|
||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
|
}
|
||||||
|
|
||||||
// Avoid a pure busy wait
|
// Avoid a pure busy wait
|
||||||
QThread::yieldCurrentThread();
|
QThread::yieldCurrentThread();
|
||||||
|
@ -1942,8 +1944,11 @@ QScriptValue ScriptEngine::require(const QString& moduleId) {
|
||||||
// to inspect particular entries and invalidate them by deleting the key:
|
// to inspect particular entries and invalidate them by deleting the key:
|
||||||
// `delete Script.require.cache[Script.require.resolve(moduleId)];`
|
// `delete Script.require.cache[Script.require.resolve(moduleId)];`
|
||||||
|
|
||||||
|
// Check to see if we should invalidate the cache based on a user setting.
|
||||||
|
Setting::Handle<bool> getCachebustSetting {"cachebustScriptRequire", false };
|
||||||
|
|
||||||
// cacheMeta is just used right now to tell deleted keys apart from undefined ones
|
// cacheMeta is just used right now to tell deleted keys apart from undefined ones
|
||||||
bool invalidateCache = module.isUndefined() && cacheMeta.property(moduleId).isValid();
|
bool invalidateCache = getCachebustSetting.get() || (module.isUndefined() && cacheMeta.property(moduleId).isValid());
|
||||||
|
|
||||||
// reset the cacheMeta record so invalidation won't apply next time, even if the module fails to load
|
// reset the cacheMeta record so invalidation won't apply next time, even if the module fails to load
|
||||||
cacheMeta.setProperty(modulePath, QScriptValue());
|
cacheMeta.setProperty(modulePath, QScriptValue());
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
//
|
//
|
||||||
// Created by Brad Hefta-Gaub on 12/14/13.
|
// Created by Brad Hefta-Gaub on 12/14/13.
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
// Copyright 2013 High Fidelity, Inc.
|
||||||
|
// Copyright 2020 Vircadia contributors.
|
||||||
//
|
//
|
||||||
// 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
|
||||||
|
@ -196,7 +197,7 @@ public:
|
||||||
Q_INVOKABLE void stop(bool marshal = false);
|
Q_INVOKABLE void stop(bool marshal = false);
|
||||||
|
|
||||||
// Stop any evaluating scripts and wait for the scripting thread to finish.
|
// Stop any evaluating scripts and wait for the scripting thread to finish.
|
||||||
void waitTillDoneRunning();
|
void waitTillDoneRunning(bool shutdown = false);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can
|
// NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can
|
||||||
|
|
|
@ -182,7 +182,7 @@ void ScriptEngines::shutdownScripting() {
|
||||||
// want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing
|
// want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing
|
||||||
// any application state after we leave this stopAllScripts() method
|
// any application state after we leave this stopAllScripts() method
|
||||||
qCDebug(scriptengine) << "waiting on script:" << scriptName;
|
qCDebug(scriptengine) << "waiting on script:" << scriptName;
|
||||||
scriptEngine->waitTillDoneRunning();
|
scriptEngine->waitTillDoneRunning(true);
|
||||||
qCDebug(scriptengine) << "done waiting on script:" << scriptName;
|
qCDebug(scriptengine) << "done waiting on script:" << scriptName;
|
||||||
}
|
}
|
||||||
// Once the script is stopped, we can remove it from our set
|
// Once the script is stopped, we can remove it from our set
|
||||||
|
|
|
@ -53,5 +53,5 @@ set(DIR "opusCodec")
|
||||||
add_subdirectory(${DIR})
|
add_subdirectory(${DIR})
|
||||||
|
|
||||||
# example plugins
|
# example plugins
|
||||||
set(DIR "KasenAPIExample")
|
set(DIR "JSAPIExample")
|
||||||
add_subdirectory(${DIR})
|
add_subdirectory(${DIR})
|
||||||
|
|
3
plugins/JSAPIExample/CMakeLists.txt
Normal file
3
plugins/JSAPIExample/CMakeLists.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
set(TARGET_NAME JSAPIExample)
|
||||||
|
setup_hifi_client_server_plugin(scripting)
|
||||||
|
link_hifi_libraries(shared plugins)
|
251
plugins/JSAPIExample/src/JSAPIExample.cpp
Normal file
251
plugins/JSAPIExample/src/JSAPIExample.cpp
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
//
|
||||||
|
// JSAPIExample.cpp
|
||||||
|
// plugins/JSAPIExample/src
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019-2020 humbletim (humbletim@gmail.com)
|
||||||
|
// Copyright (c) 2019 Kalila L. (kasenvr@gmail.com)
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
|
// Example of prototyping new JS APIs by leveraging the existing plugin system.
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
#include <QtCore/QDir>
|
||||||
|
#include <QtCore/QFileInfo>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QLoggingCategory>
|
||||||
|
#include <QtCore/QThread>
|
||||||
|
#include <QtCore/QTimer>
|
||||||
|
#include <QtScript/QScriptEngine>
|
||||||
|
#include <QtScript/QScriptable>
|
||||||
|
|
||||||
|
#include <SettingHelpers.h> // for ::settingsFilename()
|
||||||
|
#include <SharedUtil.h> // for ::usecTimestampNow()
|
||||||
|
#include <shared/ScriptInitializerMixin.h>
|
||||||
|
|
||||||
|
// NOTE: replace this with your own namespace when starting a new plugin (to avoid .so/.dll symbol clashes)
|
||||||
|
namespace REPLACE_ME_WITH_UNIQUE_NAME {
|
||||||
|
|
||||||
|
static constexpr auto JSAPI_SEMANTIC_VERSION = "0.0.1";
|
||||||
|
static constexpr auto JSAPI_EXPORT_NAME = "JSAPIExample";
|
||||||
|
|
||||||
|
QLoggingCategory logger { "jsapiexample" };
|
||||||
|
|
||||||
|
inline QVariant raiseScriptingError(QScriptContext* context, const QString& message, const QVariant& returnValue = QVariant()) {
|
||||||
|
if (context) {
|
||||||
|
// when a QScriptContext is available throw an actual JS Exception (which can be caught using try/catch on JS side)
|
||||||
|
context->throwError(message);
|
||||||
|
} else {
|
||||||
|
// otherwise just log the error
|
||||||
|
qCWarning(logger) << "error:" << message;
|
||||||
|
}
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject* createScopedSettings(const QString& scope, QObject* parent, QString& error);
|
||||||
|
|
||||||
|
class JSAPIExample : public QObject, public QScriptable {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID "JSAPIExample" FILE "plugin.json")
|
||||||
|
Q_PROPERTY(QString version MEMBER _version CONSTANT)
|
||||||
|
public:
|
||||||
|
JSAPIExample() {
|
||||||
|
setObjectName(JSAPI_EXPORT_NAME);
|
||||||
|
auto scriptInit = DependencyManager::get<ScriptInitializers>();
|
||||||
|
if (!scriptInit) {
|
||||||
|
qCWarning(logger) << "COULD NOT INITIALIZE (ScriptInitializers unavailable)" << qApp << this;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qCWarning(logger) << "registering w/ScriptInitializerMixin..." << scriptInit.data();
|
||||||
|
scriptInit->registerScriptInitializer([this](QScriptEngine* engine) {
|
||||||
|
auto value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater);
|
||||||
|
engine->globalObject().setProperty(objectName(), value);
|
||||||
|
// qCDebug(logger) << "setGlobalInstance" << objectName() << engine->property("fileName");
|
||||||
|
});
|
||||||
|
// qCInfo(logger) << "plugin loaded" << qApp << toString() << QThread::currentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTES: everything within the "public slots:" section below will be available from JS via overall plugin QObject
|
||||||
|
// also, to demonstrate future-proofing JS API code, QVariant's are used throughout most of these examples --
|
||||||
|
// which still makes them very Qt-specific, but avoids depending directly on deprecated QtScript/QScriptValue APIs.
|
||||||
|
// (as such this plugin class and its methods remain forward-compatible with other engines like QML's QJSEngine)
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
// pretty-printed representation for logging eg: print(JSAPIExample)
|
||||||
|
// (note: Qt script engines automatically look for a ".toString" method on native classes when coercing values to strings)
|
||||||
|
QString toString() const { return QString("[%1 version=%2]").arg(objectName()).arg(_version); }
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Returns current microseconds (usecs) since Epoch. note: 1000usecs == 1ms
|
||||||
|
* @example <caption>Measure current setTimeout accuracy.</caption>
|
||||||
|
* var expected = 1000;
|
||||||
|
* var start = JSAPIExample.now();
|
||||||
|
* Script.setTimeout(function () {
|
||||||
|
* var elapsed = (JSAPIExample.now() - start)/1000;
|
||||||
|
* print("expected (ms):", expected, "actual (ms):", elapsed);
|
||||||
|
* }, expected);
|
||||||
|
*/
|
||||||
|
QVariant now() const { return usecTimestampNow(); }
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Example of returning a JS Object key-value map
|
||||||
|
* @example <caption>"zip" a list of keys and corresponding values to form key-value map</caption>
|
||||||
|
* print(JSON.stringify(JSAPIExample.zip(["a","b"], [1,2])); // { "a": 1, "b": 2 }
|
||||||
|
*/
|
||||||
|
QVariant zip(const QStringList& keys, const QVariantList& values) const {
|
||||||
|
QVariantMap out;
|
||||||
|
for (int i = 0; i < keys.size(); i++) {
|
||||||
|
out[keys[i]] = i < values.size() ? values[i] : QVariant();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Example of returning a JS Array result
|
||||||
|
* @example <caption>emulate Object.values(keyValues)</caption>
|
||||||
|
* print(JSON.stringify(JSAPIExample.values({ "a": 1, "b": 2 }))); // [1,2]
|
||||||
|
*/
|
||||||
|
QVariant values(const QVariantMap& keyValues) const {
|
||||||
|
QVariantList values = keyValues.values();
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Another example of returning JS Array data
|
||||||
|
* @example <caption>generate an integer sequence (inclusive of [from, to])</caption>
|
||||||
|
* print(JSON.stringify(JSAPIExample.seq(1,5)));// [1,2,3,4,5]
|
||||||
|
*/
|
||||||
|
QVariant seq(int from, int to) const {
|
||||||
|
QVariantList out;
|
||||||
|
for (int i = from; i <= to; i++) {
|
||||||
|
out.append(i);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Example of returning arbitrary binary data from C++ (resulting in a JS ArrayBuffer)
|
||||||
|
* see also: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer#Examples
|
||||||
|
* @example <caption>return compressed/decompressed versions of the input data</caption>
|
||||||
|
* var data = "testing 1 2 3";
|
||||||
|
* var z = JSAPIExample.qCompressString(data); // z will be an ArrayBuffer
|
||||||
|
* var u = JSAPIExample.qUncompressString(z); // u will be a String value
|
||||||
|
* print(JSON.stringify({ input: data, compressed: z.byteLength, output: u, uncompressed: u.length }));
|
||||||
|
*/
|
||||||
|
QVariant qCompressString(const QString& jsString, int compress_level = -1) const {
|
||||||
|
QByteArray arrayBuffer = qCompress(jsString.toUtf8(), compress_level);
|
||||||
|
return arrayBuffer;
|
||||||
|
}
|
||||||
|
QVariant qUncompressString(const QByteArray& arrayBuffer) const {
|
||||||
|
QString jsString = QString::fromUtf8(qUncompress(arrayBuffer));
|
||||||
|
return jsString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example of exposing a custom "managed" C++ QObject to JS
|
||||||
|
* The lifecycle of the created QObject* instance becomes managed by the invoking QScriptEngine --
|
||||||
|
* it will be automatically cleaned up once no longer reachable from any JS variables/closures.
|
||||||
|
* @example <caption>access persistent settings stored in separate .json files</caption>
|
||||||
|
* var settings = JSAPIExample.getScopedSettings("example");
|
||||||
|
* print("example settings stored in:", settings.fileName());
|
||||||
|
* print("(before) example::timestamp", settings.getValue("timestamp"));
|
||||||
|
* settings.setValue("timestamp", Date.now());
|
||||||
|
* print("(after) example::timestamp", settings.getValue("timestamp"));
|
||||||
|
* print("all example::* keys", settings.allKeys());
|
||||||
|
* settings = null; // optional best pratice; allows the object to be reclaimed ASAP by the JS garbage collector
|
||||||
|
*/
|
||||||
|
QScriptValue getScopedSettings(const QString& scope) {
|
||||||
|
auto engine = QScriptable::engine();
|
||||||
|
if (!engine) {
|
||||||
|
return QScriptValue::NullValue;
|
||||||
|
}
|
||||||
|
QString error;
|
||||||
|
auto cppValue = createScopedSettings(scope, engine, error);
|
||||||
|
if (!cppValue) {
|
||||||
|
raiseScriptingError(context(), "error creating scoped settings instance: " + error);
|
||||||
|
return QScriptValue::NullValue;
|
||||||
|
}
|
||||||
|
return engine->newQObject(cppValue, QScriptEngine::ScriptOwnership, QScriptEngine::ExcludeDeleteLater);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const QString _version { JSAPI_SEMANTIC_VERSION };
|
||||||
|
};
|
||||||
|
|
||||||
|
// JSSettingsHelper wraps a scoped (prefixed/separate) QSettings and exposes a subset of QSetting APIs as slots
|
||||||
|
class JSSettingsHelper : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
JSSettingsHelper(const QString& scope, QObject* parent = nullptr);
|
||||||
|
~JSSettingsHelper();
|
||||||
|
operator bool() const;
|
||||||
|
public slots:
|
||||||
|
QString fileName() const;
|
||||||
|
QString toString() const;
|
||||||
|
QVariant getValue(const QString& key, const QVariant& defaultValue = QVariant());
|
||||||
|
bool setValue(const QString& key, const QVariant& value);
|
||||||
|
QStringList allKeys() const;
|
||||||
|
protected:
|
||||||
|
QString _scope;
|
||||||
|
QString _fileName;
|
||||||
|
QSharedPointer<QSettings> _settings;
|
||||||
|
QString getLocalSettingsPath(const QString& scope) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// verifies the requested scope is sensible and creates/returns a scoped JSSettingsHelper instance
|
||||||
|
QObject* createScopedSettings(const QString& scope, QObject* parent, QString& error) {
|
||||||
|
const QRegExp VALID_SETTINGS_SCOPE { "[-_A-Za-z0-9]{1,64}" };
|
||||||
|
if (!VALID_SETTINGS_SCOPE.exactMatch(scope)) {
|
||||||
|
error = QString("invalid scope (expected alphanumeric <= 64 chars not '%1')").arg(scope);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return new JSSettingsHelper(scope, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// ----- inline JSSettingsHelper implementation -----
|
||||||
|
JSSettingsHelper::operator bool() const {
|
||||||
|
return (bool)_settings;
|
||||||
|
}
|
||||||
|
JSSettingsHelper::JSSettingsHelper(const QString& scope, QObject* parent) :
|
||||||
|
QObject(parent), _scope(scope), _fileName(getLocalSettingsPath(scope)),
|
||||||
|
_settings(_fileName.isEmpty() ? nullptr : new QSettings(_fileName, JSON_FORMAT)) {
|
||||||
|
}
|
||||||
|
JSSettingsHelper::~JSSettingsHelper() {
|
||||||
|
qCDebug(logger) << "~JSSettingsHelper" << _scope << _fileName << this;
|
||||||
|
}
|
||||||
|
QString JSSettingsHelper::fileName() const {
|
||||||
|
return _settings ? _settings->fileName() : "";
|
||||||
|
}
|
||||||
|
QString JSSettingsHelper::toString() const {
|
||||||
|
return QString("[JSSettingsHelper scope=%1 valid=%2]").arg(_scope).arg((bool)_settings);
|
||||||
|
}
|
||||||
|
QVariant JSSettingsHelper::getValue(const QString& key, const QVariant& defaultValue) {
|
||||||
|
return _settings ? _settings->value(key, defaultValue) : defaultValue;
|
||||||
|
}
|
||||||
|
bool JSSettingsHelper::setValue(const QString& key, const QVariant& value) {
|
||||||
|
if (_settings) {
|
||||||
|
if (value.isValid()) {
|
||||||
|
_settings->setValue(key, value);
|
||||||
|
} else {
|
||||||
|
_settings->remove(key);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QStringList JSSettingsHelper::allKeys() const {
|
||||||
|
return _settings ? _settings->allKeys() : QStringList{};
|
||||||
|
}
|
||||||
|
QString JSSettingsHelper::getLocalSettingsPath(const QString& scope) const {
|
||||||
|
// generate a prefixed filename (relative to the main application's Interface.json file)
|
||||||
|
const QString fileName = QString("jsapi_%1.json").arg(scope);
|
||||||
|
return QFileInfo(::settingsFilename()).dir().filePath(fileName);
|
||||||
|
}
|
||||||
|
// ----- /inline JSSettingsHelper implementation -----
|
||||||
|
|
||||||
|
} // namespace REPLACE_ME_WITH_UNIQUE_NAME
|
||||||
|
|
||||||
|
#include "JSAPIExample.moc"
|
4
plugins/JSAPIExample/src/plugin.json
Normal file
4
plugins/JSAPIExample/src/plugin.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name":"JS API Example",
|
||||||
|
"version": 1
|
||||||
|
}
|
|
@ -1,3 +0,0 @@
|
||||||
set(TARGET_NAME KasenAPIExample)
|
|
||||||
setup_hifi_client_server_plugin(scripting)
|
|
||||||
link_hifi_libraries(shared plugins avatars networking graphics gpu)
|
|
|
@ -1,58 +0,0 @@
|
||||||
//
|
|
||||||
// ExampleScriptPlugin.h
|
|
||||||
// plugins/KasenAPIExample/src
|
|
||||||
//
|
|
||||||
// Created by Kasen IO on 2019.07.14 | realities.dev | kasenvr@gmail.com
|
|
||||||
// Copyright 2019 Kasen IO
|
|
||||||
//
|
|
||||||
// Authored by: Humbletim (humbletim@gmail.com)
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
// Supporting file containing all QtScript specific integration.
|
|
||||||
|
|
||||||
#ifndef EXAMPLE_SCRIPT_PLUGIN_H
|
|
||||||
#define EXAMPLE_SCRIPT_PLUGIN_H
|
|
||||||
|
|
||||||
#if DEV_BUILD
|
|
||||||
#pragma message("QtScript is deprecated see: doc.qt.io/qt-5/topics-scripting.html")
|
|
||||||
#endif
|
|
||||||
#include <QtScript/QScriptEngine>
|
|
||||||
|
|
||||||
#include <QtCore/QLoggingCategory>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <shared/ScriptInitializerMixin.h>
|
|
||||||
|
|
||||||
namespace example {
|
|
||||||
|
|
||||||
extern const QLoggingCategory& logger;
|
|
||||||
|
|
||||||
inline void setGlobalInstance(QScriptEngine* engine, const QString& name, QObject* object) {
|
|
||||||
auto value = engine->newQObject(object, QScriptEngine::QtOwnership);
|
|
||||||
engine->globalObject().setProperty(name, value);
|
|
||||||
qCDebug(logger) << "setGlobalInstance" << name << engine->property("fileName");
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScriptPlugin : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
QString _version;
|
|
||||||
Q_PROPERTY(QString version MEMBER _version CONSTANT)
|
|
||||||
protected:
|
|
||||||
inline ScriptPlugin(const QString& name, const QString& version) : _version(version) {
|
|
||||||
setObjectName(name);
|
|
||||||
if (!DependencyManager::get<ScriptInitializers>()) {
|
|
||||||
qCWarning(logger) << "COULD NOT INITIALIZE (ScriptInitializers unavailable)" << qApp << this;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
qCWarning(logger) << "registering w/ScriptInitializerMixin..." << DependencyManager::get<ScriptInitializers>().data();
|
|
||||||
DependencyManager::get<ScriptInitializers>()->registerScriptInitializer(
|
|
||||||
[this](QScriptEngine* engine) { setGlobalInstance(engine, objectName(), this); });
|
|
||||||
}
|
|
||||||
public slots:
|
|
||||||
inline QString toString() const { return QString("[%1 version=%2]").arg(objectName()).arg(_version); }
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace example
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,141 +0,0 @@
|
||||||
//
|
|
||||||
// KasenAPIExample.cpp
|
|
||||||
// plugins/KasenAPIExample/src
|
|
||||||
//
|
|
||||||
// Created by Kasen IO on 2019.07.14 | realities.dev | kasenvr@gmail.com
|
|
||||||
// Copyright 2019 Kasen IO
|
|
||||||
//
|
|
||||||
// Authored by: Humbletim (humbletim@gmail.com)
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
// Example of prototyping new JS APIs by leveraging the existing plugin system.
|
|
||||||
|
|
||||||
#include "ExampleScriptPlugin.h"
|
|
||||||
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QtCore/QJsonObject>
|
|
||||||
#include <QtCore/QLoggingCategory>
|
|
||||||
#include <QtCore/QThread>
|
|
||||||
#include <QtCore/QTimer>
|
|
||||||
|
|
||||||
#include <SharedUtil.h>
|
|
||||||
#include <AvatarHashMap.h>
|
|
||||||
|
|
||||||
namespace custom_api_example {
|
|
||||||
|
|
||||||
QLoggingCategory logger{ "custom_api_example" };
|
|
||||||
|
|
||||||
class KasenAPIExample : public example::ScriptPlugin {
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PLUGIN_METADATA(IID "KasenAPIExample" FILE "plugin.json")
|
|
||||||
public:
|
|
||||||
KasenAPIExample() : example::ScriptPlugin("KasenAPIExample", "0.0.1") {
|
|
||||||
qCInfo(logger) << "plugin loaded" << qApp << toString() << QThread::currentThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
/**jsdoc
|
|
||||||
* Returns current microseconds (usecs) since Epoch. note: 1000usecs == 1ms
|
|
||||||
* @example <caption>Measure current setTimeout accuracy.</caption>
|
|
||||||
* var expected = 1000;
|
|
||||||
* var start = KasenAPIExample.now();
|
|
||||||
* Script.setTimeout(function () {
|
|
||||||
* var elapsed = (KasenAPIExample.now() - start)/1000;
|
|
||||||
* print("expected (ms):", expected, "actual (ms):", elapsed);
|
|
||||||
* }, expected);
|
|
||||||
*/
|
|
||||||
QVariant now() const {
|
|
||||||
return usecTimestampNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**jsdoc
|
|
||||||
* Returns the available blendshape names for an avatar.
|
|
||||||
* @example <caption>Get blendshape names</caption>
|
|
||||||
* print(JSON.stringify(KasenAPIExample.getBlendshapeNames(MyAvatar.sessionUUID)));
|
|
||||||
*/
|
|
||||||
QStringList getBlendshapeNames(const QUuid& avatarID) const {
|
|
||||||
QVector<QString> out;
|
|
||||||
if (auto head = getAvatarHead(avatarID)) {
|
|
||||||
for (const auto& kv : head->getBlendshapeMap().toStdMap()) {
|
|
||||||
if (kv.second >= out.size()) out.resize(kv.second+1);
|
|
||||||
out[kv.second] = kv.first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**jsdoc
|
|
||||||
* Returns a key-value object with active (non-zero) blendshapes.
|
|
||||||
* eg: { JawOpen: 1.0, ... }
|
|
||||||
* @example <caption>Get active blendshape map</caption>
|
|
||||||
* print(JSON.stringify(KasenAPIExample.getActiveBlendshapes(MyAvatar.sessionUUID)));
|
|
||||||
*/
|
|
||||||
QVariant getActiveBlendshapes(const QUuid& avatarID) const {
|
|
||||||
if (auto head = getAvatarHead(avatarID)) {
|
|
||||||
return head->toJson()["blendShapes"].toVariant();
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant getBlendshapeMapping(const QUuid& avatarID) const {
|
|
||||||
QVariantMap out;
|
|
||||||
if (auto head = getAvatarHead(avatarID)) {
|
|
||||||
for (const auto& kv : head->getBlendshapeMap().toStdMap()) {
|
|
||||||
out[kv.first] = kv.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant getBlendshapes(const QUuid& avatarID) const {
|
|
||||||
QVariantMap result;
|
|
||||||
if (auto head = getAvatarHead(avatarID)) {
|
|
||||||
QStringList names = getBlendshapeNames(avatarID);
|
|
||||||
auto states = head->getBlendshapeStates();
|
|
||||||
result = {
|
|
||||||
{ "base", zipNonZeroValues(names, states.base) },
|
|
||||||
{ "summed", zipNonZeroValues(names, states.summed) },
|
|
||||||
{ "transient", zipNonZeroValues(names, states.transient) },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static QVariantMap zipNonZeroValues(const QStringList& keys, const QVector<float>& values) {
|
|
||||||
QVariantMap out;
|
|
||||||
for (int i=1; i < values.size(); i++) {
|
|
||||||
if (fabs(values[i]) > 1.0e-6f) {
|
|
||||||
out[keys.value(i)] = values[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
struct _HeadHelper : public HeadData {
|
|
||||||
QMap<QString,int> getBlendshapeMap() const {
|
|
||||||
return BLENDSHAPE_LOOKUP_MAP;
|
|
||||||
}
|
|
||||||
struct States { QVector<float> base, summed, transient; };
|
|
||||||
States getBlendshapeStates() const {
|
|
||||||
return {
|
|
||||||
_blendshapeCoefficients,
|
|
||||||
_summedBlendshapeCoefficients,
|
|
||||||
_transientBlendshapeCoefficients
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
static const _HeadHelper* getAvatarHead(const QUuid& avatarID) {
|
|
||||||
auto avatars = DependencyManager::get<AvatarHashMap>();
|
|
||||||
auto avatar = avatars ? avatars->getAvatarBySessionID(avatarID) : nullptr;
|
|
||||||
auto head = avatar ? avatar->getHeadData() : nullptr;
|
|
||||||
return reinterpret_cast<const _HeadHelper*>(head);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const QLoggingCategory& example::logger{ custom_api_example::logger };
|
|
||||||
|
|
||||||
#include "KasenAPIExample.moc"
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name":"Kasen JS API Example",
|
|
||||||
"version": 1,
|
|
||||||
"package": {
|
|
||||||
"author": "Revofire",
|
|
||||||
"homepage": "www.realities.dev",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"engines": {
|
|
||||||
"hifi-interface": ">= 0.83.0",
|
|
||||||
"hifi-assignment-client": ">= 0.83.0"
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"client": true,
|
|
||||||
"entity_client": true,
|
|
||||||
"entity_server": true,
|
|
||||||
"edit_filter": true,
|
|
||||||
"agent": true,
|
|
||||||
"avatar": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -102,7 +102,7 @@ def parse_args():
|
||||||
if True:
|
if True:
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
else:
|
else:
|
||||||
args = parser.parse_args(['--android', 'questInterface', '--build-root', 'C:/git/hifi/android/apps/questInterface/.externalNativeBuild/cmake/debug/arm64-v8a'])
|
args = parser.parse_args(['--android', 'questInterface', '--build-root', 'C:/git/project-athena/android/apps/questInterface/.externalNativeBuild/cmake/debug/arm64-v8a'])
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
@ -146,20 +146,23 @@ var DEFAULT_DIMENSIONS = {
|
||||||
|
|
||||||
var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS);
|
var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS);
|
||||||
|
|
||||||
|
var SUBMENU_ENTITY_EDITOR_PREFERENCES = "Edit > Create Application - Preferences";
|
||||||
var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select";
|
var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select";
|
||||||
var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus";
|
var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus";
|
||||||
var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Create Mode";
|
var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Create Mode";
|
||||||
var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Create Mode";
|
var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Create Mode";
|
||||||
|
|
||||||
var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)";
|
var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)";
|
||||||
var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models";
|
var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models";
|
||||||
var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models";
|
var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models";
|
||||||
var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights";
|
var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights";
|
||||||
|
var MENU_ENTITY_LIST_DEFAULT_RADIUS = "Entity List Default Radius";
|
||||||
|
|
||||||
var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect";
|
var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect";
|
||||||
var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus";
|
var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus";
|
||||||
var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode";
|
var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode";
|
||||||
var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode";
|
var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode";
|
||||||
|
var SETTING_EDITOR_COLUMNS_SETUP = "editorColumnsSetup";
|
||||||
|
var SETTING_ENTITY_LIST_DEFAULT_RADIUS = "entityListDefaultRadius";
|
||||||
|
|
||||||
var SETTING_EDIT_PREFIX = "Edit/";
|
var SETTING_EDIT_PREFIX = "Edit/";
|
||||||
|
|
||||||
|
@ -267,8 +270,6 @@ function adjustPositionPerBoundingBox(position, direction, registration, dimensi
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit";
|
|
||||||
|
|
||||||
// Handles any edit mode updates required when domains have switched
|
// Handles any edit mode updates required when domains have switched
|
||||||
function checkEditPermissionsAndUpdate() {
|
function checkEditPermissionsAndUpdate() {
|
||||||
if ((createButton === null) || (createButton === undefined)) {
|
if ((createButton === null) || (createButton === undefined)) {
|
||||||
|
@ -878,7 +879,12 @@ var toolBar = (function () {
|
||||||
|
|
||||||
addButton("importEntitiesButton", function() {
|
addButton("importEntitiesButton", function() {
|
||||||
Window.browseChanged.connect(onFileOpenChanged);
|
Window.browseChanged.connect(onFileOpenChanged);
|
||||||
Window.browseAsync("Select Model to Import", "", "*.json");
|
Window.browseAsync("Select .json to Import", "", "*.json");
|
||||||
|
});
|
||||||
|
|
||||||
|
addButton("importEntitiesFromUrlButton", function() {
|
||||||
|
Window.promptTextChanged.connect(onPromptTextChanged);
|
||||||
|
Window.promptAsync("URL of a .json to import", "");
|
||||||
});
|
});
|
||||||
|
|
||||||
addButton("openAssetBrowserButton", function() {
|
addButton("openAssetBrowserButton", function() {
|
||||||
|
@ -1378,11 +1384,9 @@ Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||||
// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already
|
// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already
|
||||||
// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that
|
// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that
|
||||||
// added it.
|
// added it.
|
||||||
var modelMenuAddedDelete = false;
|
|
||||||
var originalLightsArePickable = Entities.getLightsArePickable();
|
var originalLightsArePickable = Entities.getLightsArePickable();
|
||||||
|
|
||||||
function setupModelMenus() {
|
function setupModelMenus() {
|
||||||
// adj our menuitems
|
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Edit",
|
menuName: "Edit",
|
||||||
menuItemName: "Undo",
|
menuItemName: "Undo",
|
||||||
|
@ -1396,118 +1400,69 @@ function setupModelMenus() {
|
||||||
position: 1,
|
position: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
Menu.addMenuItem({
|
Menu.addMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES);
|
||||||
menuName: "Edit",
|
|
||||||
menuItemName: "Entities",
|
|
||||||
isSeparator: true
|
|
||||||
});
|
|
||||||
if (!Menu.menuItemExists("Edit", "Delete")) {
|
|
||||||
Menu.addMenuItem({
|
|
||||||
menuName: "Edit",
|
|
||||||
menuItemName: "Delete",
|
|
||||||
shortcutKeyEvent: {
|
|
||||||
text: "delete"
|
|
||||||
},
|
|
||||||
afterItem: "Entities",
|
|
||||||
});
|
|
||||||
modelMenuAddedDelete = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Edit",
|
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
|
||||||
menuItemName: "Parent Entity to Last",
|
|
||||||
afterItem: "Entities"
|
|
||||||
});
|
|
||||||
|
|
||||||
Menu.addMenuItem({
|
|
||||||
menuName: "Edit",
|
|
||||||
menuItemName: "Unparent Entity",
|
|
||||||
afterItem: "Parent Entity to Last"
|
|
||||||
});
|
|
||||||
|
|
||||||
Menu.addMenuItem({
|
|
||||||
menuName: GRABBABLE_ENTITIES_MENU_CATEGORY,
|
|
||||||
menuItemName: MENU_CREATE_ENTITIES_GRABBABLE,
|
menuItemName: MENU_CREATE_ENTITIES_GRABBABLE,
|
||||||
afterItem: "Unparent Entity",
|
position: 0,
|
||||||
isCheckable: true,
|
isCheckable: true,
|
||||||
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, false)
|
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, false)
|
||||||
});
|
});
|
||||||
|
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Edit",
|
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
|
||||||
menuItemName: MENU_ALLOW_SELECTION_LARGE,
|
menuItemName: MENU_ALLOW_SELECTION_LARGE,
|
||||||
afterItem: MENU_CREATE_ENTITIES_GRABBABLE,
|
afterItem: MENU_CREATE_ENTITIES_GRABBABLE,
|
||||||
isCheckable: true,
|
isCheckable: true,
|
||||||
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true)
|
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true)
|
||||||
});
|
});
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Edit",
|
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
|
||||||
menuItemName: MENU_ALLOW_SELECTION_SMALL,
|
menuItemName: MENU_ALLOW_SELECTION_SMALL,
|
||||||
afterItem: MENU_ALLOW_SELECTION_LARGE,
|
afterItem: MENU_ALLOW_SELECTION_LARGE,
|
||||||
isCheckable: true,
|
isCheckable: true,
|
||||||
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true)
|
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true)
|
||||||
});
|
});
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Edit",
|
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
|
||||||
menuItemName: MENU_ALLOW_SELECTION_LIGHTS,
|
menuItemName: MENU_ALLOW_SELECTION_LIGHTS,
|
||||||
afterItem: MENU_ALLOW_SELECTION_SMALL,
|
afterItem: MENU_ALLOW_SELECTION_SMALL,
|
||||||
isCheckable: true,
|
isCheckable: true,
|
||||||
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false)
|
isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false)
|
||||||
});
|
});
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Edit",
|
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
|
||||||
menuItemName: "Select All Entities In Box",
|
|
||||||
afterItem: "Allow Selecting of Lights"
|
|
||||||
});
|
|
||||||
Menu.addMenuItem({
|
|
||||||
menuName: "Edit",
|
|
||||||
menuItemName: "Select All Entities Touching Box",
|
|
||||||
afterItem: "Select All Entities In Box"
|
|
||||||
});
|
|
||||||
|
|
||||||
Menu.addMenuItem({
|
|
||||||
menuName: "Edit",
|
|
||||||
menuItemName: "Export Entities",
|
|
||||||
afterItem: "Entities"
|
|
||||||
});
|
|
||||||
Menu.addMenuItem({
|
|
||||||
menuName: "Edit",
|
|
||||||
menuItemName: "Import Entities",
|
|
||||||
afterItem: "Export Entities"
|
|
||||||
});
|
|
||||||
Menu.addMenuItem({
|
|
||||||
menuName: "Edit",
|
|
||||||
menuItemName: "Import Entities from URL",
|
|
||||||
afterItem: "Import Entities"
|
|
||||||
});
|
|
||||||
|
|
||||||
Menu.addMenuItem({
|
|
||||||
menuName: "Edit",
|
|
||||||
menuItemName: MENU_AUTO_FOCUS_ON_SELECT,
|
menuItemName: MENU_AUTO_FOCUS_ON_SELECT,
|
||||||
|
afterItem: MENU_ALLOW_SELECTION_LIGHTS,
|
||||||
isCheckable: true,
|
isCheckable: true,
|
||||||
isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true"
|
isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true"
|
||||||
});
|
});
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Edit",
|
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
|
||||||
menuItemName: MENU_EASE_ON_FOCUS,
|
menuItemName: MENU_EASE_ON_FOCUS,
|
||||||
afterItem: MENU_AUTO_FOCUS_ON_SELECT,
|
afterItem: MENU_AUTO_FOCUS_ON_SELECT,
|
||||||
isCheckable: true,
|
isCheckable: true,
|
||||||
isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true"
|
isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true"
|
||||||
});
|
});
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Edit",
|
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
|
||||||
menuItemName: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE,
|
menuItemName: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE,
|
||||||
afterItem: MENU_EASE_ON_FOCUS,
|
afterItem: MENU_EASE_ON_FOCUS,
|
||||||
isCheckable: true,
|
isCheckable: true,
|
||||||
isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false"
|
isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false"
|
||||||
});
|
});
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Edit",
|
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
|
||||||
menuItemName: MENU_SHOW_ZONES_IN_EDIT_MODE,
|
menuItemName: MENU_SHOW_ZONES_IN_EDIT_MODE,
|
||||||
afterItem: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE,
|
afterItem: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE,
|
||||||
isCheckable: true,
|
isCheckable: true,
|
||||||
isChecked: Settings.getValue(SETTING_SHOW_ZONES_IN_EDIT_MODE) !== "false"
|
isChecked: Settings.getValue(SETTING_SHOW_ZONES_IN_EDIT_MODE) !== "false"
|
||||||
});
|
});
|
||||||
|
Menu.addMenuItem({
|
||||||
|
menuName: SUBMENU_ENTITY_EDITOR_PREFERENCES,
|
||||||
|
menuItemName: MENU_ENTITY_LIST_DEFAULT_RADIUS,
|
||||||
|
afterItem: MENU_SHOW_ZONES_IN_EDIT_MODE
|
||||||
|
});
|
||||||
|
|
||||||
Entities.setLightsArePickable(false);
|
Entities.setLightsArePickable(false);
|
||||||
}
|
}
|
||||||
|
@ -1518,29 +1473,16 @@ function cleanupModelMenus() {
|
||||||
Menu.removeMenuItem("Edit", "Undo");
|
Menu.removeMenuItem("Edit", "Undo");
|
||||||
Menu.removeMenuItem("Edit", "Redo");
|
Menu.removeMenuItem("Edit", "Redo");
|
||||||
|
|
||||||
Menu.removeSeparator("Edit", "Entities");
|
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LARGE);
|
||||||
if (modelMenuAddedDelete) {
|
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_SMALL);
|
||||||
// delete our menuitems
|
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ALLOW_SELECTION_LIGHTS);
|
||||||
Menu.removeMenuItem("Edit", "Delete");
|
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_AUTO_FOCUS_ON_SELECT);
|
||||||
}
|
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_EASE_ON_FOCUS);
|
||||||
|
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE);
|
||||||
Menu.removeMenuItem("Edit", "Parent Entity to Last");
|
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_SHOW_ZONES_IN_EDIT_MODE);
|
||||||
Menu.removeMenuItem("Edit", "Unparent Entity");
|
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_CREATE_ENTITIES_GRABBABLE);
|
||||||
Menu.removeMenuItem("Edit", "Allow Selecting of Large Models");
|
Menu.removeMenuItem(SUBMENU_ENTITY_EDITOR_PREFERENCES, MENU_ENTITY_LIST_DEFAULT_RADIUS);
|
||||||
Menu.removeMenuItem("Edit", "Allow Selecting of Small Models");
|
Menu.removeMenu(SUBMENU_ENTITY_EDITOR_PREFERENCES);
|
||||||
Menu.removeMenuItem("Edit", "Allow Selecting of Lights");
|
|
||||||
Menu.removeMenuItem("Edit", "Select All Entities In Box");
|
|
||||||
Menu.removeMenuItem("Edit", "Select All Entities Touching Box");
|
|
||||||
|
|
||||||
Menu.removeMenuItem("Edit", "Export Entities");
|
|
||||||
Menu.removeMenuItem("Edit", "Import Entities");
|
|
||||||
Menu.removeMenuItem("Edit", "Import Entities from URL");
|
|
||||||
|
|
||||||
Menu.removeMenuItem("Edit", MENU_AUTO_FOCUS_ON_SELECT);
|
|
||||||
Menu.removeMenuItem("Edit", MENU_EASE_ON_FOCUS);
|
|
||||||
Menu.removeMenuItem("Edit", MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE);
|
|
||||||
Menu.removeMenuItem("Edit", MENU_SHOW_ZONES_IN_EDIT_MODE);
|
|
||||||
Menu.removeMenuItem("Edit", MENU_CREATE_ENTITIES_GRABBABLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Script.scriptEnding.connect(function () {
|
Script.scriptEnding.connect(function () {
|
||||||
|
@ -1881,48 +1823,39 @@ function onPromptTextChanged(prompt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onPromptTextChangedDefaultRadiusUserPref(prompt) {
|
||||||
|
Window.promptTextChanged.disconnect(onPromptTextChangedDefaultRadiusUserPref);
|
||||||
|
if (prompt !== "") {
|
||||||
|
var radius = parseInt(prompt);
|
||||||
|
if (radius < 0 || isNaN(radius)){
|
||||||
|
radius = 100;
|
||||||
|
}
|
||||||
|
Settings.setValue(SETTING_ENTITY_LIST_DEFAULT_RADIUS, radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleMenuEvent(menuItem) {
|
function handleMenuEvent(menuItem) {
|
||||||
if (menuItem === "Allow Selecting of Small Models") {
|
if (menuItem === MENU_ALLOW_SELECTION_SMALL) {
|
||||||
allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models");
|
allowSmallModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL);
|
||||||
} else if (menuItem === "Allow Selecting of Large Models") {
|
} else if (menuItem === MENU_ALLOW_SELECTION_LARGE) {
|
||||||
allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models");
|
allowLargeModels = Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE);
|
||||||
} else if (menuItem === "Allow Selecting of Lights") {
|
} else if (menuItem === MENU_ALLOW_SELECTION_LIGHTS) {
|
||||||
Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights"));
|
Entities.setLightsArePickable(Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS));
|
||||||
} else if (menuItem === "Delete") {
|
} else if (menuItem === "Delete") {
|
||||||
deleteSelectedEntities();
|
deleteSelectedEntities();
|
||||||
} else if (menuItem === "Undo") {
|
} else if (menuItem === "Undo") {
|
||||||
undoHistory.undo();
|
undoHistory.undo();
|
||||||
} else if (menuItem === "Redo") {
|
} else if (menuItem === "Redo") {
|
||||||
undoHistory.redo();
|
undoHistory.redo();
|
||||||
} else if (menuItem === "Parent Entity to Last") {
|
|
||||||
parentSelectedEntities();
|
|
||||||
} else if (menuItem === "Unparent Entity") {
|
|
||||||
unparentSelectedEntities();
|
|
||||||
} else if (menuItem === "Export Entities") {
|
|
||||||
if (!selectionManager.hasSelection()) {
|
|
||||||
Window.notifyEditError("No entities have been selected.");
|
|
||||||
} else {
|
|
||||||
Window.saveFileChanged.connect(onFileSaveChanged);
|
|
||||||
Window.saveAsync("Select Where to Save", "", "*.json");
|
|
||||||
}
|
|
||||||
} else if (menuItem === "Import Entities" || menuItem === "Import Entities from URL") {
|
|
||||||
if (menuItem === "Import Entities") {
|
|
||||||
Window.browseChanged.connect(onFileOpenChanged);
|
|
||||||
Window.browseAsync("Select Model to Import", "", "*.json");
|
|
||||||
} else {
|
|
||||||
Window.promptTextChanged.connect(onPromptTextChanged);
|
|
||||||
Window.promptAsync("URL of SVO to import", "");
|
|
||||||
}
|
|
||||||
} else if (menuItem === "Select All Entities In Box") {
|
|
||||||
selectAllEntitiesInCurrentSelectionBox(false);
|
|
||||||
} else if (menuItem === "Select All Entities Touching Box") {
|
|
||||||
selectAllEntitiesInCurrentSelectionBox(true);
|
|
||||||
} else if (menuItem === MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) {
|
} else if (menuItem === MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) {
|
||||||
entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE));
|
entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE));
|
||||||
} else if (menuItem === MENU_SHOW_ZONES_IN_EDIT_MODE) {
|
} else if (menuItem === MENU_SHOW_ZONES_IN_EDIT_MODE) {
|
||||||
Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
|
Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
|
||||||
} else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) {
|
} else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) {
|
||||||
Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem));
|
Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem));
|
||||||
|
} else if (menuItem === MENU_ENTITY_LIST_DEFAULT_RADIUS) {
|
||||||
|
Window.promptTextChanged.connect(onPromptTextChangedDefaultRadiusUserPref);
|
||||||
|
Window.promptAsync("Entity List Default Radius (in meters)", "" + Settings.getValue(SETTING_ENTITY_LIST_DEFAULT_RADIUS, 100));
|
||||||
}
|
}
|
||||||
tooltip.show(false);
|
tooltip.show(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -371,6 +371,16 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
||||||
SelectionManager.teleportToEntity();
|
SelectionManager.teleportToEntity();
|
||||||
} else if (data.type === 'moveEntitySelectionToAvatar') {
|
} else if (data.type === 'moveEntitySelectionToAvatar') {
|
||||||
SelectionManager.moveEntitiesSelectionToAvatar();
|
SelectionManager.moveEntitiesSelectionToAvatar();
|
||||||
|
} else if (data.type === 'loadConfigSetting') {
|
||||||
|
var columnsData = Settings.getValue(SETTING_EDITOR_COLUMNS_SETUP, "NO_DATA");
|
||||||
|
var defaultRadius = Settings.getValue(SETTING_ENTITY_LIST_DEFAULT_RADIUS, 100);
|
||||||
|
emitJSONScriptEvent({
|
||||||
|
"type": "loadedConfigSetting",
|
||||||
|
"columnsData": columnsData,
|
||||||
|
"defaultRadius": defaultRadius
|
||||||
|
});
|
||||||
|
} else if (data.type === 'saveColumnsConfigSetting') {
|
||||||
|
Settings.setValue(SETTING_EDITOR_COLUMNS_SETUP, data.columnsData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -209,13 +209,13 @@
|
||||||
</button>
|
</button>
|
||||||
<button class="menu-button" id="selectfamily" >
|
<button class="menu-button" id="selectfamily" >
|
||||||
<div class = "menu-item">
|
<div class = "menu-item">
|
||||||
<div class = "menu-item-caption">Select Family</div>
|
<div class = "menu-item-caption">Select Parent And All Its Children</div>
|
||||||
<div class = "menu-item-shortcut"></div>
|
<div class = "menu-item-shortcut"></div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button class="menu-button" id="selecttopfamily" >
|
<button class="menu-button" id="selecttopfamily" >
|
||||||
<div class = "menu-item">
|
<div class = "menu-item">
|
||||||
<div class = "menu-item-caption">Select Top Family</div>
|
<div class = "menu-item-caption">Select Top Parent And All Its Children</div>
|
||||||
<div class = "menu-item-shortcut"></div>
|
<div class = "menu-item-shortcut"></div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -20,7 +20,7 @@ const EMPTY_ENTITY_ID = "0";
|
||||||
const MAX_LENGTH_RADIUS = 9;
|
const MAX_LENGTH_RADIUS = 9;
|
||||||
const MINIMUM_COLUMN_WIDTH = 24;
|
const MINIMUM_COLUMN_WIDTH = 24;
|
||||||
const SCROLLBAR_WIDTH = 20;
|
const SCROLLBAR_WIDTH = 20;
|
||||||
const RESIZER_WIDTH = 10;
|
const RESIZER_WIDTH = 13; //Must be the number of COLUMNS - 1.
|
||||||
const DELTA_X_MOVE_COLUMNS_THRESHOLD = 2;
|
const DELTA_X_MOVE_COLUMNS_THRESHOLD = 2;
|
||||||
const DELTA_X_COLUMN_SWAP_POSITION = 5;
|
const DELTA_X_COLUMN_SWAP_POSITION = 5;
|
||||||
const CERTIFIED_PLACEHOLDER = "** Certified **";
|
const CERTIFIED_PLACEHOLDER = "** Certified **";
|
||||||
|
@ -188,6 +188,8 @@ let selectedEntities = [];
|
||||||
let entityList = null; // The ListView
|
let entityList = null; // The ListView
|
||||||
|
|
||||||
let hmdMultiSelectMode = false;
|
let hmdMultiSelectMode = false;
|
||||||
|
|
||||||
|
let lastSelectedEntity;
|
||||||
/**
|
/**
|
||||||
* @type EntityListContextMenu
|
* @type EntityListContextMenu
|
||||||
*/
|
*/
|
||||||
|
@ -283,6 +285,9 @@ const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) {
|
||||||
|
|
||||||
function loaded() {
|
function loaded() {
|
||||||
openEventBridge(function() {
|
openEventBridge(function() {
|
||||||
|
|
||||||
|
var isColumnsSettingLoaded = false;
|
||||||
|
|
||||||
elEntityTable = document.getElementById("entity-table");
|
elEntityTable = document.getElementById("entity-table");
|
||||||
elEntityTableHeader = document.getElementById("entity-table-header");
|
elEntityTableHeader = document.getElementById("entity-table-header");
|
||||||
elEntityTableBody = document.getElementById("entity-table-body");
|
elEntityTableBody = document.getElementById("entity-table-body");
|
||||||
|
@ -765,10 +770,10 @@ function loaded() {
|
||||||
let selectedIndex = selectedEntities.indexOf(entityID);
|
let selectedIndex = selectedEntities.indexOf(entityID);
|
||||||
if (selectedIndex >= 0) {
|
if (selectedIndex >= 0) {
|
||||||
selection = [];
|
selection = [];
|
||||||
selection = selection.concat(selectedEntities);
|
selection = selectedEntities.concat(selection);
|
||||||
selection.splice(selectedIndex, 1);
|
selection.splice(selectedIndex, 1);
|
||||||
} else {
|
} else {
|
||||||
selection = selection.concat(selectedEntities);
|
selection = selectedEntities.concat(selection);
|
||||||
}
|
}
|
||||||
} else if (clickEvent.shiftKey && selectedEntities.length > 0) {
|
} else if (clickEvent.shiftKey && selectedEntities.length > 0) {
|
||||||
let previousItemFound = -1;
|
let previousItemFound = -1;
|
||||||
|
@ -1044,6 +1049,8 @@ function loaded() {
|
||||||
function updateSelectedEntities(selectedIDs, autoScroll) {
|
function updateSelectedEntities(selectedIDs, autoScroll) {
|
||||||
let notFound = false;
|
let notFound = false;
|
||||||
|
|
||||||
|
lastSelectedEntity = selectedIDs[selectedIDs.length - 1];
|
||||||
|
|
||||||
// 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];
|
||||||
|
@ -1063,8 +1070,12 @@ function loaded() {
|
||||||
if (entity !== undefined) {
|
if (entity !== undefined) {
|
||||||
entity.selected = true;
|
entity.selected = true;
|
||||||
if (entity.elRow) {
|
if (entity.elRow) {
|
||||||
|
if (id === lastSelectedEntity) {
|
||||||
|
entity.elRow.className = 'last-selected';
|
||||||
|
} else {
|
||||||
entity.elRow.className = 'selected';
|
entity.elRow.className = 'selected';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
notFound = true;
|
notFound = true;
|
||||||
}
|
}
|
||||||
|
@ -1132,7 +1143,11 @@ function loaded() {
|
||||||
|
|
||||||
// if this entity was previously selected flag it's row as selected
|
// if this entity was previously selected flag it's row as selected
|
||||||
if (itemData.selected) {
|
if (itemData.selected) {
|
||||||
|
if (itemData.id === lastSelectedEntity) {
|
||||||
|
elRow.className = 'last-selected';
|
||||||
|
} else {
|
||||||
elRow.className = 'selected';
|
elRow.className = 'selected';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
elRow.className = '';
|
elRow.className = '';
|
||||||
}
|
}
|
||||||
|
@ -1409,6 +1424,10 @@ function loaded() {
|
||||||
column.elResizer.style.visibility = columnVisible && visibleColumns > 0 ? "visible" : "hidden";
|
column.elResizer.style.visibility = columnVisible && visibleColumns > 0 ? "visible" : "hidden";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isColumnsSettingLoaded) {
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'saveColumnsConfigSetting', columnsData: columns }));
|
||||||
|
}
|
||||||
|
|
||||||
entityList.refresh();
|
entityList.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1660,6 +1679,53 @@ function loaded() {
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("hmdmultiselect").style.display = "none";
|
document.getElementById("hmdmultiselect").style.display = "none";
|
||||||
}
|
}
|
||||||
|
} else if (data.type === "loadedConfigSetting") {
|
||||||
|
if (typeof(data.defaultRadius) === "number") {
|
||||||
|
elFilterRadius.value = data.defaultRadius;
|
||||||
|
onRadiusChange();
|
||||||
|
}
|
||||||
|
if (data.columnsData !== "NO_DATA" && typeof(data.columnsData) === "object") {
|
||||||
|
var isValid = true;
|
||||||
|
var originalColumnIDs = [];
|
||||||
|
for (let originalColumnID in COLUMNS) {
|
||||||
|
originalColumnIDs.push(originalColumnID);
|
||||||
|
}
|
||||||
|
for (let columnSetupIndex in data.columnsData) {
|
||||||
|
var checkPresence = originalColumnIDs.indexOf(data.columnsData[columnSetupIndex].columnID);
|
||||||
|
if (checkPresence === -1) {
|
||||||
|
isValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isValid) {
|
||||||
|
for (var columnIndex = 0; columnIndex < data.columnsData.length; columnIndex++) {
|
||||||
|
if (data.columnsData[columnIndex].data.alwaysShown !== true) {
|
||||||
|
var columnDropdownID = "entity-table-column-" + data.columnsData[columnIndex].columnID;
|
||||||
|
if (data.columnsData[columnIndex].width !== 0) {
|
||||||
|
document.getElementById(columnDropdownID).checked = false;
|
||||||
|
document.getElementById(columnDropdownID).click();
|
||||||
|
} else {
|
||||||
|
document.getElementById(columnDropdownID).checked = true;
|
||||||
|
document.getElementById(columnDropdownID).click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (columnIndex = 0; columnIndex < data.columnsData.length; columnIndex++) {
|
||||||
|
let currentColumnIndex = originalColumnIDs.indexOf(data.columnsData[columnIndex].columnID);
|
||||||
|
if (currentColumnIndex !== -1 && columnIndex !== currentColumnIndex) {
|
||||||
|
for (var i = currentColumnIndex; i > columnIndex; i--) {
|
||||||
|
swapColumns(i - 1, i);
|
||||||
|
var swappedContent = originalColumnIDs[i - 1];
|
||||||
|
originalColumnIDs[i - 1] = originalColumnIDs[i];
|
||||||
|
originalColumnIDs[i] = swappedContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'saveColumnsConfigSetting', columnsData: "" }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isColumnsSettingLoaded = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1668,6 +1734,8 @@ function loaded() {
|
||||||
refreshEntities();
|
refreshEntities();
|
||||||
|
|
||||||
window.addEventListener("resize", updateColumnWidths);
|
window.addEventListener("resize", updateColumnWidths);
|
||||||
|
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'loadConfigSetting' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
augmentSpinButtons();
|
augmentSpinButtons();
|
||||||
|
@ -1683,6 +1751,7 @@ function loaded() {
|
||||||
// close context menu when switching focus to another window
|
// close context menu when switching focus to another window
|
||||||
$(window).blur(function() {
|
$(window).blur(function() {
|
||||||
entityListContextMenu.close();
|
entityListContextMenu.close();
|
||||||
|
closeAllEntityListMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
function closeAllEntityListMenu() {
|
function closeAllEntityListMenu() {
|
||||||
|
|
|
@ -668,6 +668,7 @@ SelectionManager = (function() {
|
||||||
var newPosition = Vec3.sum(relativePosition, targetPosition);
|
var newPosition = Vec3.sum(relativePosition, targetPosition);
|
||||||
Entities.editEntity(id, { "position": newPosition });
|
Entities.editEntity(id, { "position": newPosition });
|
||||||
}
|
}
|
||||||
|
pushCommandForSelections();
|
||||||
that._update(false, this);
|
that._update(false, this);
|
||||||
} else {
|
} else {
|
||||||
audioFeedback.rejection();
|
audioFeedback.rejection();
|
||||||
|
@ -797,6 +798,7 @@ SelectionDisplay = (function() {
|
||||||
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: 160, green: 160, blue: 160 };
|
const COLOR_BOUNDING_EDGE = { red: 160, green: 160, blue: 160 };
|
||||||
const COLOR_BOUNDING_EDGE_PARENT = { red: 194, green: 123, blue: 0 };
|
const COLOR_BOUNDING_EDGE_PARENT = { red: 194, green: 123, blue: 0 };
|
||||||
|
const COLOR_BOUNDING_EDGE_PARENT_AND_CHILDREN = { red: 179, green: 0, blue: 134 };
|
||||||
const COLOR_BOUNDING_EDGE_CHILDREN = { red: 0, green: 168, blue: 214 };
|
const COLOR_BOUNDING_EDGE_CHILDREN = { red: 0, green: 168, blue: 214 };
|
||||||
const COLOR_SCALE_CUBE = { red: 192, green: 192, blue: 192 };
|
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 };
|
||||||
|
@ -1933,10 +1935,10 @@ SelectionDisplay = (function() {
|
||||||
var parentState = getParentState(SelectionManager.selections[0]);
|
var parentState = getParentState(SelectionManager.selections[0]);
|
||||||
if (parentState === "CHILDREN") {
|
if (parentState === "CHILDREN") {
|
||||||
handleBoundingBoxColor = COLOR_BOUNDING_EDGE_CHILDREN;
|
handleBoundingBoxColor = COLOR_BOUNDING_EDGE_CHILDREN;
|
||||||
} else {
|
} else if (parentState === "PARENT") {
|
||||||
if (parentState === "PARENT" || parentState === "PARENT_CHILDREN") {
|
|
||||||
handleBoundingBoxColor = COLOR_BOUNDING_EDGE_PARENT;
|
handleBoundingBoxColor = COLOR_BOUNDING_EDGE_PARENT;
|
||||||
}
|
} else if (parentState === "PARENT_CHILDREN") {
|
||||||
|
handleBoundingBoxColor = COLOR_BOUNDING_EDGE_PARENT_AND_CHILDREN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -201,11 +201,11 @@ TabBar {
|
||||||
|
|
||||||
HifiControls.Button {
|
HifiControls.Button {
|
||||||
id: importButton
|
id: importButton
|
||||||
text: "Import Entities (.json)"
|
text: "Import Entities (.json) from a File"
|
||||||
color: hifi.buttons.black
|
color: hifi.buttons.black
|
||||||
colorScheme: hifi.colorSchemes.dark
|
colorScheme: hifi.colorSchemes.dark
|
||||||
anchors.right: parent.right
|
anchors.right: parent.horizontalCenter
|
||||||
anchors.rightMargin: 30
|
anchors.rightMargin: 10
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 30
|
anchors.leftMargin: 30
|
||||||
anchors.top: assetServerButton.bottom
|
anchors.top: assetServerButton.bottom
|
||||||
|
@ -217,6 +217,25 @@ TabBar {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HifiControls.Button {
|
||||||
|
id: importButtonFromUrl
|
||||||
|
text: "Import Entities (.json) from a URL"
|
||||||
|
color: hifi.buttons.black
|
||||||
|
colorScheme: hifi.colorSchemes.dark
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 30
|
||||||
|
anchors.left: parent.horizontalCenter
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.top: assetServerButton.bottom
|
||||||
|
anchors.topMargin: 20
|
||||||
|
onClicked: {
|
||||||
|
editRoot.sendToScript({
|
||||||
|
method: "newEntityButtonClicked",
|
||||||
|
params: { buttonName: "importEntitiesFromUrlButton" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // Flickable
|
} // Flickable
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,11 +207,11 @@ TabBar {
|
||||||
|
|
||||||
HifiControls.Button {
|
HifiControls.Button {
|
||||||
id: importButton
|
id: importButton
|
||||||
text: "Import Entities (.json)"
|
text: "Import Entities (.json) from a File"
|
||||||
color: hifi.buttons.black
|
color: hifi.buttons.black
|
||||||
colorScheme: hifi.colorSchemes.dark
|
colorScheme: hifi.colorSchemes.dark
|
||||||
anchors.right: parent.right
|
anchors.right: parent.horizontalCenter
|
||||||
anchors.rightMargin: 55
|
anchors.rightMargin: 10
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 55
|
anchors.leftMargin: 55
|
||||||
anchors.top: assetServerButton.bottom
|
anchors.top: assetServerButton.bottom
|
||||||
|
@ -223,6 +223,25 @@ TabBar {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HifiControls.Button {
|
||||||
|
id: importButtonFromUrl
|
||||||
|
text: "Import Entities (.json) from a URL"
|
||||||
|
color: hifi.buttons.black
|
||||||
|
colorScheme: hifi.colorSchemes.dark
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 55
|
||||||
|
anchors.left: parent.horizontalCenter
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.top: assetServerButton.bottom
|
||||||
|
anchors.topMargin: 20
|
||||||
|
onClicked: {
|
||||||
|
editRoot.sendToScript({
|
||||||
|
method: "newEntityButtonClicked",
|
||||||
|
params: { buttonName: "importEntitiesFromUrlButton" }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // Flickable
|
} // Flickable
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,6 @@ table {
|
||||||
thead {
|
thead {
|
||||||
font-family: Raleway-Regular;
|
font-family: Raleway-Regular;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-transform: uppercase;
|
|
||||||
background-color: #1c1c1c;
|
background-color: #1c1c1c;
|
||||||
padding: 1px 0;
|
padding: 1px 0;
|
||||||
border-bottom: 1px solid #575757;
|
border-bottom: 1px solid #575757;
|
||||||
|
@ -184,6 +183,15 @@ tr.selected + tr.selected {
|
||||||
border-top: 1px solid #2e2e2e;
|
border-top: 1px solid #2e2e2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tr.last-selected {
|
||||||
|
color: #000000;
|
||||||
|
background-color: #0064ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.last-selected + tr.last-selected {
|
||||||
|
border-top: 1px solid #2e2e2e;
|
||||||
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
word-wrap: nowrap;
|
word-wrap: nowrap;
|
||||||
|
|
|
@ -30,7 +30,8 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addGotoButton("dev-mobile");
|
addGotoButton("hub.daleglass.net");
|
||||||
addGotoButton("quest-dev");
|
addGotoButton("lq-hub.vircadia.com");
|
||||||
|
addGotoButton("file:///~/serverless/tutorial.json");
|
||||||
|
|
||||||
}()); // END LOCAL_SCOPE
|
}()); // END LOCAL_SCOPE
|
||||||
|
|
Loading…
Reference in a new issue