diff --git a/.eslintrc.js b/.eslintrc.js
index 5667a04984..67921be395 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -6,79 +6,110 @@ module.exports = {
},
"globals": {
"Account": false,
+ "Agent": false,
"AnimationCache": false,
"Assets": false,
"Audio": false,
"AudioDevice": false,
"AudioEffectOptions": false,
+ "AudioScope": false,
+ "AudioStats": false,
+ "Avatar": false,
+ "AvatarBookmarks": false,
+ "AvatarInputs": false,
"AvatarList": false,
"AvatarManager": false,
"Camera": false,
"Clipboard": false,
+ "console": false,
+ "ContextOverlay": false,
"Controller": false,
- "DialogsManager": false,
"DebugDraw": false,
+ "Desktop": false,
+ "DesktopPreviewProvider": false,
+ "DialogsManager": false,
+ "document": false,
"Entities": false,
+ "EntityViewer": false,
+ "EventBridge": false,
"FaceTracker": false,
"GlobalServices": false,
+ "GooglePoly": false,
+ "Graphics": false,
"HMD": false,
+ "LaserPointers": false,
+ "location": true,
+ "LocationBookmarks": false,
"LODManager": false,
"Mat4": false,
"Menu": false,
"Messages": false,
+ "Midi": false,
"ModelCache": false,
+ "module": false,
"MyAvatar": false,
+ "OffscreenFlags": false,
"Overlays": false,
"OverlayWebWindow": false,
+ "OverlayWindow": false,
"Paths": false,
+ "Picks": false,
+ "PickType": false,
+ "PointerEvent": false,
+ "Pointers": false,
+ "print": false,
+ "QmlFragment": false,
"Quat": false,
"Rates": false,
+ "RayPick": false,
"Recording": false,
+ "Render": false,
"Resource": false,
"Reticle": false,
"Scene": false,
"Script": false,
"ScriptDiscoveryService": false,
+ "Selection": false,
"Settings": false,
+ "Snapshot": false,
"SoundCache": false,
+ "SpeechRecognizer": false,
"Stats": false,
+ "Steam": false,
"Tablet": false,
"TextureCache": false,
"Toolbars": false,
- "Uuid": false,
"UndoStack": false,
"UserActivityLogger": false,
+ "Users": false,
+ "Uuid": false,
"Vec3": false,
+ "Wallet": false,
"WebSocket": false,
"WebWindow": false,
"Window": false,
- "XMLHttpRequest": false,
- "location": false,
- "print": false,
- "RayPick": false,
- "LaserPointers": false,
- "ContextOverlay": false,
- "module": false
+ "XMLHttpRequest": false
},
"rules": {
- "brace-style": ["error", "1tbs", { "allowSingleLine": false }],
- "comma-dangle": ["error", "never"],
+ "brace-style": ["error", "1tbs", {"allowSingleLine": false}],
"camelcase": ["error"],
+ "comma-dangle": ["error", "never"],
"curly": ["error", "all"],
"eqeqeq": ["error", "always"],
- "indent": ["error", 4, { "SwitchCase": 1 }],
- "keyword-spacing": ["error", { "before": true, "after": true }],
+ "indent": ["error", 4, {"SwitchCase": 1}],
+ "key-spacing": ["error", {"beforeColon": false, "afterColon": true, "mode": "strict"}],
+ "keyword-spacing": ["error", {"before": true, "after": true}],
"max-len": ["error", 128, 4],
"new-cap": ["error"],
+ "no-console": ["off"],
"no-floating-decimal": ["error"],
- //"no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreArrayIndexes": true }],
- "no-multiple-empty-lines": ["error"],
+ "no-magic-numbers": ["error", {"ignore": [0.5, -1, 0, 1, 2], "ignoreArrayIndexes": true}],
"no-multi-spaces": ["error"],
- "no-unused-vars": ["error", { "args": "none", "vars": "local" }],
+ "no-multiple-empty-lines": ["error"],
+ "no-unused-vars": ["error", {"args": "none", "vars": "local"}],
"semi": ["error", "always"],
- "spaced-comment": ["error", "always", {
- "line": { "markers": ["/"] }
- }],
- "space-before-function-paren": ["error", {"anonymous": "ignore", "named": "never"}]
+ "space-before-blocks": ["error"],
+ "space-before-function-paren": ["error", {"anonymous": "ignore", "named": "never"}],
+ "spaced-comment": ["error", "always", {"line": {"markers": ["/"]}}]
}
};
diff --git a/.gitignore b/.gitignore
index df91e0ca7b..8d92fe770b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -91,3 +91,6 @@ interface/compiledResources
# GPUCache
interface/resources/GPUCache/*
+
+# package lock file for JSDoc tool
+tools/jsdoc/package-lock.json
diff --git a/BUILD.md b/BUILD.md
index feed677828..ba38f4b51d 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -1,7 +1,7 @@
### Dependencies
- [cmake](https://cmake.org/download/): 3.9
-- [Qt](https://www.qt.io/download-open-source): 5.9.1
+- [Qt](https://www.qt.io/download-open-source): 5.10.1
- [OpenSSL](https://www.openssl.org/): Use the latest available 1.0 version (**NOT** 1.1) of OpenSSL to avoid security vulnerabilities.
- [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
@@ -46,8 +46,8 @@ This can either be entered directly into your shell session before you build or
The path it needs to be set to will depend on where and how Qt5 was installed. e.g.
- export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/clang_64/lib/cmake/
- export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake
+ export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.10.1/clang_64/lib/cmake/
+ export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.10.1/lib/cmake
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
#### Generating build files
@@ -66,7 +66,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E
For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation:
- cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/lib/cmake
+ cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.10.1/lib/cmake
#### Finding Dependencies
diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md
index 038f53154c..0daef5ae05 100644
--- a/BUILD_LINUX.md
+++ b/BUILD_LINUX.md
@@ -11,11 +11,11 @@ Should you choose not to install Qt5 via a package manager that handles dependen
## Ubuntu 16.04 specific build guide
### Prepare environment
-
+hifiqt5.10.1
Install qt:
```bash
-wget http://debian.highfidelity.com/pool/h/hi/hifi-qt5.6.1_5.6.1_amd64.deb
-sudo dpkg -i hifi-qt5.6.1_5.6.1_amd64.deb
+wget http://debian.highfidelity.com/pool/h/hi/hifiqt5.10.1_5.10.1_amd64.deb
+sudo dpkg -i hifiqt5.10.1_5.10.1_amd64.deb
```
Install build dependencies:
@@ -66,7 +66,7 @@ cd hifi/build
Prepare makefiles:
```bash
-cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.6.1/5.6/gcc_64/lib/cmake ..
+cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10/gcc_64/lib/cmake ..
```
Start compilation and get a cup of coffee:
@@ -74,7 +74,7 @@ Start compilation and get a cup of coffee:
make domain-server assignment-client interface
```
-In a server does not make sense to compile interface
+In a server does not make sense to compile interface
### Running the software
diff --git a/BUILD_OSX.md b/BUILD_OSX.md
index 6b66863534..62102b3e18 100644
--- a/BUILD_OSX.md
+++ b/BUILD_OSX.md
@@ -20,7 +20,7 @@ Note that this uses the version from the homebrew formula at the time of this wr
Assuming you've installed Qt using the homebrew instructions above, you'll need to set QT_CMAKE_PREFIX_PATH so CMake can find your installations.
For Qt installed via homebrew, set QT_CMAKE_PREFIX_PATH:
- export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.9.1/lib/cmake
+ export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.10.1/lib/cmake
Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change.
diff --git a/BUILD_WIN.md b/BUILD_WIN.md
index eea1f85e5b..5836d5bfb5 100644
--- a/BUILD_WIN.md
+++ b/BUILD_WIN.md
@@ -1,31 +1,33 @@
This is a stand-alone guide for creating your first High Fidelity build for Windows 64-bit.
## Building High Fidelity
-Note: We are now using Visual Studio 2017 and Qt 5.9.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide.
+Note: We are now using Visual Studio 2017 and Qt 5.10.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide.
Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory.
### Step 1. Visual Studio 2017
-If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/).
+If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/).
-When selecting components, check "Desktop development with C++." Also check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)" on the Summary toolbar on the right.
+When selecting components, check "Desktop development with C++." Also on the right on the Summary toolbar, check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)".
### Step 2. Installing CMake
-Download and install the latest version of CMake 3.9. Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). Make sure to check "Add CMake to system PATH for all users" when prompted during installation.
+Download and install the latest version of CMake 3.9.
+
+Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.9 Version page](https://cmake.org/files/v3.9/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted.
### Step 3. Installing Qt
-Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.9.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)".
+Download and install the [Qt Open Source Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.10.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)".
-Note: Installing the Sources is optional but recommended if you have room for them (~2GB).
+Note: Installing the Sources is optional but recommended if you have room for them (~2GB).
### Step 4. Setting Qt Environment Variable
Go to `Control Panel > System > Advanced System Settings > Environment Variables > New...` (or search “Environment Variables” in Start Search).
* Set "Variable name": `QT_CMAKE_PREFIX_PATH`
-* Set "Variable value": `C:\Qt\5.9.1\msvc2017_64\lib\cmake`
+* Set "Variable value": `C:\Qt\5.10.1\msvc2017_64\lib\cmake`
### Step 5. Installing [vcpkg](https://github.com/Microsoft/vcpkg)
@@ -39,7 +41,7 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables
* In the vcpkg directory, install the 64 bit OpenSSL package with the command `vcpkg install openssl:x64-windows`
* Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl`
-
+
### Step 7. Running CMake to Generate Build Files
Run Command Prompt from Start and run the following commands:
@@ -49,16 +51,16 @@ mkdir build
cd build
cmake .. -G "Visual Studio 15 Win64"
```
-
+
Where `%HIFI_DIR%` is the directory for the highfidelity repository.
### Step 8. Making a Build
Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
-Change the Solution Configuration (next to the green play button) from "Debug" to "Release" for best performance.
+Change the Solution Configuration (menu ribbon under the menu bar, next to the green play button) from "Debug" to "Release" for best performance.
-Run `Build > Build Solution`.
+Run from the menu bar `Build > Build Solution`.
### Step 9. Testing Interface
@@ -66,7 +68,7 @@ Create another environment variable (see Step #4)
* Set "Variable name": `_NO_DEBUG_HEAP`
* Set "Variable value": `1`
-In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run `Debug > Start Debugging`.
+In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run from the menu bar `Debug > Start Debugging`.
Now, you should have a full build of High Fidelity and be able to run the Interface using Visual Studio. Please check our [Docs](https://wiki.highfidelity.com/wiki/Main_Page) for more information regarding the programming workflow.
@@ -74,10 +76,10 @@ Note: You can also run Interface by launching it from command line or File Explo
## Troubleshooting
-For any problems after Step #7, first try this:
+For any problems after Step #7, first try this:
* Delete your locally cloned copy of the highfidelity repository
* Restart your computer
-* Redownload the [repository](https://github.com/highfidelity/hifi)
+* Redownload the [repository](https://github.com/highfidelity/hifi)
* Restart directions from Step #7
#### CMake gives you the same error message repeatedly after the build fails
@@ -90,4 +92,4 @@ Remove `CMakeCache.txt` found in the `%HIFI_DIR%\build` directory. Verify that
#### Qt is throwing an error
-Make sure you have the correct version (5.9.1) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly.
+Make sure you have the correct version (5.10.1) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 93b784b462..54505717d4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -179,7 +179,12 @@ endif()
add_subdirectory(tools)
if (BUILD_TESTS)
+ # Turn on testing so that add_test works
+ # MUST be in the root cmake file for ctest to work
+ include(CTest)
+ enable_testing()
add_subdirectory(tests)
+ add_subdirectory(tests-manual)
endif()
if (BUILD_INSTALLER)
diff --git a/INSTALL.md b/INSTALL.md
index e07d28a43d..00be5f2f8f 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -19,6 +19,9 @@ To produce an executable installer on Windows, the following are required:
- [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6
- [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
+- [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7
+- [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in)
+- [ApplicationID plug-in for Nullsoft](http://nsis.sourceforge.net/ApplicationID_plug-in) - 1.0
Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 97267258e2..f780abdea0 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -4,12 +4,15 @@ android {
compileSdkVersion 26
//buildToolsVersion '27.0.3'
+ def appVersionCode = Integer.valueOf(RELEASE_NUMBER ?: 1)
+ def appVersionName = RELEASE_NUMBER ?: "1.0"
+
defaultConfig {
applicationId "io.highfidelity.hifiinterface"
minSdkVersion 24
targetSdkVersion 26
- versionCode 1
- versionName "1.0"
+ versionCode appVersionCode
+ versionName appVersionName
ndk { abiFilters 'arm64-v8a' }
externalNativeBuild {
cmake {
@@ -22,11 +25,19 @@ android {
'-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED,
'-DRELEASE_NUMBER=' + RELEASE_NUMBER,
'-DRELEASE_TYPE=' + RELEASE_TYPE,
- '-DBUILD_BRANCH=' + BUILD_BRANCH,
+ '-DSTABLE_BUILD=' + STABLE_BUILD,
'-DDISABLE_QML=OFF',
'-DDISABLE_KTX_CACHE=OFF'
}
}
+ signingConfigs {
+ release {
+ storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null
+ storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : ''
+ keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : ''
+ keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : ''
+ }
+ }
}
compileOptions {
@@ -38,6 +49,10 @@ android {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ signingConfig project.hasProperty("HIFI_ANDROID_KEYSTORE") &&
+ project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") &&
+ project.hasProperty("HIFI_ANDROID_KEY_ALIAS") &&
+ project.hasProperty("HIFI_ANDROID_KEY_PASSWORD")? signingConfigs.release : null
}
}
@@ -98,6 +113,21 @@ android {
dependencies {
implementation 'com.google.vr:sdk-audio:1.80.0'
implementation 'com.google.vr:sdk-base:1.80.0'
+
+
+ implementation 'com.android.support.constraint:constraint-layout:1.0.2'
+ implementation 'com.android.support:design:26.1.0'
+ implementation 'com.android.support:appcompat-v7:26.1.0'
+ compile 'com.android.support:recyclerview-v7:26.1.0'
+ compile 'com.android.support:cardview-v7:26.1.0'
+
+ compile 'com.squareup.retrofit2:retrofit:2.4.0'
+ compile 'com.squareup.retrofit2:converter-gson:2.4.0'
+ implementation 'com.squareup.picasso:picasso:2.71828'
+
+ compile 'com.squareup.retrofit2:retrofit:2.4.0'
+ compile 'com.squareup.retrofit2:converter-gson:2.4.0'
+ implementation 'com.squareup.picasso:picasso:2.71828'
+
implementation fileTree(include: ['*.jar'], dir: 'libs')
}
-
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index b3a8c87649..0b52046057 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -38,19 +38,17 @@
-->
+
+
-
-
-
-
-
-
@@ -60,6 +58,15 @@
+
+
+
+
diff --git a/android/app/src/main/assets/privacy_policy.html b/android/app/src/main/assets/privacy_policy.html
new file mode 100644
index 0000000000..f6cdbfcfc7
--- /dev/null
+++ b/android/app/src/main/assets/privacy_policy.html
@@ -0,0 +1,121 @@
+
+
+High Fidelity Privacy Policy
+
+
+
High Fidelity, Inc. ("High Fidelity") respects the privacy of its online visitors and users of its products and services. We recognize the importance of protecting information collected from our users and have adopted this Privacy Policy to inform you about how we gather, store and use information derived from your use of our products, services and online sites in accordance with local law in the places where we operate.
+By using our online sites, products, and services (collectively, the "Service"), you agree that we may collect personally identifiable information (as defined below). We will not share your personally identifiable information except as described herein.
+
+1. Types of Information We Collect
+
+ We collect two basic types of information - personal information and anonymous information - and we may use personal and anonymous information to create a third type of information, aggregate information. Personal Information means information that identifies (whether directly or indirectly) a particular individual, such as the individual's first and last name, postal address, e-mail address and/or telephone number. Anonymous Information means information that does not directly or indirectly identify, and cannot reasonably be used to identify, an individual (including an individual's computing device). Aggregate Information means information about groups or categories of individuals which does not identify and cannot reasonably be used to identify an individual. We may share Aggregate and Anonymous Information with other parties without restriction.
+
+ We collect the following categories of information:
+
+ Registration information you provide when you create an account, which may include your first name and surname, country of residence, gender, date of birth, e-mail address, username and password.
+
+ Transaction information you provide when you request information or purchase a product or service from us, whether on our sites or through our applications, including your postal address, telephone number and payment information. If you conduct transactions, we may collect and retain some or all of the information related to these transactions, including transaction amount(s), parties involved, time and manner of exchange, and other transaction circumstances.
+
+ Information you provide in public forums on our Service. Please note that our sites and applications may offer chat, forums, community environments (including multiplayer gameplay) or other tools that do not have a restricted audience. If you provide Personal Information when you use any of these features, that Personal Information may be publicly posted and otherwise disclosed without limitation as to its use by us or by a third party. We have no obligation to keep private personally identifiable information that you have made available to other users or the public using these functions. To request removal of your Personal Information from a public forum on one of our sites or applications, please contact Customer Support.
+
+ Information sent either one-to-one or within a limited group using our message, chat, post or similar functionality, where we are permitted by law to collect this information.
+
+ Information you provide to us when you use our sites and applications, our applications on third-party sites or platforms (such as social networking sites), or link your profile from a third-party site or platform to your registered account.
+
+ Location information when you visit our sites or use our applications, including location information either provided by a mobile device interacting with one of our sites or applications, or associated with your IP address, where we are permitted by law to process this information.
+
+ Usage, viewing and technical data, including your device identifier or IP address, when you visit our sites, use our applications on third-party sites or platforms, or open e-mails that we send.
+
+ Additionally there are a few special circumstances to note:
+
+ Intellectual Property Claim Notices: If you notify us of an intellectual property claim, the information in your claim notice may be shared with other parties to the disagreement or third parties in our discretion and as required by law.
+
+ Beta Service User: If you volunteer to serve as a beta participant for our pre-commercial content, we may track bug reports and individual system performance in an effort to test our technology rigorously before it is deployed.
+
+ Former Customer: If you discontinue your use of our Service, we may keep your registration file in our database for use in the event that you elect to renew your use of our Service, as well as for anti-fraud and other such protective measures.
+
+ Job Postings or Unsolicited Communications: Please note that information we receive in reference to a job posting or by unsolicited communications does not fall within the terms outlined in this Privacy Policy, however information from your resume will be used solely for the purpose of evaluating your candidacy for employment.
+
+ 2. How We Collect Your Information
+
+
+ We collect information you provide to us when you request products, services or information from us, register with us, participate in public forums or other activities on our sites and applications, respond to customer inquiries or surveys, or otherwise interact with us.
+
+ We collect information through technology, such as cookies and other technologies (such as web beacons and pixel tags), including when you visit our sites and applications or use our applications on third-party sites or platforms. A cookie is a small string of data which often includes an anonymous unique identifier sent to your Internet browser from a website's computers, which is stored on your computer's hard drive and is used to customize your use of a product or online site, keep records of your access to an online site or product, or store information needed by you on a regular basis (e.g. password retention functionality). High Fidelity (itself or through third parties acting on our behalf) use cookies for a number of purposes relating to our websites, applications and services, including to access your account information where you "login" to our websites, forums or other areas and to keep track of your website session data. You can configure your browser to accept all cookies, reject all cookies, or notify you when a cookie is set. Each browser is different, so consult the "Help" menu of your browser to learn how you change your cookie preferences. Please note that if you reject all cookies, you may not be able to use certain of our (or other companies') web pages.
+
+ We may participate in ad and/or affiliate networks operated by various third party companies. These companies collect and may use certain anonymous information about your visits to our Service as a function of referring Internet traffic to our Service. We do not permit these companies to collect any Personal Information about you; however these companies may collect your IP address. These companies may set and use cookies, web beacons, pixels and other technologies to collect anonymous information about your visits to our Service, and may otherwise aggregate, analyze and anonymize that data. If you would like to learn more about these specialized advertising technologies, the Network Advertising Initiative offers useful information about Internet advertising companies, including information about how to opt-out of certain information collection.
+
+ We acquire information from other trusted sources to update or supplement the information you provided or we collected automatically. Local law may require that you authorize the third party to share your information with us before we can acquire it.
+
+ 3. Use of Your Information by High Fidelity
+
+ High Fidelity will be the data controller for your information, and will have access to your information for use for the following purposes (unless prohibited by law):
+
+ Provide you with the products and services you request
+
+ Communicate with you about your account or transactions with us and send you information about features on our sites and applications or changes to our policies
+
+ Consistent with local law and choices and controls that may be available to you:
+
+ Send you offers and promotions for our products and services or, as permitted, third-party products and services
+
+ Personalize content and experiences on our sites and applications
+
+ Provide you with advertising based on your activity on our sites and applications and on third-party sites and applications.
+ Optimize or improve our products, services and operations
+
+ Detect, investigate and prevent activities that may violate our policies or be illegal
+
+ Except under certain limited circumstances as set forth here and in our Terms of Service, High Fidelity does not disclose to third parties the Personal Information or other account-related information that you provide to us without your permission. You understand, however, that High Fidelity may disclose your Personal Information or other account-related information under the following circumstances:
+
+ If we believe in good faith that such disclosure is necessary under applicable law, or to comply with legal process served on High Fidelity;
+
+ In order to protect and defend the rights or interests of High Fidelity, its products and services, and/or the other users of such products and services;
+
+ In order to report to law enforcement authorities, or assist in their investigation of suspected illegal or wrongful activity, or to report any instance in which we believe a person may be in danger;
+
+ To service providers with whom we have contracted to assist us with the features or operations (such as anti-fraud functions, billing, collections, registration, customer support, e-mail delivery, age verification), to fulfill your service requests, offer new content or help us improve our products and/or services.
+ Our contracts with third parties prohibit them from using any of your Personal Information for purposes unrelated to the product or services they are providing;
+
+ To other third parties (a) to provide you with services you have requested, (b) to offer you information about our products or services (e.g. events or features), or (c) to whom you explicitly ask us to send your information (or about whom you are otherwise explicitly notified and consent to when using a specific service). For instance, we may provide certain information to our payment processor, to credit card associations, banks or issuers (if you are using a credit card), to PayPal (if you are using a PayPal account), or to providers of other services you request. If you choose to use these third parties' products or services, then their use of your information is governed by their privacy policies. You should evaluate the practices of third party providers before deciding to use their services; and
+
+ To other business entities, should we plan to merge with or be acquired by that business entity.
+
+
+ 4. Sharing Your Information with Other Companies
+
+
+ We will not share your Personal Information outside of High Fidelity except in limited circumstances, including:
+
+ When you allow us to share your Personal Information with another company, such as:
+
+ Directing us to share your Personal Information with third-party sites or platforms, such as social networking sites
+ Please note that once we share your Personal Information with another company, the information received by the other company becomes subject to the other company's privacy practices.
+
+ When companies perform services on our behalf; however, these companies are prohibited from using your Personal Information for purposes other than those requested by us or required by law.
+
+ When we share Personal Information with third parties in connection with the sale of a business, to enforce our Terms of Service or rules, to ensure the safety and security of our users and third parties, to comply with legal process or in other cases if we believe in good faith that disclosure is required by law.
+
+ 5. Data Transfers, Storage and Processing Globally
+
+
+ We operate globally and may transfer your Personal Information to locations around the world for the purposes described in this Privacy Policy. Whenever your Personal Information is transferred, stored or processed by us, we will take reasonable steps to safeguard the privacy of your Personal Information in accordance with applicable law.
+
+ 6. Changes to this Privacy Policy
+
+
+ From time to time, we may change this Privacy Policy to accommodate new technologies, industry practices, regulatory requirements or for other purposes. If we decide to change our privacy policy, we will post those changes to this privacy statement, and other places we deem appropriate so that you are aware of what information we collect, how we use it, and under what circumstances, if any, we disclose it. We reserve the right to modify this privacy statement at any time, so please review it frequently. If we make material changes to this policy, we will notify you here, by email, or by means of a notice on our home page.
+
+
+ 7. Comments and Questions
+
+
+ If you have a comment or question about this Privacy Policy or our privacy practices, please send an e-mail to privacy@highfidelity.com.
+
+
+Notice to California Residents:
+
+If you are a California resident, California Civil Code Section 1798.83 permits you to request information regarding the disclosure of your Personal Information by High Fidelity to third parties for the third parties' direct marketing purposes. With respect to these entities, this Privacy Policy applies only to their activities within the State of California. To make such a request, please send an e-mail to privacy@highfidelity.com or write us at the address listed immediately above.
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp
index 13daf4c471..437505be3f 100644
--- a/android/app/src/main/cpp/native.cpp
+++ b/android/app/src/main/cpp/native.cpp
@@ -17,7 +17,17 @@
#include
#include
+#include
+#include
+#include "AndroidHelper.h"
+#include
+#include
+
+QAndroidJniObject __interfaceActivity;
+QAndroidJniObject __loginCompletedListener;
+QAndroidJniObject __loadCompleteListener;
+QAndroidJniObject __usernameChangedListener;
void tempMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (!message.isEmpty()) {
const char * local=message.toStdString().c_str();
@@ -136,25 +146,153 @@ void unpackAndroidAssets() {
extern "C" {
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) {
- qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
g_assetManager = AAssetManager_fromJava(env, asset_mgr);
+ qRegisterMetaType("QAndroidJniObject");
+ __interfaceActivity = QAndroidJniObject(instance);
auto oldMessageHandler = qInstallMessageHandler(tempMessageHandler);
unpackAndroidAssets();
qInstallMessageHandler(oldMessageHandler);
+
+ QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [](const QString& a, const bool backToScene) {
+ QAndroidJniObject string = QAndroidJniObject::fromString(a);
+ jboolean jBackToScene = (jboolean) backToScene;
+ __interfaceActivity.callMethod("openAndroidActivity", "(Ljava/lang/String;Z)V", string.object(), jBackToScene);
+ });
+
+ QObject::connect(&AndroidHelper::instance(), &AndroidHelper::hapticFeedbackRequested, [](int duration) {
+ jint iDuration = (jint) duration;
+ __interfaceActivity.callMethod("performHapticFeedback", "(I)V", iDuration);
+ });
+}
+
+JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnDestroy(JNIEnv* env, jobject obj) {
+ QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested,
+ nullptr, nullptr);
+
+}
+
+JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUrl(JNIEnv* env, jobject obj, jstring url) {
+ QAndroidJniObject jniUrl("java/lang/String", "(Ljava/lang/String;)V", url);
+ DependencyManager::get()->loadSettings(jniUrl.toString());
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) {
- qDebug() << "nativeOnPause";
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnResume(JNIEnv* env, jobject obj) {
- qDebug() << "nativeOnResume";
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnExitVr(JNIEnv* env, jobject obj) {
- qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
+}
+
+// HifiUtils
+JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_getCurrentAddress(JNIEnv *env, jobject instance) {
+ QSharedPointer addressManager = DependencyManager::get();
+ if (!addressManager) {
+ return env->NewString(nullptr, 0);
+ }
+
+ QString str;
+ if (!addressManager->getPlaceName().isEmpty()) {
+ str = addressManager->getPlaceName();
+ } else if (!addressManager->getHost().isEmpty()) {
+ str = addressManager->getHost();
+ }
+
+ QRegExp pathRegEx("(\\/[^\\/]+)");
+ if (!addressManager->currentPath().isEmpty() && addressManager->currentPath().contains(pathRegEx) && pathRegEx.matchedLength() > 0) {
+ str += pathRegEx.cap(0);
+ }
+
+ return env->NewStringUTF(str.toLatin1().data());
+}
+
+JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_protocolVersionSignature(JNIEnv *env, jobject instance) {
+ return env->NewStringUTF(protocolVersionsSignatureBase64().toLatin1().data());
+}
+
+JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_fragment_HomeFragment_nativeGetLastLocation(JNIEnv *env, jobject instance) {
+ Setting::Handle currentAddressHandle(QStringList() << "AddressManager" << "address", QString());
+ QUrl lastLocation = currentAddressHandle.get();
+ return env->NewStringUTF(lastLocation.toString().toLatin1().data());
+}
+
+JNIEXPORT void JNICALL
+Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *env, jobject instance,
+ jstring username_, jstring password_,
+ jobject usernameChangedListener) {
+ const char *c_username = env->GetStringUTFChars(username_, 0);
+ const char *c_password = env->GetStringUTFChars(password_, 0);
+ QString username = QString(c_username);
+ QString password = QString(c_password);
+ env->ReleaseStringUTFChars(username_, c_username);
+ env->ReleaseStringUTFChars(password_, c_password);
+
+ auto accountManager = AndroidHelper::instance().getAccountManager();
+
+ __loginCompletedListener = QAndroidJniObject(instance);
+ __usernameChangedListener = QAndroidJniObject(usernameChangedListener);
+
+ QObject::connect(accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
+ jboolean jSuccess = (jboolean) true;
+ __loginCompletedListener.callMethod("handleLoginCompleted", "(Z)V", jSuccess);
+ });
+
+ QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() {
+ jboolean jSuccess = (jboolean) false;
+ __loginCompletedListener.callMethod("handleLoginCompleted", "(Z)V", jSuccess);
+ });
+
+ QObject::connect(accountManager.data(), &AccountManager::usernameChanged, [](const QString& username) {
+ QAndroidJniObject string = QAndroidJniObject::fromString(username);
+ __usernameChangedListener.callMethod("handleUsernameChanged", "(Ljava/lang/String;)V", string.object());
+ });
+
+ QMetaObject::invokeMethod(accountManager.data(), "requestAccessToken",
+ Q_ARG(const QString&, username), Q_ARG(const QString&, password));
+}
+
+JNIEXPORT void JNICALL
+Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(JNIEnv *env,
+ jobject instance) {
+
+ __loadCompleteListener = QAndroidJniObject(instance);
+
+ QObject::connect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, []() {
+
+ __loadCompleteListener.callMethod("onAppLoadedComplete", "()V");
+ __interfaceActivity.callMethod("onAppLoadedComplete", "()V");
+
+ QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, nullptr,
+ nullptr);
+ });
+
+}
+JNIEXPORT jboolean JNICALL
+Java_io_highfidelity_hifiinterface_MainActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
+ return AndroidHelper::instance().getAccountManager()->isLoggedIn();
+}
+
+JNIEXPORT void JNICALL
+Java_io_highfidelity_hifiinterface_MainActivity_nativeLogout(JNIEnv *env, jobject instance) {
+ AndroidHelper::instance().getAccountManager()->logout();
+}
+
+JNIEXPORT jstring JNICALL
+Java_io_highfidelity_hifiinterface_MainActivity_nativeGetDisplayName(JNIEnv *env,
+ jobject instance) {
+ QString username = AndroidHelper::instance().getAccountManager()->getAccountInfo().getUsername();
+ return env->NewStringUTF(username.toLatin1().data());
+}
+
+JNIEXPORT void JNICALL
+Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) {
+ AndroidHelper::instance().notifyEnterBackground();
+}
+
+JNIEXPORT void JNICALL
+Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEnv *env, jobject obj) {
+ AndroidHelper::instance().notifyEnterForeground();
}
}
-
-
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java
new file mode 100644
index 0000000000..f92cd0a385
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java
@@ -0,0 +1,67 @@
+package io.highfidelity.hifiinterface;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * Created by Gabriel Calero & Cristian Duarte on 4/13/18.
+ */
+
+public class HifiUtils {
+
+ public static final String METAVERSE_BASE_URL = "https://metaverse.highfidelity.com";
+
+ private static HifiUtils instance;
+
+ private HifiUtils() {
+ }
+
+ public static HifiUtils getInstance() {
+ if (instance == null) {
+ instance = new HifiUtils();
+ }
+ return instance;
+ }
+
+ public String sanitizeHifiUrl(String urlString) {
+ urlString = urlString.trim();
+ if (!urlString.isEmpty()) {
+ URI uri;
+ try {
+ uri = new URI(urlString);
+ } catch (URISyntaxException e) {
+ return urlString;
+ }
+ if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
+ urlString = "hifi://" + urlString;
+ }
+ }
+ return urlString;
+ }
+
+
+ public String absoluteHifiAssetUrl(String urlString) {
+ return absoluteHifiAssetUrl(urlString, METAVERSE_BASE_URL);
+ }
+
+ public String absoluteHifiAssetUrl(String urlString, String baseUrl) {
+ urlString = urlString.trim();
+ if (!urlString.isEmpty()) {
+ URI uri;
+ try {
+ uri = new URI(urlString);
+ } catch (URISyntaxException e) {
+ return urlString;
+ }
+ if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
+ urlString = baseUrl + urlString;
+ }
+ }
+ return urlString;
+ }
+
+ public native String getCurrentAddress();
+
+ public native String protocolVersionSignature();
+
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
index de3bcee88d..28acc77609 100644
--- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
@@ -14,9 +14,16 @@ package io.highfidelity.hifiinterface;
import android.content.Intent;
import android.content.res.AssetManager;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Vibrator;
+import android.view.HapticFeedbackConstants;
import android.view.WindowManager;
import android.util.Log;
+
+import org.qtproject.qt5.android.QtLayout;
+import org.qtproject.qt5.android.QtSurface;
import org.qtproject.qt5.android.bindings.QtActivity;
/*import com.google.vr.cardboard.DisplaySynchronizer;
@@ -28,17 +35,22 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.view.View;
+import android.widget.FrameLayout;
+
+import java.lang.reflect.Field;
public class InterfaceActivity extends QtActivity {
+ public static final String DOMAIN_URL = "url";
+ private static final String TAG = "Interface";
+ private Vibrator mVibrator;
+
//public static native void handleHifiURL(String hifiURLString);
private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
- //private native void nativeOnPause();
- //private native void nativeOnResume();
- //private native void nativeOnStop();
- //private native void nativeOnStart();
- //private native void saveRealScreenSize(int width, int height);
- //private native void setAppVersion(String version);
+ private native void nativeOnDestroy();
+ private native void nativeGotoUrl(String url);
+ private native void nativeEnterBackground();
+ private native void nativeEnterForeground();
private native long nativeOnExitVr();
private AssetManager assetManager;
@@ -57,11 +69,14 @@ public class InterfaceActivity extends QtActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
+ super.isLoading = true;
+ Intent intent = getIntent();
+ if (intent.hasExtra(DOMAIN_URL) && !intent.getStringExtra(DOMAIN_URL).isEmpty()) {
+ intent.putExtra("applicationArguments", "--url " + intent.getStringExtra(DOMAIN_URL));
+ }
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
- // Get the intent that started this activity in case we have a hifi:// URL to parse
- Intent intent = getIntent();
+
if (intent.getAction() == Intent.ACTION_VIEW) {
Uri data = intent.getData();
@@ -82,7 +97,6 @@ public class InterfaceActivity extends QtActivity {
Point size = new Point();
getWindowManager().getDefaultDisplay().getRealSize(size);
-// saveRealScreenSize(size.x, size.y);
try {
PackageInfo pInfo = this.getPackageManager().getPackageInfo(getPackageName(), 0);
@@ -95,44 +109,48 @@ public class InterfaceActivity extends QtActivity {
final View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
// This is a workaround to hide the menu bar when the virtual keyboard is shown from Qt
- rootView.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- int heightDiff = rootView.getRootView().getHeight() - rootView.getHeight();
- if (getActionBar().isShowing()) {
- getActionBar().hide();
- }
+ rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ if (getActionBar() != null && getActionBar().isShowing()) {
+ getActionBar().hide();
}
});
+ startActivity(new Intent(this, SplashActivity.class));
+ mVibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE);
}
@Override
protected void onPause() {
super.onPause();
- //nativeOnPause();
+ nativeEnterBackground();
//gvrApi.pauseTracking();
}
@Override
protected void onStart() {
super.onStart();
-// nativeOnStart();
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
@Override
protected void onStop() {
- Log.d("[Background]","Calling nativeOnStop from InterfaceActivity");
-// nativeOnStop();
super.onStop();
+
}
@Override
protected void onResume() {
super.onResume();
- //nativeOnResume();
+ nativeEnterForeground();
+ surfacesWorkaround();
//gvrApi.resumeTracking();
}
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ nativeOnDestroy();
+ }
+
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -148,6 +166,41 @@ public class InterfaceActivity extends QtActivity {
Log.w("[VR]", "Portrait detected but not in VR mode. Should not happen");
}
}
+ surfacesWorkaround();
+ }
+
+ private void surfacesWorkaround() {
+ FrameLayout fl = findViewById(android.R.id.content);
+ if (fl.getChildCount() > 0) {
+ QtLayout qtLayout = (QtLayout) fl.getChildAt(0);
+ if (qtLayout.getChildCount() > 1) {
+ QtSurface s1 = (QtSurface) qtLayout.getChildAt(0);
+ QtSurface s2 = (QtSurface) qtLayout.getChildAt(1);
+ Integer subLayer1 = 0;
+ Integer subLayer2 = 0;
+ try {
+ String field;
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ field = "mSubLayer";
+ } else {
+ field = "mWindowType";
+ }
+ Field f = s1.getClass().getSuperclass().getDeclaredField(field);
+ f.setAccessible(true);
+ subLayer1 = (Integer) f.get(s1);
+ subLayer2 = (Integer) f.get(s2);
+ if (subLayer1 < subLayer2) {
+ s1.setVisibility(View.VISIBLE);
+ s2.setVisibility(View.INVISIBLE);
+ } else {
+ s1.setVisibility(View.INVISIBLE);
+ s2.setVisibility(View.VISIBLE);
+ }
+ } catch (ReflectiveOperationException e) {
+ Log.e(TAG, "Workaround failed");
+ }
+ }
+ }
}
public void openUrlInAndroidWebView(String urlString) {
@@ -175,4 +228,44 @@ public class InterfaceActivity extends QtActivity {
}
}
-}
\ No newline at end of file
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ if (intent.hasExtra(DOMAIN_URL)) {
+ nativeGotoUrl(intent.getStringExtra(DOMAIN_URL));
+ }
+ }
+
+ public void openAndroidActivity(String activityName, boolean backToScene) {
+ switch (activityName) {
+ case "Home":
+ case "Privacy Policy":
+ case "Login": {
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.putExtra(MainActivity.EXTRA_FRAGMENT, activityName);
+ intent.putExtra(MainActivity.EXTRA_BACK_TO_SCENE, backToScene);
+ startActivity(intent);
+ break;
+ }
+ default: {
+ Log.w(TAG, "Could not open activity by name " + activityName);
+ break;
+ }
+ }
+ }
+
+ public void onAppLoadedComplete() {
+ super.isLoading = false;
+ }
+
+ public void performHapticFeedback(int duration) {
+ if (duration > 0) {
+ mVibrator.vibrate(duration);
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ openAndroidActivity("Home", false);
+ }
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
new file mode 100644
index 0000000000..54161f60c6
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
@@ -0,0 +1,310 @@
+package io.highfidelity.hifiinterface;
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.NavigationView;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
+import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.squareup.picasso.Callback;
+import com.squareup.picasso.Picasso;
+
+import io.highfidelity.hifiinterface.fragment.HomeFragment;
+import io.highfidelity.hifiinterface.fragment.LoginFragment;
+import io.highfidelity.hifiinterface.fragment.PolicyFragment;
+import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
+
+public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
+ LoginFragment.OnLoginInteractionListener,
+ HomeFragment.OnHomeInteractionListener {
+
+ private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
+ public static final String DEFAULT_FRAGMENT = "Home";
+ public static final String EXTRA_FRAGMENT = "fragment";
+ public static final String EXTRA_BACK_TO_SCENE = "backToScene";
+
+ private String TAG = "HighFidelity";
+
+ public native boolean nativeIsLoggedIn();
+ public native void nativeLogout();
+ public native String nativeGetDisplayName();
+
+ private DrawerLayout mDrawerLayout;
+ private NavigationView mNavigationView;
+ private ImageView mProfilePicture;
+ private TextView mDisplayName;
+ private View mLoginPanel;
+ private View mProfilePanel;
+ private TextView mLogoutOption;
+
+ private boolean backToScene;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ mNavigationView = findViewById(R.id.nav_view);
+ mNavigationView.setNavigationItemSelectedListener(this);
+
+ mLoginPanel = mNavigationView.getHeaderView(0).findViewById(R.id.loginPanel);
+ mProfilePanel = mNavigationView.getHeaderView(0).findViewById(R.id.profilePanel);
+
+ mLogoutOption = mNavigationView.findViewById(R.id.logout);
+
+ mDisplayName = mNavigationView.getHeaderView(0).findViewById(R.id.displayName);
+ mProfilePicture = mNavigationView.getHeaderView(0).findViewById(R.id.profilePicture);
+
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle);
+ setSupportActionBar(toolbar);
+
+ ActionBar actionbar = getSupportActionBar();
+ actionbar.setDisplayHomeAsUpEnabled(true);
+ actionbar.setHomeAsUpIndicator(R.drawable.ic_menu);
+
+ mDrawerLayout = findViewById(R.id.drawer_layout);
+
+ Window window = getWindow();
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ window.setStatusBarColor(ContextCompat.getColor(this, R.color.statusbar_color));
+
+ if (getIntent() != null) {
+ if (getIntent().hasExtra(EXTRA_FRAGMENT)) {
+ loadFragment(getIntent().getStringExtra(EXTRA_FRAGMENT));
+ } else {
+ loadFragment(DEFAULT_FRAGMENT);
+ }
+
+ if (getIntent().hasExtra(EXTRA_BACK_TO_SCENE)) {
+ backToScene = getIntent().getBooleanExtra(EXTRA_BACK_TO_SCENE, false);
+ }
+ }
+ }
+
+ private void loadFragment(String fragment) {
+ switch (fragment) {
+ case "Login":
+ loadLoginFragment();
+ break;
+ case "Home":
+ loadHomeFragment();
+ break;
+ case "Privacy Policy":
+ loadPrivacyPolicyFragment();
+ break;
+ default:
+ Log.e(TAG, "Unknown fragment " + fragment);
+ }
+
+ }
+
+ private void loadHomeFragment() {
+ Fragment fragment = HomeFragment.newInstance();
+ loadFragment(fragment, getString(R.string.home), false);
+ }
+
+ private void loadLoginFragment() {
+ Fragment fragment = LoginFragment.newInstance();
+
+ loadFragment(fragment, getString(R.string.login), true);
+ }
+
+ private void loadPrivacyPolicyFragment() {
+ Fragment fragment = PolicyFragment.newInstance();
+
+ loadFragment(fragment, getString(R.string.privacyPolicy), true);
+ }
+
+ private void loadFragment(Fragment fragment, String title, boolean addToBackStack) {
+ FragmentManager fragmentManager = getFragmentManager();
+ FragmentTransaction ft = fragmentManager.beginTransaction();
+ ft.replace(R.id.content_frame, fragment);
+ if (addToBackStack) {
+ ft.addToBackStack(null);
+ }
+ ft.commit();
+ setTitle(title);
+ mDrawerLayout.closeDrawer(mNavigationView);
+ }
+
+
+ private void updateLoginMenu() {
+ if (nativeIsLoggedIn()) {
+ mLoginPanel.setVisibility(View.GONE);
+ mProfilePanel.setVisibility(View.VISIBLE);
+ mLogoutOption.setVisibility(View.VISIBLE);
+ updateProfileHeader();
+ } else {
+ mLoginPanel.setVisibility(View.VISIBLE);
+ mProfilePanel.setVisibility(View.GONE);
+ mLogoutOption.setVisibility(View.GONE);
+ mDisplayName.setText("");
+ }
+ }
+
+ private void updateProfileHeader() {
+ updateProfileHeader(nativeGetDisplayName());
+ }
+ private void updateProfileHeader(String username) {
+ if (!username.isEmpty()) {
+ mDisplayName.setText(username);
+ updateProfilePicture(username);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ //getMenuInflater().inflate(R.menu.menu_navigation, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+ switch (id) {
+ case android.R.id.home:
+ mDrawerLayout.openDrawer(GravityCompat.START);
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.action_home:
+ loadHomeFragment();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ updateLoginMenu();
+ }
+
+ public void onLoginClicked(View view) {
+ loadLoginFragment();
+ }
+
+ public void onLogoutClicked(View view) {
+ nativeLogout();
+ updateLoginMenu();
+ }
+
+ public void onSelectedDomain(String domainUrl) {
+ goToDomain(domainUrl);
+ }
+
+ private void goToLastLocation() {
+ goToDomain("");
+ }
+
+ private void goToDomain(String domainUrl) {
+ Intent intent = new Intent(this, InterfaceActivity.class);
+ intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl);
+ finish();
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ startActivity(intent);
+ }
+
+ @Override
+ public void onLoginCompleted() {
+ loadHomeFragment();
+ updateLoginMenu();
+ if (backToScene) {
+ backToScene = false;
+ goToLastLocation();
+ }
+ }
+
+ public void handleUsernameChanged(String username) {
+ runOnUiThread(() -> updateProfileHeader(username));
+ }
+
+ /**
+ * This is a temporary way to get the profile picture URL
+ * TODO: this should be get from an API (at the moment there is no one for this)
+ */
+ private void updateProfilePicture(String username) {
+ mProfilePicture.setImageResource(PROFILE_PICTURE_PLACEHOLDER);
+ new DownloadProfileImageTask(url -> { if (url != null && !url.isEmpty()) {
+ Picasso.get().load(url).into(mProfilePicture, new RoundProfilePictureCallback());
+ } } ).execute(username);
+ }
+
+ public void onPrivacyPolicyClicked(View view) {
+ loadPrivacyPolicyFragment();
+ }
+
+ private class RoundProfilePictureCallback implements Callback {
+ @Override
+ public void onSuccess() {
+ Bitmap imageBitmap = ((BitmapDrawable) mProfilePicture.getDrawable()).getBitmap();
+ RoundedBitmapDrawable imageDrawable = RoundedBitmapDrawableFactory.create(getResources(), imageBitmap);
+ imageDrawable.setCircular(true);
+ imageDrawable.setCornerRadius(Math.max(imageBitmap.getWidth(), imageBitmap.getHeight()) / 2.0f);
+ mProfilePicture.setImageDrawable(imageDrawable);
+ }
+
+ @Override
+ public void onError(Exception e) {
+ mProfilePicture.setImageResource(PROFILE_PICTURE_PLACEHOLDER);
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ int index = getFragmentManager().getBackStackEntryCount() - 1;
+ if (index > -1) {
+ super.onBackPressed();
+ if (backToScene) {
+ backToScene = false;
+ goToLastLocation();
+ }
+ } else {
+ finishAffinity();
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ super.onSaveInstanceState(savedInstanceState);
+ savedInstanceState.putBoolean(EXTRA_BACK_TO_SCENE, backToScene);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ backToScene = savedInstanceState.getBoolean(EXTRA_BACK_TO_SCENE, false);
+ }
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java
index 34b087ca25..45060d6d0c 100644
--- a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java
@@ -11,7 +11,7 @@ import android.app.AlertDialog;
import org.json.JSONException;
import org.json.JSONObject;
-import android.util.Log;
+
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
@@ -63,7 +63,6 @@ public class PermissionChecker extends Activity {
}
private void launchActivityWithPermissions(){
- finish();
Intent i = new Intent(this, InterfaceActivity.class);
startActivity(i);
finish();
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java
new file mode 100644
index 0000000000..e0aa967aaa
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java
@@ -0,0 +1,43 @@
+package io.highfidelity.hifiinterface;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+
+public class SplashActivity extends Activity {
+
+ private native void registerLoadCompleteListener();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_splash);
+ registerLoadCompleteListener();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ View decorView = getWindow().getDecorView();
+ // Hide the status bar.
+ int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ decorView.setSystemUiVisibility(uiOptions);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ }
+
+ public void onAppLoadedComplete() {
+ startActivity(new Intent(this, MainActivity.class));
+ SplashActivity.this.finish();
+ }
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java
new file mode 100644
index 0000000000..b98849d051
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java
@@ -0,0 +1,164 @@
+package io.highfidelity.hifiinterface.fragment;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import io.highfidelity.hifiinterface.HifiUtils;
+import io.highfidelity.hifiinterface.R;
+import io.highfidelity.hifiinterface.view.DomainAdapter;
+
+public class HomeFragment extends Fragment {
+
+ private DomainAdapter mDomainAdapter;
+ private RecyclerView mDomainsView;
+ private TextView searchNoResultsView;
+ private ImageView mSearchIconView;
+ private ImageView mClearSearch;
+ private EditText mSearchView;
+
+
+ private OnHomeInteractionListener mListener;
+
+ public native String nativeGetLastLocation();
+
+ public HomeFragment() {
+ // Required empty public constructor
+ }
+
+ public static HomeFragment newInstance() {
+ HomeFragment fragment = new HomeFragment();
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getArguments() != null) {
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_home, container, false);
+
+ searchNoResultsView = rootView.findViewById(R.id.searchNoResultsView);
+
+ mDomainsView = rootView.findViewById(R.id.rvDomains);
+ int numberOfColumns = 1;
+ GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns);
+ mDomainsView.setLayoutManager(gridLayoutMgr);
+ mDomainAdapter = new DomainAdapter(getContext(), HifiUtils.getInstance().protocolVersionSignature(), nativeGetLastLocation());
+ mDomainAdapter.setClickListener((view, position, domain) -> {
+ new Handler(getActivity().getMainLooper()).postDelayed(() -> {
+ if (mListener != null) {
+ mListener.onSelectedDomain(domain.url);
+ }
+ }, 400); // a delay so the ripple effect can be seen
+ });
+ mDomainAdapter.setListener(new DomainAdapter.AdapterListener() {
+ @Override
+ public void onEmptyAdapter() {
+ searchNoResultsView.setText(R.string.search_no_results);
+ searchNoResultsView.setVisibility(View.VISIBLE);
+ mDomainsView.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onNonEmptyAdapter() {
+ searchNoResultsView.setVisibility(View.GONE);
+ mDomainsView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onError(Exception e, String message) {
+
+ }
+ });
+ mDomainsView.setAdapter(mDomainAdapter);
+
+ mSearchView = rootView.findViewById(R.id.searchView);
+ mSearchIconView = rootView.findViewById(R.id.search_mag_icon);
+ mClearSearch = rootView.findViewById(R.id.search_clear);
+
+ mSearchView.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ mDomainAdapter.loadDomains(editable.toString());
+ if (editable.length() > 0) {
+ mSearchIconView.setVisibility(View.GONE);
+ mClearSearch.setVisibility(View.VISIBLE);
+ } else {
+ mSearchIconView.setVisibility(View.VISIBLE);
+ mClearSearch.setVisibility(View.GONE);
+ }
+ }
+ });
+ mSearchView.setOnKeyListener((view, i, keyEvent) -> {
+ if (i == KeyEvent.KEYCODE_ENTER) {
+ String urlString = mSearchView.getText().toString();
+ if (!urlString.trim().isEmpty()) {
+ urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString);
+ }
+ if (mListener != null) {
+ mListener.onSelectedDomain(urlString);
+ }
+ return true;
+ }
+ return false;
+ });
+
+ mClearSearch.setOnClickListener(view -> onSearchClear(view));
+
+ getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+
+ return rootView;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (context instanceof OnHomeInteractionListener) {
+ mListener = (OnHomeInteractionListener) context;
+ } else {
+ throw new RuntimeException(context.toString()
+ + " must implement OnHomeInteractionListener");
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mListener = null;
+ }
+
+ public interface OnHomeInteractionListener {
+ void onSelectedDomain(String domainUrl);
+ }
+
+ public void onSearchClear(View view) {
+ mSearchView.setText("");
+ }
+
+
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java
new file mode 100644
index 0000000000..f29c237ed7
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java
@@ -0,0 +1,205 @@
+package io.highfidelity.hifiinterface.fragment;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import io.highfidelity.hifiinterface.R;
+
+public class LoginFragment extends Fragment {
+
+ private EditText mUsername;
+ private EditText mPassword;
+ private TextView mError;
+ private TextView mForgotPassword;
+ private Button mLoginButton;
+
+ private ProgressDialog mDialog;
+
+ public native void nativeLogin(String username, String password, Activity usernameChangedListener);
+
+ private LoginFragment.OnLoginInteractionListener mListener;
+
+ public LoginFragment() {
+ // Required empty public constructor
+ }
+
+ public static LoginFragment newInstance() {
+ LoginFragment fragment = new LoginFragment();
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_login, container, false);
+
+ mUsername = rootView.findViewById(R.id.username);
+ mPassword = rootView.findViewById(R.id.password);
+ mError = rootView.findViewById(R.id.error);
+ mLoginButton = rootView.findViewById(R.id.loginButton);
+ mForgotPassword = rootView.findViewById(R.id.forgotPassword);
+
+ mUsername.addTextChangedListener(new TextWatcher() {
+ boolean ignoreNextChange = false;
+ boolean hadBlankSpace = false;
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
+ hadBlankSpace = charSequence.length() > 0 && charSequence.charAt(charSequence.length()-1) == ' ';
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ if (!ignoreNextChange) {
+ ignoreNextChange = true;
+ boolean spaceFound = false;
+ for (int i = 0; i < editable.length(); i++) {
+ if (editable.charAt(i) == ' ') {
+ spaceFound=true;
+ editable.delete(i, i + 1);
+ i--;
+ }
+ }
+
+ if (hadBlankSpace && !spaceFound && editable.length() > 0) {
+ editable.delete(editable.length()-1, editable.length());
+ }
+
+ editable.append(' ');
+ ignoreNextChange = false;
+ }
+
+ }
+ });
+
+
+ mLoginButton.setOnClickListener(view -> login());
+
+ mForgotPassword.setOnClickListener(view -> forgotPassword());
+
+ mPassword.setOnEditorActionListener(
+ (textView, actionId, keyEvent) -> {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ mLoginButton.performClick();
+ return true;
+ }
+ return false;
+ });
+ return rootView;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (context instanceof OnLoginInteractionListener) {
+ mListener = (OnLoginInteractionListener) context;
+ } else {
+ throw new RuntimeException(context.toString()
+ + " must implement OnLoginInteractionListener");
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mListener = null;
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ cancelActivityIndicator();
+ hideKeyboard();
+ }
+
+ public void login() {
+ String username = mUsername.getText().toString().trim();
+ String password = mPassword.getText().toString();
+ hideKeyboard();
+ if (username.isEmpty() || password.isEmpty()) {
+ showError(getString(R.string.login_username_or_password_incorrect));
+ } else {
+ mLoginButton.setEnabled(false);
+ hideError();
+ showActivityIndicator();
+ nativeLogin(username, password, getActivity());
+ }
+ }
+
+ private void hideKeyboard() {
+ View view = getActivity().getCurrentFocus();
+ if (view != null) {
+ InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+
+ private void forgotPassword() {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://highfidelity.com/users/password/new"));
+ startActivity(intent);
+ }
+
+ private void showActivityIndicator() {
+ if (mDialog == null) {
+ mDialog = new ProgressDialog(getContext());
+ }
+ mDialog.setMessage(getString(R.string.logging_in));
+ mDialog.setCancelable(false);
+ mDialog.show();
+ }
+
+ private void cancelActivityIndicator() {
+ if (mDialog != null) {
+ mDialog.cancel();
+ }
+ }
+ private void showError(String error) {
+ mError.setText(error);
+ mError.setVisibility(View.VISIBLE);
+ }
+
+ private void hideError() {
+ mError.setText("");
+ mError.setVisibility(View.INVISIBLE);
+ }
+
+ public void handleLoginCompleted(boolean success) {
+ Log.d("[LOGIN]", "handleLoginCompleted " + success);
+ getActivity().runOnUiThread(() -> {
+ mLoginButton.setEnabled(true);
+ cancelActivityIndicator();
+ if (success) {
+ if (mListener != null) {
+ mListener.onLoginCompleted();
+ }
+ } else {
+ showError(getString(R.string.login_username_or_password_incorrect));
+ }
+ });
+ }
+
+ public interface OnLoginInteractionListener {
+ void onLoginCompleted();
+ }
+
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/PolicyFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/PolicyFragment.java
new file mode 100644
index 0000000000..cd9aec244a
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/PolicyFragment.java
@@ -0,0 +1,60 @@
+package io.highfidelity.hifiinterface.fragment;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.text.Html;
+import android.text.Spanned;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import io.highfidelity.hifiinterface.R;
+
+public class PolicyFragment extends Fragment {
+
+ private static final String POLICY_FILE = "privacy_policy.html";
+
+ public PolicyFragment() {
+ // Required empty public constructor
+ }
+
+ public static PolicyFragment newInstance() {
+ PolicyFragment fragment = new PolicyFragment();
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_policy, container, false);
+
+ TextView policy = rootView.findViewById(R.id.policyText);
+
+
+ Spanned myStringSpanned = null;
+ try {
+ myStringSpanned = Html.fromHtml(loadHTMLFromAsset(), null, null);
+ policy.setText(myStringSpanned, TextView.BufferType.SPANNABLE);
+ } catch (IOException e) {
+ policy.setText("N/A");
+ }
+
+ return rootView;
+ }
+
+ public String loadHTMLFromAsset() throws IOException {
+ String html = null;
+ InputStream is = getContext().getAssets().open(POLICY_FILE);
+ int size = is.available();
+ byte[] buffer = new byte[size];
+ is.read(buffer);
+ is.close();
+ html = new String(buffer, "UTF-8");
+ return html;
+ }
+
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java
new file mode 100644
index 0000000000..64ca6da816
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java
@@ -0,0 +1,9 @@
+package io.highfidelity.hifiinterface.provider;
+
+/**
+ * Created by cduarte on 4/18/18.
+ */
+
+public interface Callback {
+ public void callback(T t);
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java
new file mode 100644
index 0000000000..7a2101a229
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java
@@ -0,0 +1,19 @@
+package io.highfidelity.hifiinterface.provider;
+
+import java.util.List;
+
+import io.highfidelity.hifiinterface.view.DomainAdapter;
+
+/**
+ * Created by cduarte on 4/17/18.
+ */
+
+public interface DomainProvider {
+
+ void retrieve(String filterText, DomainCallback domainCallback);
+
+ interface DomainCallback {
+ void retrieveOk(List domain);
+ void retrieveError(Exception e, String message);
+ }
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java
new file mode 100644
index 0000000000..ca5e0c17bd
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java
@@ -0,0 +1,220 @@
+package io.highfidelity.hifiinterface.provider;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import io.highfidelity.hifiinterface.HifiUtils;
+import io.highfidelity.hifiinterface.view.DomainAdapter;
+import retrofit2.Call;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+import retrofit2.http.GET;
+import retrofit2.http.Query;
+
+/**
+ * Created by cduarte on 4/17/18.
+ */
+
+public class UserStoryDomainProvider implements DomainProvider {
+
+ public static final String BASE_URL = "https://metaverse.highfidelity.com/";
+
+ private static final String INCLUDE_ACTIONS_FOR_PLACES = "concurrency";
+ private static final String INCLUDE_ACTIONS_FOR_FULL_SEARCH = "concurrency,announcements,snapshot";
+ private static final String TAGS_TO_SEARCH = "mobile";
+ private static final int MAX_PAGES_TO_GET = 10;
+
+ private String mProtocol;
+ private Retrofit mRetrofit;
+ private UserStoryDomainProviderService mUserStoryDomainProviderService;
+
+ private boolean startedToGetFromAPI = false;
+ private List allStories; // All retrieved stories from the API
+ private List suggestions; // Filtered places to show
+
+ public UserStoryDomainProvider(String protocol) {
+ mRetrofit = new Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build();
+ mUserStoryDomainProviderService = mRetrofit.create(UserStoryDomainProviderService.class);
+ mProtocol = protocol;
+ allStories = new ArrayList<>();
+ suggestions = new ArrayList<>();
+ }
+
+ @Override
+ public synchronized void retrieve(String filterText, DomainCallback domainCallback) {
+ if (!startedToGetFromAPI) {
+ startedToGetFromAPI = true;
+ fillDestinations(filterText, domainCallback);
+ } else {
+ filterChoicesByText(filterText, domainCallback);
+ }
+ }
+
+ private void fillDestinations(String filterText, DomainCallback domainCallback) {
+ StoriesFilter filter = new StoriesFilter(filterText);
+
+ List taggedStories = new ArrayList<>();
+ Set taggedStoriesIds = new HashSet<>();
+ getUserStoryPage(1, taggedStories, TAGS_TO_SEARCH,
+ e -> {
+ taggedStories.forEach(userStory -> {
+ taggedStoriesIds.add(userStory.id);
+ });
+
+ allStories.clear();
+ getUserStoryPage(1, allStories, null,
+ ex -> {
+ allStories.forEach(userStory -> {
+ if (taggedStoriesIds.contains(userStory.id)) {
+ userStory.tagFound = true;
+ }
+ filter.filterOrAdd(userStory);
+ });
+ if (domainCallback != null) {
+ domainCallback.retrieveOk(suggestions); //ended
+ }
+ }
+ );
+
+ }
+ );
+ }
+
+ private void handleError(String url, Throwable t, Callback restOfPagesCallback) {
+ restOfPagesCallback.callback(new Exception("Error accessing url [" + url + "]", t));
+ }
+
+ private void getUserStoryPage(int pageNumber, List userStoriesList, String tagsFilter, Callback restOfPagesCallback) {
+ Call userStories = mUserStoryDomainProviderService.getUserStories(
+ INCLUDE_ACTIONS_FOR_PLACES,
+ "open",
+ true,
+ mProtocol,
+ tagsFilter,
+ pageNumber);
+ Log.d("API-USER-STORY-DOMAINS", "Protocol [" + mProtocol + "] include_actions [" + INCLUDE_ACTIONS_FOR_PLACES + "]");
+ userStories.enqueue(new retrofit2.Callback() {
+ @Override
+ public void onResponse(Call call, Response response) {
+ UserStories data = response.body();
+ userStoriesList.addAll(data.user_stories);
+ if (data.current_page < data.total_pages && data.current_page <= MAX_PAGES_TO_GET) {
+ getUserStoryPage(pageNumber + 1, userStoriesList, tagsFilter, restOfPagesCallback);
+ return;
+ }
+ restOfPagesCallback.callback(null);
+ }
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+ handleError(call.request().url().toString(), t, restOfPagesCallback);
+ }
+ });
+ }
+
+ private class StoriesFilter {
+ String[] mWords = new String[]{};
+ public StoriesFilter(String filterText) {
+ mWords = filterText.trim().toUpperCase().split("\\s+");
+ if (mWords.length == 1 && (mWords[0] == null || mWords[0].length() <= 0 ) ) {
+ mWords = null;
+ }
+ }
+
+ private boolean matches(UserStory story) {
+ if (mWords == null || mWords.length <= 0) {
+ // No text filter? So filter by tag
+ return story.tagFound;
+ }
+
+ for (String word : mWords) {
+ if (!story.searchText().contains(word)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void addToSuggestions(UserStory story) {
+ suggestions.add(story.toDomain());
+ }
+
+ /**
+ * if the story matches this filter criteria it's added into suggestions
+ * */
+ public void filterOrAdd(UserStory story) {
+ if (matches(story)) {
+ addToSuggestions(story);
+ }
+ }
+ }
+
+ private void filterChoicesByText(String filterText, DomainCallback domainCallback) {
+ suggestions.clear();
+ StoriesFilter storiesFilter = new StoriesFilter(filterText);
+ allStories.forEach(story -> {
+ storiesFilter.filterOrAdd(story);
+ });
+ domainCallback.retrieveOk(suggestions);
+ }
+
+ public interface UserStoryDomainProviderService {
+ @GET("api/v1/user_stories")
+ Call getUserStories(@Query("include_actions") String includeActions,
+ @Query("restriction") String restriction,
+ @Query("require_online") boolean requireOnline,
+ @Query("protocol") String protocol,
+ @Query("tags") String tagsCommaSeparated,
+ @Query("page") int pageNumber);
+ }
+
+ class UserStory {
+ public UserStory() {}
+ String id;
+ String place_name;
+ String path;
+ String thumbnail_url;
+ String place_id;
+ String domain_id;
+ private String searchText;
+ private boolean tagFound; // Locally used
+
+ // New fields? tags, description
+
+ String searchText() {
+ if (searchText == null) {
+ searchText = place_name == null? "" : place_name.toUpperCase();
+ }
+ return searchText;
+ }
+ DomainAdapter.Domain toDomain() {
+ // TODO Proper url creation (it can or can't have hifi
+ // TODO Or use host value from api?
+ String absoluteThumbnailUrl = HifiUtils.getInstance().absoluteHifiAssetUrl(thumbnail_url);
+ DomainAdapter.Domain domain = new DomainAdapter.Domain(
+ place_name,
+ HifiUtils.getInstance().sanitizeHifiUrl(place_name) + "/" + path,
+ absoluteThumbnailUrl
+ );
+ return domain;
+ }
+ }
+
+ class UserStories {
+ String status;
+ int current_page;
+ int total_pages;
+ int total_entries;
+ List user_stories;
+ }
+
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/task/DownloadProfileImageTask.java b/android/app/src/main/java/io/highfidelity/hifiinterface/task/DownloadProfileImageTask.java
new file mode 100644
index 0000000000..f32227a31e
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/task/DownloadProfileImageTask.java
@@ -0,0 +1,71 @@
+package io.highfidelity.hifiinterface.task;
+
+import android.os.AsyncTask;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+
+import io.highfidelity.hifiinterface.HifiUtils;
+
+/**
+ * This is a temporary solution until the profile picture URL is
+ * available in an API
+ */
+public class DownloadProfileImageTask extends AsyncTask {
+
+ private static final String BASE_PROFILE_URL = "https://highfidelity.com";
+ private static final String TAG = "Interface";
+
+ private final DownloadProfileImageResultProcessor mResultProcessor;
+
+ public interface DownloadProfileImageResultProcessor {
+ void onResultAvailable(String url);
+ }
+
+ public DownloadProfileImageTask(DownloadProfileImageResultProcessor resultProcessor) {
+ mResultProcessor = resultProcessor;
+ }
+
+ @Override
+ protected String doInBackground(String... usernames) {
+ URL userPage = null;
+ for (String username: usernames) {
+ try {
+ userPage = new URL(BASE_PROFILE_URL + "/users/" + username);
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(
+ userPage.openStream()));
+
+ StringBuffer strBuff = new StringBuffer();
+ String inputLine;
+ while ((inputLine = in.readLine()) != null) {
+ strBuff.append(inputLine);
+ }
+ in.close();
+ String substr = "img class=\"users-img\" src=\"";
+ int indexBegin = strBuff.indexOf(substr) + substr.length();
+ if (indexBegin >= substr.length()) {
+ int indexEnd = strBuff.indexOf("\"", indexBegin);
+ if (indexEnd > 0) {
+ String url = strBuff.substring(indexBegin, indexEnd);
+ return HifiUtils.getInstance().absoluteHifiAssetUrl(url, BASE_PROFILE_URL);
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error getting profile picture for username " + username);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(String url) {
+ super.onPostExecute(url);
+ if (mResultProcessor != null) {
+ mResultProcessor.onResultAvailable(url);
+ }
+ }
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java
new file mode 100644
index 0000000000..4f8b33b481
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java
@@ -0,0 +1,171 @@
+package io.highfidelity.hifiinterface.view;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.squareup.picasso.Picasso;
+
+import java.util.List;
+
+import io.highfidelity.hifiinterface.R;
+import io.highfidelity.hifiinterface.provider.DomainProvider;
+import io.highfidelity.hifiinterface.provider.UserStoryDomainProvider;
+
+/**
+ * Created by Gabriel Calero & Cristian Duarte on 3/20/18.
+ */
+public class DomainAdapter extends RecyclerView.Adapter {
+
+ private static final String TAG = "HiFi Interface";
+ private static final String DEFAULT_THUMBNAIL_PLACE = "android.resource://io.highfidelity.hifiinterface/" + R.drawable.domain_placeholder;
+ private Context mContext;
+ private LayoutInflater mInflater;
+ private ItemClickListener mClickListener;
+ private String mProtocol;
+ private String mLastLocation;
+ private UserStoryDomainProvider domainProvider;
+ private AdapterListener mAdapterListener;
+
+ // references to our domains
+ private Domain[] mDomains = {};
+
+ public DomainAdapter(Context c, String protocol, String lastLocation) {
+ mContext = c;
+ this.mInflater = LayoutInflater.from(mContext);
+ mProtocol = protocol;
+ mLastLocation = lastLocation;
+ domainProvider = new UserStoryDomainProvider(mProtocol);
+ loadDomains("");
+ }
+
+ public void setListener(AdapterListener adapterListener) {
+ mAdapterListener = adapterListener;
+ }
+
+ public void loadDomains(String filterText) {
+ domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() {
+ @Override
+ public void retrieveOk(List domain) {
+ if (filterText.length() == 0) {
+ addLastLocation(domain);
+ }
+
+ overrideDefaultThumbnails(domain);
+
+ mDomains = new Domain[domain.size()];
+ mDomains = domain.toArray(mDomains);
+ notifyDataSetChanged();
+ if (mAdapterListener != null) {
+ if (mDomains.length == 0) {
+ mAdapterListener.onEmptyAdapter();
+ } else {
+ mAdapterListener.onNonEmptyAdapter();
+ }
+ }
+ }
+
+ @Override
+ public void retrieveError(Exception e, String message) {
+ Log.e("DOMAINS", message, e);
+ if (mAdapterListener != null) mAdapterListener.onError(e, message);
+ }
+ });
+ }
+
+ private void overrideDefaultThumbnails(List domain) {
+ for (Domain d : domain) {
+ // we override the default picture added in the server by an android specific version
+ if (d.thumbnail != null &&
+ d.thumbnail.endsWith("assets/places/thumbnail-default-place-e5a3f33e773ab699495774990a562f9f7693dc48ef90d8be6985c645a0280f75.png")) {
+ d.thumbnail = DEFAULT_THUMBNAIL_PLACE;
+ }
+ }
+ }
+
+ private void addLastLocation(List domain) {
+ Domain lastVisitedDomain = new Domain(mContext.getString(R.string.your_last_location), mLastLocation, DEFAULT_THUMBNAIL_PLACE);
+ if (!mLastLocation.isEmpty() && mLastLocation.contains("://")) {
+ int startIndex = mLastLocation.indexOf("://");
+ int endIndex = mLastLocation.indexOf("/", startIndex + 3);
+ String toSearch = mLastLocation.substring(0, endIndex + 1).toLowerCase();
+ for (Domain d : domain) {
+ if (d.url.toLowerCase().startsWith(toSearch)) {
+ lastVisitedDomain.thumbnail = d.thumbnail;
+ }
+ }
+ }
+ domain.add(0, lastVisitedDomain);
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = mInflater.inflate(R.layout.domain_view, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ // TODO
+ //holder.thumbnail.setImageResource(mDomains[position].thumbnail);
+ Domain domain = mDomains[position];
+ holder.mDomainName.setText(domain.name);
+ Uri uri = Uri.parse(domain.thumbnail);
+ Picasso.get().load(uri).into(holder.mThumbnail);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mDomains.length;
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+ TextView mDomainName;
+ ImageView mThumbnail;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+ mThumbnail = (ImageView) itemView.findViewById(R.id.domainThumbnail);
+ mDomainName = (TextView) itemView.findViewById(R.id.domainName);
+ itemView.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View view) {
+ int position = getAdapterPosition();
+ if (mClickListener != null) mClickListener.onItemClick(view, position, mDomains[position]);
+ }
+ }
+
+ // allows clicks events to be caught
+ public void setClickListener(ItemClickListener itemClickListener) {
+ this.mClickListener = itemClickListener;
+ }
+ // parent activity will implement this method to respond to click events
+ public interface ItemClickListener {
+ void onItemClick(View view, int position, Domain domain);
+ }
+
+ public static class Domain {
+ public String name;
+ public String url;
+ public String thumbnail;
+ public Domain(String name, String url, String thumbnail) {
+ this.name = name;
+ this.thumbnail = thumbnail;
+ this.url = url;
+ }
+ }
+
+ public interface AdapterListener {
+ void onEmptyAdapter();
+ void onNonEmptyAdapter();
+ void onError(Exception e, String message);
+ }
+}
diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java
index ed55c16cde..9fcaea6153 100644
--- a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java
+++ b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java
@@ -68,6 +68,8 @@ public class QtActivity extends Activity {
public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme.
private QtActivityLoader m_loader = new QtActivityLoader(this);
+ public boolean isLoading;
+
public QtActivity() {
}
@@ -499,7 +501,11 @@ public class QtActivity extends Activity {
@Override
protected void onPause() {
super.onPause();
- QtApplication.invokeDelegate();
+ // GC: this trick allow us to show a splash activity until Qt app finishes
+ // loading
+ if (!isLoading) {
+ QtApplication.invokeDelegate();
+ }
}
//---------------------------------------------------------------------------
@@ -640,6 +646,7 @@ public class QtActivity extends Activity {
super.onStop();
QtApplication.invokeDelegate();
}
+
//---------------------------------------------------------------------------
@Override
diff --git a/android/app/src/main/res/drawable/default_profile_avatar.xml b/android/app/src/main/res/drawable/default_profile_avatar.xml
new file mode 100644
index 0000000000..5db00acdd6
--- /dev/null
+++ b/android/app/src/main/res/drawable/default_profile_avatar.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/domain_placeholder.png b/android/app/src/main/res/drawable/domain_placeholder.png
new file mode 100644
index 0000000000..7852ec473f
Binary files /dev/null and b/android/app/src/main/res/drawable/domain_placeholder.png differ
diff --git a/android/app/src/main/res/drawable/hifi_header.xml b/android/app/src/main/res/drawable/hifi_header.xml
new file mode 100644
index 0000000000..9f7c85297a
--- /dev/null
+++ b/android/app/src/main/res/drawable/hifi_header.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/hifi_logo_header.xml b/android/app/src/main/res/drawable/hifi_logo_header.xml
new file mode 100644
index 0000000000..017e636184
--- /dev/null
+++ b/android/app/src/main/res/drawable/hifi_logo_header.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/hifi_logo_splash.xml b/android/app/src/main/res/drawable/hifi_logo_splash.xml
new file mode 100644
index 0000000000..919b2737e8
--- /dev/null
+++ b/android/app/src/main/res/drawable/hifi_logo_splash.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/ic_bookmark.xml b/android/app/src/main/res/drawable/ic_bookmark.xml
new file mode 100644
index 0000000000..ddf03e340b
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_bookmark.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_clear.xml b/android/app/src/main/res/drawable/ic_clear.xml
new file mode 100644
index 0000000000..94efe2bbdb
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_clear.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_menu.xml b/android/app/src/main/res/drawable/ic_menu.xml
new file mode 100644
index 0000000000..cf37e2a393
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_menu.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_person.xml b/android/app/src/main/res/drawable/ic_person.xml
new file mode 100644
index 0000000000..cf57059c77
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_person.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_search.xml b/android/app/src/main/res/drawable/ic_search.xml
new file mode 100644
index 0000000000..099c8ea953
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_search.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_share.xml b/android/app/src/main/res/drawable/ic_share.xml
new file mode 100644
index 0000000000..91b01694da
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_share.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/rounded_button.xml b/android/app/src/main/res/drawable/rounded_button.xml
new file mode 100644
index 0000000000..11a9f90c8b
--- /dev/null
+++ b/android/app/src/main/res/drawable/rounded_button.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/rounded_edit.xml b/android/app/src/main/res/drawable/rounded_edit.xml
new file mode 100644
index 0000000000..3c1cac4d1d
--- /dev/null
+++ b/android/app/src/main/res/drawable/rounded_edit.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/search_bg.xml b/android/app/src/main/res/drawable/search_bg.xml
new file mode 100644
index 0000000000..fd1a9ea42e
--- /dev/null
+++ b/android/app/src/main/res/drawable/search_bg.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/font/raleway.ttf b/android/app/src/main/res/font/raleway.ttf
new file mode 100644
index 0000000000..e570a2d5c3
Binary files /dev/null and b/android/app/src/main/res/font/raleway.ttf differ
diff --git a/android/app/src/main/res/font/raleway_bold.xml b/android/app/src/main/res/font/raleway_bold.xml
new file mode 100644
index 0000000000..136472c0b3
--- /dev/null
+++ b/android/app/src/main/res/font/raleway_bold.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/android/app/src/main/res/font/raleway_italic.xml b/android/app/src/main/res/font/raleway_italic.xml
new file mode 100644
index 0000000000..6bf9dfa29c
--- /dev/null
+++ b/android/app/src/main/res/font/raleway_italic.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/android/app/src/main/res/font/raleway_light_italic.xml b/android/app/src/main/res/font/raleway_light_italic.xml
new file mode 100644
index 0000000000..4acab05089
--- /dev/null
+++ b/android/app/src/main/res/font/raleway_light_italic.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/android/app/src/main/res/font/raleway_medium.xml b/android/app/src/main/res/font/raleway_medium.xml
new file mode 100644
index 0000000000..3894c95b10
--- /dev/null
+++ b/android/app/src/main/res/font/raleway_medium.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/android/app/src/main/res/font/raleway_semibold.xml b/android/app/src/main/res/font/raleway_semibold.xml
new file mode 100644
index 0000000000..39cde5a30b
--- /dev/null
+++ b/android/app/src/main/res/font/raleway_semibold.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000000..f14bb66586
--- /dev/null
+++ b/android/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/activity_splash.xml b/android/app/src/main/res/layout/activity_splash.xml
new file mode 100644
index 0000000000..ed25797917
--- /dev/null
+++ b/android/app/src/main/res/layout/activity_splash.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/android/app/src/main/res/layout/domain_view.xml b/android/app/src/main/res/layout/domain_view.xml
new file mode 100644
index 0000000000..853124edb7
--- /dev/null
+++ b/android/app/src/main/res/layout/domain_view.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/fragment_home.xml b/android/app/src/main/res/layout/fragment_home.xml
new file mode 100644
index 0000000000..cb39b8f69e
--- /dev/null
+++ b/android/app/src/main/res/layout/fragment_home.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/fragment_login.xml b/android/app/src/main/res/layout/fragment_login.xml
new file mode 100644
index 0000000000..c50e6c1380
--- /dev/null
+++ b/android/app/src/main/res/layout/fragment_login.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/fragment_policy.xml b/android/app/src/main/res/layout/fragment_policy.xml
new file mode 100644
index 0000000000..a08f2b9c9c
--- /dev/null
+++ b/android/app/src/main/res/layout/fragment_policy.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/navigation_header.xml b/android/app/src/main/res/layout/navigation_header.xml
new file mode 100644
index 0000000000..40ab589253
--- /dev/null
+++ b/android/app/src/main/res/layout/navigation_header.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/menu/menu_navigation.xml b/android/app/src/main/res/menu/menu_navigation.xml
new file mode 100644
index 0000000000..cf80c84177
--- /dev/null
+++ b/android/app/src/main/res/menu/menu_navigation.xml
@@ -0,0 +1,8 @@
+
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
index 344907f039..15895e4355 100644
--- a/android/app/src/main/res/values/colors.xml
+++ b/android/app/src/main/res/values/colors.xml
@@ -1,4 +1,20 @@
#ffffff
+ @color/backgroundLight
+ @color/backgroundDark
+ #54D7FD
+ #E3E3E3
+ #575757
+ #1EB5EC
+ #00B4EF
+ #333333
+ #4F4F4F
+ #33999999
+ #212121
+ #9e9e9e
+ #F2F2F2
+ #FF7171
+ #99000000
+ #292929
diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml
index a9ec657aa9..bb5be1c070 100644
--- a/android/app/src/main/res/values/dimens.xml
+++ b/android/app/src/main/res/values/dimens.xml
@@ -9,4 +9,32 @@
14dp12dp
-
\ No newline at end of file
+ 12dp
+ 8dp
+ 4dp
+
+
+ 47.5dp
+ 11dp
+ 24dp
+ 51dp
+ 19sp
+ 16dp
+ 16dp
+ 22dp
+ 16dp
+ 16dp
+ 22dp
+
+ 163dp
+ 14dp
+ 14dp
+ 2dp
+ 6dp
+ 64dp
+
+ 56dp
+ 101dp
+ 425dp
+
+
diff --git a/android/app/src/main/res/values/font_certs.xml b/android/app/src/main/res/values/font_certs.xml
new file mode 100644
index 0000000000..d2226ac01c
--- /dev/null
+++ b/android/app/src/main/res/values/font_certs.xml
@@ -0,0 +1,17 @@
+
+
+
+ @array/com_google_android_gms_fonts_certs_dev
+ @array/com_google_android_gms_fonts_certs_prod
+
+
+
+ MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
+
+
+
+
+ MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
+
+
+
diff --git a/android/app/src/main/res/values/preloaded_fonts.xml b/android/app/src/main/res/values/preloaded_fonts.xml
new file mode 100644
index 0000000000..11b7a7c9f7
--- /dev/null
+++ b/android/app/src/main/res/values/preloaded_fonts.xml
@@ -0,0 +1,10 @@
+
+
+
+ @font/raleway_bold
+ @font/raleway_italic
+ @font/raleway_light_italic
+ @font/raleway_medium
+ @font/raleway_semibold
+
+
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index b8080fae0f..4f5f29e671 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -1,8 +1,25 @@
Interface
+ HomeOpen in browserShare linkShared a linkShare link
+ FEATURED
+ POPULAR
+ BOOKMARKS
+ Type a domain url
+ Username or email\u00A0
+ Password\u00A0
+ Login
+ Logout
+ Forgot password?\u00A0
+ Username or password incorrect.
+ Logging into High Fidelity
+ Search for a place by name\u00A0
+ Loading places…
+ No places exist with that name
+ Privacy Policy
+ Your Last Location
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 23fe67f029..308c438fa6 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -1,14 +1,48 @@
-
+
+
-
-
+
+
+
+
+
+
+
+
@@ -21,5 +55,11 @@
@dimen/text_size_subtitle_material_toolbar
+
diff --git a/android/build.gradle b/android/build.gradle
index 1dfef97f91..f1fe4ffc7f 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -37,7 +37,7 @@ task clean(type: Delete) {
ext {
RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0'
RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
- BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : ''
+ STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0'
EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : ''
QT5_DEPS = [
'Qt5Concurrent',
@@ -66,19 +66,19 @@ ext {
def baseFolder = new File(HIFI_ANDROID_PRECOMPILED)
def appDir = new File(projectDir, 'app')
def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a')
-def baseUrl = 'https://hifi-public.s3.amazonaws.com/austin/android/'
+def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/'
def qtFile='qt-5.9.3_linux_armv8-libcpp_openssl.tgz'
def qtChecksum='04599670ccca84bd2b15f6915568eb2d'
-def qtVersionId='PeoqzN31n.YvLfs9JE2SgHgZ4.IaKAlt'
+def qtVersionId='8QbCma4ryEPgBYn_8kgYgB10IvNx9I1W'
if (Os.isFamily(Os.FAMILY_MAC)) {
qtFile = 'qt-5.9.3_osx_armv8-libcpp_openssl.tgz'
qtChecksum='4b02de9d67d6bfb202355a808d2d9c59'
- qtVersionId='HygCmtMLPYioyil0DfXckGVzhw2SXZA9'
+ qtVersionId='2gfgoYCggJGyXxKiazaPGsMs1Gn9j4og'
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
qtFile = 'qt-5.9.3_win_armv8-libcpp_openssl.tgz'
qtChecksum='c3e25db64002d0f43cf565e0ef708911'
- qtVersionId='HeVObSVLCBoc7yY7He1oBMvPIH0VkClT'
+ qtVersionId='xKIteC6HO0xrmcWeMmhQcmKyPEsnUrcZ'
}
def packages = [
@@ -88,62 +88,67 @@ def packages = [
checksum: qtChecksum,
],
bullet: [
- file: 'bullet-2.83_armv8-libcpp.tgz',
- versionId: 'ljb7v.1IjVRqyopUKVDbVnLA4z88J8Eo',
- checksum: '2c558d604fce337f5eba3eb7ec1252fd',
+ file: 'bullet-2.88_armv8-libcpp.tgz',
+ versionId: 'S8YaoED0Cl8sSb8fSV7Q2G1lQJSNDxqg',
+ checksum: '81642779ccb110f8c7338e8739ac38a0',
],
draco: [
file: 'draco_armv8-libcpp.tgz',
- versionId: 'cA3tVJSmkvb1naA3l6D_Jv2Noh.4yc4m',
+ versionId: '3.B.uBj31kWlgND3_R2xwQzT_TP6Dz_8',
checksum: '617a80d213a5ec69fbfa21a1f2f738cd',
],
glad: [
file: 'glad_armv8-libcpp.zip',
- versionId: 'Q9szthzeye8fFyAA.cY26Lgn2B8kezEE',
+ versionId: 'r5Zran.JSCtvrrB6Q4KaqfIoALPw3lYY',
checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449',
],
glm: [
- file: 'glm-0.9.8.tgz',
- versionId: 'BlkJNwaYV2Gfy5XwMeU7K0uzPDRKFMt2',
- checksum: 'd2b42cee31d2bc17bab6ce69e6b3f30a',
+ file: 'glm-0.9.8.5-patched.tgz',
+ versionId: 'cskfMoJrFlAeqI3WPxemyO_Cxt7rT9EJ',
+ checksum: '067b5fe16b220b5b1a1039ba51b062ae',
],
gvr: [
file: 'gvrsdk_v1.101.0.tgz',
- versionId: 'UTberAIFraEfF9IVjoV66u1DTPTopgeY',
+ versionId: 'nqBV_j81Uc31rC7bKIrlya_Hah4v3y5r',
checksum: '57fd02baa069176ba18597a29b6b4fc7',
],
nvtt: [
file: 'nvtt_armv8-libcpp.zip',
- versionId: 'vLqrqThvpq4gp75BHMAqO6HhfTXaa0An',
+ versionId: 'lmkBVR5t4UF1UUwMwEirnk9H_8Nt90IO',
checksum: 'eb46d0b683e66987190ed124aabf8910',
sharedLibFolder: 'lib',
includeLibs: ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'],
],
openssl: [
file: 'openssl-1.1.0g_armv8.tgz',
- versionId: 'DmahmSGFS4ltpHyTdyQvv35WOeUOiib9',
+ versionId: 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW',
checksum: 'cabb681fbccd79594f65fcc266e02f32',
],
polyvox: [
file: 'polyvox_armv8-libcpp.tgz',
- versionId: 'LDJtzMTvdm4SAc2KYg8Cg6uwWk4Vq3e3',
- checksum: '349ad5b72aaf2749ca95d847e60c5314',
+ versionId: 'A2kbKiNhpIenGq23bKRRzg7IMAI5BI92',
+ checksum: 'dba88b3a098747af4bb169e9eb9af57e',
sharedLibFolder: 'lib',
includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'],
],
tbb: [
file: 'tbb-2018_U1_armv8_libcpp.tgz',
- versionId: 'YZliDD8.Menh1IVXKEuLPeO3xAjJ1UdF',
+ versionId: 'mrRbWnv4O4evcM1quRH43RJqimlRtaKB',
checksum: '20768f298f53b195e71b414b0ae240c4',
sharedLibFolder: 'lib/release',
includeLibs: ['libtbb.so', 'libtbbmalloc.so'],
],
hifiAC: [
file: 'libplugins_libhifiCodec.zip',
- versionId: 'mzKhsRCgVmloqq5bvE.0IwYK1NjGQc_G',
+ versionId: 'i31pW.qNbvFOXRxbyiJUxg3sphaFNmZU',
checksum: '9412a8e12c88a4096c1fc843bb9fe52d',
sharedLibFolder: '',
includeLibs: ['libplugins_libhifiCodec.so']
+ ],
+ etc2comp: [
+ file: 'etc2comp-patched-armv8-libcpp.tgz',
+ versionId: 'bHhGECRAQR1vkpshBcK6ByNc1BQIM8gU',
+ checksum: '14b02795d774457a33bbc60e00a786bc'
]
]
@@ -152,15 +157,15 @@ def scribeLocalFile='scribe' + EXEC_SUFFIX
def scribeFile='scribe_linux_x86_64'
def scribeChecksum='ca4b904f52f4f993c29175ba96798fa6'
-def scribeVersion='wgpf4dB2Ltzg4Lb2jJ4nPFsHoDkmK_OO'
+def scribeVersion='u_iTrJDaE95i2abTPXOpPZckGBIim53G'
if (Os.isFamily(Os.FAMILY_MAC)) {
scribeFile = 'scribe_osx_x86_64'
scribeChecksum='72db9d32d4e1e50add755570ac5eb749'
- scribeVersion='o_NbPrktzEYtBkQf3Tn7zc1nZWzM52w6'
+ scribeVersion='DAW0DmnjCRib4MD8x93bgc2Z2MpPojZC'
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
scribeFile = 'scribe_win32_x86_64.exe'
scribeChecksum='678e43d290c90fda670c6fefe038a06d'
- scribeVersion='GCCJxlmd2irvNOFWfZR0U1UCLHndHQrC'
+ scribeVersion='PuullrA_bPlO9kXZRt8rLe536X1UI.m7'
}
def options = [
@@ -361,6 +366,7 @@ task verifyOpenSSL(type: Verify) { def p = packages['openssl']; src new File(bas
task verifyPolyvox(type: Verify) { def p = packages['polyvox']; src new File(baseFolder, p['file']); checksum p['checksum'] }
task verifyTBB(type: Verify) { def p = packages['tbb']; src new File(baseFolder, p['file']); checksum p['checksum'] }
task verifyHifiAC(type: Verify) { def p = packages['hifiAC']; src new File(baseFolder, p['file']); checksum p['checksum'] }
+task verifyEtc2Comp(type: Verify) { def p = packages['etc2comp']; src new File(baseFolder, p['file']); checksum p['checksum'] }
task verifyDependencyDownloads(dependsOn: downloadDependencies) { }
verifyDependencyDownloads.dependsOn verifyQt
@@ -371,6 +377,7 @@ verifyDependencyDownloads.dependsOn verifyOpenSSL
verifyDependencyDownloads.dependsOn verifyPolyvox
verifyDependencyDownloads.dependsOn verifyTBB
verifyDependencyDownloads.dependsOn verifyHifiAC
+verifyDependencyDownloads.dependsOn verifyEtc2Comp
task extractDependencies(dependsOn: verifyDependencyDownloads) {
doLast {
@@ -535,7 +542,7 @@ task cleanDependencies(type: Delete) {
-// FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution.
+// FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution.
// See the comment on the qtBundle task above
/*
// FIXME derive the path from the gradle environment
diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index 10b8d44545..42924a8487 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -380,7 +380,7 @@ void Agent::executeScript() {
using namespace recording;
static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME);
- Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [this, scriptedAvatar](Frame::ConstPointer frame) {
+ Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [scriptedAvatar](Frame::ConstPointer frame) {
auto recordingInterface = DependencyManager::get();
bool useFrameSkeleton = recordingInterface->getPlayerUseSkeletonModel();
@@ -548,16 +548,21 @@ void Agent::setIsAvatar(bool isAvatar) {
if (_isAvatar && !_avatarIdentityTimer) {
// set up the avatar timers
_avatarIdentityTimer = new QTimer(this);
+ _avatarQueryTimer = new QTimer(this);
// connect our slot
connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
+ connect(_avatarQueryTimer, &QTimer::timeout, this, &Agent::queryAvatars);
+
+ static const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
+ static const int AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS = 1000;
// start the timers
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets
+ _avatarQueryTimer->start(AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS);
// tell the avatarAudioTimer to start ticking
QMetaObject::invokeMethod(&_avatarAudioTimer, "start");
-
}
if (!_isAvatar) {
@@ -567,6 +572,10 @@ void Agent::setIsAvatar(bool isAvatar) {
delete _avatarIdentityTimer;
_avatarIdentityTimer = nullptr;
+ _avatarQueryTimer->stop();
+ delete _avatarQueryTimer;
+ _avatarQueryTimer = nullptr;
+
// The avatar mixer never times out a connection (e.g., based on identity or data packets)
// but rather keeps avatars in its list as long as "connected". As a result, clients timeout
// when we stop sending identity, but then get woken up again by the mixer itself, which sends
@@ -585,6 +594,7 @@ void Agent::setIsAvatar(bool isAvatar) {
nodeList->sendPacket(std::move(packet), *node);
});
}
+
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
}
}
@@ -597,6 +607,31 @@ void Agent::sendAvatarIdentityPacket() {
}
}
+void Agent::queryAvatars() {
+ auto scriptedAvatar = DependencyManager::get();
+
+ ViewFrustum view;
+ view.setPosition(scriptedAvatar->getWorldPosition());
+ view.setOrientation(scriptedAvatar->getHeadOrientation());
+ view.calculate();
+ ConicalViewFrustum conicalView { view };
+
+ auto avatarPacket = NLPacket::create(PacketType::AvatarQuery);
+ auto destinationBuffer = reinterpret_cast(avatarPacket->getPayload());
+ auto bufferStart = destinationBuffer;
+
+ uint8_t numFrustums = 1;
+ memcpy(destinationBuffer, &numFrustums, sizeof(numFrustums));
+ destinationBuffer += sizeof(numFrustums);
+
+ destinationBuffer += conicalView.serialize(destinationBuffer);
+
+ avatarPacket->setPayloadSize(destinationBuffer - bufferStart);
+
+ DependencyManager::get()->broadcastToNodes(std::move(avatarPacket),
+ { NodeType::AvatarMixer });
+}
+
void Agent::processAgentAvatar() {
if (!_scriptEngine->isFinished() && _isAvatar) {
auto scriptedAvatar = DependencyManager::get();
diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h
index 1229f06276..0fc3fbe1f9 100644
--- a/assignment-client/src/Agent.h
+++ b/assignment-client/src/Agent.h
@@ -33,6 +33,19 @@
#include "entities/EntityTreeHeadlessViewer.h"
#include "avatars/ScriptableAvatar.h"
+/**jsdoc
+ * @namespace Agent
+ *
+ * @hifi-assignment-client
+ *
+ * @property {boolean} isAvatar
+ * @property {boolean} isPlayingAvatarSound Read-only.
+ * @property {boolean} isListeningToAudioStream
+ * @property {boolean} isNoiseGateEnabled
+ * @property {number} lastReceivedAudioLoudness Read-only.
+ * @property {Uuid} sessionUUID Read-only.
+ */
+
class Agent : public ThreadedAssignment {
Q_OBJECT
@@ -60,10 +73,28 @@ public:
virtual void aboutToFinish() override;
public slots:
+ /**jsdoc
+ * @function Agent.run
+ * @deprecated This function is being removed from the API.
+ */
void run() override;
+
+ /**jsdoc
+ * @function Agent.playAvatarSound
+ * @param {object} avatarSound
+ */
void playAvatarSound(SharedSoundPointer avatarSound);
+ /**jsdoc
+ * @function Agent.setIsAvatar
+ * @param {boolean} isAvatar
+ */
void setIsAvatar(bool isAvatar);
+
+ /**jsdoc
+ * @function Agent.isAvatar
+ * @returns {boolean}
+ */
bool isAvatar() const { return _isAvatar; }
private slots:
@@ -97,6 +128,7 @@ private:
void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; }
void sendAvatarIdentityPacket();
+ void queryAvatars();
QString _scriptContents;
QTimer* _scriptRequestTimeout { nullptr };
@@ -106,6 +138,7 @@ private:
int _numAvatarSoundSentBytes = 0;
bool _isAvatar = false;
QTimer* _avatarIdentityTimer = nullptr;
+ QTimer* _avatarQueryTimer = nullptr;
QHash _outgoingScriptAudioSequenceNumbers;
AudioGate _audioGate;
diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp
index efced972a0..41e42aa0a1 100644
--- a/assignment-client/src/AssignmentClient.cpp
+++ b/assignment-client/src/AssignmentClient.cpp
@@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "AssignmentClient.h"
+
#include
#include
@@ -32,16 +34,14 @@
#include
#include
#include
-
-#include "AssignmentFactory.h"
-#include "AssignmentDynamicFactory.h"
-
-#include "AssignmentClient.h"
-#include "AssignmentClientLogging.h"
-#include "avatars/ScriptableAvatar.h"
#include
#include
+#include "AssignmentClientLogging.h"
+#include "AssignmentDynamicFactory.h"
+#include "AssignmentFactory.h"
+#include "avatars/ScriptableAvatar.h"
+
const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client";
const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000;
diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp
index 1868ccfafe..2847d4ebf1 100644
--- a/assignment-client/src/AssignmentClientMonitor.cpp
+++ b/assignment-client/src/AssignmentClientMonitor.cpp
@@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "AssignmentClientMonitor.h"
+
#include
#include
@@ -19,7 +21,6 @@
#include
#include
-#include "AssignmentClientMonitor.h"
#include "AssignmentClientApp.h"
#include "AssignmentClientChildData.h"
#include "SharedUtil.h"
diff --git a/assignment-client/src/AssignmentDynamic.cpp b/assignment-client/src/AssignmentDynamic.cpp
index 7adbd55c39..447097ac74 100644
--- a/assignment-client/src/AssignmentDynamic.cpp
+++ b/assignment-client/src/AssignmentDynamic.cpp
@@ -9,10 +9,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-#include "EntitySimulation.h"
-
#include "AssignmentDynamic.h"
+#include "EntitySimulation.h"
+
AssignmentDynamic::AssignmentDynamic(EntityDynamicType type, const QUuid& id, EntityItemPointer ownerEntity) :
EntityDynamicInterface(type, id),
_data(QByteArray()),
diff --git a/assignment-client/src/AssignmentFactory.cpp b/assignment-client/src/AssignmentFactory.cpp
index 38eb72649f..405039d833 100644
--- a/assignment-client/src/AssignmentFactory.cpp
+++ b/assignment-client/src/AssignmentFactory.cpp
@@ -9,11 +9,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "AssignmentFactory.h"
+
#include
#include "Agent.h"
#include "assets/AssetServer.h"
-#include "AssignmentFactory.h"
#include "audio/AudioMixer.h"
#include "avatars/AvatarMixer.h"
#include "entities/EntityServer.h"
diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp
index 1eb43a45a5..e0c35b7148 100644
--- a/assignment-client/src/assets/AssetServer.cpp
+++ b/assignment-client/src/assets/AssetServer.cpp
@@ -61,13 +61,13 @@ static const ScriptBakeVersion CURRENT_SCRIPT_BAKE_VERSION = (ScriptBakeVersion)
BakedAssetType assetTypeForExtension(const QString& extension) {
auto extensionLower = extension.toLower();
if (BAKEABLE_MODEL_EXTENSIONS.contains(extensionLower)) {
- return Model;
+ return BakedAssetType::Model;
} else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extensionLower.toLocal8Bit())) {
- return Texture;
+ return BakedAssetType::Texture;
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extensionLower)) {
- return Script;
+ return BakedAssetType::Script;
}
- return Undefined;
+ return BakedAssetType::Undefined;
}
BakedAssetType assetTypeForFilename(const QString& filename) {
@@ -82,11 +82,11 @@ BakedAssetType assetTypeForFilename(const QString& filename) {
QString bakedFilenameForAssetType(BakedAssetType type) {
switch (type) {
- case Model:
+ case BakedAssetType::Model:
return BAKED_MODEL_SIMPLE_NAME;
- case Texture:
+ case BakedAssetType::Texture:
return BAKED_TEXTURE_SIMPLE_NAME;
- case Script:
+ case BakedAssetType::Script:
return BAKED_SCRIPT_SIMPLE_NAME;
default:
return "";
@@ -95,11 +95,11 @@ QString bakedFilenameForAssetType(BakedAssetType type) {
BakeVersion currentBakeVersionForAssetType(BakedAssetType type) {
switch (type) {
- case Model:
+ case BakedAssetType::Model:
return (BakeVersion)CURRENT_MODEL_BAKE_VERSION;
- case Texture:
+ case BakedAssetType::Texture:
return (BakeVersion)CURRENT_TEXTURE_BAKE_VERSION;
- case Script:
+ case BakedAssetType::Script:
return (BakeVersion)CURRENT_SCRIPT_BAKE_VERSION;
default:
return 0;
@@ -222,7 +222,7 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU
BakedAssetType type = assetTypeForFilename(path);
- if (type == Undefined) {
+ if (type == BakedAssetType::Undefined) {
return false;
}
@@ -241,7 +241,7 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU
AssetMeta meta;
std::tie(loaded, meta) = readMetaFile(assetHash);
- if (type == Texture && !loaded) {
+ if (type == BakedAssetType::Texture && !loaded) {
return false;
}
@@ -291,18 +291,6 @@ AssetServer::AssetServer(ReceivedMessage& message) :
_bakingTaskPool(this),
_filesizeLimit(AssetUtils::MAX_UPLOAD_SIZE)
{
- // store the current state of image compression so we can reset it when this assignment is complete
- _wasColorTextureCompressionEnabled = image::isColorTexturesCompressionEnabled();
- _wasGrayscaleTextureCompressionEnabled = image::isGrayscaleTexturesCompressionEnabled();
- _wasNormalTextureCompressionEnabled = image::isNormalTexturesCompressionEnabled();
- _wasCubeTextureCompressionEnabled = image::isCubeTexturesCompressionEnabled();
-
- // enable compression in image library
- image::setColorTexturesCompressionEnabled(true);
- image::setGrayscaleTexturesCompressionEnabled(true);
- image::setNormalTexturesCompressionEnabled(true);
- image::setCubeTexturesCompressionEnabled(true);
-
BAKEABLE_TEXTURE_EXTENSIONS = image::getSupportedFormats();
qDebug() << "Supported baking texture formats:" << BAKEABLE_MODEL_EXTENSIONS;
@@ -354,12 +342,6 @@ void AssetServer::aboutToFinish() {
while (_pendingBakes.size() > 0) {
QCoreApplication::processEvents();
}
-
- // re-set defaults in image library
- image::setColorTexturesCompressionEnabled(_wasCubeTextureCompressionEnabled);
- image::setGrayscaleTexturesCompressionEnabled(_wasGrayscaleTextureCompressionEnabled);
- image::setNormalTexturesCompressionEnabled(_wasNormalTextureCompressionEnabled);
- image::setCubeTexturesCompressionEnabled(_wasCubeTextureCompressionEnabled);
}
void AssetServer::run() {
@@ -901,7 +883,7 @@ void AssetServer::handleAssetUpload(QSharedPointer message, Sha
if (canWriteToAssetServer) {
- qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(message->getSourceID());
+ qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << message->getSourceID();
auto task = new UploadAssetTask(message, senderNode, _filesDirectory, _filesizeLimit);
_transferTaskPool.start(task);
@@ -1486,16 +1468,16 @@ std::pair AssetServer::readMetaFile(AssetUtils::AssetHash hash)
if (error.error == QJsonParseError::NoError && doc.isObject()) {
auto root = doc.object();
- auto bakeVersion = root[BAKE_VERSION_KEY].toInt(-1);
+ auto bakeVersion = root[BAKE_VERSION_KEY];
auto failedLastBake = root[FAILED_LAST_BAKE_KEY];
auto lastBakeErrors = root[LAST_BAKE_ERRORS_KEY];
- if (bakeVersion != -1
+ if (bakeVersion.isDouble()
&& failedLastBake.isBool()
&& lastBakeErrors.isString()) {
AssetMeta meta;
- meta.bakeVersion = bakeVersion;
+ meta.bakeVersion = bakeVersion.toInt();
meta.failedLastBake = failedLastBake.toBool();
meta.lastBakeErrors = lastBakeErrors.toString();
@@ -1546,7 +1528,7 @@ bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool
auto it = _fileMappings.find(path);
if (it != _fileMappings.end()) {
auto type = assetTypeForFilename(path);
- if (type == Undefined) {
+ if (type == BakedAssetType::Undefined) {
continue;
}
QString bakedFilename = bakedFilenameForAssetType(type);
diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h
index a55a15e6fc..b3d0f18a8f 100644
--- a/assignment-client/src/assets/AssetServer.h
+++ b/assignment-client/src/assets/AssetServer.h
@@ -27,7 +27,7 @@ using BakeVersion = int;
static const BakeVersion INITIAL_BAKE_VERSION = 0;
static const BakeVersion NEEDS_BAKING_BAKE_VERSION = -1;
-enum BakedAssetType : int {
+enum class BakedAssetType : int {
Model = 0,
Texture,
Script,
@@ -36,10 +36,11 @@ enum BakedAssetType : int {
Undefined
};
-// ATTENTION! If you change the current version for an asset type, you will also
-// need to update the function currentBakeVersionForAssetType() inside of AssetServer.cpp.
+// ATTENTION! Do not remove baking versions, and do not reorder them. If you add
+// a new value, it will immediately become the "current" version.
enum class ModelBakeVersion : BakeVersion {
Initial = INITIAL_BAKE_VERSION,
+ MetaTextureJson,
COUNT
};
@@ -47,6 +48,7 @@ enum class ModelBakeVersion : BakeVersion {
// ATTENTION! See above.
enum class TextureBakeVersion : BakeVersion {
Initial = INITIAL_BAKE_VERSION,
+ MetaTextureJson,
COUNT
};
@@ -63,7 +65,7 @@ struct AssetMeta {
AssetMeta() {
}
- BakeVersion bakeVersion;
+ BakeVersion bakeVersion { INITIAL_BAKE_VERSION };
bool failedLastBake { false };
QString lastBakeErrors;
};
@@ -165,11 +167,6 @@ private:
using RequestQueue = QVector, SharedNodePointer>>;
RequestQueue _queuedRequests;
- bool _wasColorTextureCompressionEnabled { false };
- bool _wasGrayscaleTextureCompressionEnabled { false };
- bool _wasNormalTextureCompressionEnabled { false };
- bool _wasCubeTextureCompressionEnabled { false };
-
uint64_t _filesizeLimit;
};
diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index 8af4eec934..34eb138697 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -117,12 +117,13 @@ void AudioMixer::queueAudioPacket(QSharedPointer message, Share
void AudioMixer::queueReplicatedAudioPacket(QSharedPointer message) {
// make sure we have a replicated node for the original sender of the packet
auto nodeList = DependencyManager::get();
-
- QUuid nodeID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
+
+ // Node ID is now part of user data, since replicated audio packets are non-sourced.
+ QUuid nodeID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
auto replicatedNode = nodeList->addOrUpdateNode(nodeID, NodeType::Agent,
message->getSenderSockAddr(), message->getSenderSockAddr(),
- true, true);
+ Node::NULL_LOCAL_ID, true, true);
replicatedNode->setLastHeardMicrostamp(usecTimestampNow());
// construct a "fake" audio received message from the byte array and packet list information
@@ -136,7 +137,7 @@ void AudioMixer::queueReplicatedAudioPacket(QSharedPointer mess
auto replicatedMessage = QSharedPointer::create(audioData, rewrittenType,
versionForPacketType(rewrittenType),
- message->getSenderSockAddr(), nodeID);
+ message->getSenderSockAddr(), Node::NULL_LOCAL_ID);
getOrCreateClientData(replicatedNode.data())->queuePacket(replicatedMessage, replicatedNode);
}
diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index d4d4f847ee..bc08c6f24a 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -624,8 +624,8 @@ AudioMixerClientData::IgnoreZone& AudioMixerClientData::IgnoreZoneMemo::get(unsi
scale = MIN_IGNORE_BOX_SCALE;
}
- // quadruple the scale (this is arbitrary number chosen for comfort)
- const float IGNORE_BOX_SCALE_FACTOR = 4.0f;
+ // (this is arbitrary number determined empirically for comfort)
+ const float IGNORE_BOX_SCALE_FACTOR = 2.4f;
scale *= IGNORE_BOX_SCALE_FACTOR;
// create the box (we use a box for the zone for convenience)
diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp
index ccdd2fca4f..b447048ac9 100644
--- a/assignment-client/src/audio/AudioMixerSlave.cpp
+++ b/assignment-client/src/audio/AudioMixerSlave.cpp
@@ -322,8 +322,16 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU
// stereo sources are not passed through HRTF
if (streamToAdd.isStereo()) {
- for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; ++i) {
- _mixSamples[i] += float(streamPopOutput[i] * gain / AudioConstants::MAX_SAMPLE_VALUE);
+
+ // apply the avatar gain adjustment
+ auto& hrtf = listenerNodeData.hrtfForStream(sourceNodeID, streamToAdd.getStreamIdentifier());
+ gain *= hrtf.getGainAdjustment();
+
+ const float scale = 1/32768.0f; // int16_t to float
+
+ for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
+ _mixSamples[2*i+0] += (float)streamPopOutput[2*i+0] * gain * scale;
+ _mixSamples[2*i+1] += (float)streamPopOutput[2*i+1] * gain * scale;
}
++stats.manualStereoMixes;
@@ -332,10 +340,13 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU
// echo sources are not passed through HRTF
if (isEcho) {
- for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i += 2) {
- auto monoSample = float(streamPopOutput[i / 2] * gain / AudioConstants::MAX_SAMPLE_VALUE);
- _mixSamples[i] += monoSample;
- _mixSamples[i + 1] += monoSample;
+
+ const float scale = 1/32768.0f; // int16_t to float
+
+ for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
+ float sample = (float)streamPopOutput[i] * gain * scale;
+ _mixSamples[2*i+0] += sample;
+ _mixSamples[2*i+1] += sample;
}
++stats.manualEchoMixes;
@@ -365,6 +376,11 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU
return;
}
+ if (streamToAdd.getType() == PositionalAudioStream::Injector) {
+ // apply per-avatar gain to positional audio injectors, which wouldn't otherwise be affected by PAL sliders
+ hrtf.setGainAdjustment(listenerNodeData.hrtfForStream(sourceNodeID, QUuid()).getGainAdjustment());
+ }
+
hrtf.render(_bufferSamples, _mixSamples, HRTF_DATASET_INDEX, azimuth, distance, gain,
AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
diff --git a/assignment-client/src/audio/AudioMixerSlavePool.cpp b/assignment-client/src/audio/AudioMixerSlavePool.cpp
index e28c96e259..dfe7ef56aa 100644
--- a/assignment-client/src/audio/AudioMixerSlavePool.cpp
+++ b/assignment-client/src/audio/AudioMixerSlavePool.cpp
@@ -9,11 +9,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "AudioMixerSlavePool.h"
+
#include
#include
-#include "AudioMixerSlavePool.h"
-
void AudioMixerSlaveThread::run() {
while (true) {
wait();
diff --git a/assignment-client/src/audio/AvatarAudioStream.cpp b/assignment-client/src/audio/AvatarAudioStream.cpp
index 42495b4dd0..22ea8c0617 100644
--- a/assignment-client/src/audio/AvatarAudioStream.cpp
+++ b/assignment-client/src/audio/AvatarAudioStream.cpp
@@ -9,10 +9,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "AvatarAudioStream.h"
+
#include
#include "AudioLogging.h"
-#include "AvatarAudioStream.h"
AvatarAudioStream::AvatarAudioStream(bool isStereo, int numStaticJitterFrames) :
PositionalAudioStream(PositionalAudioStream::Microphone, isStereo, numStaticJitterFrames) {}
diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp
index 929941c05c..9b5c4d4f30 100644
--- a/assignment-client/src/avatars/AvatarMixer.cpp
+++ b/assignment-client/src/avatars/AvatarMixer.cpp
@@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "AvatarMixer.h"
+
#include
#include
#include
@@ -31,8 +33,6 @@
#include
#include
-#include "AvatarMixer.h"
-
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
// FIXME - what we'd actually like to do is send to users at ~50% of their present rate down to 30hz. Assume 90 for now.
@@ -47,7 +47,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
auto& packetReceiver = DependencyManager::get()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AvatarData, this, "queueIncomingPacket");
packetReceiver.registerListener(PacketType::AdjustAvatarSorting, this, "handleAdjustAvatarSorting");
- packetReceiver.registerListener(PacketType::ViewFrustum, this, "handleViewFrustumPacket");
+ packetReceiver.registerListener(PacketType::AvatarQuery, this, "handleAvatarQueryPacket");
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
@@ -74,7 +74,7 @@ SharedNodePointer addOrUpdateReplicatedNode(const QUuid& nodeID, const HifiSockA
auto replicatedNode = DependencyManager::get()->addOrUpdateNode(nodeID, NodeType::Agent,
senderSockAddr,
senderSockAddr,
- true, true);
+ Node::NULL_LOCAL_ID, true, true);
replicatedNode->setLastHeardMicrostamp(usecTimestampNow());
@@ -112,8 +112,8 @@ void AvatarMixer::handleReplicatedPacket(QSharedPointer message
void AvatarMixer::handleReplicatedBulkAvatarPacket(QSharedPointer message) {
while (message->getBytesLeftToRead()) {
// first, grab the node ID for this replicated avatar
+ // Node ID is now part of user data, since ReplicatedBulkAvatarPacket is non-sourced.
auto nodeID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
-
// make sure we have an upstream replicated node that matches
auto replicatedNode = addOrUpdateReplicatedNode(nodeID, message->getSenderSockAddr());
@@ -127,7 +127,7 @@ void AvatarMixer::handleReplicatedBulkAvatarPacket(QSharedPointer::create(avatarByteArray, PacketType::AvatarData,
versionForPacketType(PacketType::AvatarData),
- message->getSenderSockAddr(), nodeID);
+ message->getSenderSockAddr(), Node::NULL_LOCAL_ID);
// queue up the replicated avatar data with the client data for the replicated node
auto start = usecTimestampNow();
@@ -517,15 +517,13 @@ void AvatarMixer::handleAdjustAvatarSorting(QSharedPointer mess
}
-void AvatarMixer::handleViewFrustumPacket(QSharedPointer message, SharedNodePointer senderNode) {
+void AvatarMixer::handleAvatarQueryPacket(QSharedPointer message, SharedNodePointer senderNode) {
auto start = usecTimestampNow();
getOrCreateClientData(senderNode);
- if (senderNode->getLinkedData()) {
- AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData());
- if (nodeData != nullptr) {
- nodeData->readViewFrustumPacket(message->getMessage());
- }
+ AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData());
+ if (nodeData) {
+ nodeData->readViewFrustumPacket(message->getMessage());
}
auto end = usecTimestampNow();
@@ -685,7 +683,7 @@ void AvatarMixer::sendStatsPacket() {
incomingPacketStats["handleNodeIgnoreRequestPacket"] = TIGHT_LOOP_STAT_UINT64(_handleNodeIgnoreRequestPacketElapsedTime);
incomingPacketStats["handleRadiusIgnoreRequestPacket"] = TIGHT_LOOP_STAT_UINT64(_handleRadiusIgnoreRequestPacketElapsedTime);
incomingPacketStats["handleRequestsDomainListDataPacket"] = TIGHT_LOOP_STAT_UINT64(_handleRequestsDomainListDataPacketElapsedTime);
- incomingPacketStats["handleViewFrustumPacket"] = TIGHT_LOOP_STAT_UINT64(_handleViewFrustumPacketElapsedTime);
+ incomingPacketStats["handleAvatarQueryPacket"] = TIGHT_LOOP_STAT_UINT64(_handleViewFrustumPacketElapsedTime);
singleCoreTasks["incoming_packets"] = incomingPacketStats;
singleCoreTasks["sendStats"] = (float)_sendStatsElapsedTime;
diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h
index 1fbfd7338b..9ef5903eec 100644
--- a/assignment-client/src/avatars/AvatarMixer.h
+++ b/assignment-client/src/avatars/AvatarMixer.h
@@ -46,7 +46,7 @@ public slots:
private slots:
void queueIncomingPacket(QSharedPointer message, SharedNodePointer node);
void handleAdjustAvatarSorting(QSharedPointer message, SharedNodePointer senderNode);
- void handleViewFrustumPacket(QSharedPointer message, SharedNodePointer senderNode);
+ void handleAvatarQueryPacket(QSharedPointer message, SharedNodePointer senderNode);
void handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode);
void handleKillAvatarPacket(QSharedPointer message, SharedNodePointer senderNode);
void handleNodeIgnoreRequestPacket(QSharedPointer message, SharedNodePointer senderNode);
diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp
index 268aba62d6..e185fe9167 100644
--- a/assignment-client/src/avatars/AvatarMixerClientData.cpp
+++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp
@@ -9,18 +9,16 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "AvatarMixerClientData.h"
+
#include
#include
#include
-#include "AvatarMixerClientData.h"
-
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) :
NodeData(nodeID)
{
- _currentViewFrustum.invalidate();
-
// in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID
_avatar->setID(nodeID);
}
@@ -129,11 +127,27 @@ void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self,
}
void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) {
- _currentViewFrustum.fromByteArray(message);
+ _currentViewFrustums.clear();
+
+ auto sourceBuffer = reinterpret_cast(message.constData());
+
+ uint8_t numFrustums = 0;
+ memcpy(&numFrustums, sourceBuffer, sizeof(numFrustums));
+ sourceBuffer += sizeof(numFrustums);
+
+ for (uint8_t i = 0; i < numFrustums; ++i) {
+ ConicalViewFrustum frustum;
+ sourceBuffer += frustum.deserialize(sourceBuffer);
+
+ _currentViewFrustums.push_back(frustum);
+ }
}
bool AvatarMixerClientData::otherAvatarInView(const AABox& otherAvatarBox) {
- return _currentViewFrustum.boxIntersectsKeyhole(otherAvatarBox);
+ return std::any_of(std::begin(_currentViewFrustums), std::end(_currentViewFrustums),
+ [&](const ConicalViewFrustum& viewFrustum) {
+ return viewFrustum.intersects(otherAvatarBox);
+ });
}
void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h
index 3c2e660cbc..e038e81505 100644
--- a/assignment-client/src/avatars/AvatarMixerClientData.h
+++ b/assignment-client/src/avatars/AvatarMixerClientData.h
@@ -28,7 +28,7 @@
#include
#include
#include
-#include
+#include
const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps";
const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps";
@@ -110,16 +110,12 @@ public:
bool getRequestsDomainListData() { return _requestsDomainListData; }
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
- ViewFrustum getViewFrustum() const { return _currentViewFrustum; }
+ const ConicalViewFrustums& getViewFrustums() const { return _currentViewFrustums; }
uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const;
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time);
- QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) {
- auto& lastOtherAvatarSentJoints = _lastOtherAvatarSentJoints[otherAvatar];
- lastOtherAvatarSentJoints.resize(_avatar->getJointCount());
- return lastOtherAvatarSentJoints;
- }
+ QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; }
void queuePacket(QSharedPointer message, SharedNodePointer node);
int processPackets(); // returns number of packets processed
@@ -154,7 +150,7 @@ private:
SimpleMovingAverage _avgOtherAvatarDataRate;
std::unordered_set _radiusIgnoredOthers;
- ViewFrustum _currentViewFrustum;
+ ConicalViewFrustums _currentViewFrustums;
int _recentOtherAvatarsInView { 0 };
int _recentOtherAvatarsOutOfView { 0 };
diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
index fb4b65726a..563aac879f 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
@@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "AvatarMixerSlave.h"
+
#include
#include
@@ -28,10 +30,8 @@
#include
#include
-
#include "AvatarMixer.h"
#include "AvatarMixerClientData.h"
-#include "AvatarMixerSlave.h"
void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) {
_begin = begin;
@@ -222,8 +222,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
};
// prepare to sort
- ViewFrustum cameraView = nodeData->getViewFrustum();
- PrioritySortUtil::PriorityQueue sortedAvatars(cameraView,
+ const auto& cameraViews = nodeData->getViewFrustums();
+ PrioritySortUtil::PriorityQueue sortedAvatars(cameraViews,
AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter,
AvatarData::_avatarSortCoefficientAge);
@@ -271,8 +271,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) {
otherNodeBox.setScaleStayCentered(minBubbleSize);
}
- // Quadruple the scale of both bounding boxes
- otherNodeBox.embiggen(4.0f);
+ // Change the scale of both bounding boxes
+ // (This is an arbitrary number determined empirically)
+ otherNodeBox.embiggen(2.4f);
// Perform the collision check between the two bounding boxes
if (nodeBox.touches(otherNodeBox)) {
@@ -381,6 +382,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
bool includeThisAvatar = true;
auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID());
QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID());
+
+ lastSentJointsForOther.resize(otherAvatar->getJointCount());
+
bool distanceAdjust = true;
glm::vec3 viewerPosition = myPosition;
AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray
@@ -392,21 +396,26 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
quint64 end = usecTimestampNow();
_stats.toByteArrayElapsedTime += (end - start);
- static const int MAX_ALLOWED_AVATAR_DATA = (1400 - NUM_BYTES_RFC4122_UUID);
- if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) {
- qCWarning(avatars) << "otherAvatar.toByteArray() resulted in very large buffer:" << bytes.size() << "... attempt to drop facial data";
+ auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID;
+ if (bytes.size() > maxAvatarDataBytes) {
+ qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
+ << "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data";
dropFaceTracking = true; // first try dropping the facial data
bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
- if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) {
- qCWarning(avatars) << "otherAvatar.toByteArray() without facial data resulted in very large buffer:" << bytes.size() << "... reduce to MinimumData";
+ if (bytes.size() > maxAvatarDataBytes) {
+ qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
+ << "without facial data resulted in very large buffer of" << bytes.size()
+ << "bytes - reducing to MinimumData";
bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther,
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
- if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) {
- qCWarning(avatars) << "otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!";
+ if (bytes.size() > maxAvatarDataBytes) {
+ qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
+ << "MinimumData resulted in very large buffer of" << bytes.size()
+ << "bytes - refusing to send avatar";
includeThisAvatar = false;
}
}
diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h
index bdddd5ceab..7be119c4b2 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.h
+++ b/assignment-client/src/avatars/AvatarMixerSlave.h
@@ -12,6 +12,8 @@
#ifndef hifi_AvatarMixerSlave_h
#define hifi_AvatarMixerSlave_h
+#include
+
class AvatarMixerClientData;
class AvatarMixerSlaveStats {
diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp
index 25b88686b7..962bba21d2 100644
--- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp
@@ -9,11 +9,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "AvatarMixerSlavePool.h"
+
#include
#include
-#include "AvatarMixerSlavePool.h"
-
void AvatarMixerSlaveThread::run() {
while (true) {
wait();
diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp
index 1f3f770867..e7210db83a 100644
--- a/assignment-client/src/avatars/ScriptableAvatar.cpp
+++ b/assignment-client/src/avatars/ScriptableAvatar.cpp
@@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "ScriptableAvatar.h"
+
#include
#include
#include
@@ -16,7 +18,6 @@
#include
#include
#include
-#include "ScriptableAvatar.h"
QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {
diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h
index b1039b5ac0..d34ad2d21e 100644
--- a/assignment-client/src/avatars/ScriptableAvatar.h
+++ b/assignment-client/src/avatars/ScriptableAvatar.h
@@ -17,20 +17,144 @@
#include
#include
+/**jsdoc
+ * The Avatar API is used to manipulate scriptable avatars on the domain. This API is a subset of the
+ * {@link MyAvatar} API.
+ *
+ *
Note: In the examples, use "Avatar" instead of "MyAvatar".
+ *
+ * @namespace Avatar
+ *
+ * @hifi-assignment-client
+ *
+ * @property {Vec3} position
+ * @property {number} scale
+ * @property {number} density Read-only.
+ * @property {Vec3} handPosition
+ * @property {number} bodyYaw - The rotation left or right about an axis running from the head to the feet of the avatar.
+ * Yaw is sometimes called "heading".
+ * @property {number} bodyPitch - The rotation about an axis running from shoulder to shoulder of the avatar. Pitch is
+ * sometimes called "elevation".
+ * @property {number} bodyRoll - The rotation about an axis running from the chest to the back of the avatar. Roll is
+ * sometimes called "bank".
+ * @property {Quat} orientation
+ * @property {Quat} headOrientation - The orientation of the avatar's head.
+ * @property {number} headPitch - The rotation about an axis running from ear to ear of the avatar's head. Pitch is
+ * sometimes called "elevation".
+ * @property {number} headYaw - The rotation left or right about an axis running from the base to the crown of the avatar's
+ * head. Yaw is sometimes called "heading".
+ * @property {number} headRoll - The rotation about an axis running from the nose to the back of the avatar's head. Roll is
+ * sometimes called "bank".
+ * @property {Vec3} velocity
+ * @property {Vec3} angularVelocity
+ * @property {number} audioLoudness
+ * @property {number} audioAverageLoudness
+ * @property {string} displayName
+ * @property {string} sessionDisplayName - Sanitized, defaulted version displayName that is defined by the AvatarMixer
+ * rather than by Interface clients. The result is unique among all avatars present at the time.
+ * @property {boolean} lookAtSnappingEnabled
+ * @property {string} skeletonModelURL
+ * @property {AttachmentData[]} attachmentData
+ * @property {string[]} jointNames - The list of joints in the current avatar model. Read-only.
+ * @property {Uuid} sessionUUID Read-only.
+ * @property {Mat4} sensorToWorldMatrix Read-only.
+ * @property {Mat4} controllerLeftHandMatrix Read-only.
+ * @property {Mat4} controllerRightHandMatrix Read-only.
+ * @property {number} sensorToWorldScale Read-only.
+ *
+ * @borrows MyAvatar.getDomainMinScale as getDomainMinScale
+ * @borrows MyAvatar.getDomainMaxScale as getDomainMaxScale
+ * @borrows MyAvatar.canMeasureEyeHeight as canMeasureEyeHeight
+ * @borrows MyAvatar.getEyeHeight as getEyeHeight
+ * @borrows MyAvatar.getHeight as getHeight
+ * @borrows MyAvatar.setHandState as setHandState
+ * @borrows MyAvatar.getHandState as getHandState
+ * @borrows MyAvatar.setRawJointData as setRawJointData
+ * @borrows MyAvatar.setJointData as setJointData
+ * @borrows MyAvatar.setJointRotation as setJointRotation
+ * @borrows MyAvatar.setJointTranslation as setJointTranslation
+ * @borrows MyAvatar.clearJointData as clearJointData
+ * @borrows MyAvatar.isJointDataValid as isJointDataValid
+ * @borrows MyAvatar.getJointRotation as getJointRotation
+ * @borrows MyAvatar.getJointTranslation as getJointTranslation
+ * @borrows MyAvatar.getJointRotations as getJointRotations
+ * @borrows MyAvatar.getJointTranslations as getJointTranslations
+ * @borrows MyAvatar.setJointRotations as setJointRotations
+ * @borrows MyAvatar.setJointTranslations as setJointTranslations
+ * @borrows MyAvatar.clearJointsData as clearJointsData
+ * @borrows MyAvatar.getJointIndex as getJointIndex
+ * @borrows MyAvatar.getJointNames as getJointNames
+ * @borrows MyAvatar.setBlendshape as setBlendshape
+ * @borrows MyAvatar.getAttachmentsVariant as getAttachmentsVariant
+ * @borrows MyAvatar.setAttachmentsVariant as setAttachmentsVariant
+ * @borrows MyAvatar.updateAvatarEntity as updateAvatarEntity
+ * @borrows MyAvatar.clearAvatarEntity as clearAvatarEntity
+ * @borrows MyAvatar.setForceFaceTrackerConnected as setForceFaceTrackerConnected
+ * @borrows MyAvatar.getAttachmentData as getAttachmentData
+ * @borrows MyAvatar.setAttachmentData as setAttachmentData
+ * @borrows MyAvatar.attach as attach
+ * @borrows MyAvatar.detachOne as detachOne
+ * @borrows MyAvatar.detachAll as detachAll
+ * @borrows MyAvatar.getAvatarEntityData as getAvatarEntityData
+ * @borrows MyAvatar.setAvatarEntityData as setAvatarEntityData
+ * @borrows MyAvatar.getSensorToWorldMatrix as getSensorToWorldMatrix
+ * @borrows MyAvatar.getSensorToWorldScale as getSensorToWorldScale
+ * @borrows MyAvatar.getControllerLeftHandMatrix as getControllerLeftHandMatrix
+ * @borrows MyAvatar.getControllerRightHandMatrix as getControllerRightHandMatrix
+ * @borrows MyAvatar.getDataRate as getDataRate
+ * @borrows MyAvatar.getUpdateRate as getUpdateRate
+ * @borrows MyAvatar.displayNameChanged as displayNameChanged
+ * @borrows MyAvatar.sessionDisplayNameChanged as sessionDisplayNameChanged
+ * @borrows MyAvatar.skeletonModelURLChanged as skeletonModelURLChanged
+ * @borrows MyAvatar.lookAtSnappingChanged as lookAtSnappingChanged
+ * @borrows MyAvatar.sessionUUIDChanged as sessionUUIDChanged
+ * @borrows MyAvatar.sendAvatarDataPacket as sendAvatarDataPacket
+ * @borrows MyAvatar.sendIdentityPacket as sendIdentityPacket
+ * @borrows MyAvatar.setJointMappingsFromNetworkReply as setJointMappingsFromNetworkReply
+ * @borrows MyAvatar.setSessionUUID as setSessionUUID
+ * @borrows MyAvatar.getAbsoluteJointRotationInObjectFrame as getAbsoluteJointRotationInObjectFrame
+ * @borrows MyAvatar.getAbsoluteJointTranslationInObjectFrame as getAbsoluteJointTranslationInObjectFrame
+ * @borrows MyAvatar.setAbsoluteJointRotationInObjectFrame as setAbsoluteJointRotationInObjectFrame
+ * @borrows MyAvatar.setAbsoluteJointTranslationInObjectFrame as setAbsoluteJointTranslationInObjectFrame
+ * @borrows MyAvatar.getTargetScale as getTargetScale
+ * @borrows MyAvatar.resetLastSent as resetLastSent
+ */
+
class ScriptableAvatar : public AvatarData, public Dependency {
Q_OBJECT
public:
-
+
+ /**jsdoc
+ * @function Avatar.startAnimation
+ * @param {string} url
+ * @param {number} [fps=30]
+ * @param {number} [priority=1]
+ * @param {boolean} [loop=false]
+ * @param {boolean} [hold=false]
+ * @param {number} [firstFrame=0]
+ * @param {number} [lastFrame=3.403e+38]
+ * @param {string[]} [maskedJoints=[]]
+ */
/// Allows scripts to run animations.
Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false,
- bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
+ bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX,
+ const QStringList& maskedJoints = QStringList());
+
+ /**jsdoc
+ * @function Avatar.stopAnimation
+ */
Q_INVOKABLE void stopAnimation();
+
+ /**jsdoc
+ * @function Avatar.getAnimationDetails
+ * @returns {Avatar.AnimationDetails}
+ */
Q_INVOKABLE AnimationDetails getAnimationDetails();
+
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override;
-
private slots:
void update(float deltatime);
diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp
deleted file mode 100644
index 999a05f2e2..0000000000
--- a/assignment-client/src/entities/EntityPriorityQueue.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-// EntityPriorityQueue.cpp
-// assignment-client/src/entities
-//
-// Created by Andrew Meadows 2017.08.08
-// Copyright 2017 High Fidelity, Inc.
-//
-// Distributed under the Apache License, Version 2.0.
-// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-#include "EntityPriorityQueue.h"
-
-const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f;
-const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f;
-const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f;
-
-void ConicalView::set(const ViewFrustum& viewFrustum) {
- // The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part.
- // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum.
- _position = viewFrustum.getPosition();
- _direction = viewFrustum.getDirection();
-
- // We cache the sin and cos of the half angle of the cone that bounds the frustum.
- // (the math here is left as an exercise for the reader)
- float A = viewFrustum.getAspectRatio();
- float t = tanf(0.5f * viewFrustum.getFieldOfView());
- _cosAngle = 1.0f / sqrtf(1.0f + (A * A + 1.0f) * (t * t));
- _sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle);
-
- _radius = viewFrustum.getCenterRadius();
-}
-
-float ConicalView::computePriority(const AACube& cube) const {
- glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame
- float d = glm::length(p); // distance to center of bounding sphere
- float r = 0.5f * cube.getScale(); // radius of bounding sphere
- if (d < _radius + r) {
- return r;
- }
- // We check the angle between the center of the cube and the _direction of the view.
- // If it is less than the sum of the half-angle from center of cone to outer edge plus
- // the half apparent angle of the bounding sphere then it is in view.
- //
- // The math here is left as an exercise for the reader with the following hints:
- // (1) We actually check the dot product of the cube's local position rather than the angle and
- // (2) we take advantage of this trig identity: cos(A+B) = cos(A)*cos(B) - sin(A)*sin(B)
- if (glm::dot(p, _direction) > sqrtf(d * d - r * r) * _cosAngle - r * _sinAngle) {
- const float AVOID_DIVIDE_BY_ZERO = 0.001f;
- return r / (d + AVOID_DIVIDE_BY_ZERO);
- }
- return PrioritizedEntity::DO_NOT_SEND;
-}
diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h
deleted file mode 100644
index e308d9b549..0000000000
--- a/assignment-client/src/entities/EntityPriorityQueue.h
+++ /dev/null
@@ -1,66 +0,0 @@
-//
-// EntityPriorityQueue.h
-// assignment-client/src/entities
-//
-// Created by Andrew Meadows 2017.08.08
-// Copyright 2017 High Fidelity, Inc.
-//
-// Distributed under the Apache License, Version 2.0.
-// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-#ifndef hifi_EntityPriorityQueue_h
-#define hifi_EntityPriorityQueue_h
-
-#include
-
-#include
-#include
-
-const float SQRT_TWO_OVER_TWO = 0.7071067811865f;
-const float DEFAULT_VIEW_RADIUS = 10.0f;
-
-// ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority.
-class ConicalView {
-public:
- ConicalView() {}
- ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); }
- void set(const ViewFrustum& viewFrustum);
- float computePriority(const AACube& cube) const;
-private:
- glm::vec3 _position { 0.0f, 0.0f, 0.0f };
- glm::vec3 _direction { 0.0f, 0.0f, 1.0f };
- float _sinAngle { SQRT_TWO_OVER_TWO };
- float _cosAngle { SQRT_TWO_OVER_TWO };
- float _radius { DEFAULT_VIEW_RADIUS };
-};
-
-// PrioritizedEntity is a placeholder in a sorted queue.
-class PrioritizedEntity {
-public:
- static const float DO_NOT_SEND;
- static const float FORCE_REMOVE;
- static const float WHEN_IN_DOUBT_PRIORITY;
-
- PrioritizedEntity(EntityItemPointer entity, float priority, bool forceRemove = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceRemove(forceRemove) {}
- EntityItemPointer getEntity() const { return _weakEntity.lock(); }
- EntityItem* getRawEntityPointer() const { return _rawEntityPointer; }
- float getPriority() const { return _priority; }
- bool shouldForceRemove() const { return _forceRemove; }
-
- class Compare {
- public:
- bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; }
- };
- friend class Compare;
-
-private:
- EntityItemWeakPointer _weakEntity;
- EntityItem* _rawEntityPointer;
- float _priority;
- bool _forceRemove;
-};
-
-using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >;
-
-#endif // hifi_EntityPriorityQueue_h
diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp
index d2bcbf2886..3ca8c1ecd1 100644
--- a/assignment-client/src/entities/EntityServer.cpp
+++ b/assignment-client/src/entities/EntityServer.cpp
@@ -9,21 +9,23 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "EntityServer.h"
+
#include
#include
+#include
+#include
+
#include
#include
#include
#include
#include
#include
-#include
-#include
#include
#include "AssignmentParentFinder.h"
#include "EntityNodeData.h"
-#include "EntityServer.h"
#include "EntityServerConsts.h"
#include "EntityTreeSendThread.h"
@@ -42,6 +44,7 @@ EntityServer::EntityServer(ReceivedMessage& message) :
auto& packetReceiver = DependencyManager::get()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
+ PacketType::EntityClone,
PacketType::EntityEdit,
PacketType::EntityErase,
PacketType::EntityPhysics,
@@ -363,7 +366,9 @@ void EntityServer::nodeAdded(SharedNodePointer node) {
void EntityServer::nodeKilled(SharedNodePointer node) {
EntityTreePointer tree = std::static_pointer_cast(_tree);
- tree->deleteDescendantsOfAvatar(node->getUUID());
+ tree->withWriteLock([&] {
+ tree->deleteDescendantsOfAvatar(node->getUUID());
+ });
tree->forgetAvatarID(node->getUUID());
OctreeServer::nodeKilled(node);
}
@@ -378,10 +383,15 @@ void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const
}
void EntityServer::trackViewerGone(const QUuid& sessionID) {
- QWriteLocker locker(&_viewerSendingStatsLock);
- _viewerSendingStats.remove(sessionID);
+ {
+ QWriteLocker locker(&_viewerSendingStatsLock);
+ _viewerSendingStats.remove(sessionID);
+ }
+
if (_entitySimulation) {
- _entitySimulation->clearOwnership(sessionID);
+ _tree->withReadLock([&] {
+ _entitySimulation->clearOwnership(sessionID);
+ });
}
}
@@ -451,8 +461,6 @@ void EntityServer::domainSettingsRequestFailed() {
void EntityServer::startDynamicDomainVerification() {
qCDebug(entities) << "Starting Dynamic Domain Verification...";
- QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}"));
-
EntityTreePointer tree = std::static_pointer_cast(_tree);
QHash localMap(tree->getEntityCertificateIDMap());
@@ -460,15 +468,19 @@ void EntityServer::startDynamicDomainVerification() {
qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap";
while (i.hasNext()) {
i.next();
+ const auto& certificateID = i.key();
+ const auto& entityID = i.value();
- EntityItemPointer entity = tree->findEntityByEntityItemID(i.value());
+ EntityItemPointer entity = tree->findEntityByEntityItemID(entityID);
if (entity) {
if (!entity->getProperties().verifyStaticCertificateProperties()) {
- qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed"
+ qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << entityID << "failed"
<< "static certificate verification.";
// Delete the entity if it doesn't pass static certificate verification
- tree->deleteEntity(i.value(), true);
+ tree->withWriteLock([&] {
+ tree->deleteEntity(entityID, true);
+ });
} else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
@@ -477,39 +489,46 @@ void EntityServer::startDynamicDomainVerification() {
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location");
QJsonObject request;
- request["certificate_id"] = i.key();
+ request["certificate_id"] = certificateID;
networkRequest.setUrl(requestURL);
- QNetworkReply* networkReply = NULL;
- networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
+ QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
+
+ connect(networkReply, &QNetworkReply::finished, this, [this, entityID, networkReply] {
+ EntityTreePointer tree = std::static_pointer_cast(_tree);
- connect(networkReply, &QNetworkReply::finished, [=]() {
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
jsonObject = jsonObject["data"].toObject();
if (networkReply->error() == QNetworkReply::NoError) {
+ QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}"));
if (jsonObject["domain_id"].toString() != thisDomainID) {
+ EntityItemPointer entity = tree->findEntityByEntityItemID(entityID);
if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) {
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
- << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value();
- tree->deleteEntity(i.value(), true);
+ << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID;
+ tree->withWriteLock([&] {
+ tree->deleteEntity(entityID, true);
+ });
} else {
- qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << i.value();
+ qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID;
}
} else {
- qCDebug(entities) << "Entity passed dynamic domain verification:" << i.value();
+ qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID;
}
} else {
- qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << i.value()
+ qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityID
<< "More info:" << jsonObject;
- tree->deleteEntity(i.value(), true);
+ tree->withWriteLock([&] {
+ tree->deleteEntity(entityID, true);
+ });
}
networkReply->deleteLater();
});
}
} else {
- qCWarning(entities) << "During DDV, an entity with ID" << i.value() << "was NOT found in the Entity Tree!";
+ qCWarning(entities) << "During DDV, an entity with ID" << entityID << "was NOT found in the Entity Tree!";
}
}
diff --git a/assignment-client/src/entities/EntityTreeHeadlessViewer.h b/assignment-client/src/entities/EntityTreeHeadlessViewer.h
index 17bb37186a..f4d5911821 100644
--- a/assignment-client/src/entities/EntityTreeHeadlessViewer.h
+++ b/assignment-client/src/entities/EntityTreeHeadlessViewer.h
@@ -23,6 +23,13 @@
class EntitySimulation;
+/**jsdoc
+ * @namespace EntityViewer
+ *
+ * @hifi-assignment-client
+ */
+// API functions are defined in OctreeHeadlessViewer.
+
// Generic client side Octree renderer class.
class EntityTreeHeadlessViewer : public OctreeHeadlessViewer {
Q_OBJECT
diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp
index 94d21f1c9a..f008ef9925 100644
--- a/assignment-client/src/entities/EntityTreeSendThread.cpp
+++ b/assignment-client/src/entities/EntityTreeSendThread.cpp
@@ -103,48 +103,41 @@ void EntityTreeSendThread::preDistributionProcessing() {
void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene) {
if (viewFrustumChanged || _traversal.finished()) {
- ViewFrustum viewFrustum;
- nodeData->copyCurrentViewFrustum(viewFrustum);
EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot());
+
+
+ DiffTraversal::View newView;
+ newView.viewFrustums = nodeData->getCurrentViews();
+
int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
- startNewTraversal(viewFrustum, root, lodLevelOffset, nodeData->getUsesFrustum());
+ newView.lodScaleFactor = powf(2.0f, lodLevelOffset);
+
+ startNewTraversal(newView, root);
// When the viewFrustum changed the sort order may be incorrect, so we re-sort
// and also use the opportunity to cull anything no longer in view
if (viewFrustumChanged && !_sendQueue.empty()) {
EntityPriorityQueue prevSendQueue;
- _sendQueue.swap(prevSendQueue);
- _entitiesInQueue.clear();
+ std::swap(_sendQueue, prevSendQueue);
+ assert(_sendQueue.empty());
+
// Re-add elements from previous traversal if they still need to be sent
- float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
- glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
while (!prevSendQueue.empty()) {
EntityItemPointer entity = prevSendQueue.top().getEntity();
bool forceRemove = prevSendQueue.top().shouldForceRemove();
prevSendQueue.pop();
if (entity) {
- if (!forceRemove) {
- bool success = false;
- AACube cube = entity->getQueryAACube(success);
- if (success) {
- if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
- float priority = _conicalView.computePriority(cube);
- if (priority != PrioritizedEntity::DO_NOT_SEND) {
- float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
- float angularDiameter = cube.getScale() / distance;
- if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
- _sendQueue.push(PrioritizedEntity(entity, priority));
- _entitiesInQueue.insert(entity.get());
- }
- }
- }
- } else {
- _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
- _entitiesInQueue.insert(entity.get());
- }
+ float priority = PrioritizedEntity::DO_NOT_SEND;
+
+ if (forceRemove) {
+ priority = PrioritizedEntity::FORCE_REMOVE;
} else {
- _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
- _entitiesInQueue.insert(entity.get());
+ const auto& view = _traversal.getCurrentView();
+ priority = view.computePriority(entity);
+ }
+
+ if (priority != PrioritizedEntity::DO_NOT_SEND) {
+ _sendQueue.emplace(entity, priority, forceRemove);
}
}
}
@@ -215,10 +208,9 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil
return hasNewChild || hasNewDescendants;
}
-void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset,
- bool usesViewFrustum) {
+void EntityTreeSendThread::startNewTraversal(const DiffTraversal::View& view, EntityTreeElementPointer root) {
- DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum);
+ DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root);
// there are three types of traversal:
//
// (1) FirstTime = at login --> find everything in view
@@ -226,167 +218,80 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTree
// (3) Differential = view has changed --> find what has changed or in new view but not old
//
// The "scanCallback" we provide to the traversal depends on the type:
- //
- // The _conicalView is updated here as a cached view approximation used by the lambdas for efficient
- // computation of entity sorting priorities.
- //
- _conicalView.set(_traversal.getCurrentView());
switch (type) {
case DiffTraversal::First:
// When we get to a First traversal, clear the _knownState
_knownState.clear();
- if (usesViewFrustum) {
- float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
- glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
- _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
- next.element->forEachEntity([=](EntityItemPointer entity) {
- // Bail early if we've already checked this entity this frame
- if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
- return;
- }
- bool success = false;
- AACube cube = entity->getQueryAACube(success);
- if (success) {
- if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
- // Check the size of the entity, it's possible that a "too small to see" entity is included in a
- // larger octree cell because of its position (for example if it crosses the boundary of a cell it
- // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
- // before we consider including it.
- float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
- float angularDiameter = cube.getScale() / distance;
- if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
- float priority = _conicalView.computePriority(cube);
- _sendQueue.push(PrioritizedEntity(entity, priority));
- _entitiesInQueue.insert(entity.get());
- }
- }
- } else {
- _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
- _entitiesInQueue.insert(entity.get());
- }
- });
- });
- } else {
- _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
- next.element->forEachEntity([this](EntityItemPointer entity) {
- // Bail early if we've already checked this entity this frame
- if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
- return;
- }
- _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
- _entitiesInQueue.insert(entity.get());
- });
- });
- }
- break;
- case DiffTraversal::Repeat:
- if (usesViewFrustum) {
- float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
- glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
- _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
- uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
- if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
- next.element->forEachEntity([=](EntityItemPointer entity) {
- // Bail early if we've already checked this entity this frame
- if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
- return;
- }
- auto knownTimestamp = _knownState.find(entity.get());
- if (knownTimestamp == _knownState.end()) {
- bool success = false;
- AACube cube = entity->getQueryAACube(success);
- if (success) {
- if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
- // See the DiffTraversal::First case for an explanation of the "entity is too small" check
- float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
- float angularDiameter = cube.getScale() / distance;
- if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
- float priority = _conicalView.computePriority(cube);
- _sendQueue.push(PrioritizedEntity(entity, priority));
- _entitiesInQueue.insert(entity.get());
- }
- }
- } else {
- _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
- _entitiesInQueue.insert(entity.get());
- }
- } else if (entity->getLastEdited() > knownTimestamp->second) {
- // it is known and it changed --> put it on the queue with any priority
- // TODO: sort these correctly
- _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
- _entitiesInQueue.insert(entity.get());
- }
- });
- }
- });
- } else {
- _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
- uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
- if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
- next.element->forEachEntity([this](EntityItemPointer entity) {
- // Bail early if we've already checked this entity this frame
- if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
- return;
- }
- auto knownTimestamp = _knownState.find(entity.get());
- if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) {
- _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
- _entitiesInQueue.insert(entity.get());
- }
- });
- }
- });
- }
- break;
- case DiffTraversal::Differential:
- assert(usesViewFrustum);
- float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
- glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
- float completedLODScaleFactor = _traversal.getCompletedLODScaleFactor();
- glm::vec3 completedViewPosition = _traversal.getCompletedView().getPosition();
- _traversal.setScanCallback([=] (DiffTraversal::VisibleElement& next) {
- next.element->forEachEntity([=](EntityItemPointer entity) {
+ _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
+ next.element->forEachEntity([&](EntityItemPointer entity) {
// Bail early if we've already checked this entity this frame
- if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
+ if (_sendQueue.contains(entity.get())) {
return;
}
+ const auto& view = _traversal.getCurrentView();
+ float priority = view.computePriority(entity);
+
+ if (priority != PrioritizedEntity::DO_NOT_SEND) {
+ _sendQueue.emplace(entity, priority);
+ }
+ });
+ });
+ break;
+ case DiffTraversal::Repeat:
+ _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
+ uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
+ if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
+ next.element->forEachEntity([&](EntityItemPointer entity) {
+ // Bail early if we've already checked this entity this frame
+ if (_sendQueue.contains(entity.get())) {
+ return;
+ }
+ float priority = PrioritizedEntity::DO_NOT_SEND;
+
+ auto knownTimestamp = _knownState.find(entity.get());
+ if (knownTimestamp == _knownState.end()) {
+ const auto& view = _traversal.getCurrentView();
+ priority = view.computePriority(entity);
+
+ } else if (entity->getLastEdited() > knownTimestamp->second ||
+ entity->getLastChangedOnServer() > knownTimestamp->second) {
+ // it is known and it changed --> put it on the queue with any priority
+ // TODO: sort these correctly
+ priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
+ }
+
+ if (priority != PrioritizedEntity::DO_NOT_SEND) {
+ _sendQueue.emplace(entity, priority);
+ }
+ });
+ }
+ });
+ break;
+ case DiffTraversal::Differential:
+ assert(view.usesViewFrustums());
+ _traversal.setScanCallback([this] (DiffTraversal::VisibleElement& next) {
+ next.element->forEachEntity([&](EntityItemPointer entity) {
+ // Bail early if we've already checked this entity this frame
+ if (_sendQueue.contains(entity.get())) {
+ return;
+ }
+ float priority = PrioritizedEntity::DO_NOT_SEND;
+
auto knownTimestamp = _knownState.find(entity.get());
if (knownTimestamp == _knownState.end()) {
- bool success = false;
- AACube cube = entity->getQueryAACube(success);
- if (success) {
- if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
- // See the DiffTraversal::First case for an explanation of the "entity is too small" check
- float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
- float angularDiameter = cube.getScale() / distance;
- if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
- if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) {
- float priority = _conicalView.computePriority(cube);
- _sendQueue.push(PrioritizedEntity(entity, priority));
- _entitiesInQueue.insert(entity.get());
- } else {
- // If this entity was skipped last time because it was too small, we still need to send it
- distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE;
- angularDiameter = cube.getScale() / distance;
- if (angularDiameter <= MIN_ENTITY_ANGULAR_DIAMETER * completedLODScaleFactor) {
- // this object was skipped in last completed traversal
- float priority = _conicalView.computePriority(cube);
- _sendQueue.push(PrioritizedEntity(entity, priority));
- _entitiesInQueue.insert(entity.get());
- }
- }
- }
- }
- } else {
- _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
- _entitiesInQueue.insert(entity.get());
- }
- } else if (entity->getLastEdited() > knownTimestamp->second) {
+ const auto& view = _traversal.getCurrentView();
+ priority = view.computePriority(entity);
+
+ } else if (entity->getLastEdited() > knownTimestamp->second ||
+ entity->getLastChangedOnServer() > knownTimestamp->second) {
// it is known and it changed --> put it on the queue with any priority
// TODO: sort these correctly
- _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
- _entitiesInQueue.insert(entity.get());
+ priority = PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY;
+ }
+
+ if (priority != PrioritizedEntity::DO_NOT_SEND) {
+ _sendQueue.emplace(entity, priority);
}
});
});
@@ -475,11 +380,10 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
}
}
_sendQueue.pop();
- _entitiesInQueue.erase(entity.get());
}
nodeData->stats.encodeStopped();
if (_sendQueue.empty()) {
- assert(_entitiesInQueue.empty());
+ assert(_sendQueue.empty());
params.stopReason = EncodeBitstreamParams::FINISHED;
_extraEncodeData->entities.clear();
}
@@ -497,18 +401,15 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) {
if (entity) {
- if (_entitiesInQueue.find(entity.get()) == _entitiesInQueue.end() && _knownState.find(entity.get()) != _knownState.end()) {
- bool success = false;
- AACube cube = entity->getQueryAACube(success);
- if (success) {
- // We can force a removal from _knownState if the current view is used and entity is out of view
- if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
- _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
- _entitiesInQueue.insert(entity.get());
- }
- } else {
- _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true));
- _entitiesInQueue.insert(entity.get());
+ if (!_sendQueue.contains(entity.get()) && _knownState.find(entity.get()) != _knownState.end()) {
+ const auto& view = _traversal.getCurrentView();
+ float priority = view.computePriority(entity);
+
+ // We can force a removal from _knownState if the current view is used and entity is out of view
+ if (priority == PrioritizedEntity::DO_NOT_SEND) {
+ _sendQueue.emplace(entity, PrioritizedEntity::FORCE_REMOVE, true);
+ } else if (priority == PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY) {
+ _sendQueue.emplace(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true);
}
}
}
diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h
index 594f423838..1305d7bfc7 100644
--- a/assignment-client/src/entities/EntityTreeSendThread.h
+++ b/assignment-client/src/entities/EntityTreeSendThread.h
@@ -17,8 +17,9 @@
#include "../octree/OctreeSendThread.h"
#include
+#include
+#include
-#include "EntityPriorityQueue.h"
class EntityNodeData;
class EntityItem;
@@ -41,21 +42,16 @@ private:
bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
- void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset,
- bool usesViewFrustum);
+ void startNewTraversal(const DiffTraversal::View& viewFrustum, EntityTreeElementPointer root);
bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override;
void preDistributionProcessing() override;
bool hasSomethingToSend(OctreeQueryNode* nodeData) override { return !_sendQueue.empty(); }
bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) override { return viewFrustumChanged || _traversal.finished(); }
- void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) override {};
- bool shouldTraverseAndSend(OctreeQueryNode* nodeData) override { return true; }
DiffTraversal _traversal;
EntityPriorityQueue _sendQueue;
- std::unordered_set _entitiesInQueue;
std::unordered_map _knownState;
- ConicalView _conicalView; // cached optimized view for fast priority calculations
// packet construction stuff
EntityTreeElementExtraEncodeDataPointer _extraEncodeData { new EntityTreeElementExtraEncodeData() };
diff --git a/assignment-client/src/messages/MessagesMixer.cpp b/assignment-client/src/messages/MessagesMixer.cpp
index 4bf708cf34..c11c8f40a0 100644
--- a/assignment-client/src/messages/MessagesMixer.cpp
+++ b/assignment-client/src/messages/MessagesMixer.cpp
@@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "MessagesMixer.h"
+
#include
#include
#include
@@ -16,7 +18,6 @@
#include
#include
#include
-#include "MessagesMixer.h"
const QString MESSAGES_MIXER_LOGGING_NAME = "messages-mixer";
diff --git a/assignment-client/src/octree/OctreeHeadlessViewer.cpp b/assignment-client/src/octree/OctreeHeadlessViewer.cpp
index d3b20fb623..039dbdab78 100644
--- a/assignment-client/src/octree/OctreeHeadlessViewer.cpp
+++ b/assignment-client/src/octree/OctreeHeadlessViewer.cpp
@@ -14,32 +14,21 @@
#include
#include
-
-OctreeHeadlessViewer::OctreeHeadlessViewer() {
- _viewFrustum.setProjection(glm::perspective(glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES), DEFAULT_ASPECT_RATIO, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP));
-}
-
void OctreeHeadlessViewer::queryOctree() {
char serverType = getMyNodeType();
PacketType packetType = getMyQueryMessageType();
- _octreeQuery.setCameraPosition(_viewFrustum.getPosition());
- _octreeQuery.setCameraOrientation(_viewFrustum.getOrientation());
- _octreeQuery.setCameraFov(_viewFrustum.getFieldOfView());
- _octreeQuery.setCameraAspectRatio(_viewFrustum.getAspectRatio());
- _octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
- _octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
- _octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
- _octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
- _octreeQuery.setOctreeSizeScale(_voxelSizeScale);
- _octreeQuery.setBoundaryLevelAdjust(_boundaryLevelAdjust);
+ if (_hasViewFrustum) {
+ ConicalViewFrustums views { _viewFrustum };
+ _octreeQuery.setConicalViews(views);
+ } else {
+ _octreeQuery.clearConicalViews();
+ }
auto nodeList = DependencyManager::get();
auto node = nodeList->soloNodeOfType(serverType);
if (node && node->getActiveSocket()) {
- _octreeQuery.setMaxQueryPacketsPerSecond(getMaxPacketsPerSecond());
-
auto queryPacket = NLPacket::create(packetType);
// encode the query data
diff --git a/assignment-client/src/octree/OctreeHeadlessViewer.h b/assignment-client/src/octree/OctreeHeadlessViewer.h
index feb8211c39..a2a49dceb8 100644
--- a/assignment-client/src/octree/OctreeHeadlessViewer.h
+++ b/assignment-client/src/octree/OctreeHeadlessViewer.h
@@ -20,46 +20,114 @@
class OctreeHeadlessViewer : public OctreeProcessor {
Q_OBJECT
public:
- OctreeHeadlessViewer();
- virtual ~OctreeHeadlessViewer() {};
-
OctreeQuery& getOctreeQuery() { return _octreeQuery; }
static int parseOctreeStats(QSharedPointer message, SharedNodePointer sourceNode);
static void trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket);
public slots:
+
+ /**jsdoc
+ * @function EntityViewer.queryOctree
+ */
void queryOctree();
+
// setters for camera attributes
- void setPosition(const glm::vec3& position) { _viewFrustum.setPosition(position); }
- void setOrientation(const glm::quat& orientation) { _viewFrustum.setOrientation(orientation); }
- void setCenterRadius(float radius) { _viewFrustum.setCenterRadius(radius); }
- void setKeyholeRadius(float radius) { _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
+
+ /**jsdoc
+ * @function EntityViewer.setPosition
+ * @param {Vec3} position
+ */
+ void setPosition(const glm::vec3& position) { _hasViewFrustum = true; _viewFrustum.setPosition(position); }
+
+ /**jsdoc
+ * @function EntityViewer.setOrientation
+ * @param {Quat} orientation
+ */
+ void setOrientation(const glm::quat& orientation) { _hasViewFrustum = true; _viewFrustum.setOrientation(orientation); }
+
+ /**jsdoc
+ * @function EntityViewer.setCenterRadius
+ * @param {number} radius
+ */
+ void setCenterRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); }
+
+ /**jsdoc
+ * @function EntityViewer.setKeyholeRadius
+ * @param {number} radius
+ * @deprecated Use {@link EntityViewer.setCenterRadius|setCenterRadius} instead.
+ */
+ void setKeyholeRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
+
// setters for LOD and PPS
- void setVoxelSizeScale(float sizeScale) { _voxelSizeScale = sizeScale; }
- void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; }
- void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _maxPacketsPerSecond = maxPacketsPerSecond; }
+
+ /**jsdoc
+ * @function EntityViewer.setVoxelSizeScale
+ * @param {number} sizeScale
+ */
+ void setVoxelSizeScale(float sizeScale) { _octreeQuery.setOctreeSizeScale(sizeScale) ; }
+
+ /**jsdoc
+ * @function EntityViewer.setBoundaryLevelAdjust
+ * @param {number} boundaryLevelAdjust
+ */
+ void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _octreeQuery.setBoundaryLevelAdjust(boundaryLevelAdjust); }
+
+ /**jsdoc
+ * @function EntityViewer.setMaxPacketsPerSecond
+ * @param {number} maxPacketsPerSecond
+ */
+ void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _octreeQuery.setMaxQueryPacketsPerSecond(maxPacketsPerSecond); }
// getters for camera attributes
+
+ /**jsdoc
+ * @function EntityViewer.getPosition
+ * @returns {Vec3}
+ */
const glm::vec3& getPosition() const { return _viewFrustum.getPosition(); }
+
+ /**jsdoc
+ * @function EntityViewer.getOrientation
+ * @returns {Quat}
+ */
const glm::quat& getOrientation() const { return _viewFrustum.getOrientation(); }
- // getters for LOD and PPS
- float getVoxelSizeScale() const { return _voxelSizeScale; }
- int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
- int getMaxPacketsPerSecond() const { return _maxPacketsPerSecond; }
+ // getters for LOD and PPS
+
+ /**jsdoc
+ * @function EntityViewer.getVoxelSizeScale
+ * @returns {number}
+ */
+ float getVoxelSizeScale() const { return _octreeQuery.getOctreeSizeScale(); }
+
+ /**jsdoc
+ * @function EntityViewer.getBoundaryLevelAdjust
+ * @returns {number}
+ */
+ int getBoundaryLevelAdjust() const { return _octreeQuery.getBoundaryLevelAdjust(); }
+
+ /**jsdoc
+ * @function EntityViewer.getMaxPacketsPerSecond
+ * @returns {number}
+ */
+ int getMaxPacketsPerSecond() const { return _octreeQuery.getMaxQueryPacketsPerSecond(); }
+
+
+ /**jsdoc
+ * @function EntityViewer.getOctreeElementsCount
+ * @returns {number}
+ */
unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); }
private:
OctreeQuery _octreeQuery;
+ bool _hasViewFrustum { false };
ViewFrustum _viewFrustum;
- float _voxelSizeScale { DEFAULT_OCTREE_SIZE_SCALE };
- int _boundaryLevelAdjust { 0 };
- int _maxPacketsPerSecond { DEFAULT_MAX_OCTREE_PPS };
};
#endif // hifi_OctreeHeadlessViewer_h
diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp
index bce6e7fe44..ef532bb33f 100644
--- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp
+++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp
@@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "OctreeInboundPacketProcessor.h"
+
#include
#include
@@ -17,7 +19,6 @@
#include "OctreeServer.h"
#include "OctreeServerConsts.h"
-#include "OctreeInboundPacketProcessor.h"
static QUuid DEFAULT_NODE_ID_REF;
const quint64 TOO_LONG_SINCE_LAST_NACK = 1 * USECS_PER_SECOND;
diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp
index d5b9da7353..e9aa44b970 100644
--- a/assignment-client/src/octree/OctreeSendThread.cpp
+++ b/assignment-client/src/octree/OctreeSendThread.cpp
@@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#include "OctreeSendThread.h"
+
#include
#include
@@ -17,7 +19,6 @@
#include
#include
-#include "OctreeSendThread.h"
#include "OctreeServer.h"
#include "OctreeServerConsts.h"
#include "OctreeLogging.h"
@@ -304,23 +305,6 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
return numPackets;
}
-void OctreeSendThread::preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) {
- // If we're starting a full scene, then definitely we want to empty the elementBag
- if (isFullScene) {
- nodeData->elementBag.deleteAll();
- }
-
- // This is the start of "resending" the scene.
- bool dontRestartSceneOnMove = false; // this is experimental
- if (dontRestartSceneOnMove) {
- if (nodeData->elementBag.isEmpty()) {
- nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
- }
- } else {
- nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
- }
-}
-
/// Version of octree element distributor that sends the deepest LOD level at once
int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
OctreeServer::didPacketDistributor(this);
@@ -347,8 +331,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
} else {
// we aren't forcing a full scene, check if something else suggests we should
isFullScene = nodeData->haveJSONParametersChanged() ||
- (nodeData->getUsesFrustum()
- && ((!viewFrustumChanged && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged()));
+ (nodeData->hasConicalViews() &&
+ (nodeData->getViewFrustumJustStoppedChanging() ||
+ nodeData->hasLodChanged()));
}
if (nodeData->isPacketWaiting()) {
@@ -366,16 +351,8 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
// the current view frustum for things to send.
if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) {
- // if our view has changed, we need to reset these things...
- if (viewFrustumChanged) {
- if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) {
- nodeData->dumpOutOfView();
- }
- }
-
// track completed scenes and send out the stats packet accordingly
nodeData->stats.sceneCompleted();
- nodeData->setLastRootTimestamp(_myServer->getOctree()->getRoot()->getLastChanged());
_myServer->getOctree()->releaseSceneEncodeData(&nodeData->extraEncodeData);
// TODO: add these to stats page
@@ -389,109 +366,74 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
// TODO: add these to stats page
//::startSceneSleepTime = _usleepTime;
- nodeData->sceneStart(usecTimestampNow() - CHANGE_FUDGE);
// start tracking our stats
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot());
-
- preStartNewScene(nodeData, isFullScene);
}
- // If we have something in our elementBag, then turn them into packets and send them out...
- if (shouldTraverseAndSend(nodeData)) {
- quint64 start = usecTimestampNow();
+ quint64 start = usecTimestampNow();
+ _myServer->getOctree()->withReadLock([&]{
traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
+ });
- // Here's where we can/should allow the server to send other data...
- // send the environment packet
- // TODO: should we turn this into a while loop to better handle sending multiple special packets
- if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) {
- int specialPacketsSent = 0;
- int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent);
- nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed
- _truePacketsSent += specialPacketsSent;
- _trueBytesSent += specialBytesSent;
- _packetsSentThisInterval += specialPacketsSent;
+ // Here's where we can/should allow the server to send other data...
+ // send the environment packet
+ // TODO: should we turn this into a while loop to better handle sending multiple special packets
+ if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) {
+ int specialPacketsSent = 0;
+ int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent);
+ nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed
+ _truePacketsSent += specialPacketsSent;
+ _trueBytesSent += specialBytesSent;
+ _packetsSentThisInterval += specialPacketsSent;
- _totalPackets += specialPacketsSent;
- _totalBytes += specialBytesSent;
+ _totalPackets += specialPacketsSent;
+ _totalBytes += specialBytesSent;
- _totalSpecialPackets += specialPacketsSent;
- _totalSpecialBytes += specialBytesSent;
+ _totalSpecialPackets += specialPacketsSent;
+ _totalSpecialBytes += specialBytesSent;
+ }
+
+ // calculate max number of packets that can be sent during this interval
+ int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
+ int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
+
+ // Re-send packets that were nacked by the client
+ while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) {
+ const NLPacket* packet = nodeData->getNextNackedPacket();
+ if (packet) {
+ DependencyManager::get()->sendUnreliablePacket(*packet, *node);
+ int numBytes = packet->getDataSize();
+ _truePacketsSent++;
+ _trueBytesSent += numBytes;
+ _packetsSentThisInterval++;
+
+ _totalPackets++;
+ _totalBytes += numBytes;
+ _totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize();
}
+ }
- // calculate max number of packets that can be sent during this interval
- int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
- int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
+ quint64 end = usecTimestampNow();
+ int elapsedmsec = (end - start) / USECS_PER_MSEC;
+ OctreeServer::trackLoopTime(elapsedmsec);
- // Re-send packets that were nacked by the client
- while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) {
- const NLPacket* packet = nodeData->getNextNackedPacket();
- if (packet) {
- DependencyManager::get()->sendUnreliablePacket(*packet, *node);
- int numBytes = packet->getDataSize();
- _truePacketsSent++;
- _trueBytesSent += numBytes;
- _packetsSentThisInterval++;
+ // if we've sent everything, then we want to remember that we've sent all
+ // the octree elements from the current view frustum
+ if (!hasSomethingToSend(nodeData)) {
+ nodeData->setViewSent(true);
- _totalPackets++;
- _totalBytes += numBytes;
- _totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize();
- }
+ // If this was a full scene then make sure we really send out a stats packet at this point so that
+ // the clients will know the scene is stable
+ if (isFullScene) {
+ nodeData->stats.sceneCompleted();
+ handlePacketSend(node, nodeData, true);
}
-
- quint64 end = usecTimestampNow();
- int elapsedmsec = (end - start) / USECS_PER_MSEC;
- OctreeServer::trackLoopTime(elapsedmsec);
-
- // if after sending packets we've emptied our bag, then we want to remember that we've sent all
- // the octree elements from the current view frustum
- if (!hasSomethingToSend(nodeData)) {
- nodeData->updateLastKnownViewFrustum();
- nodeData->setViewSent(true);
-
- // If this was a full scene then make sure we really send out a stats packet at this point so that
- // the clients will know the scene is stable
- if (isFullScene) {
- nodeData->stats.sceneCompleted();
- handlePacketSend(node, nodeData, true);
- }
- }
-
- } // end if bag wasn't empty, and so we sent stuff...
+ }
return _truePacketsSent;
}
-bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) {
- bool somethingToSend = false;
- OctreeQueryNode* nodeData = static_cast(params.nodeData);
- if (!nodeData->elementBag.isEmpty()) {
- quint64 encodeStart = usecTimestampNow();
- quint64 lockWaitStart = encodeStart;
-
- _myServer->getOctree()->withReadLock([&]{
- OctreeServer::trackTreeWaitTime((float)(usecTimestampNow() - lockWaitStart));
-
- OctreeElementPointer subTree = nodeData->elementBag.extract();
- if (subTree) {
- // NOTE: this is where the tree "contents" are actually packed
- nodeData->stats.encodeStarted();
- _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params);
- nodeData->stats.encodeStopped();
-
- somethingToSend = true;
- }
- });
-
- OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart));
- } else {
- OctreeServer::trackTreeWaitTime(OctreeServer::SKIP_TIME);
- OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME);
- }
- return somethingToSend;
-}
-
void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) {
// calculate max number of packets that can be sent during this interval
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
@@ -500,21 +442,11 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
int extraPackingAttempts = 0;
// init params once outside the while loop
- int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
- int boundaryLevelAdjust = boundaryLevelAdjustClient +
- (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
- float octreeSizeScale = nodeData->getOctreeSizeScale();
- EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
- viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
- isFullScene, nodeData);
+ EncodeBitstreamParams params(WANT_EXISTS_BITS, nodeData);
// Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
_myServer->trackSend(dataID, dataEdited, _nodeUuid);
};
- nodeData->copyCurrentViewFrustum(params.viewFrustum);
- if (viewFrustumChanged) {
- nodeData->copyLastKnownViewFrustum(params.lastViewFrustum);
- }
bool somethingToSend = true; // assume we have something
bool hadSomething = hasSomethingToSend(nodeData);
@@ -534,8 +466,8 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
extraPackingAttempts++;
}
- // If the bag had contents but is now empty then we know we've sent the entire scene.
- bool completedScene = hadSomething && nodeData->elementBag.isEmpty();
+ // If we had something to send, but now we don't, then we know we've sent the entire scene.
+ bool completedScene = hadSomething;
if (completedScene || lastNodeDidntFit) {
// we probably want to flush what has accumulated in nodeData but:
// do we have more data to send? and is there room?
diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h
index 220952e209..91c0ec7adc 100644
--- a/assignment-client/src/octree/OctreeSendThread.h
+++ b/assignment-client/src/octree/OctreeSendThread.h
@@ -54,7 +54,7 @@ protected:
virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene);
- virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters);
+ virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) = 0;
OctreePacketData _packetData;
QWeakPointer _node;
@@ -63,14 +63,12 @@ protected:
private:
/// Called before a packetDistributor pass to allow for pre-distribution processing
- virtual void preDistributionProcessing() {};
+ virtual void preDistributionProcessing() = 0;
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false);
int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
- virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) { return !nodeData->elementBag.isEmpty(); }
- virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) { return viewFrustumChanged || !hasSomethingToSend(nodeData); }
- virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene);
- virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); }
+ virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) = 0;
+ virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) = 0;
int _truePacketsSent { 0 }; // available for debug stats
int _trueBytesSent { 0 }; // available for debug stats
diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp
index 0dbd24fe9c..e993bea358 100644
--- a/assignment-client/src/octree/OctreeServer.cpp
+++ b/assignment-client/src/octree/OctreeServer.cpp
@@ -231,19 +231,19 @@ void OctreeServer::trackProcessWaitTime(float time) {
OctreeServer::OctreeServer(ReceivedMessage& message) :
ThreadedAssignment(message),
_argc(0),
- _argv(NULL),
- _parsedArgV(NULL),
- _httpManager(NULL),
+ _argv(nullptr),
+ _parsedArgV(nullptr),
+ _httpManager(nullptr),
_statusPort(0),
_packetsPerClientPerInterval(10),
_packetsTotalPerInterval(DEFAULT_PACKETS_PER_INTERVAL),
- _tree(NULL),
+ _tree(nullptr),
_wantPersist(true),
_debugSending(false),
_debugReceiving(false),
_verboseDebug(false),
- _octreeInboundPacketProcessor(NULL),
- _persistThread(NULL),
+ _octreeInboundPacketProcessor(nullptr),
+ _persistManager(nullptr),
_started(time(0)),
_startedUSecs(usecTimestampNow())
{
@@ -266,11 +266,8 @@ OctreeServer::~OctreeServer() {
_octreeInboundPacketProcessor->deleteLater();
}
- if (_persistThread) {
- _persistThread->terminating();
- _persistThread->terminate();
- _persistThread->deleteLater();
- }
+ qDebug() << "Waiting for persist thread to come down";
+ _persistThread.wait();
// cleanup our tree here...
qDebug() << qPrintable(_safeServerName) << "server START cleaning up octree... [" << this << "]";
@@ -285,7 +282,7 @@ void OctreeServer::initHTTPManager(int port) {
QString documentRoot = QString("%1/web").arg(PathUtils::getAppDataPath());
// setup an httpManager with us as the request handler and the parent
- _httpManager = new HTTPManager(QHostAddress::AnyIPv4, port, documentRoot, this, this);
+ _httpManager.reset(new HTTPManager(QHostAddress::AnyIPv4, port, documentRoot, this));
}
bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
@@ -876,10 +873,6 @@ void OctreeServer::parsePayload() {
}
}
-OctreeServer::UniqueSendThread OctreeServer::newSendThread(const SharedNodePointer& node) {
- return std::unique_ptr(new OctreeSendThread(this, node));
-}
-
OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) {
auto sendThread = newSendThread(node);
@@ -1057,19 +1050,13 @@ void OctreeServer::readConfiguration() {
_persistAsFileType = "json.gz";
_persistInterval = OctreePersistThread::DEFAULT_PERSIST_INTERVAL;
- readOptionInt(QString("persistInterval"), settingsSectionObject, _persistInterval);
- qDebug() << "persistInterval=" << _persistInterval;
-
- bool noBackup;
- readOptionBool(QString("NoBackup"), settingsSectionObject, noBackup);
- _wantBackup = !noBackup;
- qDebug() << "wantBackup=" << _wantBackup;
-
- if (!readOptionString("backupDirectoryPath", settingsSectionObject, _backupDirectoryPath)) {
- _backupDirectoryPath = "";
+ int result { -1 };
+ readOptionInt(QString("persistInterval"), settingsSectionObject, result);
+ if (result != -1) {
+ _persistInterval = std::chrono::milliseconds(result);
}
- qDebug() << "backupDirectoryPath=" << _backupDirectoryPath;
+ qDebug() << "persistInterval=" << _persistInterval.count();
readOptionBool(QString("persistFileDownload"), settingsSectionObject, _persistFileDownload);
qDebug() << "persistFileDownload=" << _persistFileDownload;
@@ -1133,111 +1120,14 @@ void OctreeServer::run() {
}
void OctreeServer::domainSettingsRequestComplete() {
- if (_state != OctreeServerState::WaitingForDomainSettings) {
- qCWarning(octree_server) << "Received domain settings after they have already been received";
- return;
- }
-
auto& packetReceiver = DependencyManager::get()->getPacketReceiver();
- packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket");
packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket");
-
- packetReceiver.registerListener(PacketType::OctreeDataFileReply, this, "handleOctreeDataFileReply");
+ packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket");
qDebug(octree_server) << "Received domain settings";
readConfiguration();
- _state = OctreeServerState::WaitingForOctreeDataNegotation;
-
- auto nodeList = DependencyManager::get();
- const DomainHandler& domainHandler = nodeList->getDomainHandler();
-
- auto packet = NLPacket::create(PacketType::OctreeDataFileRequest, -1, true, false);
-
- OctreeUtils::RawOctreeData data;
- qCDebug(octree_server) << "Reading octree data from" << _persistAbsoluteFilePath;
- if (data.readOctreeDataInfoFromFile(_persistAbsoluteFilePath)) {
- qCDebug(octree_server) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")";
- packet->writePrimitive(true);
- auto id = data.id.toRfc4122();
- packet->write(id);
- packet->writePrimitive(data.version);
- } else {
- qCWarning(octree_server) << "No octree data found";
- packet->writePrimitive(false);
- }
-
- qCDebug(octree_server) << "Sending request for octree data to DS";
- nodeList->sendPacket(std::move(packet), domainHandler.getSockAddr());
-}
-
-void OctreeServer::handleOctreeDataFileReply(QSharedPointer message) {
- if (_state != OctreeServerState::WaitingForOctreeDataNegotation) {
- qCWarning(octree_server) << "Server received ocree data file reply but is not currently negotiating.";
- return;
- }
-
- bool includesNewData;
- message->readPrimitive(&includesNewData);
- QByteArray replaceData;
- if (includesNewData) {
- replaceData = message->readAll();
- qDebug() << "Got reply to octree data file request, new data sent";
- } else {
- qDebug() << "Got reply to octree data file request, current entity data is sufficient";
-
- OctreeUtils::RawEntityData data;
- qCDebug(octree_server) << "Reading octree data from" << _persistAbsoluteFilePath;
- if (data.readOctreeDataInfoFromFile(_persistAbsoluteFilePath)) {
- if (data.id.isNull()) {
- qCDebug(octree_server) << "Current octree data has a null id, updating";
- data.resetIdAndVersion();
-
- QFile file(_persistAbsoluteFilePath);
- if (file.open(QIODevice::WriteOnly)) {
- auto entityData = data.toGzippedByteArray();
- file.write(entityData);
- file.close();
- } else {
- qCDebug(octree_server) << "Failed to update octree data";
- }
- }
- }
- }
-
- _state = OctreeServerState::Running;
- beginRunning(replaceData);
-}
-
-void OctreeServer::beginRunning(QByteArray replaceData) {
- if (_state != OctreeServerState::Running) {
- qCWarning(octree_server) << "Server is not running";
- return;
- }
-
- auto nodeList = DependencyManager::get();
-
- // we need to ask the DS about agents so we can ping/reply with them
- nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
-
- beforeRun(); // after payload has been processed
-
- connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
- connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
-
-#ifndef WIN32
- setvbuf(stdout, NULL, _IOLBF, 0);
-#endif
-
- nodeList->linkedDataCreateCallback = [this](Node* node) {
- auto queryNodeData = createOctreeQueryNode();
- queryNodeData->init();
- node->setLinkedData(std::move(queryNodeData));
- };
-
- srand((unsigned)time(0));
-
// if we want Persistence, set up the local file and persist thread
if (_wantPersist) {
static const QString ENTITY_PERSIST_EXTENSION = ".json.gz";
@@ -1293,40 +1183,40 @@ void OctreeServer::beginRunning(QByteArray replaceData) {
}
auto persistFileDirectory = QFileInfo(_persistAbsoluteFilePath).absolutePath();
- if (_backupDirectoryPath.isEmpty()) {
- // Use the persist file's directory to store backups
- _backupDirectoryPath = persistFileDirectory;
- } else {
- // The backup directory has been set.
- // If relative, make it relative to the entities directory in the application data directory
- // If absolute, no resolution is necessary
- QDir backupDirectory { _backupDirectoryPath };
- QString absoluteBackupDirectory;
- if (backupDirectory.isRelative()) {
- absoluteBackupDirectory = QDir(PathUtils::getAppDataFilePath("entities/")).absoluteFilePath(_backupDirectoryPath);
- absoluteBackupDirectory = QDir(absoluteBackupDirectory).absolutePath();
- } else {
- absoluteBackupDirectory = backupDirectory.absolutePath();
- }
- backupDirectory = QDir(absoluteBackupDirectory);
- if (!backupDirectory.exists()) {
- if (backupDirectory.mkpath(".")) {
- qDebug() << "Created backup directory";
- } else {
- qDebug() << "ERROR creating backup directory, using persist file directory";
- _backupDirectoryPath = persistFileDirectory;
- }
- } else {
- _backupDirectoryPath = absoluteBackupDirectory;
- }
- }
- qDebug() << "Backups will be stored in: " << _backupDirectoryPath;
// now set up PersistThread
- _persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval,
- _wantBackup, _settings, _debugTimestampNow, _persistAsFileType, replaceData);
- _persistThread->initialize(true);
+ _persistManager = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _persistInterval, _debugTimestampNow,
+ _persistAsFileType);
+ _persistManager->moveToThread(&_persistThread);
+ connect(&_persistThread, &QThread::finished, _persistManager, &QObject::deleteLater);
+ connect(&_persistThread, &QThread::started, _persistManager, &OctreePersistThread::start);
+ connect(_persistManager, &OctreePersistThread::loadCompleted, this, [this]() {
+ beginRunning();
+ });
+ _persistThread.start();
+ } else {
+ beginRunning();
}
+}
+
+void OctreeServer::beginRunning() {
+ auto nodeList = DependencyManager::get();
+
+ // we need to ask the DS about agents so we can ping/reply with them
+ nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
+
+ beforeRun(); // after payload has been processed
+
+ connect(nodeList.data(), &NodeList::nodeAdded, this, &OctreeServer::nodeAdded);
+ connect(nodeList.data(), &NodeList::nodeKilled, this, &OctreeServer::nodeKilled);
+
+ nodeList->linkedDataCreateCallback = [this](Node* node) {
+ auto queryNodeData = createOctreeQueryNode();
+ queryNodeData->init();
+ node->setLinkedData(std::move(queryNodeData));
+ };
+
+ srand((unsigned)time(0));
// set up our OctreeServerPacketProcessor
_octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this);
@@ -1389,7 +1279,7 @@ void OctreeServer::aboutToFinish() {
qDebug() << qPrintable(_safeServerName) << "inform Octree Inbound Packet Processor that we are shutting down...";
- // we're going down - set the NodeList linkedDataCallback to NULL so we do not create any more OctreeQueryNode objects.
+ // we're going down - set the NodeList linkedDataCallback to nullptr so we do not create any more OctreeQueryNode objects.
// This ensures that we don't get any more newly connecting nodes
DependencyManager::get()->linkedDataCreateCallback = nullptr;
@@ -1407,9 +1297,8 @@ void OctreeServer::aboutToFinish() {
// which waits on the thread to be done before returning
_sendThreads.clear(); // Cleans up all the send threads.
- if (_persistThread) {
- _persistThread->aboutToFinish();
- _persistThread->terminating();
+ if (_persistManager) {
+ _persistThread.quit();
}
qDebug() << qPrintable(_safeServerName) << "server ENDING about to finish...";
diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h
index e7efc731f2..07b1e334b1 100644
--- a/assignment-client/src/octree/OctreeServer.h
+++ b/assignment-client/src/octree/OctreeServer.h
@@ -33,12 +33,6 @@ Q_DECLARE_LOGGING_CATEGORY(octree_server)
const int DEFAULT_PACKETS_PER_INTERVAL = 2000; // some 120,000 packets per second total
-enum class OctreeServerState {
- WaitingForDomainSettings,
- WaitingForOctreeDataNegotation,
- Running
-};
-
/// Handles assignments of type OctreeServer - sending octrees to various clients.
class OctreeServer : public ThreadedAssignment, public HTTPRequestHandler {
Q_OBJECT
@@ -46,8 +40,6 @@ public:
OctreeServer(ReceivedMessage& message);
~OctreeServer();
- OctreeServerState _state { OctreeServerState::WaitingForDomainSettings };
-
/// allows setting of run arguments
void setArguments(int argc, char** argv);
@@ -68,12 +60,12 @@ public:
static void clientConnected() { _clientCount++; }
static void clientDisconnected() { _clientCount--; }
- bool isInitialLoadComplete() const { return (_persistThread) ? _persistThread->isInitialLoadComplete() : true; }
- bool isPersistEnabled() const { return (_persistThread) ? true : false; }
- quint64 getLoadElapsedTime() const { return (_persistThread) ? _persistThread->getLoadElapsedTime() : 0; }
- QString getPersistFilename() const { return (_persistThread) ? _persistThread->getPersistFilename() : ""; }
- QString getPersistFileMimeType() const { return (_persistThread) ? _persistThread->getPersistFileMimeType() : "text/plain"; }
- QByteArray getPersistFileContents() const { return (_persistThread) ? _persistThread->getPersistFileContents() : QByteArray(); }
+ bool isInitialLoadComplete() const { return (_persistManager) ? _persistManager->isInitialLoadComplete() : true; }
+ bool isPersistEnabled() const { return (_persistManager) ? true : false; }
+ quint64 getLoadElapsedTime() const { return (_persistManager) ? _persistManager->getLoadElapsedTime() : 0; }
+ QString getPersistFilename() const { return (_persistManager) ? _persistManager->getPersistFilename() : ""; }
+ QString getPersistFileMimeType() const { return (_persistManager) ? _persistManager->getPersistFileMimeType() : "text/plain"; }
+ QByteArray getPersistFileContents() const { return (_persistManager) ? _persistManager->getPersistFileContents() : QByteArray(); }
// Subclasses must implement these methods
virtual std::unique_ptr createOctreeQueryNode() = 0;
@@ -149,7 +141,6 @@ private slots:
void domainSettingsRequestComplete();
void handleOctreeQueryPacket(QSharedPointer message, SharedNodePointer senderNode);
void handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode);
- void handleOctreeDataFileReply(QSharedPointer message);
void removeSendThread();
protected:
@@ -171,10 +162,10 @@ protected:
QString getConfiguration();
QString getStatusLink();
- void beginRunning(QByteArray replaceData);
+ void beginRunning();
UniqueSendThread createSendThread(const SharedNodePointer& node);
- virtual UniqueSendThread newSendThread(const SharedNodePointer& node);
+ virtual UniqueSendThread newSendThread(const SharedNodePointer& node) = 0;
int _argc;
const char** _argv;
@@ -183,14 +174,13 @@ protected:
bool _isShuttingDown = false;
- HTTPManager* _httpManager;
+ std::unique_ptr _httpManager;
int _statusPort;
QString _statusHost;
QString _persistFilePath;
QString _persistAbsoluteFilePath;
QString _persistAsFileType;
- QString _backupDirectoryPath;
int _packetsPerClientPerInterval;
int _packetsTotalPerInterval;
OctreePointer _tree; // this IS a reaveraging tree
@@ -200,13 +190,11 @@ protected:
bool _debugTimestampNow;
bool _verboseDebug;
OctreeInboundPacketProcessor* _octreeInboundPacketProcessor;
- OctreePersistThread* _persistThread;
+ OctreePersistThread* _persistManager;
+ QThread _persistThread;
- int _persistInterval;
- bool _wantBackup;
+ std::chrono::milliseconds _persistInterval;
bool _persistFileDownload;
- QString _backupExtensionFormat;
- int _backupInterval;
int _maxBackupVersions;
time_t _started;
diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp
index d242b393bf..eea8e8b470 100644
--- a/assignment-client/src/scripts/EntityScriptServer.cpp
+++ b/assignment-client/src/scripts/EntityScriptServer.cpp
@@ -294,7 +294,6 @@ void EntityScriptServer::run() {
queryJSONParameters[EntityJSONQueryProperties::FLAGS_PROPERTY] = queryFlags;
// setup the JSON parameters so that OctreeQuery does not use a frustum and uses our JSON filter
- _entityViewer.getOctreeQuery().setUsesFrustum(false);
_entityViewer.getOctreeQuery().setJSONParameters(queryJSONParameters);
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
diff --git a/cmake/externals/LibOVR/CMakeLists.txt b/cmake/externals/LibOVR/CMakeLists.txt
index 97508be0c5..ed76f181e7 100644
--- a/cmake/externals/LibOVR/CMakeLists.txt
+++ b/cmake/externals/LibOVR/CMakeLists.txt
@@ -17,8 +17,8 @@ if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
- URL https://static.oculus.com/sdk-downloads/1.11.0/Public/1486063832/ovr_sdk_win_1.11.0_public.zip
- URL_MD5 ea484403757cbfdfa743b6577fb1f9d2
+ URL http://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.26.0_public.zip
+ URL_MD5 06804ff9727b910dcd04a37c800053b5
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=
PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" /CMakeLists.txt
LOG_DOWNLOAD 1
diff --git a/cmake/externals/LibOVR/LibOVRCMakeLists.txt b/cmake/externals/LibOVR/LibOVRCMakeLists.txt
index 556533f0c2..a52cff5463 100644
--- a/cmake/externals/LibOVR/LibOVRCMakeLists.txt
+++ b/cmake/externals/LibOVR/LibOVRCMakeLists.txt
@@ -4,7 +4,7 @@ project(LibOVR)
include_directories(LibOVR/Include LibOVR/Src)
file(GLOB HEADER_FILES LibOVR/Include/*.h)
file(GLOB EXTRA_HEADER_FILES LibOVR/Include/Extras/*.h)
-file(GLOB_RECURSE SOURCE_FILES LibOVR/Src/*.c LibOVR/Src/*.cpp)
+file(GLOB_RECURSE SOURCE_FILES LibOVR/Shim/*.c LibOVR/Shim/*.cpp)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DOVR_BUILD_DEBUG")
add_library(LibOVR STATIC ${SOURCE_FILES} ${HEADER_FILES} ${EXTRA_HEADER_FILES})
set_target_properties(LibOVR PROPERTIES DEBUG_POSTFIX "d")
diff --git a/cmake/externals/bullet/CMakeLists.txt b/cmake/externals/bullet/CMakeLists.txt
index 317e3302d9..91860a7470 100644
--- a/cmake/externals/bullet/CMakeLists.txt
+++ b/cmake/externals/bullet/CMakeLists.txt
@@ -17,8 +17,8 @@ include(ExternalProject)
if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
- URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.83-ccd-and-cmake-fixes.tgz
- URL_MD5 03051bf112dcc78ddd296f9cab38fd68
+ URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.88.tgz
+ URL_MD5 0a6876607ebe83e227427215f15946fd
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH= -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_BULLET3=0 -DBUILD_OPENGL3_DEMOS=0 -DBUILD_BULLET2_DEMOS=0 -DBUILD_UNIT_TESTS=0 -DUSE_GLUT=0 -DUSE_DX11=0
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
@@ -28,8 +28,8 @@ if (WIN32)
else ()
ExternalProject_Add(
${EXTERNAL_NAME}
- URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.83-ccd-and-cmake-fixes.tgz
- URL_MD5 03051bf112dcc78ddd296f9cab38fd68
+ URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.88.tgz
+ URL_MD5 0a6876607ebe83e227427215f15946fd
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH= -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_BULLET3=0 -DBUILD_OPENGL3_DEMOS=0 -DBUILD_BULLET2_DEMOS=0 -DBUILD_UNIT_TESTS=0 -DUSE_GLUT=0
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
diff --git a/cmake/externals/crashpad/CMakeLists.txt b/cmake/externals/crashpad/CMakeLists.txt
index c464dcbc1b..e509e115e4 100644
--- a/cmake/externals/crashpad/CMakeLists.txt
+++ b/cmake/externals/crashpad/CMakeLists.txt
@@ -6,8 +6,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
- URL https://backtrace.io/download/crashpad_062317.zip
- URL_MD5 65817e564b3628492abfc1dbd2a1e98b
+ URL http://public.highfidelity.com/dependencies/crashpad_062317.1.zip
+ URL_MD5 9c84b77f5f23daf939da1371825ed2b1
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
diff --git a/cmake/externals/etc2comp/CMakeLists.txt b/cmake/externals/etc2comp/CMakeLists.txt
new file mode 100644
index 0000000000..d6d21d6703
--- /dev/null
+++ b/cmake/externals/etc2comp/CMakeLists.txt
@@ -0,0 +1,55 @@
+set(EXTERNAL_NAME etc2comp)
+
+if (ANDROID)
+ set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19")
+endif ()
+
+if (APPLE)
+ set(EXTRA_CMAKE_FLAGS -DCMAKE_CXX_FLAGS=-stdlib=libc++ -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++)
+endif ()
+
+include(ExternalProject)
+# We use a patched version of etc2comp that properly generates all the necessary mips
+# See https://github.com/google/etc2comp/pull/29
+# We also use part of https://github.com/google/etc2comp/pull/1, which fixes a bug
+# that would override CMAKE_CXX_FLAGS
+ExternalProject_Add(
+ ${EXTERNAL_NAME}
+ URL https://hifi-public.s3.amazonaws.com/dependencies/etc2comp-patched.zip
+ URL_MD5 4c96153eb179acbe619e3d99d3330595
+ CMAKE_ARGS ${ANDROID_CMAKE_ARGS} ${EXTRA_CMAKE_FLAGS}
+ BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
+ INSTALL_COMMAND ""
+ LOG_DOWNLOAD 1
+ LOG_CONFIGURE 1
+ LOG_BUILD 1
+)
+
+# Hide this external target (for ide users)
+set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
+
+ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
+ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
+
+string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
+
+if (WIN32)
+ set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/build/EtcLib/Debug/EtcLib.lib CACHE FILEPATH "Path to Etc2Comp debug library")
+
+ # use generator expression to ensure the correct library is found when building different configurations in VS
+ set(_LIB_FOLDER "$<$:build/EtcLib/RelWithDebInfo>")
+ set(_LIB_FOLDER "${_LIB_FOLDER}$<$:build/EtcLib/MinSizeRel>")
+ set(_LIB_FOLDER "${_LIB_FOLDER}$<$,$>:build/EtcLib/Release>")
+
+ set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/${_LIB_FOLDER}/EtcLib.lib CACHE FILEPATH "Path to Etc2Comp release library")
+elseif (APPLE)
+ set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/build/EtcLib/Debug/libEtcLib.a CACHE FILEPATH "Path to EtcLib debug library")
+ set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/build/EtcLib/Release/libEtcLib.a CACHE FILEPATH "Path to EtcLib release library")
+else ()
+ set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "Path to EtcLib debug library")
+ set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/build/EtcLib/libEtcLib.a CACHE FILEPATH "Path to EtcLib release library")
+endif ()
+
+set(ETC_INCLUDE_DIR ${SOURCE_DIR}/EtcLib/Etc CACHE FILEPATH "Path to Etc2Comp/Etc include directory")
+set(ETCCODEC_INCLUDE_DIR ${SOURCE_DIR}/EtcLib/EtcCodec CACHE FILEPATH "Path to Etc2Comp/EtcCodec include directory")
+# ETC2COMP_INCLUDE_DIRS will be set later by FindEtc2Comp
\ No newline at end of file
diff --git a/cmake/externals/glm/CMakeLists.txt b/cmake/externals/glm/CMakeLists.txt
index bc8089074f..0a83004438 100644
--- a/cmake/externals/glm/CMakeLists.txt
+++ b/cmake/externals/glm/CMakeLists.txt
@@ -3,8 +3,8 @@ set(EXTERNAL_NAME glm)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
- URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.zip
- URL_MD5 579ac77a3110befa3244d68c0ceb7281
+ URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.5-patched.zip
+ URL_MD5 7d39ecc1cea275427534c3cfd6dd63f0
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= ${EXTERNAL_ARGS}
LOG_DOWNLOAD 1
@@ -18,4 +18,4 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
-set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glm include directories")
\ No newline at end of file
+set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glm include directories")
diff --git a/cmake/externals/quazip/CMakeLists.txt b/cmake/externals/quazip/CMakeLists.txt
index f2690e0a7d..7bf6f05d9f 100644
--- a/cmake/externals/quazip/CMakeLists.txt
+++ b/cmake/externals/quazip/CMakeLists.txt
@@ -41,6 +41,9 @@ if (APPLE)
elseif (WIN32)
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip5.lib CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/quazip5d.lib CACHE FILEPATH "Location of QuaZip release library")
+elseif (CMAKE_SYSTEM_NAME MATCHES "Linux")
+ set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library")
+ set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.so CACHE FILEPATH "Location of QuaZip release library")
else ()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library")
diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt
index 4d0773f5f5..81b82e8651 100644
--- a/cmake/externals/serverless-content/CMakeLists.txt
+++ b/cmake/externals/serverless-content/CMakeLists.txt
@@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
ExternalProject_Add(
${EXTERNAL_NAME}
- URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66-v2.zip
- URL_MD5 d76bdb3e2bf7ae5d20115bd97b0c44a8
+ URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC68-v2.zip
+ URL_MD5 f7d290471baf7f5694c147217b8fc548
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
diff --git a/cmake/macros/GenerateInstallers.cmake b/cmake/macros/GenerateInstallers.cmake
index 742c5b5b94..acafd9b6c7 100644
--- a/cmake/macros/GenerateInstallers.cmake
+++ b/cmake/macros/GenerateInstallers.cmake
@@ -14,12 +14,24 @@ macro(GENERATE_INSTALLERS)
set(CPACK_MODULE_PATH ${CPACK_MODULE_PATH} "${HF_CMAKE_DIR}/templates")
- set(_DISPLAY_NAME ${BUILD_ORGANIZATION})
+
+ if (CLIENT_ONLY)
+ set(_PACKAGE_NAME_EXTRA "-Interface")
+ set(INSTALLER_TYPE "client_only")
+ string(REGEX REPLACE "High Fidelity" "High Fidelity Interface" _DISPLAY_NAME ${BUILD_ORGANIZATION})
+ elseif (SERVER_ONLY)
+ set(_PACKAGE_NAME_EXTRA "-Sandbox")
+ set(INSTALLER_TYPE "server_only")
+ string(REGEX REPLACE "High Fidelity" "High Fidelity Sandbox" _DISPLAY_NAME ${BUILD_ORGANIZATION})
+ else ()
+ set(_DISPLAY_NAME ${BUILD_ORGANIZATION})
+ set(INSTALLER_TYPE "full")
+ endif ()
set(CPACK_PACKAGE_NAME ${_DISPLAY_NAME})
set(CPACK_PACKAGE_VENDOR "High Fidelity")
set(CPACK_PACKAGE_VERSION ${BUILD_VERSION})
- set(CPACK_PACKAGE_FILE_NAME "HighFidelity-Beta-${BUILD_VERSION}")
+ set(CPACK_PACKAGE_FILE_NAME "HighFidelity-Beta${_PACKAGE_NAME_EXTRA}-${BUILD_VERSION}")
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME})
if (PR_BUILD)
diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake
index 3ca5cb0e54..2c8443d510 100644
--- a/cmake/macros/SetPackagingParameters.cmake
+++ b/cmake/macros/SetPackagingParameters.cmake
@@ -17,14 +17,13 @@ macro(SET_PACKAGING_PARAMETERS)
set(DEV_BUILD 0)
set(BUILD_GLOBAL_SERVICES "DEVELOPMENT")
set(USE_STABLE_GLOBAL_SERVICES 0)
+ set(BUILD_NUMBER 0)
+ set(APP_USER_MODEL_ID "com.highfidelity.sandbox-dev")
set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV")
set_from_env(RELEASE_NUMBER RELEASE_NUMBER "")
- set_from_env(BUILD_BRANCH BRANCH "")
- string(TOLOWER "${BUILD_BRANCH}" BUILD_BRANCH)
+ set_from_env(STABLE_BUILD STABLE_BUILD 0)
- message(STATUS "The BUILD_BRANCH variable is: ${BUILD_BRANCH}")
- message(STATUS "The BRANCH environment variable is: $ENV{BRANCH}")
message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}")
# setup component categories for installer
@@ -46,17 +45,17 @@ macro(SET_PACKAGING_PARAMETERS)
# if the build is a PRODUCTION_BUILD from the "stable" branch
# then use the STABLE gobal services
- if (BUILD_BRANCH STREQUAL "stable")
- message(STATUS "The RELEASE_TYPE is PRODUCTION and the BUILD_BRANCH is stable...")
+ if (STABLE_BUILD)
+ message(STATUS "The RELEASE_TYPE is PRODUCTION and STABLE_BUILD is 1")
set(BUILD_GLOBAL_SERVICES "STABLE")
set(USE_STABLE_GLOBAL_SERVICES 1)
- endif()
+ endif ()
elseif (RELEASE_TYPE STREQUAL "PR")
set(DEPLOY_PACKAGE TRUE)
set(PR_BUILD 1)
set(BUILD_VERSION "PR${RELEASE_NUMBER}")
- set(BUILD_ORGANIZATION "High Fidelity - ${BUILD_VERSION}")
+ set(BUILD_ORGANIZATION "High Fidelity - PR${RELEASE_NUMBER}")
set(INTERFACE_BUNDLE_NAME "Interface")
set(INTERFACE_ICON_PREFIX "interface-beta")
@@ -73,6 +72,56 @@ macro(SET_PACKAGING_PARAMETERS)
add_definitions(-DDEV_BUILD)
endif ()
+ string(TIMESTAMP BUILD_TIME "%d/%m/%Y")
+
+ # if STABLE_BUILD is 1, PRODUCTION_BUILD must be 1 and
+ # DEV_BUILD and PR_BUILD must be 0
+ if (STABLE_BUILD)
+ if ((NOT PRODUCTION_BUILD) OR PR_BUILD OR DEV_BUILD)
+ message(FATAL_ERROR "Cannot produce STABLE_BUILD without PRODUCTION_BUILD")
+ endif ()
+ endif ()
+
+ if ((PRODUCTION_BUILD OR PR_BUILD) AND NOT STABLE_BUILD)
+ # append the abbreviated commit SHA to the build version
+ # since this is a PR build or master/nightly builds
+
+ # for PR_BUILDS, we need to grab the abbreviated SHA
+ # for the second parent of HEAD (not HEAD) since that is the
+ # SHA of the commit merged to master for the build
+ if (PR_BUILD)
+ set(_GIT_LOG_FORMAT "%p")
+ else ()
+ set(_GIT_LOG_FORMAT "%h")
+ endif ()
+
+ execute_process(
+ COMMAND git log -1 --abbrev=7 --format=${_GIT_LOG_FORMAT}
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_VARIABLE _GIT_LOG_OUTPUT
+ ERROR_VARIABLE _GIT_LOG_ERROR
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+
+ if (PR_BUILD)
+ separate_arguments(_COMMIT_PARENTS UNIX_COMMAND ${_GIT_LOG_OUTPUT})
+ list(GET _COMMIT_PARENTS 1 GIT_COMMIT_HASH)
+ else ()
+ set(GIT_COMMIT_HASH ${_GIT_LOG_OUTPUT})
+ endif ()
+
+ if (_GIT_LOG_ERROR OR NOT GIT_COMMIT_HASH)
+ message(FATAL_ERROR "Could not retreive abbreviated SHA for PR or production master build")
+ endif ()
+
+ set(BUILD_VERSION_NO_SHA ${BUILD_VERSION})
+ set(BUILD_VERSION "${BUILD_VERSION}-${GIT_COMMIT_HASH}")
+
+ # pass along a release number without the SHA in case somebody
+ # wants to compare master or PR builds as integers
+ set(BUILD_NUMBER ${RELEASE_NUMBER})
+ endif ()
+
if (DEPLOY_PACKAGE)
# for deployed packages always grab the serverless content
set(DOWNLOAD_SERVERLESS_CONTENT ON)
@@ -124,9 +173,10 @@ macro(SET_PACKAGING_PARAMETERS)
if (PRODUCTION_BUILD)
set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface")
set(CONSOLE_SHORTCUT_NAME "Sandbox")
+ set(APP_USER_MODEL_ID "com.highfidelity.sandbox")
else ()
- set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION}")
- set(CONSOLE_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION}")
+ set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION_NO_SHA}")
+ set(CONSOLE_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION_NO_SHA}")
endif ()
set(INTERFACE_HF_SHORTCUT_NAME "${INTERFACE_SHORTCUT_NAME}")
diff --git a/cmake/macros/SetupHifiTestCase.cmake b/cmake/macros/SetupHifiTestCase.cmake
index 6c7d38e19c..017a0222f5 100644
--- a/cmake/macros/SetupHifiTestCase.cmake
+++ b/cmake/macros/SetupHifiTestCase.cmake
@@ -61,16 +61,21 @@ macro(SETUP_HIFI_TESTCASE)
endif()
endforeach()
+
# Find test classes to build into test executables.
# Warn about any .cpp files that are *not* test classes (*Test[s].cpp), since those files will not be used.
foreach (SRC_FILE ${TEST_PROJ_SRC_FILES})
string(REGEX MATCH ".+Tests?\\.cpp$" TEST_CPP_FILE ${SRC_FILE})
string(REGEX MATCH ".+\\.cpp$" NON_TEST_CPP_FILE ${SRC_FILE})
+ string(REGEX MATCH ".+\\.qrc$" QRC_FILE ${SRC_FILE})
if (TEST_CPP_FILE)
list(APPEND TEST_CASE_FILES ${TEST_CPP_FILE})
elseif (NON_TEST_CPP_FILE)
message(WARNING "ignoring .cpp file (not a test class -- this will not be linked or compiled!): " ${NON_TEST_CPP_FILE})
endif ()
+ if (QRC_FILE)
+ list(APPEND EXTRA_FILES ${QRC_FILE})
+ endif()
endforeach ()
if (TEST_CASE_FILES)
@@ -88,7 +93,7 @@ macro(SETUP_HIFI_TESTCASE)
# grab the implemenation and header files
set(TARGET_SRCS ${TEST_FILE}) # only one source / .cpp file (the test class)
- add_executable(${TARGET_NAME} ${TEST_FILE})
+ add_executable(${TARGET_NAME} ${TEST_FILE} ${EXTRA_FILES})
add_test(${TARGET_NAME}-test ${TARGET_NAME})
set_target_properties(${TARGET_NAME} PROPERTIES
EXCLUDE_FROM_DEFAULT_BUILD TRUE
@@ -124,15 +129,14 @@ macro(SETUP_HIFI_TESTCASE)
# This target will also build + run the other test targets using ctest when built.
add_custom_target(${TEST_TARGET}
- COMMAND ctest .
SOURCES ${TEST_PROJ_SRC_FILES} # display source files under the testcase target
DEPENDS ${${TEST_PROJ_NAME}_TARGETS})
+
set_target_properties(${TEST_TARGET} PROPERTIES
+ FOLDER "Tests"
EXCLUDE_FROM_DEFAULT_BUILD TRUE
EXCLUDE_FROM_ALL TRUE)
- set_target_properties(${TEST_TARGET} PROPERTIES FOLDER "Tests")
-
list (APPEND ALL_TEST_TARGETS ${TEST_TARGET})
set(ALL_TEST_TARGETS "${ALL_TEST_TARGETS}" PARENT_SCOPE)
else ()
diff --git a/cmake/macros/SetupQt.cmake b/cmake/macros/SetupQt.cmake
index 00a398761b..71f5314d2f 100644
--- a/cmake/macros/SetupQt.cmake
+++ b/cmake/macros/SetupQt.cmake
@@ -1,10 +1,10 @@
-#
+#
# Created by Bradley Austin Davis on 2017/09/02
# Copyright 2013-2017 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-#
+#
# Construct a default QT location from a root path, a version and an architecture
function(calculate_default_qt_dir _RESULT_NAME)
@@ -27,7 +27,7 @@ function(calculate_default_qt_dir _RESULT_NAME)
endif()
set_from_env(QT_ROOT QT_ROOT ${QT_DEFAULT_ROOT})
- set_from_env(QT_VERSION QT_VERSION "5.9.1")
+ set_from_env(QT_VERSION QT_VERSION "5.10.1")
set_from_env(QT_ARCH QT_ARCH ${QT_DEFAULT_ARCH})
set(${_RESULT_NAME} "${QT_ROOT}/${QT_VERSION}/${QT_ARCH}" PARENT_SCOPE)
@@ -60,11 +60,11 @@ macro(setup_qt)
#if (NOT EXISTS "${QT_DIR}/include/QtCore/QtGlobal")
# message(FATAL_ERROR "Unable to locate Qt includes in ${QT_DIR}")
#endif()
-
+
if (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}/Qt5Core/Qt5CoreConfig.cmake")
message(FATAL_ERROR "Unable to locate Qt cmake config in ${QT_CMAKE_PREFIX_PATH}")
endif()
-
+
message(STATUS "The Qt build in use is: \"${QT_DIR}\"")
# Instruct CMake to run moc automatically when needed.
@@ -72,7 +72,7 @@ macro(setup_qt)
# Instruct CMake to run rcc automatically when needed
set(CMAKE_AUTORCC ON)
-
+
if (WIN32)
add_paths_to_fixup_libs("${QT_DIR}/bin")
endif ()
diff --git a/cmake/macros/TargetEtc2Comp.cmake b/cmake/macros/TargetEtc2Comp.cmake
new file mode 100644
index 0000000000..44152a58d2
--- /dev/null
+++ b/cmake/macros/TargetEtc2Comp.cmake
@@ -0,0 +1,22 @@
+#
+# Copyright 2018 High Fidelity, Inc.
+# Created by Sam Gondelman on 5/2/2018
+#
+# Distributed under the Apache License, Version 2.0.
+# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+#
+macro(TARGET_ETC2COMP)
+ if (ANDROID)
+ set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/etc2comp)
+ set(ETC2COMP_INCLUDE_DIRS "${INSTALL_DIR}/include/Etc" "${INSTALL_DIR}/include/EtcCodec")
+ set(ETC2COMP_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libEtcLib.a)
+ set(ETC2COMP_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libEtcLib.a)
+ select_library_configurations(ETC2COMP)
+ else()
+ add_dependency_external_projects(etc2comp)
+ find_package(Etc2Comp REQUIRED)
+ endif()
+
+ target_include_directories(${TARGET_NAME} PRIVATE ${ETC2COMP_INCLUDE_DIRS})
+ target_link_libraries(${TARGET_NAME} ${ETC2COMP_LIBRARIES})
+endmacro()
diff --git a/cmake/modules/FindEtc2Comp.cmake b/cmake/modules/FindEtc2Comp.cmake
new file mode 100644
index 0000000000..1b990368fd
--- /dev/null
+++ b/cmake/modules/FindEtc2Comp.cmake
@@ -0,0 +1,37 @@
+#
+# FindEtc2Comp.cmake
+#
+# Try to find the Etc2Comp compression library.
+#
+# Once done this will define
+#
+# ETC2COMP_FOUND - system found Etc2Comp
+# ETC2COMP_INCLUDE_DIRS - the Etc2Comp include directory
+# ETC2COMP_LIBRARIES - link to this to use Etc2Comp
+#
+# Created on 5/2/2018 by Sam Gondelman
+# Copyright 2018 High Fidelity, Inc.
+#
+# Distributed under the Apache License, Version 2.0.
+# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+#
+
+include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
+hifi_library_search_hints("etc2comp")
+
+find_path(ETC_INCLUDE_DIR NAMES Etc.h HINTS ${ETC2COMP_SEARCH_DIRS})
+find_path(ETCCODEC_INCLUDE_DIR NAMES EtcBlock4x4.h HINTS ${ETC2COMP_SEARCH_DIRS})
+set(ETC2COMP_INCLUDE_DIRS "${ETC_INCLUDE_DIR}" "${ETCCODEC_INCLUDE_DIR}")
+
+find_library(ETC2COMP_LIBRARY_DEBUG NAMES ETC2COMP ETC2COMP_LIB PATH_SUFFIXES EtcLib/Debug HINTS ${ETC2COMP_SEARCH_DIRS})
+find_library(ETC2COMP_LIBRARY_RELEASE NAMES ETC2COMP ETC2COMP_LIB PATH_SUFFIXES EtcLib/Release EtcLib HINTS ${ETC2COMP_SEARCH_DIRS})
+
+include(SelectLibraryConfigurations)
+select_library_configurations(ETC2COMP)
+
+set(ETC2COMP_LIBRARIES ${ETC2COMP_LIBRARY})
+
+find_package_handle_standard_args(ETC2COMP "Could NOT find ETC2COMP, try to set the path to ETC2COMP root folder in the system variable ETC2COMP_ROOT_DIR or create a directory etc2comp in HIFI_LIB_DIR and paste the necessary files there"
+ ETC2COMP_INCLUDE_DIRS ETC2COMP_LIBRARIES)
+
+mark_as_advanced(ETC2COMP_INCLUDE_DIRS ETC2COMP_LIBRARIES ETC2COMP_SEARCH_DIRS)
diff --git a/cmake/modules/FindGverb.cmake b/cmake/modules/FindGverb.cmake
deleted file mode 100644
index 0c149a7ca1..0000000000
--- a/cmake/modules/FindGverb.cmake
+++ /dev/null
@@ -1,25 +0,0 @@
-# FindGVerb.cmake
-#
-# Try to find the Gverb library.
-#
-# You must provide a GVERB_ROOT_DIR which contains src and include directories
-#
-# Once done this will define
-#
-# GVERB_FOUND - system found Gverb
-# GVERB_INCLUDE_DIRS - the Gverb include directory
-#
-# Copyright 2014 High Fidelity, Inc.
-#
-# Distributed under the Apache License, Version 2.0.
-# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-#
-
-include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
-hifi_library_search_hints("gverb")
-
-find_path(GVERB_INCLUDE_DIRS gverb.h PATH_SUFFIXES include HINTS ${GVERB_SEARCH_DIRS})
-find_library(GVERB_LIBRARIES gverb PATH_SUFFIXES lib HINTS ${GVERB_SEARCH_DIRS})
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(Gverb DEFAULT_MSG GVERB_INCLUDE_DIRS GVERB_LIBRARIES)
\ No newline at end of file
diff --git a/cmake/templates/BuildInfo.h.in b/cmake/templates/BuildInfo.h.in
index b0af1c0524..9fc9d9be81 100644
--- a/cmake/templates/BuildInfo.h.in
+++ b/cmake/templates/BuildInfo.h.in
@@ -24,6 +24,26 @@ namespace BuildInfo {
const QString MODIFIED_ORGANIZATION = "@BUILD_ORGANIZATION@";
const QString ORGANIZATION_DOMAIN = "highfidelity.io";
const QString VERSION = "@BUILD_VERSION@";
- const QString BUILD_BRANCH = "@BUILD_BRANCH@";
+ const QString BUILD_NUMBER = "@BUILD_NUMBER@";
const QString BUILD_GLOBAL_SERVICES = "@BUILD_GLOBAL_SERVICES@";
+ const QString BUILD_TIME = "@BUILD_TIME@";
+
+ enum BuildType {
+ Dev,
+ PR,
+ Master,
+ Stable
+ };
+
+#if defined(PR_BUILD)
+ const BuildType BUILD_TYPE = PR;
+ const QString BUILD_TYPE_STRING = "pr";
+#elif defined(PRODUCTION_BUILD)
+ const BuildType BUILD_TYPE = @STABLE_BUILD@ ? Stable : Master;
+ const QString BUILD_TYPE_STRING = @STABLE_BUILD@ ? "stable" : "master";
+#else
+ const BuildType BUILD_TYPE = Dev;
+ const QString BUILD_TYPE_STRING = "dev";
+#endif
+
}
diff --git a/cmake/templates/CPackProperties.cmake.in b/cmake/templates/CPackProperties.cmake.in
index 9c303f7532..68fa098508 100644
--- a/cmake/templates/CPackProperties.cmake.in
+++ b/cmake/templates/CPackProperties.cmake.in
@@ -48,3 +48,5 @@ set(UNINSTALLER_HEADER_IMAGE "@UNINSTALLER_HEADER_IMAGE@")
set(ADD_REMOVE_ICON_PATH "@ADD_REMOVE_ICON_PATH@")
set(SERVER_COMPONENT_CONDITIONAL "@SERVER_COMPONENT_CONDITIONAL@")
set(CLIENT_COMPONENT_CONDITIONAL "@CLIENT_COMPONENT_CONDITIONAL@")
+set(INSTALLER_TYPE "@INSTALLER_TYPE@")
+set(APP_USER_MODEL_ID "@APP_USER_MODEL_ID@")
diff --git a/cmake/templates/FixupBundlePostBuild.cmake.in b/cmake/templates/FixupBundlePostBuild.cmake.in
index 57379bb48b..bb96fe49f3 100644
--- a/cmake/templates/FixupBundlePostBuild.cmake.in
+++ b/cmake/templates/FixupBundlePostBuild.cmake.in
@@ -11,6 +11,35 @@
include(BundleUtilities)
+# replace copy_resolved_item_into_bundle
+#
+# The official version of copy_resolved_item_into_bundle will print out a "warning:" when
+# the resolved item matches the resolved embedded item. This not not really an issue that
+# should rise to the level of a "warning" so we replace this message with a "status:"
+#
+# Source: https://github.com/jherico/OculusMinimalExample/blob/master/cmake/templates/FixupBundlePostBuild.cmake.in
+#
+function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item)
+ if (WIN32)
+ # ignore case on Windows
+ string(TOLOWER "${resolved_item}" resolved_item_compare)
+ string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare)
+ else()
+ set(resolved_item_compare "${resolved_item}")
+ set(resolved_embedded_item_compare "${resolved_embedded_item}")
+ endif()
+
+ if ("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}")
+ # this is our only change from the original version
+ message(STATUS "status: resolved_item == resolved_embedded_item - not copying...")
+ else()
+ execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}")
+ if (UNIX AND NOT APPLE)
+ file(RPATH_REMOVE FILE "${resolved_embedded_item}")
+ endif()
+ endif()
+endfunction()
+
function(gp_resolved_file_type_override resolved_file type_var)
if( file MATCHES ".*VCRUNTIME140.*" )
set(type "system" PARENT_SCOPE)
diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in
index 7faa67d1b0..64e3fbe889 100644
--- a/cmake/templates/NSIS.template.in
+++ b/cmake/templates/NSIS.template.in
@@ -87,6 +87,10 @@
;--------------------------------
;--------------------------------
;General
+
+ ; hide install details since we show an image slideshow in their place
+ ShowInstDetails nevershow
+
; leverage the UAC NSIS plugin to promote uninstaller to elevated privileges
!include UAC.nsh
@@ -446,6 +450,7 @@ SectionEnd
Page custom PostInstallOptionsPage ReadPostInstallOptions
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageInstallFilesPre
+ !define MUI_PAGE_CUSTOMFUNCTION_SHOW StartInstallSlideshow
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
@@ -544,11 +549,33 @@ Var Express
${EndIf}
!macroend
+!macro DownloadSlideshowImages
+ InitPluginsDir
+
+ Push $0
+
+ ; figure out where to download installer slideshow images from
+ StrCpy $0 "http://cdn.highfidelity.com/installer/slideshow"
+
+ ${If} $CampaignName == ""
+ StrCpy $0 "$0/default"
+ ${Else}
+ StrCpy $0 "$0/$CampaignName"
+ ${EndIf}
+
+ NSISdl::download_quiet $0/1.jpg "$PLUGINSDIR\1.jpg"
+ NSISdl::download_quiet $0/2.jpg "$PLUGINSDIR\2.jpg"
+ NSISdl::download_quiet $0/3.jpg "$PLUGINSDIR\3.jpg"
+
+ Pop $0
+!macroend
+
Function OnUserAbort
!insertmacro GoogleAnalytics "Installer" "Abort" "User Abort" ""
FunctionEnd
Function PageWelcomePre
!insertmacro GoogleAnalytics "Installer" "Welcome" "" ""
+ !insertmacro DownloadSlideshowImages
FunctionEnd
Function PageLicensePre
!insertmacro GoogleAnalytics "Installer" "License" "" ""
@@ -640,6 +667,56 @@ Function ChangeCustomLabel
Pop $R1
FunctionEnd
+!macro AddImageToSlideshowFile ImageFilename
+ ${If} ${FileExists} "$PLUGINSDIR\${ImageFilename}.jpg"
+ FileWrite $0 "= ${ImageFilename}.jpg,500,5000,$\"$\"$\r$\n"
+ StrCpy $1 "1"
+ ${EndIf}
+!macroend
+
+Function StartInstallSlideshow
+ ; create a slideshow file based on what files we have available
+
+ ; stash $0 and $1
+ Push $0
+ Push $1
+
+ ; start $1 as 0, indicating we have no images present
+ StrCpy $1 "0"
+
+ FileOpen $0 "$PLUGINSDIR\slideshow.dat" w
+
+ ; write the language value to the slideshow file for english
+ FileWrite $0 "[1033]$\r$\n"
+
+ ; for each of 1.jpg, 2.jpg, 3.jpg
+ ; if the image is present add it to the dat file and set our flag
+ ; to show we found at least one image
+ !insertmacro AddImageToSlideshowFile "1"
+ !insertmacro AddImageToSlideshowFile "2"
+ !insertmacro AddImageToSlideshowFile "3"
+
+ FileClose $0
+
+ ; NOTE: something inside of nsisSlideshow::show isn't keeping the stack clean
+ ; so we need to push things back BEFORE we call it
+
+ ${If} $1 == "1"
+ Pop $1
+ Pop $0
+
+ ; show the slideshow using the created data file
+ nsisSlideshow::show /NOUNLOAD "/auto=$PLUGINSDIR\slideshow.dat"
+ ${Else}
+ Pop $1
+ Pop $0
+
+ ; show the install details because we didn't end up with slideshow images to show
+ SetDetailsView show
+ ${EndIf}
+
+FunctionEnd
+
Function PostInstallOptionsPage
!insertmacro MaybeSkipPage
!insertmacro GoogleAnalytics "Installer" "Post Install Options" "" ""
@@ -710,11 +787,9 @@ Function PostInstallOptionsPage
!insertmacro SetInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED}
${EndIf}
- ${If} @SERVER_COMPONENT_CONDITIONAL@
- ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)"
- Pop $CleanInstallCheckbox
- IntOp $CurrentOffset $CurrentOffset + 15
- ${EndIf}
+ ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)"
+ Pop $CleanInstallCheckbox
+ IntOp $CurrentOffset $CurrentOffset + 15
${If} @PR_BUILD@ == 1
; a PR build defaults all install options expect LaunchServerNowCheckbox, LaunchClientNowCheckbox and the settings copy to unchecked
@@ -809,10 +884,8 @@ Function ReadPostInstallOptions
${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState
${EndIf}
- ${If} @CLIENT_COMPONENT_CONDITIONAL@
- ; check if the user asked for a clean install
- ${NSD_GetState} $CleanInstallCheckbox $CleanInstallState
- ${EndIf}
+ ; check if the user asked for a clean install
+ ${NSD_GetState} $CleanInstallCheckbox $CleanInstallState
FunctionEnd
Function HandlePostInstallOptions
@@ -832,6 +905,8 @@ Function HandlePostInstallOptions
${If} $DesktopServerState == ${BST_CHECKED}
CreateShortCut "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
!insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES
+ ; Set appUserModelId
+ ApplicationID::Set "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@"
${Else}
!insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO
${EndIf}
@@ -856,13 +931,23 @@ Function HandlePostInstallOptions
${EndIf}
${EndIf}
- ${If} @CLIENT_COMPONENT_CONDITIONAL@
- ; check if the user asked for a clean install
- ${If} $CleanInstallState == ${BST_CHECKED}
- SetShellVarContext current
- RMDir /r "$APPDATA\@BUILD_ORGANIZATION@"
- RMDir /r "$LOCALAPPDATA\@BUILD_ORGANIZATION@"
+ ; check if the user asked for a clean install
+ ${If} $CleanInstallState == ${BST_CHECKED}
+ SetShellVarContext current
+
+ ${If} @SERVER_COMPONENT_CONDITIONAL@
+ RMDir /r "$APPDATA\@BUILD_ORGANIZATION@\Server Console"
+ RMDir /r "$APPDATA\@BUILD_ORGANIZATION@\assignment-client"
+ RMDir /r "$APPDATA\@BUILD_ORGANIZATION@\domain-server"
+ Delete "$APPDATA\@BUILD_ORGANIZATION@\domain-server.json"
${EndIf}
+
+ ${If} @CLIENT_COMPONENT_CONDITIONAL@
+ Delete "$APPDATA\@BUILD_ORGANIZATION@\Interface\AccountInfo.bin"
+ Delete "$APPDATA\@BUILD_ORGANIZATION@\Interface.json"
+ ${EndIf}
+
+ RMDir /r "$LOCALAPPDATA\@BUILD_ORGANIZATION@"
${EndIf}
${If} @PR_BUILD@ == 1
@@ -922,10 +1007,28 @@ Function HandlePostInstallOptions
${EndIf}
FunctionEnd
+Function OptionallyDownloadCampaignServerless
+ ${If} $CampaignName != ""
+ InitPluginsDir
+
+ NSISdl::download_quiet http://cdn.highfidelity.com/installer/serverless/$CampaignName.zip $PLUGINSDIR\$CampaignName.zip
+
+ ${If} ${FileExists} $PLUGINSDIR\$CampaignName.zip
+ ; replace the installed serverless content with the campaign content
+
+ RMDir /r "$INSTDIR\resources\serverless"
+ CreateDirectory "$INSTDIR\resources\serverless"
+ nsisunz::Unzip "$PLUGINSDIR\$CampaignName.zip" "$INSTDIR\resources\serverless"
+ ${EndIf}
+
+ ${Endif}
+FunctionEnd
+
;--------------------------------
;Installer Sections
Section "-Core installation"
+
;The following delete blocks are temporary and can be removed once users who had the initial installer have updated
;Delete any server-console files installed before it was placed in sub-folder
@@ -976,6 +1079,15 @@ Section "-Core installation"
;Store installation folder
WriteRegStr HKLM "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR
+ ;Write some information about this install to the installation folder
+ Push $0
+ FileOpen $0 "$INSTDIR\installer.ini" w
+ FileWrite $0 "type=@INSTALLER_TYPE@$\r$\n"
+ FileWrite $0 "campaign=$CampaignName$\r$\n"
+ FileWrite $0 "exepath=$EXEPATH$\r$\n"
+ FileClose $0
+ Pop $0
+
;Package the signed uninstaller produced by the inner loop
!ifndef INNER
; this packages the signed uninstaller
@@ -1052,6 +1164,8 @@ Section "-Core installation"
${If} @SERVER_COMPONENT_CONDITIONAL@
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" \
"$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
+ ; Set appUserModelId
+ ApplicationID::Set "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@"
${EndIf}
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\@UNINSTALLER_NAME@"
@@ -1065,6 +1179,9 @@ Section "-Core installation"
@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@
+ ; see if we have a campaign that we might need to grab special content for
+ Call OptionallyDownloadCampaignServerless
+
; Handle whichever post install options were set
Call HandlePostInstallOptions
@@ -1221,6 +1338,9 @@ Section "Uninstall"
@CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS@
+ ;Remove the installer information file
+ Delete "$INSTDIR\installer.ini"
+
;Remove files we installed.
;Keep the list of directories here in sync with the File commands above.
@CPACK_NSIS_DELETE_FILES@
diff --git a/cmake/templates/console-build-info.json.in b/cmake/templates/console-build-info.json.in
index c1ef010e08..a68df664c6 100644
--- a/cmake/templates/console-build-info.json.in
+++ b/cmake/templates/console-build-info.json.in
@@ -1,4 +1,8 @@
{
"releaseType": "@RELEASE_TYPE@",
- "buildIdentifier": "@BUILD_VERSION@"
+ "buildNumber": "@BUILD_NUMBER@",
+ "stableBuild": "@STABLE_BUILD@",
+ "buildIdentifier": "@BUILD_VERSION@",
+ "organization": "@BUILD_ORGANIZATION@",
+ "appUserModelId": "@APP_USER_MODEL_ID@"
}
diff --git a/domain-server/resources/web/wizard/index.shtml b/domain-server/resources/web/wizard/index.shtml
index 5a3286296d..3bc7503b44 100644
--- a/domain-server/resources/web/wizard/index.shtml
+++ b/domain-server/resources/web/wizard/index.shtml
@@ -26,7 +26,7 @@
Place names are similar to web addresses. Users who want to visit your domain can
enter its Place Name in High Fidelity's Interface. You can choose a Place Name for your domain.
- People can also use your domain's IP address (shown below) to visit your High Fidelity domain.
+ Your domain may also be reachable by IP address.
@@ -35,10 +35,10 @@