From 2d2a4804f7790f41a8b9af373c5274ff73e7c84c Mon Sep 17 00:00:00 2001 From: Gabriel Calero <gcalero1984@gmail.com> Date: Thu, 6 Sep 2018 17:49:52 -0300 Subject: [PATCH 01/56] Add android settings screen. Add AEC setting --- android/app/src/main/AndroidManifest.xml | 10 +++ android/app/src/main/cpp/native.cpp | 46 ++++++++++++++ .../hifiinterface/InterfaceActivity.java | 7 +++ .../hifiinterface/MainActivity.java | 23 +++++++ .../fragment/SettingsFragment.java | 63 +++++++++++++++++++ .../receiver/HeadsetStateReceiver.java | 20 ++++++ .../app/src/main/res/menu/menu_navigation.xml | 5 ++ android/app/src/main/res/values/strings.xml | 5 ++ android/app/src/main/res/xml/settings.xml | 11 ++++ interface/src/AndroidHelper.cpp | 17 +++++ interface/src/AndroidHelper.h | 1 + libraries/audio-client/src/AudioClient.cpp | 6 +- libraries/audio-client/src/AudioClient.h | 8 +++ 13 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java create mode 100644 android/app/src/main/res/xml/settings.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7255e1f295..b216819ed0 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true"/> <uses-feature android:name="android.hardware.sensor.gyroscope" android:required="true"/> @@ -75,6 +76,15 @@ android:enabled="true" android:exported="false" android:process=":breakpad_uploader"/> + + <receiver + android:name=".receiver.HeadsetStateReceiver" + android:enabled="true" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.HEADSET_PLUG" /> + </intent-filter> + </receiver> </application> <uses-feature android:name="android.software.vr.mode" android:required="true"/> diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index ce5af01f29..6b44b2dc7a 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -355,5 +355,51 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_WebViewActivity_nativeProcessU AndroidHelper::instance().processURL(QString::fromUtf8(nativeString)); } +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_updateHifiSetting(JNIEnv *env, + jobject instance, + jstring group_, + jstring key_, + jboolean value_) { + const char *c_group = env->GetStringUTFChars(group_, 0); + const char *c_key = env->GetStringUTFChars(key_, 0); + + const QString group = QString::fromUtf8(c_group); + const QString key = QString::fromUtf8(c_key); + + env->ReleaseStringUTFChars(group_, c_group); + env->ReleaseStringUTFChars(key_, c_key); + + bool value = value_; + + Setting::Handle<bool> setting { QStringList() << group << key , !value }; + setting.set(value); +} + +JNIEXPORT jboolean JNICALL +Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_getHifiSettingBoolean(JNIEnv *env, + jobject instance, + jstring group_, + jstring key_, + jboolean defaultValue) { + const char *c_group = env->GetStringUTFChars(group_, 0); + const char *c_key = env->GetStringUTFChars(key_, 0); + + const QString group = QString::fromUtf8(c_group); + const QString key = QString::fromUtf8(c_key); + + env->ReleaseStringUTFChars(group_, c_group); + env->ReleaseStringUTFChars(key_, c_key); + + Setting::Handle<bool> setting { QStringList() << group << key , defaultValue}; + return setting.get(); +} + +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_receiver_HeadsetStateReceiver_notifyHeadsetOn(JNIEnv *env, + jobject instance, + jboolean pluggedIn) { + AndroidHelper::instance().notifyHeadsetOn(pluggedIn); +} } 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 f161783d6a..08e66a2f42 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -13,6 +13,7 @@ package io.highfidelity.hifiinterface; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -40,6 +41,7 @@ import java.util.HashMap; import java.util.List; import io.highfidelity.hifiinterface.fragment.WebViewFragment; +import io.highfidelity.hifiinterface.receiver.HeadsetStateReceiver; /*import com.google.vr.cardboard.DisplaySynchronizer; import com.google.vr.cardboard.DisplayUtils; @@ -55,6 +57,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW private static final int NORMAL_DPI = 160; private Vibrator mVibrator; + private HeadsetStateReceiver headsetStateReceiver; //public static native void handleHifiURL(String hifiURLString); private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager); @@ -151,6 +154,8 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW layoutParams.resolveLayoutDirection(View.LAYOUT_DIRECTION_RTL); qtLayout.addView(webSlidingDrawer, layoutParams); webSlidingDrawer.setVisibility(View.GONE); + + headsetStateReceiver = new HeadsetStateReceiver(); } @Override @@ -161,6 +166,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW } else { nativeEnterBackground(); } + unregisterReceiver(headsetStateReceiver); //gvrApi.pauseTracking(); } @@ -183,6 +189,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW nativeEnterForeground(); surfacesWorkaround(); keepInterfaceRunning = false; + registerReceiver(headsetStateReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); //gvrApi.resumeTracking(); } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index db6f0fca24..4c6d05a3e8 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -33,6 +33,7 @@ import io.highfidelity.hifiinterface.fragment.FriendsFragment; import io.highfidelity.hifiinterface.fragment.HomeFragment; import io.highfidelity.hifiinterface.fragment.LoginFragment; import io.highfidelity.hifiinterface.fragment.PolicyFragment; +import io.highfidelity.hifiinterface.fragment.SettingsFragment; import io.highfidelity.hifiinterface.task.DownloadProfileImageTask; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, @@ -80,6 +81,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people); + updateDebugMenu(mNavigationView.getMenu()); + Toolbar toolbar = findViewById(R.id.toolbar); toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle); setSupportActionBar(toolbar); @@ -108,6 +111,16 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } } + private void updateDebugMenu(Menu menu) { + if (BuildConfig.DEBUG) { + for (int i=0; i < menu.size(); i++) { + if (menu.getItem(i).getItemId() == R.id.action_debug_settings) { + menu.getItem(i).setVisible(true); + } + } + } + } + private void loadFragment(String fragment) { switch (fragment) { case "Login": @@ -151,6 +164,13 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true); } + private void loadSettingsFragment() { + SettingsFragment fragment = SettingsFragment.newInstance(); + + loadFragment(fragment, getString(R.string.settings), getString(R.string.tagSettings), true); + } + + private void loadFragment(Fragment fragment, String title, String tag, boolean addToBackStack) { FragmentManager fragmentManager = getFragmentManager(); @@ -241,6 +261,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On case R.id.action_people: loadPeopleFragment(); return true; + case R.id.action_debug_settings: + loadSettingsFragment(); + return true; } return false; } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java new file mode 100644 index 0000000000..cc23665e72 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java @@ -0,0 +1,63 @@ +package io.highfidelity.hifiinterface.fragment; + +import android.content.SharedPreferences; +import android.media.audiofx.AcousticEchoCanceler; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.support.annotation.Nullable; + +import io.highfidelity.hifiinterface.R; + +public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { + + public native void updateHifiSetting(String group, String key, boolean value); + public native boolean getHifiSettingBoolean(String group, String key, boolean defaultValue); + + private final String HIFI_SETTINGS_ANDROID_GROUP = "Android"; + private final String HIFI_SETTINGS_AEC_KEY = "aec"; + private final String PREFERENCE_KEY_AEC = "aec"; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings); + + if (!AcousticEchoCanceler.isAvailable()) { + getPreferenceScreen().getPreferenceManager().findPreference("aec").setEnabled(false); + } + + getPreferenceScreen().getSharedPreferences().edit().putBoolean(PREFERENCE_KEY_AEC, + getHifiSettingBoolean(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, false)); + } + + public static SettingsFragment newInstance() { + SettingsFragment fragment = new SettingsFragment(); + return fragment; + } + + @Override + public void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + Preference pref = findPreference(key); + switch (key) { + case "aec": + updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, sharedPreferences.getBoolean(key, false)); + break; + default: + break; + } + } +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java new file mode 100644 index 0000000000..29bc1c49f2 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java @@ -0,0 +1,20 @@ +package io.highfidelity.hifiinterface.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.util.Log; + +public class HeadsetStateReceiver extends BroadcastReceiver { + + private native void notifyHeadsetOn(boolean pluggedIn); + + @Override + public void onReceive(Context context, Intent intent) { + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + + Log.d("[HEADSET] " , "BR - Wired headset on:" + audioManager.isWiredHeadsetOn()); + notifyHeadsetOn(audioManager.isWiredHeadsetOn()); + } +} diff --git a/android/app/src/main/res/menu/menu_navigation.xml b/android/app/src/main/res/menu/menu_navigation.xml index 3cce64f9f5..142af5d146 100644 --- a/android/app/src/main/res/menu/menu_navigation.xml +++ b/android/app/src/main/res/menu/menu_navigation.xml @@ -9,4 +9,9 @@ android:id="@+id/action_people" android:title="@string/people" /> + <item + android:id="@+id/action_debug_settings" + android:title="@string/settings" + android:visible="false" + /> </menu> diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index b158aba59d..abde15f484 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -29,4 +29,9 @@ <string name="tagFragmentLogin">tagFragmentLogin</string> <string name="tagFragmentPolicy">tagFragmentPolicy</string> <string name="tagFragmentPeople">tagFragmentPeople</string> + <string name="tagSettings">tagSettings</string> + <string name="settings">Settings</string> + <string name="AEC">AEC</string> + <string name="acoustic_echo_cancellation">Acoustic Echo Cancellation</string> + <string name="settings_developer">Developer</string> </resources> diff --git a/android/app/src/main/res/xml/settings.xml b/android/app/src/main/res/xml/settings.xml new file mode 100644 index 0000000000..5ec47b1aff --- /dev/null +++ b/android/app/src/main/res/xml/settings.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <PreferenceCategory + android:title="@string/settings_developer" + android:key="pref_key_developer"> + <SwitchPreference + android:key="aec" + android:title="@string/AEC" + android:summary="@string/acoustic_echo_cancellation" /> + </PreferenceCategory> +</PreferenceScreen> \ No newline at end of file diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp index 419382f2cb..35bf094591 100644 --- a/interface/src/AndroidHelper.cpp +++ b/interface/src/AndroidHelper.cpp @@ -10,6 +10,7 @@ // #include "AndroidHelper.h" #include <QDebug> +#include <AudioClient.h> #include "Application.h" #if defined(qApp) @@ -18,6 +19,7 @@ #define qApp (static_cast<Application*>(QCoreApplication::instance())) AndroidHelper::AndroidHelper() { + qRegisterMetaType<QAudio::Mode>("QAudio::Mode"); } AndroidHelper::~AndroidHelper() { @@ -56,3 +58,18 @@ void AndroidHelper::processURL(const QString &url) { qApp->acceptURL(url); } } + +void AndroidHelper::notifyHeadsetOn(bool pluggedIn) { +#if defined (Q_OS_ANDROID) + auto audioClient = DependencyManager::get<AudioClient>(); + if (audioClient) { + QAudioDeviceInfo activeDev = audioClient->getActiveAudioDevice(QAudio::AudioInput); + Setting::Handle<bool> enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false); + if ((pluggedIn || !enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_RECOGNITION) { + QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_RECOGNITION)); + } else if ( (!pluggedIn && enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_COMMUNICATION) { + QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_COMMUNICATION)); + } + } +#endif +} diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h index 03d92f91d9..11b84e4025 100644 --- a/interface/src/AndroidHelper.h +++ b/interface/src/AndroidHelper.h @@ -29,6 +29,7 @@ public: void performHapticFeedback(int duration); void processURL(const QString &url); + void notifyHeadsetOn(bool pluggedIn); AndroidHelper(AndroidHelper const&) = delete; void operator=(AndroidHelper const&) = delete; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6a9363f309..e23b8ac3cd 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -53,7 +53,6 @@ #include "AudioHelpers.h" #if defined(Q_OS_ANDROID) -#define VOICE_RECOGNITION "voicerecognition" #include <QtAndroidExtras/QAndroidJniObject> #endif @@ -451,9 +450,12 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #if defined (Q_OS_ANDROID) if (mode == QAudio::AudioInput) { + Setting::Handle<bool> enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false); + bool aecEnabled = enableAEC.get(); auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); for (auto inputDevice : inputDevices) { - if (inputDevice.deviceName() == VOICE_RECOGNITION) { + if ((aecEnabled && inputDevice.deviceName() == VOICE_COMMUNICATION) || + (!aecEnabled && inputDevice.deviceName() == VOICE_RECOGNITION)) { return inputDevice; } } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 8599c990a3..fa7ac40a16 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -64,6 +64,14 @@ #pragma warning( pop ) #endif +#if defined (Q_OS_ANDROID) +#define VOICE_RECOGNITION "voicerecognition" +#define VOICE_COMMUNICATION "voicecommunication" + +#define ANDROID_SETTINGS_GROUP "Android" +#define SETTING_AEC_KEY "aec" +#endif + class QAudioInput; class QAudioOutput; class QIODevice; From 2943502c9b5d0d9630c97b76793ffa5b3c205561 Mon Sep 17 00:00:00 2001 From: Gabriel Calero <gcalero1984@gmail.com> Date: Wed, 12 Sep 2018 16:36:04 -0300 Subject: [PATCH 02/56] Headset plug/unplug detection --- .../receiver/HeadsetStateReceiver.java | 2 -- interface/src/AndroidHelper.cpp | 8 +---- libraries/audio-client/src/AudioClient.cpp | 32 +++++++++++++++++-- libraries/audio-client/src/AudioClient.h | 11 +++++-- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java index 29bc1c49f2..5645912d73 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java @@ -13,8 +13,6 @@ public class HeadsetStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - - Log.d("[HEADSET] " , "BR - Wired headset on:" + audioManager.isWiredHeadsetOn()); notifyHeadsetOn(audioManager.isWiredHeadsetOn()); } } diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp index 35bf094591..400085a62a 100644 --- a/interface/src/AndroidHelper.cpp +++ b/interface/src/AndroidHelper.cpp @@ -63,13 +63,7 @@ void AndroidHelper::notifyHeadsetOn(bool pluggedIn) { #if defined (Q_OS_ANDROID) auto audioClient = DependencyManager::get<AudioClient>(); if (audioClient) { - QAudioDeviceInfo activeDev = audioClient->getActiveAudioDevice(QAudio::AudioInput); - Setting::Handle<bool> enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false); - if ((pluggedIn || !enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_RECOGNITION) { - QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_RECOGNITION)); - } else if ( (!pluggedIn && enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_COMMUNICATION) { - QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_COMMUNICATION)); - } + QMetaObject::invokeMethod(audioClient.data(), "setHeadsetPluggedIn", Q_ARG(bool, pluggedIn)); } #endif } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index e23b8ac3cd..f0ba0307cc 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -209,6 +209,7 @@ AudioClient::AudioClient() : _positionGetter(DEFAULT_POSITION_GETTER), #if defined(Q_OS_ANDROID) _checkInputTimer(this), + _isHeadsetPluggedIn(false), #endif _orientationGetter(DEFAULT_ORIENTATION_GETTER) { // avoid putting a lock in the device callback @@ -450,12 +451,14 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #if defined (Q_OS_ANDROID) if (mode == QAudio::AudioInput) { - Setting::Handle<bool> enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false); + Setting::Handle<bool> enableAEC(SETTING_AEC_KEY, false); bool aecEnabled = enableAEC.get(); + auto audioClient = DependencyManager::get<AudioClient>(); + bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false ; auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); for (auto inputDevice : inputDevices) { - if ((aecEnabled && inputDevice.deviceName() == VOICE_COMMUNICATION) || - (!aecEnabled && inputDevice.deviceName() == VOICE_RECOGNITION)) { + if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) || + ((!headsetOn && aecEnabled) && inputDevice.deviceName() == VOICE_COMMUNICATION)) { return inputDevice; } } @@ -1632,6 +1635,29 @@ void AudioClient::checkInputTimeout() { #endif } +void AudioClient::setHeadsetPluggedIn(bool pluggedIn) { +#if defined(Q_OS_ANDROID) + if (pluggedIn == !_isHeadsetPluggedIn && !_inputDeviceInfo.isNull()) { + QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField<jstring>("android/os/Build", "BRAND"); + // some samsung phones needs more time to shutdown the previous input device + if (brand.toString().contains("samsung", Qt::CaseInsensitive)) { + switchInputToAudioDevice(QAudioDeviceInfo(), true); + QThread::msleep(200); + } + + Setting::Handle<bool> enableAEC(SETTING_AEC_KEY, false); + bool aecEnabled = enableAEC.get(); + + if ((pluggedIn || !aecEnabled) && _inputDeviceInfo.deviceName() != VOICE_RECOGNITION) { + switchAudioDevice(QAudio::AudioInput, VOICE_RECOGNITION); + } else if (!pluggedIn && aecEnabled && _inputDeviceInfo.deviceName() != VOICE_COMMUNICATION) { + switchAudioDevice(QAudio::AudioInput, VOICE_COMMUNICATION); + } + } + _isHeadsetPluggedIn = pluggedIn; +#endif +} + void AudioClient::outputNotify() { int recentUnfulfilled = _audioOutputIODevice.getRecentUnfulfilledReads(); if (recentUnfulfilled > 0) { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index fa7ac40a16..b1ccb496b6 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -68,8 +68,7 @@ #define VOICE_RECOGNITION "voicerecognition" #define VOICE_COMMUNICATION "voicecommunication" -#define ANDROID_SETTINGS_GROUP "Android" -#define SETTING_AEC_KEY "aec" +#define SETTING_AEC_KEY "Android/aec" #endif class QAudioInput; @@ -176,6 +175,10 @@ public: static QString getWinDeviceName(wchar_t* guid); #endif +#if defined(Q_OS_ANDROID) + bool isHeadsetPluggedIn() { return _isHeadsetPluggedIn; } +#endif + public slots: void start(); void stop(); @@ -224,6 +227,9 @@ public slots: bool switchAudioDevice(QAudio::Mode mode, const QAudioDeviceInfo& deviceInfo = QAudioDeviceInfo()); bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName); + // Qt opensles plugin is not able to detect when the headset is plugged in + void setHeadsetPluggedIn(bool pluggedIn); + float getInputVolume() const { return (_audioInput) ? (float)_audioInput->volume() : 0.0f; } void setInputVolume(float volume, bool emitSignal = true); void setReverb(bool reverb); @@ -285,6 +291,7 @@ private: #ifdef Q_OS_ANDROID QTimer _checkInputTimer; long _inputReadsSinceLastCheck = 0l; + bool _isHeadsetPluggedIn; #endif class Gate { From 27c7592d986af1424c4e85a7df5d44e2a50b2d7f Mon Sep 17 00:00:00 2001 From: Gabriel Calero <gcalero1984@gmail.com> Date: Thu, 13 Sep 2018 14:30:42 -0300 Subject: [PATCH 03/56] Patch qt 5.11.1 to support voice_communication opensl es preset --- android/build.gradle | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index a6de0d469c..a5d40b9b43 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -73,16 +73,16 @@ def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/' def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms") def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl.tgz' -def qtChecksum='f312c47cd8b8dbca824c32af4eec5e66' -def qtVersionId='nyCGcb91S4QbYeJhUkawO5x1lrLdSNB_' +def qtChecksum='bf9e734d9c4e77c4807a38c93515e25c' +def qtVersionId='Qt3rvI0O7l5gLkacia2vA6KAchTgFkFf' if (Os.isFamily(Os.FAMILY_MAC)) { - qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl.tgz' - qtChecksum='a0c8b394aec5b0fcd46714ca3a53278a' - qtVersionId='QNa.lwNJaPc0eGuIL.xZ8ebeTuLL7rh8' + qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz' + qtChecksum='5f12fd4fb25efe648c2fd161fd44998a' + qtVersionId='XXJ7ii1.xYzgFdWPnU.8mXnCj.q6y0Q5' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { - qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl.tgz' - qtChecksum='d80aed4233ce9e222aae8376e7a94bf9' - qtVersionId='iDVXu0i3WEXRFIxQCtzcJ2XuKrE8RIqB' + qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz' + qtChecksum='0582191cc55431aa4f660848a542883e' + qtVersionId='JfWM0P_Mz5Qp0LwpzhrsRwN3fqlLSFeT' } def packages = [ From ec2c0226a5aadcf0ab762d463b10a30bc10c05ca Mon Sep 17 00:00:00 2001 From: Gabriel Calero <gcalero1984@gmail.com> Date: Thu, 13 Sep 2018 16:42:12 -0300 Subject: [PATCH 04/56] Replace qt build version (osx) --- android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index a5d40b9b43..8a3f4d75b9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -77,8 +77,8 @@ def qtChecksum='bf9e734d9c4e77c4807a38c93515e25c' def qtVersionId='Qt3rvI0O7l5gLkacia2vA6KAchTgFkFf' if (Os.isFamily(Os.FAMILY_MAC)) { qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz' - qtChecksum='5f12fd4fb25efe648c2fd161fd44998a' - qtVersionId='XXJ7ii1.xYzgFdWPnU.8mXnCj.q6y0Q5' + qtChecksum='c83cc477c08a892e00c71764dca051a0' + qtVersionId='QNa.lwNJaPc0eGuIL.xZ8ebeTuLL7rh8' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz' qtChecksum='0582191cc55431aa4f660848a542883e' From 74ea1548a929f4936684cc1d9ab59adfc553a85a Mon Sep 17 00:00:00 2001 From: Simon Walton <simon@highfidelity.io> Date: Thu, 13 Sep 2018 14:24:49 -0700 Subject: [PATCH 05/56] Use chrono::system_clock for usecTimestampNow() on all platforms --- libraries/shared/src/SharedUtil.cpp | 81 ++--------------------------- 1 file changed, 5 insertions(+), 76 deletions(-) diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index bb22a1e753..886445824b 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -19,8 +19,8 @@ #include <time.h> #include <mutex> #include <thread> -#include <set> #include <unordered_map> +#include <chrono> #include <glm/glm.hpp> @@ -122,87 +122,16 @@ void setGlobalInstance(const char* propertyName, const QVariant& variant) { } } +// Do we still need this? static qint64 usecTimestampNowAdjust = 0; // in usec void usecTimestampNowForceClockSkew(qint64 clockSkew) { ::usecTimestampNowAdjust = clockSkew; } -static std::atomic<qint64> TIME_REFERENCE { 0 }; // in usec -static std::once_flag usecTimestampNowIsInitialized; -static QElapsedTimer timestampTimer; - quint64 usecTimestampNow(bool wantDebug) { - std::call_once(usecTimestampNowIsInitialized, [&] { - TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec - timestampTimer.start(); - }); - - quint64 now; - quint64 nsecsElapsed = timestampTimer.nsecsElapsed(); - quint64 usecsElapsed = nsecsElapsed / NSECS_PER_USEC; // nsec to usec - - // QElapsedTimer may not advance if the CPU has gone to sleep. In which case it - // will begin to deviate from real time. We detect that here, and reset if necessary - quint64 msecsCurrentTime = QDateTime::currentMSecsSinceEpoch(); - quint64 msecsEstimate = (TIME_REFERENCE + usecsElapsed) / USECS_PER_MSEC; // usecs to msecs - int possibleSkew = msecsEstimate - msecsCurrentTime; - const int TOLERANCE = 10 * MSECS_PER_SECOND; // up to 10 seconds of skew is tolerated - if (abs(possibleSkew) > TOLERANCE) { - // reset our TIME_REFERENCE and timer - TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec - timestampTimer.restart(); - now = TIME_REFERENCE + ::usecTimestampNowAdjust; - - if (wantDebug) { - qCDebug(shared) << "usecTimestampNow() - resetting QElapsedTimer. "; - qCDebug(shared) << " msecsCurrentTime:" << msecsCurrentTime; - qCDebug(shared) << " msecsEstimate:" << msecsEstimate; - qCDebug(shared) << " possibleSkew:" << possibleSkew; - qCDebug(shared) << " TOLERANCE:" << TOLERANCE; - - qCDebug(shared) << " nsecsElapsed:" << nsecsElapsed; - qCDebug(shared) << " usecsElapsed:" << usecsElapsed; - - QDateTime currentLocalTime = QDateTime::currentDateTime(); - - quint64 msecsNow = now / 1000; // usecs to msecs - QDateTime nowAsString; - nowAsString.setMSecsSinceEpoch(msecsNow); - - qCDebug(shared) << " now:" << now; - qCDebug(shared) << " msecsNow:" << msecsNow; - - qCDebug(shared) << " nowAsString:" << nowAsString.toString("yyyy-MM-dd hh:mm:ss.zzz"); - qCDebug(shared) << " currentLocalTime:" << currentLocalTime.toString("yyyy-MM-dd hh:mm:ss.zzz"); - } - } else { - now = TIME_REFERENCE + usecsElapsed + ::usecTimestampNowAdjust; - } - - if (wantDebug) { - QDateTime currentLocalTime = QDateTime::currentDateTime(); - - quint64 msecsNow = now / 1000; // usecs to msecs - QDateTime nowAsString; - nowAsString.setMSecsSinceEpoch(msecsNow); - - quint64 msecsTimeReference = TIME_REFERENCE / 1000; // usecs to msecs - QDateTime timeReferenceAsString; - timeReferenceAsString.setMSecsSinceEpoch(msecsTimeReference); - - qCDebug(shared) << "usecTimestampNow() - details... "; - qCDebug(shared) << " TIME_REFERENCE:" << TIME_REFERENCE; - qCDebug(shared) << " timeReferenceAsString:" << timeReferenceAsString.toString("yyyy-MM-dd hh:mm:ss.zzz"); - qCDebug(shared) << " usecTimestampNowAdjust:" << usecTimestampNowAdjust; - qCDebug(shared) << " nsecsElapsed:" << nsecsElapsed; - qCDebug(shared) << " usecsElapsed:" << usecsElapsed; - qCDebug(shared) << " now:" << now; - qCDebug(shared) << " msecsNow:" << msecsNow; - qCDebug(shared) << " nowAsString:" << nowAsString.toString("yyyy-MM-dd hh:mm:ss.zzz"); - qCDebug(shared) << " currentLocalTime:" << currentLocalTime.toString("yyyy-MM-dd hh:mm:ss.zzz"); - } - - return now; + using namespace std::chrono; + static const auto unixEpoch = system_clock::from_time_t(0); + return duration_cast<microseconds>(system_clock::now() - unixEpoch).count() + usecTimestampNowAdjust; } float secTimestampNow() { From 1215ddbe88a6669173c7a635be0f0b8514e36fce Mon Sep 17 00:00:00 2001 From: Gabriel Calero <gcalero1984@gmail.com> Date: Thu, 13 Sep 2018 18:50:46 -0300 Subject: [PATCH 06/56] Remove debug files from linux qt build. Remove extra space --- android/build.gradle | 6 +++--- libraries/audio-client/src/AudioClient.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 8a3f4d75b9..14f0779e29 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -72,9 +72,9 @@ def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a') def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/' def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms") -def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl.tgz' -def qtChecksum='bf9e734d9c4e77c4807a38c93515e25c' -def qtVersionId='Qt3rvI0O7l5gLkacia2vA6KAchTgFkFf' +def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl_patched.tgz' +def qtChecksum='aa449d4bfa963f3bc9a9dfe558ba29df' +def qtVersionId='3S97HBM5G5Xw9EfE52sikmgdN3t6C2MN' if (Os.isFamily(Os.FAMILY_MAC)) { qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz' qtChecksum='c83cc477c08a892e00c71764dca051a0' diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index f0ba0307cc..814cbf87e4 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -454,7 +454,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { Setting::Handle<bool> enableAEC(SETTING_AEC_KEY, false); bool aecEnabled = enableAEC.get(); auto audioClient = DependencyManager::get<AudioClient>(); - bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false ; + bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false; auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); for (auto inputDevice : inputDevices) { if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) || From 5795ef3c6323c9dbfd5782b486d3687d3016e322 Mon Sep 17 00:00:00 2001 From: Gabriel Calero <gcalero1984@gmail.com> Date: Thu, 13 Sep 2018 19:43:28 -0300 Subject: [PATCH 07/56] Fix qt build (osx) file version --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 14f0779e29..aa7aa399b2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -78,7 +78,7 @@ def qtVersionId='3S97HBM5G5Xw9EfE52sikmgdN3t6C2MN' if (Os.isFamily(Os.FAMILY_MAC)) { qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz' qtChecksum='c83cc477c08a892e00c71764dca051a0' - qtVersionId='QNa.lwNJaPc0eGuIL.xZ8ebeTuLL7rh8' + qtVersionId='OxBD7iKINv1HbyOXmAmDrBb8AF3N.Kup' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz' qtChecksum='0582191cc55431aa4f660848a542883e' From 727aea20251af8bbe3c2b77d4ec35b9feda02662 Mon Sep 17 00:00:00 2001 From: Simon Walton <simon@highfidelity.io> Date: Thu, 13 Sep 2018 18:21:16 -0700 Subject: [PATCH 08/56] Remove comment --- libraries/shared/src/SharedUtil.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 886445824b..012e7aa1f5 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -122,7 +122,6 @@ void setGlobalInstance(const char* propertyName, const QVariant& variant) { } } -// Do we still need this? static qint64 usecTimestampNowAdjust = 0; // in usec void usecTimestampNowForceClockSkew(qint64 clockSkew) { ::usecTimestampNowAdjust = clockSkew; From b0cde2afcc40c94e10ef1b683af320fec6994947 Mon Sep 17 00:00:00 2001 From: Anthony Thibault <tony@highfidelity.io> Date: Fri, 14 Sep 2018 15:05:17 -0700 Subject: [PATCH 09/56] Small addition to Anim Stats * During a state machine interp the previous state now shows up in AlphaValues panel, with parentheses around the name. * Added 3 new fields * Position - in world coordinate frame. * Heading - the facing angle in the world corrdinate frame. * Local Vel - the velocity of the character in the local coordinate frame. (left, forward, up). --- interface/resources/qml/AnimStats.qml | 11 ++++++++-- interface/src/ui/AnimStats.cpp | 23 ++++++++++++++++++++ interface/src/ui/AnimStats.h | 20 ++++++++++++++--- libraries/animation/src/AnimStateMachine.cpp | 4 ++++ libraries/animation/src/AnimVariant.cpp | 9 ++++++++ 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml index 35ed3799a6..d000f3d9be 100644 --- a/interface/resources/qml/AnimStats.qml +++ b/interface/resources/qml/AnimStats.qml @@ -47,6 +47,9 @@ Item { id: firstCol spacing: 4; x: 4; y: 4; + StatText { + text: root.positionText + } StatText { text: "State Machines:---------------------------------------------------------------------------" } @@ -73,10 +76,12 @@ Item { id: secondCol spacing: 4; x: 4; y: 4; + StatText { + text: root.rotationText + } StatText { text: "Anim Vars:--------------------------------------------------------------------------------" } - ListView { width: secondCol.width height: root.animVars.length * 15 @@ -113,10 +118,12 @@ Item { id: thirdCol spacing: 4; x: 4; y: 4; + StatText { + text: root.velocityText + } StatText { text: "Alpha Values:--------------------------------------------------------------------------" } - ListView { width: thirdCol.width height: root.animAlphaValues.length * 15 diff --git a/interface/src/ui/AnimStats.cpp b/interface/src/ui/AnimStats.cpp index 7b4778e365..e3579fa2dc 100644 --- a/interface/src/ui/AnimStats.cpp +++ b/interface/src/ui/AnimStats.cpp @@ -42,6 +42,29 @@ void AnimStats::updateStats(bool force) { auto myAvatar = avatarManager->getMyAvatar(); auto debugAlphaMap = myAvatar->getSkeletonModel()->getRig().getDebugAlphaMap(); + glm::vec3 position = myAvatar->getWorldPosition(); + glm::quat rotation = myAvatar->getWorldOrientation(); + glm::vec3 velocity = myAvatar->getWorldVelocity(); + + _positionText = QString("Position: (%1, %2, %3)"). + arg(QString::number(position.x, 'f', 2)). + arg(QString::number(position.y, 'f', 2)). + arg(QString::number(position.z, 'f', 2)); + emit positionTextChanged(); + + glm::vec3 eulerRotation = safeEulerAngles(rotation); + _rotationText = QString("Heading: %1"). + arg(QString::number(glm::degrees(eulerRotation.y), 'f', 2)); + emit rotationTextChanged(); + + // transform velocity into rig coordinate frame. z forward. + glm::vec3 localVelocity = Quaternions::Y_180 * glm::inverse(rotation) * velocity; + _velocityText = QString("Local Vel: (%1, %2, %3)"). + arg(QString::number(localVelocity.x, 'f', 2)). + arg(QString::number(localVelocity.y, 'f', 2)). + arg(QString::number(localVelocity.z, 'f', 2)); + emit velocityTextChanged(); + // update animation debug alpha values QStringList newAnimAlphaValues; qint64 now = usecTimestampNow(); diff --git a/interface/src/ui/AnimStats.h b/interface/src/ui/AnimStats.h index 1a6795b498..7b6aaf7b54 100644 --- a/interface/src/ui/AnimStats.h +++ b/interface/src/ui/AnimStats.h @@ -19,6 +19,9 @@ class AnimStats : public QQuickItem { Q_PROPERTY(QStringList animAlphaValues READ animAlphaValues NOTIFY animAlphaValuesChanged) Q_PROPERTY(QStringList animVars READ animVars NOTIFY animVarsChanged) Q_PROPERTY(QStringList animStateMachines READ animStateMachines NOTIFY animStateMachinesChanged) + Q_PROPERTY(QString positionText READ positionText NOTIFY positionTextChanged) + Q_PROPERTY(QString rotationText READ rotationText NOTIFY rotationTextChanged) + Q_PROPERTY(QString velocityText READ velocityText NOTIFY velocityTextChanged) public: static AnimStats* getInstance(); @@ -27,9 +30,13 @@ public: void updateStats(bool force = false); - QStringList animAlphaValues() { return _animAlphaValues; } - QStringList animVars() { return _animVarsList; } - QStringList animStateMachines() { return _animStateMachines; } + QStringList animAlphaValues() const { return _animAlphaValues; } + QStringList animVars() const { return _animVarsList; } + QStringList animStateMachines() const { return _animStateMachines; } + + QString positionText() const { return _positionText; } + QString rotationText() const { return _rotationText; } + QString velocityText() const { return _velocityText; } public slots: void forceUpdateStats() { updateStats(true); } @@ -39,6 +46,9 @@ signals: void animAlphaValuesChanged(); void animVarsChanged(); void animStateMachinesChanged(); + void positionTextChanged(); + void rotationTextChanged(); + void velocityTextChanged(); private: QStringList _animAlphaValues; @@ -50,6 +60,10 @@ private: std::map<QString, qint64> _animVarChangedTimers; // last time animVar value has changed. QStringList _animStateMachines; + + QString _positionText; + QString _rotationText; + QString _velocityText; }; #endif // hifi_AnimStats_h diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 7f46cd614a..fb13b8e71c 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -88,6 +88,10 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co processOutputJoints(triggersOut); context.addStateMachineInfo(_id, _currentState->getID(), _previousState->getID(), _duringInterp, _alpha); + if (_duringInterp) { + // hack: add previoius state to debug alpha map, with parens around it's name. + context.setDebugAlpha(QString("(%1)").arg(_previousState->getID()), 1.0f - _alpha, AnimNodeType::Clip); + } return _poses; } diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp index 509462984a..21fe234f7b 100644 --- a/libraries/animation/src/AnimVariant.cpp +++ b/libraries/animation/src/AnimVariant.cpp @@ -140,14 +140,19 @@ std::map<QString, QString> AnimVariantMap::toDebugMap() const { result[pair.first] = QString::number(pair.second.getFloat(), 'f', 3); break; case AnimVariant::Type::Vec3: { + // To prevent filling up debug stats, don't show vec3 values + /* glm::vec3 value = pair.second.getVec3(); result[pair.first] = QString("(%1, %2, %3)"). arg(QString::number(value.x, 'f', 3)). arg(QString::number(value.y, 'f', 3)). arg(QString::number(value.z, 'f', 3)); + */ break; } case AnimVariant::Type::Quat: { + // To prevent filling up the anim stats, don't show quat values + /* glm::quat value = pair.second.getQuat(); result[pair.first] = QString("(%1, %2, %3, %4)"). arg(QString::number(value.x, 'f', 3)). @@ -155,10 +160,14 @@ std::map<QString, QString> AnimVariantMap::toDebugMap() const { arg(QString::number(value.z, 'f', 3)). arg(QString::number(value.w, 'f', 3)); break; + */ } case AnimVariant::Type::String: + // To prevent filling up anim stats, don't show string values + /* result[pair.first] = pair.second.getString(); break; + */ default: assert(("invalid AnimVariant::Type", false)); } From 1a44054c494af587aa617e93eeb06d7e2e3bb343 Mon Sep 17 00:00:00 2001 From: Seth Alves <seth.alves@gmail.com> Date: Sat, 15 Sep 2018 09:15:43 -0700 Subject: [PATCH 10/56] attempt fix for fb-17131 --- libraries/entities/src/EntityScriptingInterface.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index d9924cb9fd..8f0fde5c9a 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -132,8 +132,8 @@ EntityItemProperties convertPropertiesToScriptSemantics(const EntityItemProperti EntityItemProperties scriptSideProperties = entitySideProperties; scriptSideProperties.setLocalPosition(entitySideProperties.getPosition()); scriptSideProperties.setLocalRotation(entitySideProperties.getRotation()); - scriptSideProperties.setLocalVelocity(entitySideProperties.getLocalVelocity()); - scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getLocalAngularVelocity()); + scriptSideProperties.setLocalVelocity(entitySideProperties.getVelocity()); + scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getAngularVelocity()); scriptSideProperties.setLocalDimensions(entitySideProperties.getDimensions()); bool success; @@ -181,8 +181,6 @@ EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProper EntityItemProperties entitySideProperties = scriptSideProperties; bool success; - // TODO -- handle velocity and angularVelocity - if (scriptSideProperties.localPositionChanged()) { entitySideProperties.setPosition(scriptSideProperties.getLocalPosition()); } else if (scriptSideProperties.positionChanged()) { From f86075b2dda4303f706b65ebc9fb5f467615c302 Mon Sep 17 00:00:00 2001 From: Seth Alves <seth.alves@gmail.com> Date: Sat, 15 Sep 2018 09:21:12 -0700 Subject: [PATCH 11/56] Revert "Fix for angularVelocity zeroing out on duplication or undo" This reverts commit 9c96a10f6cf0c60eb39cab3ea3e7460d0dcda0a5. --- scripts/system/libraries/entitySelectionTool.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 5bca58b1ac..1fc0f0611c 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -29,17 +29,6 @@ Script.include([ SelectionManager = (function() { var that = {}; - /** - * @description Removes known to be broken properties from a properties object - * @param properties - * @return properties - */ - var fixRemoveBrokenProperties = function (properties) { - // Reason: Entity property is always set to 0,0,0 which causes it to override angularVelocity (see MS17131) - delete properties.localAngularVelocity; - return properties; - } - // FUNCTION: SUBSCRIBE TO UPDATE MESSAGES function subscribeToUpdateMessages() { Messages.subscribe("entityToolUpdates"); @@ -130,7 +119,7 @@ SelectionManager = (function() { that.savedProperties = {}; for (var i = 0; i < that.selections.length; i++) { var entityID = that.selections[i]; - that.savedProperties[entityID] = fixRemoveBrokenProperties(Entities.getEntityProperties(entityID)); + that.savedProperties[entityID] = Entities.getEntityProperties(entityID); } }; @@ -258,7 +247,7 @@ SelectionManager = (function() { var originalEntityID = entitiesToDuplicate[i]; var properties = that.savedProperties[originalEntityID]; if (properties === undefined) { - properties = fixRemoveBrokenProperties(Entities.getEntityProperties(originalEntityID)); + properties = Entities.getEntityProperties(originalEntityID); } if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) { if (nonDynamicEntityIsBeingGrabbedByAvatar(properties)) { From 11fe657c0588943a4d275f66923cf78c6237fbd2 Mon Sep 17 00:00:00 2001 From: Anthony Thibault <tony@highfidelity.io> Date: Mon, 17 Sep 2018 10:33:04 -0700 Subject: [PATCH 12/56] Swaped AnimVar and StatMachine panels --- interface/resources/qml/AnimStats.qml | 59 ++++++++++++++------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml index d000f3d9be..91ed0eb69b 100644 --- a/interface/resources/qml/AnimStats.qml +++ b/interface/resources/qml/AnimStats.qml @@ -50,35 +50,6 @@ Item { StatText { text: root.positionText } - StatText { - text: "State Machines:---------------------------------------------------------------------------" - } - ListView { - width: firstCol.width - height: root.animStateMachines.length * 15 - visible: root.animStateMchines.length > 0; - model: root.animStateMachines - delegate: StatText { - text: { - return modelData; - } - } - } - } - } - - Rectangle { - width: secondCol.width + 8 - height: secondCol.height + 8 - color: root.bgColor; - - Column { - id: secondCol - spacing: 4; x: 4; y: 4; - - StatText { - text: root.rotationText - } StatText { text: "Anim Vars:--------------------------------------------------------------------------------" } @@ -109,6 +80,36 @@ Item { } } + Rectangle { + width: secondCol.width + 8 + height: secondCol.height + 8 + color: root.bgColor; + + Column { + id: secondCol + spacing: 4; x: 4; y: 4; + + StatText { + text: root.rotationText + } + StatText { + text: "State Machines:---------------------------------------------------------------------------" + } + ListView { + width: firstCol.width + height: root.animStateMachines.length * 15 + visible: root.animStateMchines.length > 0; + model: root.animStateMachines + delegate: StatText { + text: { + return modelData; + } + } + } + + } + } + Rectangle { width: thirdCol.width + 8 height: thirdCol.height + 8 From 8229c14c4486c57e963f470dd9f0c54a56950d41 Mon Sep 17 00:00:00 2001 From: Anthony Thibault <ajt@highfidelity.io> Date: Mon, 17 Sep 2018 15:17:43 -0700 Subject: [PATCH 13/56] Fix for type in AnimStats.qml --- interface/resources/qml/AnimStats.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml index 91ed0eb69b..b1900cf0a7 100644 --- a/interface/resources/qml/AnimStats.qml +++ b/interface/resources/qml/AnimStats.qml @@ -98,7 +98,7 @@ Item { ListView { width: firstCol.width height: root.animStateMachines.length * 15 - visible: root.animStateMchines.length > 0; + visible: root.animStateMachines.length > 0; model: root.animStateMachines delegate: StatText { text: { From f2a046da9160edafec7b94d838f7ed19983a46ce Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Thu, 13 Sep 2018 12:15:18 -0700 Subject: [PATCH 14/56] Install marketplace item tester --- .../MarketplaceItemTester.qml | 157 ++++++++++++++++++ interface/src/Application.cpp | 9 +- interface/src/commerce/QmlCommerce.cpp | 1 + scripts/system/commerce/wallet.js | 23 +++ 4 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml new file mode 100644 index 0000000000..80cf82678f --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -0,0 +1,157 @@ +// +// marketplaceItemTester +// qml/hifi/commerce/marketplaceItemTester +// +// Load items not in the marketplace for testing purposes +// +// Created by Zach Fox on 2018-09-05 +// 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 +// + +import QtQuick 2.5 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Dialogs 1.0 +import QtQuick.Layouts 1.1 +import Hifi 1.0 as Hifi +import "../../../styles-uit" as HifiStylesUit +import "../../../controls-uit" as HifiControlsUit + + + +Rectangle { + id:root + HifiStylesUit.HifiConstants { id: hifi } + color: hifi.colors.white + ListModel { id: listModel } + ListView { + anchors.fill: parent + anchors.leftMargin: 12 + anchors.bottomMargin: 40 + model: listModel + spacing: 5 + delegate: RowLayout { + anchors.left: parent.left + width: parent.width + spacing: 5 + Text { + text: { + var match = resource.match(/\/([^/]*)$/); + return match ? match[1] : resource; + } + font.pointSize: 12 + Layout.preferredWidth: root.width * .6 + horizontalAlignment: Text.AlignBottom + } + Text { + text: assetType + font.pointSize: 10 + Layout.preferredWidth: root.width * .2 + horizontalAlignment: Text.AlignBottom + } + property var actions: { + "forward": function(resource, assetType){ + if ("application" == assetType) { + Commerce.installApp(resource); + Commerce.openApp(resource); + } + // XXX support other resource types here. + }, + "trash": function(){ + if ("application" == assetType) { + Commerce.uninstallApp(resource); + } + // XXX support other resource types here. + listModel.remove(index); + } + } + Repeater { + model: [ + { "name": "forward", "glyph": hifi.glyphs.forward, "size": 30 }, + { "name": "trash", "glyph": hifi.glyphs.trash, "size": 22} + ] + HifiStylesUit.HiFiGlyphs { + text: modelData.glyph + size: modelData.size + color: hifi.colors.black + horizontalAlignment: Text.AlignHCenter + MouseArea { + anchors.fill: parent + onClicked: { + actions[modelData.name](resource, assetType); + } + } + } + } + } + headerPositioning: ListView.OverlayHeader + header: HifiStylesUit.RalewayRegular { + id: rootHeader + text: "Marketplace Item Tester" + height: 80 + width: paintedWidth + size: 22 + color: hifi.colors.black + anchors.left: parent.left + anchors.leftMargin: 12 + } + footerPositioning: ListView.OverlayFooter + footer: Row { + id: rootActions + spacing: 20 + anchors.horizontalCenter: parent.horizontalCenter + property string currentAction + function assetType(resource) { + return (resource.match(/\.app\.json$/) ? "application" : + resource.match(/\.(?:fbx|fst)$/) ? "avatar" : + resource.match(/\.json\.gz$/) ? "content set" : + resource.match(/\.json$/) ? "entity or wearable" : + "unknown") + } + function onResourceSelected(resource) { + // It is possible that we received the present signal + // from something other than our browserAsync window. + // Alas, there is nothing we can do about that so charge + // ahead as though we are sure the present signal is one + // we expect. + if ("load file" == currentAction) { + print("disconnecting load file"); + Window.browseChanged.disconnect(onResourceSelected); + } else if ("load url" == currentAction) { + print("disconnecting load url"); + Window.promptTextChanged.disconnect(onResourceSelected); + } + if (resource) { + listModel.append( { + "resource": resource.trim(), + "assetType": assetType(resource.trim()) } ); + } + } + property var actions: { + "Load File": function(){ + rootActions.currentAction = "load file"; + Window.browseChanged.connect(onResourceSelected); + Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)"); + }, + "Load URL": function(){ + rootActions.currentAction = "load url"; + Window.promptTextChanged.connect(onResourceSelected); + Window.promptAsync("Please enter a URL", ""); + } + } + Repeater { + model: [ "Load File", "Load URL" ] + HifiControlsUit.Button { + color: hifi.buttons.blue + fontSize: 20 + text: modelData + width: root.width / 3 + height: 40 + onClicked: actions[text]() + } + } + } + } +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 826f6a87a9..0415475397 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1691,21 +1691,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return DependencyManager::get<OffscreenUi>()->navigationFocused() ? 1 : 0; }); _applicationStateDevice->setInputVariant(STATE_PLATFORM_WINDOWS, []() -> float { -#if defined(Q_OS_WIN) +#if defined(Q_OS_WIN) return 1; #else return 0; #endif }); _applicationStateDevice->setInputVariant(STATE_PLATFORM_MAC, []() -> float { -#if defined(Q_OS_MAC) +#if defined(Q_OS_MAC) return 1; #else return 0; #endif }); _applicationStateDevice->setInputVariant(STATE_PLATFORM_ANDROID, []() -> float { -#if defined(Q_OS_ANDROID) +#if defined(Q_OS_ANDROID) return 1; #else return 0; @@ -2881,9 +2881,10 @@ void Application::initializeUi() { QUrl{ "hifi/commerce/common/CommerceLightbox.qml" }, QUrl{ "hifi/commerce/common/EmulatedMarketplaceHeader.qml" }, QUrl{ "hifi/commerce/common/FirstUseTutorial.qml" }, - QUrl{ "hifi/commerce/common/SortableListModel.qml" }, QUrl{ "hifi/commerce/common/sendAsset/SendAsset.qml" }, + QUrl{ "hifi/commerce/common/SortableListModel.qml" }, QUrl{ "hifi/commerce/inspectionCertificate/InspectionCertificate.qml" }, + QUrl{ "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"}, QUrl{ "hifi/commerce/purchases/PurchasedItem.qml" }, QUrl{ "hifi/commerce/purchases/Purchases.qml" }, QUrl{ "hifi/commerce/wallet/Help.qml" }, diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 06da18148f..10be228310 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -228,6 +228,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { // Thus, we protect against deleting the .app.json from the user's disk (below) // by skipping that check for the app we just installed. if ((justInstalledAppID != "") && ((justInstalledAppID + ".app.json") == appFileName)) { + installedAppsFromMarketplace += appFileName + ","; continue; } diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 5939b36438..12ec7dce6b 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -583,6 +583,27 @@ // // Manage the connection between the button and the window. // + var DEVELOPER_MENU = "Developer"; + var MARKETPLACE_ITEM_TESTER_LABEL = "Marktplace Item Tester"; + var MARKETPLACE_ITEM_TESTER_QML_SOURCE = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; + function installMarketplaceItemTester() { + if (!Menu.menuExists(DEVELOPER_MENU)) { + Menu.addMenu(DEVELOPER_MENU); + } + + if (!Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)) { + Menu.addMenuItem({ menuName: DEVELOPER_MENU, + menuItemName: MARKETPLACE_ITEM_TESTER_LABEL, + isCheckable: false }) + } + + Menu.menuItemEvent.connect(function (menuItem) { + if (menuItem === MARKETPLACE_ITEM_TESTER_LABEL) { + tablet.loadQMLSource(MARKETPLACE_ITEM_TESTER_QML_SOURCE); + } + }); + } + var button; var buttonName = "WALLET"; var tablet = null; @@ -600,7 +621,9 @@ button.clicked.connect(onButtonClicked); tablet.screenChanged.connect(onTabletScreenChanged); } + installMarketplaceItemTester(); } + var isWired = false; var isUpdateOverlaysWired = false; function off() { From 8f5923d5624026d492ef5067dc915ccdfe56d4dc Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Tue, 18 Sep 2018 11:23:32 -0700 Subject: [PATCH 15/56] Show installed apps on start of marketplace item tester Also, refactor a bit. --- .../MarketplaceItemTester.qml | 120 ++++++++++++------ 1 file changed, 79 insertions(+), 41 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 80cf82678f..b36deb8b70 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -22,39 +22,61 @@ import "../../../controls-uit" as HifiControlsUit Rectangle { - id:root + id: root + property string installedApps HifiStylesUit.HifiConstants { id: hifi } + ListModel { id: resourceListModel } + color: hifi.colors.white - ListModel { id: listModel } + + function buildResourceObj(resource) { + resource = resource.trim(); + var assetType = (resource.match(/\.app\.json$/) ? "application" : + resource.match(/\.(?:fbx|fst)$/) ? "avatar" : + resource.match(/\.json\.gz$/) ? "content set" : + resource.match(/\.json$/) ? "entity or wearable" : + "unknown"); + return { "resource": resource, "assetType": assetType }; + } + + function installResourceObj(resourceObj) { + if ("application" == resourceObj["assetType"]) { + Commerce.installApp(resourceObj["resource"]); + } + // XXX support other asset types here + } + + function addAllInstalledAppsToList() { + var i, apps = Commerce.getInstalledApps().split(","), len = apps.length; + for(i = 0; i < len - 1; ++i) { + if (i in apps) { + resourceListModel.append(buildResourceObj(apps[i])); + } + } + } + + Component.onCompleted: { + // On startup, list includes all tester-installed assets. + addAllInstalledAppsToList(); + // XXX support other asset types here + } + ListView { anchors.fill: parent anchors.leftMargin: 12 anchors.bottomMargin: 40 - model: listModel + anchors.rightMargin: 12 + model: resourceListModel spacing: 5 + delegate: RowLayout { anchors.left: parent.left width: parent.width spacing: 5 - Text { - text: { - var match = resource.match(/\/([^/]*)$/); - return match ? match[1] : resource; - } - font.pointSize: 12 - Layout.preferredWidth: root.width * .6 - horizontalAlignment: Text.AlignBottom - } - Text { - text: assetType - font.pointSize: 10 - Layout.preferredWidth: root.width * .2 - horizontalAlignment: Text.AlignBottom - } + property var actions: { "forward": function(resource, assetType){ if ("application" == assetType) { - Commerce.installApp(resource); Commerce.openApp(resource); } // XXX support other resource types here. @@ -64,9 +86,27 @@ Rectangle { Commerce.uninstallApp(resource); } // XXX support other resource types here. - listModel.remove(index); + resourceListModel.remove(index); } } + + Text { + text: { + var match = resource.match(/\/([^/]*)$/); + return match ? match[1] : resource; + } + font.pointSize: 12 + Layout.preferredWidth: root.width * .6 + horizontalAlignment: Text.AlignBottom + } + + Text { + text: assetType + font.pointSize: 10 + Layout.preferredWidth: root.width * .2 + horizontalAlignment: Text.AlignBottom + } + Repeater { model: [ { "name": "forward", "glyph": hifi.glyphs.forward, "size": 30 }, @@ -86,6 +126,7 @@ Rectangle { } } } + headerPositioning: ListView.OverlayHeader header: HifiStylesUit.RalewayRegular { id: rootHeader @@ -97,19 +138,27 @@ Rectangle { anchors.left: parent.left anchors.leftMargin: 12 } + footerPositioning: ListView.OverlayFooter footer: Row { id: rootActions spacing: 20 anchors.horizontalCenter: parent.horizontalCenter + property string currentAction - function assetType(resource) { - return (resource.match(/\.app\.json$/) ? "application" : - resource.match(/\.(?:fbx|fst)$/) ? "avatar" : - resource.match(/\.json\.gz$/) ? "content set" : - resource.match(/\.json$/) ? "entity or wearable" : - "unknown") + property var actions: { + "Load File": function(){ + rootActions.currentAction = "load file"; + Window.browseChanged.connect(onResourceSelected); + Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)"); + }, + "Load URL": function(){ + rootActions.currentAction = "load url"; + Window.promptTextChanged.connect(onResourceSelected); + Window.promptAsync("Please enter a URL", ""); + } } + function onResourceSelected(resource) { // It is possible that we received the present signal // from something other than our browserAsync window. @@ -124,23 +173,12 @@ Rectangle { Window.promptTextChanged.disconnect(onResourceSelected); } if (resource) { - listModel.append( { - "resource": resource.trim(), - "assetType": assetType(resource.trim()) } ); - } - } - property var actions: { - "Load File": function(){ - rootActions.currentAction = "load file"; - Window.browseChanged.connect(onResourceSelected); - Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)"); - }, - "Load URL": function(){ - rootActions.currentAction = "load url"; - Window.promptTextChanged.connect(onResourceSelected); - Window.promptAsync("Please enter a URL", ""); + var resourceObj = buildResourceObj(resource); + installResourceObj(resourceObj); + resourceListModel.append(resourceObj); } } + Repeater { model: [ "Load File", "Load URL" ] HifiControlsUit.Button { From 645ad2bb7a3493a73f5aace63a49b8d03b605759 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Tue, 18 Sep 2018 12:36:03 -0700 Subject: [PATCH 16/56] Enable marketplace item test for avatar url --- .../marketplaceItemTester/MarketplaceItemTester.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index b36deb8b70..fed8480239 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -76,8 +76,12 @@ Rectangle { property var actions: { "forward": function(resource, assetType){ - if ("application" == assetType) { - Commerce.openApp(resource); + switch(assetType) { + case "application": + Commerce.openApp(resource); + break + case "avatar": + MyAvatar.useFullAvatarURL(resource); } // XXX support other resource types here. }, From a592565b7b0ee6c941cb6e6de561d0bbd1bbe160 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Tue, 18 Sep 2018 13:25:01 -0700 Subject: [PATCH 17/56] Enable marketplace item test for avatar file --- .../commerce/marketplaceItemTester/MarketplaceItemTester.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index fed8480239..4ef6109752 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -32,7 +32,7 @@ Rectangle { function buildResourceObj(resource) { resource = resource.trim(); var assetType = (resource.match(/\.app\.json$/) ? "application" : - resource.match(/\.(?:fbx|fst)$/) ? "avatar" : + resource.match(/\.fst$/) ? "avatar" : resource.match(/\.json\.gz$/) ? "content set" : resource.match(/\.json$/) ? "entity or wearable" : "unknown"); @@ -154,7 +154,7 @@ Rectangle { "Load File": function(){ rootActions.currentAction = "load file"; Window.browseChanged.connect(onResourceSelected); - Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)"); + Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)"); }, "Load URL": function(){ rootActions.currentAction = "load url"; From cfa9d185f02dfd764abb7cf2a7473ebaa097f5c0 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Tue, 18 Sep 2018 14:02:16 -0700 Subject: [PATCH 18/56] Add full resource paths to list in marketplace item tester --- .../MarketplaceItemTester.qml | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 4ef6109752..8b0ecf3436 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -94,21 +94,32 @@ Rectangle { } } - Text { - text: { - var match = resource.match(/\/([^/]*)$/); - return match ? match[1] : resource; - } - font.pointSize: 12 + Column { Layout.preferredWidth: root.width * .6 - horizontalAlignment: Text.AlignBottom + spacing: 5 + Text { + text: { + var match = resource.match(/\/([^/]*)$/); + return match ? match[1] : resource; + } + font.pointSize: 12 + horizontalAlignment: Text.AlignBottom + } + Text { + text: resource + font.pointSize: 8 + width: root.width * .6 + horizontalAlignment: Text.AlignBottom + wrapMode: Text.WrapAnywhere + } } Text { text: assetType font.pointSize: 10 Layout.preferredWidth: root.width * .2 - horizontalAlignment: Text.AlignBottom + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Test.AlignVCenter } Repeater { From 3a7785d10fcd660876d81d9a0437d1821d756bef Mon Sep 17 00:00:00 2001 From: Stephen Birarda <commit@birarda.com> Date: Tue, 18 Sep 2018 14:01:12 -0700 Subject: [PATCH 19/56] fix for deadlock in other avatar entity removal --- interface/src/avatar/AvatarManager.cpp | 40 +++++++++------- interface/src/avatar/AvatarManager.h | 5 +- libraries/avatars/src/AvatarHashMap.cpp | 62 ++++++++++++++++++------- libraries/avatars/src/AvatarHashMap.h | 2 + 4 files changed, 74 insertions(+), 35 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1faf17ea9a..d92a5c81da 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -456,25 +456,29 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar } void AvatarManager::clearOtherAvatars() { - // Remove other avatars from the world but don't actually remove them from _avatarHash - // each will either be removed on timeout or will re-added to the world on receipt of update. - const render::ScenePointer& scene = qApp->getMain3DScene(); - render::Transaction transaction; - - QReadLocker locker(&_hashLock); - AvatarHash::iterator avatarIterator = _avatarHash.begin(); - while (avatarIterator != _avatarHash.end()) { - auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value()); - if (avatar != _myAvatar) { - handleRemovedAvatar(avatar); - avatarIterator = _avatarHash.erase(avatarIterator); - } else { - ++avatarIterator; - } - } - assert(scene); - scene->enqueueTransaction(transaction); _myAvatar->clearLookAtTargetAvatar(); + + // setup a vector of removed avatars outside the scope of the hash lock + std::vector<AvatarSharedPointer> removedAvatars; + + { + QWriteLocker locker(&_hashLock); + + auto avatarIterator = _avatarHash.begin(); + while (avatarIterator != _avatarHash.end()) { + auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value()); + if (avatar != _myAvatar) { + removedAvatars.push_back(avatar); + avatarIterator = _avatarHash.erase(avatarIterator); + } else { + ++avatarIterator; + } + } + } + + for (auto& av : removedAvatars) { + handleRemovedAvatar(av); + } } void AvatarManager::deleteAllAvatars() { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 306ba6f39b..407b3c50de 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -204,7 +204,10 @@ private: void simulateAvatarFades(float deltaTime); AvatarSharedPointer newSharedAvatar() override; - void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; + + // must not be called while holding the hash lock + void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, + KillAvatarReason removalReason = KillAvatarReason::NoReason) override; QVector<AvatarSharedPointer> _avatarsToFade; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index d205a915f8..2ee51cca17 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -66,6 +66,22 @@ void AvatarReplicas::removeReplicas(const QUuid& parentID) { } } +std::vector<AvatarSharedPointer> AvatarReplicas::takeReplicas(const QUuid& parentID) { + std::vector<AvatarSharedPointer> replicas; + + auto it = _replicasMap.find(parentID); + + if (it != _replicasMap.end()) { + // take a copy of the replica shared pointers for this parent + replicas = it->second; + + // erase the replicas for this parent from our map + _replicasMap.erase(it); + } + + return replicas; +} + void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) { if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; @@ -386,24 +402,31 @@ void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, S } void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) { - QWriteLocker locker(&_hashLock); + std::vector<AvatarSharedPointer> removedAvatars; - auto replicaIDs = _replicas.getReplicaIDs(sessionUUID); - _replicas.removeReplicas(sessionUUID); - for (auto id : replicaIDs) { - auto removedReplica = _avatarHash.take(id); - if (removedReplica) { - handleRemovedAvatar(removedReplica, removalReason); + { + QWriteLocker locker(&_hashLock); + + auto replicas = _replicas.takeReplicas(sessionUUID); + + for (auto& replica : replicas) { + auto removedReplica = _avatarHash.take(replica->getID()); + if (removedReplica) { + removedAvatars.push_back(removedReplica); + } + } + + _pendingAvatars.remove(sessionUUID); + auto removedAvatar = _avatarHash.take(sessionUUID); + + if (removedAvatar) { + removedAvatars.push_back(removedAvatar); } } - _pendingAvatars.remove(sessionUUID); - auto removedAvatar = _avatarHash.take(sessionUUID); - - if (removedAvatar) { + for (auto& removedAvatar: removedAvatars) { handleRemovedAvatar(removedAvatar, removalReason); } - } void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { @@ -421,11 +444,18 @@ void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& ol } void AvatarHashMap::clearOtherAvatars() { - QWriteLocker locker(&_hashLock); + QList<AvatarSharedPointer> removedAvatars; - for (auto& av : _avatarHash) { - handleRemovedAvatar(av); + { + QWriteLocker locker(&_hashLock); + + // grab a copy of the current avatars so we can call handleRemoveAvatar for them + removedAvatars = _avatarHash.values(); + + _avatarHash.clear(); } - _avatarHash.clear(); + for (auto& av : removedAvatars) { + handleRemovedAvatar(av); + } } diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 70d7f8c04d..724dd1deac 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -49,6 +49,7 @@ public: void parseDataFromBuffer(const QUuid& parentID, const QByteArray& buffer); void processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged); void removeReplicas(const QUuid& parentID); + std::vector<AvatarSharedPointer> takeReplicas(const QUuid& parentID); void processTrait(const QUuid& parentID, AvatarTraits::TraitType traitType, QByteArray traitBinaryData); void processDeletedTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID); void processTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, @@ -180,6 +181,7 @@ protected: virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason); + // must not be called while holding the hash lock virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); AvatarHash _avatarHash; From 795580f4e1076e2ba9800d54f82e621f2cc030b7 Mon Sep 17 00:00:00 2001 From: Wayne Chen <wayne@highfidelity.io> Date: Wed, 19 Sep 2018 09:55:15 -0700 Subject: [PATCH 20/56] adding 404 redirect toggle with interstitial --- interface/src/ConnectionMonitor.cpp | 28 ++++++++++++++++++---- libraries/networking/src/AddressManager.h | 21 ++++++---------- libraries/networking/src/DomainHandler.cpp | 20 +++++++++++----- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 3c85cfb339..5eacc5259f 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -18,11 +18,15 @@ #include <DomainHandler.h> #include <AddressManager.h> #include <NodeList.h> +#include <SettingHandle.h> // Because the connection monitor is created at startup, the time we wait on initial load // should be longer to allow the application to initialize. static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000; static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000; +static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000; +static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000; +Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; void ConnectionMonitor::init() { // Connect to domain disconnected message @@ -37,20 +41,36 @@ void ConnectionMonitor::init() { _timer.setSingleShot(true); if (!domainHandler.isConnected()) { - _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); + if (enableInterstitialMode.get()) { + _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); + } else { + _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); + } } connect(&_timer, &QTimer::timeout, this, [this]() { - qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; // set in a timeout error - emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); + if (enableInterstitialMode.get()) { + qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; + emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); + } else { + qDebug() << "ConnectionMonitor: Showing connection failure window"; + DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(true); + } }); } void ConnectionMonitor::startTimer() { - _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); + if (enableInterstitialMode.get()) { + _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); + } else { + _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); + } } void ConnectionMonitor::stopTimer() { _timer.stop(); + if (!enableInterstitialMode.get()) { + DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(false); + } } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index c7cdf8f4ea..17041a5fd7 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -140,8 +140,7 @@ public: * </table> * @typedef {number} location.LookupTrigger */ - enum LookupTrigger - { + enum LookupTrigger { UserInput, Back, Forward, @@ -207,9 +206,8 @@ public slots: // functions and signals that should be exposed are moved to a scripting interface class. // // we currently expect this to be called from NodeList once handleLookupString has been called with a path - bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) { - return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); - } + bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) + { return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); } /**jsdoc * Go back to the previous location in your navigation history, if there is one. @@ -231,8 +229,7 @@ public slots: * location history is correctly maintained. */ void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { - handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); - } + handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); } /**jsdoc * Go to the default "welcome" metaverse address. @@ -364,8 +361,7 @@ signals: * location.locationChangeRequired.connect(onLocationChangeRequired); */ void locationChangeRequired(const glm::vec3& newPosition, - bool hasOrientationChange, - const glm::quat& newOrientation, + bool hasOrientationChange, const glm::quat& newOrientation, bool shouldFaceLocation); /**jsdoc @@ -448,11 +444,8 @@ private: bool handleNetworkAddress(const QString& lookupString, LookupTrigger trigger, bool& hostChanged); void handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly = false); - bool handleViewpoint(const QString& viewpointString, - bool shouldFace, - LookupTrigger trigger, - bool definitelyPathOnly = false, - const QString& pathString = QString()); + bool handleViewpoint(const QString& viewpointString, bool shouldFace, LookupTrigger trigger, + bool definitelyPathOnly = false, const QString& pathString = QString()); bool handleUsername(const QString& lookupString); bool handleDomainID(const QString& host); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index f34a93de96..d02cb75e8b 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -14,6 +14,7 @@ #include <math.h> #include <PathUtils.h> +#include <SettingHandle.h> #include <QtCore/QJsonDocument> #include <QtCore/QDataStream> @@ -29,6 +30,8 @@ #include "UserActivityLogger.h" #include "NetworkLogging.h" +Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; + DomainHandler::DomainHandler(QObject* parent) : QObject(parent), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), @@ -485,14 +488,19 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec #if defined(Q_OS_ANDROID) emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo); #else - if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) { - // ingest the error - this is a "hard" connection refusal. - setRedirectErrorState(_errorDomainURL, (int)reasonCode); - } else { + if (enableInterstitialMode.get()) { + if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) { + // ingest the error - this is a "hard" connection refusal. + setRedirectErrorState(_errorDomainURL, (int)reasonCode); + } else { + emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo); + } + _lastDomainConnectionError = (int)reasonCode; + } + else { emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo); } - _lastDomainConnectionError = (int)reasonCode; -#endif + #endif } auto accountManager = DependencyManager::get<AccountManager>(); From 01594d7a52a07c4a7a2e5443fbdcca6b8328a18e Mon Sep 17 00:00:00 2001 From: Wayne Chen <wayne@highfidelity.io> Date: Wed, 19 Sep 2018 10:17:14 -0700 Subject: [PATCH 21/56] fixing settings check in domain handler --- libraries/networking/src/DomainHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index d02cb75e8b..d2d576d8b7 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -30,8 +30,6 @@ #include "UserActivityLogger.h" #include "NetworkLogging.h" -Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; - DomainHandler::DomainHandler(QObject* parent) : QObject(parent), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), @@ -488,6 +486,8 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec #if defined(Q_OS_ANDROID) emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo); #else + Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; + if (enableInterstitialMode.get()) { if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) { // ingest the error - this is a "hard" connection refusal. From a43799fffa0199352221535f8237a0f1c18fc3dc Mon Sep 17 00:00:00 2001 From: Wayne Chen <wayne@highfidelity.io> Date: Wed, 19 Sep 2018 10:40:49 -0700 Subject: [PATCH 22/56] moving settings check within slotted functions --- interface/src/ConnectionMonitor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 5eacc5259f..8f0c12634c 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -26,7 +26,6 @@ static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000; static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000; static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000; static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000; -Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; void ConnectionMonitor::init() { // Connect to domain disconnected message @@ -41,6 +40,7 @@ void ConnectionMonitor::init() { _timer.setSingleShot(true); if (!domainHandler.isConnected()) { + Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; if (enableInterstitialMode.get()) { _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } else { @@ -50,6 +50,7 @@ void ConnectionMonitor::init() { connect(&_timer, &QTimer::timeout, this, [this]() { // set in a timeout error + Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; if (enableInterstitialMode.get()) { qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); @@ -61,6 +62,7 @@ void ConnectionMonitor::init() { } void ConnectionMonitor::startTimer() { + Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; if (enableInterstitialMode.get()) { _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } else { @@ -70,6 +72,7 @@ void ConnectionMonitor::startTimer() { void ConnectionMonitor::stopTimer() { _timer.stop(); + Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; if (!enableInterstitialMode.get()) { DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(false); } From 7e796f7e7f268a8ab3fd49c4b1f3ef90e41c2447 Mon Sep 17 00:00:00 2001 From: Wayne Chen <wayne@highfidelity.io> Date: Wed, 19 Sep 2018 14:26:30 -0700 Subject: [PATCH 23/56] committing requested changes --- interface/src/ConnectionMonitor.cpp | 13 ++++--------- interface/src/ConnectionMonitor.h | 5 ++++- libraries/networking/src/DomainHandler.cpp | 7 ++----- libraries/networking/src/DomainHandler.h | 3 +++ 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 8f0c12634c..ca90d039f4 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -18,7 +18,6 @@ #include <DomainHandler.h> #include <AddressManager.h> #include <NodeList.h> -#include <SettingHandle.h> // Because the connection monitor is created at startup, the time we wait on initial load // should be longer to allow the application to initialize. @@ -40,8 +39,7 @@ void ConnectionMonitor::init() { _timer.setSingleShot(true); if (!domainHandler.isConnected()) { - Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; - if (enableInterstitialMode.get()) { + if (_enableInterstitialMode.get()) { _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } else { _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); @@ -50,8 +48,7 @@ void ConnectionMonitor::init() { connect(&_timer, &QTimer::timeout, this, [this]() { // set in a timeout error - Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; - if (enableInterstitialMode.get()) { + if (_enableInterstitialMode.get()) { qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); } else { @@ -62,8 +59,7 @@ void ConnectionMonitor::init() { } void ConnectionMonitor::startTimer() { - Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; - if (enableInterstitialMode.get()) { + if (_enableInterstitialMode.get()) { _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } else { _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); @@ -72,8 +68,7 @@ void ConnectionMonitor::startTimer() { void ConnectionMonitor::stopTimer() { _timer.stop(); - Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; - if (!enableInterstitialMode.get()) { + if (!_enableInterstitialMode.get()) { DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(false); } } diff --git a/interface/src/ConnectionMonitor.h b/interface/src/ConnectionMonitor.h index 5e75e2618b..f0589a3b8c 100644 --- a/interface/src/ConnectionMonitor.h +++ b/interface/src/ConnectionMonitor.h @@ -15,6 +15,8 @@ #include <QObject> #include <QTimer> +#include <SettingHandle.h> + class QUrl; class QString; @@ -32,6 +34,7 @@ private slots: private: QTimer _timer; + Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false }; }; -#endif // hifi_ConnectionMonitor_h \ No newline at end of file +#endif // hifi_ConnectionMonitor_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index d2d576d8b7..b2f118c5c8 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -14,7 +14,6 @@ #include <math.h> #include <PathUtils.h> -#include <SettingHandle.h> #include <QtCore/QJsonDocument> #include <QtCore/QDataStream> @@ -486,9 +485,8 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec #if defined(Q_OS_ANDROID) emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo); #else - Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false }; - if (enableInterstitialMode.get()) { + if (_enableInterstitialMode.get()) { if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) { // ingest the error - this is a "hard" connection refusal. setRedirectErrorState(_errorDomainURL, (int)reasonCode); @@ -496,8 +494,7 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo); } _lastDomainConnectionError = (int)reasonCode; - } - else { + } else { emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo); } #endif diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 3d361482ac..5fa920a554 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -19,6 +19,8 @@ #include <QtCore/QUrl> #include <QtNetwork/QHostInfo> +#include <SettingHandle.h> + #include "HifiSockAddr.h" #include "NetworkPeer.h" #include "NLPacket.h" @@ -221,6 +223,7 @@ private: NetworkPeer _icePeer; bool _isConnected { false }; bool _isInErrorState { false }; + Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false }; QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; From 8805ce49e3e1349c4a123cca909c00aaf915e934 Mon Sep 17 00:00:00 2001 From: Ryan Huffman <ryanhuffman@gmail.com> Date: Wed, 19 Sep 2018 15:06:58 -0700 Subject: [PATCH 24/56] Remove verbose logging of keyboard messages in edit.js and gridTool.js --- scripts/system/edit.js | 4 +--- scripts/system/libraries/gridTool.js | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 88ae2852be..b1f01e1ea9 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2134,9 +2134,7 @@ var PropertiesTool = function (opts) { var onWebEventReceived = function(data) { try { data = JSON.parse(data); - } - catch(e) { - print('Edit.js received web event that was not valid json.'); + } catch(e) { return; } var i, properties, dY, diff, newPosition; diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index 3a114f23c7..6f62742e8f 100644 --- a/scripts/system/libraries/gridTool.js +++ b/scripts/system/libraries/gridTool.js @@ -267,7 +267,6 @@ GridTool = function(opts) { try { data = JSON.parse(data); } catch (e) { - print("gridTool.js: Error parsing JSON: " + e.name + " data " + data); return; } From 5bc7d944f961fb34e3d5b2ce1e35dd2e952ad5ea Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Wed, 19 Sep 2018 15:17:13 -0700 Subject: [PATCH 25/56] Remove whitespace --- .../hifi/commerce/purchases/PurchasedItem.qml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 032d9b0199..eeb9ac3c54 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -59,7 +59,7 @@ Item { Connections { target: Commerce; - + onContentSetChanged: { if (contentSetHref === root.itemHref) { showConfirmation = true; @@ -135,7 +135,7 @@ Item { anchors.topMargin: 8; width: 30; height: width; - + HiFiGlyphs { id: closeContextMenuGlyph; text: hifi.glyphs.close; @@ -376,7 +376,7 @@ Item { } } } - + transform: Rotation { id: rotation; origin.x: flipable.width/2; @@ -509,7 +509,7 @@ Item { } verticalAlignment: Text.AlignTop; } - + HiFiGlyphs { id: statusIcon; text: { @@ -588,7 +588,7 @@ Item { border.width: 1; border.color: "#E2334D"; } - + HiFiGlyphs { id: contextMenuGlyph; text: hifi.glyphs.verticalEllipsis; @@ -615,7 +615,7 @@ Item { } } } - + Rectangle { id: rezzedNotifContainer; z: 998; @@ -663,13 +663,13 @@ Item { Tablet.playSound(TabletEnums.ButtonHover); } } - + onFocusChanged: { if (focus) { Tablet.playSound(TabletEnums.ButtonHover); } } - + onClicked: { Tablet.playSound(TabletEnums.ButtonClick); if (root.itemType === "contentSet") { @@ -775,7 +775,7 @@ Item { // Style color: hifi.colors.redAccent; horizontalAlignment: Text.AlignRight; - + MouseArea { anchors.fill: parent; hoverEnabled: true; From 66bf45c3ef1819b98ec880c4559592f94660a571 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Wed, 19 Sep 2018 15:18:18 -0700 Subject: [PATCH 26/56] Update location only if certificate id --- interface/src/commerce/QmlCommerce.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 10be228310..7c5df0f3e3 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -199,12 +199,18 @@ void QmlCommerce::transferAssetToUsername(const QString& username, } void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) { - auto ledger = DependencyManager::get<Ledger>(); - ledger->updateLocation(certificateID, DependencyManager::get<AddressManager>()->getPlaceName(), true); + if (!certificateID.isEmpty()) { + auto ledger = DependencyManager::get<Ledger>(); + ledger->updateLocation( + certificateID, + DependencyManager::get<AddressManager>()->getPlaceName(), + true); + } qApp->replaceDomainContent(itemHref); - QJsonObject messageProperties = { { "status", "SuccessfulRequestToReplaceContent" }, { "content_set_url", itemHref } }; + QJsonObject messageProperties = { + { "status", "SuccessfulRequestToReplaceContent" }, + { "content_set_url", itemHref } }; UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties); - emit contentSetChanged(itemHref); } From 38fd9523ee25081005753486dbd45d067080f358 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Wed, 19 Sep 2018 15:57:29 -0700 Subject: [PATCH 27/56] Attempt to load content set This does not result in the user seeing the content set. --- .../MarketplaceItemTester.qml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 8b0ecf3436..69e1a6122c 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -55,6 +55,11 @@ Rectangle { } } + function isHttp(str) { + var httpPattern = /^http/i; + return httpPattern.test(str); + } + Component.onCompleted: { // On startup, list includes all tester-installed assets. addAllInstalledAppsToList(); @@ -79,9 +84,15 @@ Rectangle { switch(assetType) { case "application": Commerce.openApp(resource); - break + break; case "avatar": MyAvatar.useFullAvatarURL(resource); + break; + case "content set": + resource = isHttp(resource) ? resource : "file:///" + resource; + Commerce.replaceContentSet(resource, ""); + urlHandler.handleUrl("hifi://localhost/0,0,0"); + break; } // XXX support other resource types here. }, @@ -119,7 +130,7 @@ Rectangle { font.pointSize: 10 Layout.preferredWidth: root.width * .2 horizontalAlignment: Text.AlignHCenter - verticalAlignment: Test.AlignVCenter + verticalAlignment: Text.AlignVCenter } Repeater { From 5445d6f6704ab702947a4da45ddf2ec528e96bc5 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Wed, 19 Sep 2018 16:06:43 -0700 Subject: [PATCH 28/56] Switch to localhost before replacing content --- .../commerce/marketplaceItemTester/MarketplaceItemTester.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 69e1a6122c..23c3bc976f 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -90,8 +90,8 @@ Rectangle { break; case "content set": resource = isHttp(resource) ? resource : "file:///" + resource; - Commerce.replaceContentSet(resource, ""); urlHandler.handleUrl("hifi://localhost/0,0,0"); + Commerce.replaceContentSet(resource, ""); break; } // XXX support other resource types here. From afa8e44e66e359475ec07b68f1cc0f72121f0096 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Thu, 20 Sep 2018 10:53:32 -0700 Subject: [PATCH 29/56] Refactor --- .../marketplaceItemTester/MarketplaceItemTester.qml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 23c3bc976f..d582e9aa82 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -55,9 +55,9 @@ Rectangle { } } - function isHttp(str) { + function toUrl(resource) { var httpPattern = /^http/i; - return httpPattern.test(str); + return httpPattern.test(str) ? resource : "file:///" + resource; } Component.onCompleted: { @@ -89,9 +89,10 @@ Rectangle { MyAvatar.useFullAvatarURL(resource); break; case "content set": - resource = isHttp(resource) ? resource : "file:///" + resource; urlHandler.handleUrl("hifi://localhost/0,0,0"); - Commerce.replaceContentSet(resource, ""); + Commerce.replaceContentSet(toUrl(resource), ""); + break; + case "entity": break; } // XXX support other resource types here. From 72adfb7ff09ea4f708ff1edae0f3062e85882f4a Mon Sep 17 00:00:00 2001 From: Ryan Huffman <ryanhuffman@gmail.com> Date: Tue, 18 Sep 2018 11:08:40 -0700 Subject: [PATCH 30/56] Fix entity list selection failing when selecting entities not in list --- scripts/system/html/js/entityList.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 9cfdf6df22..0e025ef4c4 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -161,7 +161,10 @@ function loaded() { selectedEntities.forEach(function(entityID) { if (selection.indexOf(entityID) === -1) { - entitiesByID[entityID].el.className = ''; + let entity = entitiesByID[entityID]; + if (entity !== undefined) { + entity.el.className = ''; + } } }); @@ -385,15 +388,18 @@ function loaded() { let notFound = false; selectedEntities.forEach(function(id) { - entitiesByID[id].el.className = ''; + let entity = entitiesByID[id]; + if (entity !== undefined) { + entity.el.className = ''; + } }); selectedEntities = []; for (let i = 0; i < selectedIDs.length; i++) { let id = selectedIDs[i]; selectedEntities.push(id); - if (id in entitiesByID) { - let entity = entitiesByID[id]; + let entity = entitiesByID[id]; + if (entity !== undefined) { entity.el.className = 'selected'; } else { notFound = true; From ae4352f450ba93ecf448b1f74434ecc7614800c9 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Thu, 20 Sep 2018 13:55:22 -0700 Subject: [PATCH 31/56] Rez entity in marketplace item tester --- .../MarketplaceItemTester.qml | 16 +++++- scripts/system/marketplaces/marketplaces.js | 51 ++++++++++++++----- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index d582e9aa82..9927c072db 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -23,7 +23,10 @@ import "../../../controls-uit" as HifiControlsUit Rectangle { id: root + property string installedApps + signal sendToScript(var message) + HifiStylesUit.HifiConstants { id: hifi } ListModel { id: resourceListModel } @@ -57,7 +60,11 @@ Rectangle { function toUrl(resource) { var httpPattern = /^http/i; - return httpPattern.test(str) ? resource : "file:///" + resource; + return httpPattern.test(resource) ? resource : "file:///" + resource; + } + + function rezEntity(itemHref, itemType) { + } Component.onCompleted: { @@ -92,7 +99,12 @@ Rectangle { urlHandler.handleUrl("hifi://localhost/0,0,0"); Commerce.replaceContentSet(toUrl(resource), ""); break; - case "entity": + case "entity or wearable": + print("going to rez " + toUrl(resource)); + sendToScript({ + method: 'tester_rezClicked', + itemHref: toUrl(resource), + itemType: "entity"}); break; } // XXX support other resource types here. diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 7b4f05193f..01c21044d2 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -21,20 +21,20 @@ var selectionDisplay = null; // for gridTool.js to ignore Script.include("/~/system/libraries/gridTool.js"); Script.include("/~/system/libraries/connectionUtils.js"); - var METAVERSE_SERVER_URL = Account.metaverseServerURL; + // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; + var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; + var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; + var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; + var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; + var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. - var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); - var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); - var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; - var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; - var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; + var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); + var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); + var METAVERSE_SERVER_URL = Account.metaverseServerURL; var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav")); - var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; - // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; - // Event bridge messages. var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; var CLARA_IO_STATUS = "CLARA.IO STATUS"; @@ -786,7 +786,7 @@ var selectionDisplay = null; // for gridTool.js to ignore } sendAssetRecipient = null; } - + var savedDisablePreviewOptionLocked = false; var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");; function maybeEnableHMDPreview() { @@ -855,6 +855,8 @@ var selectionDisplay = null; // for gridTool.js to ignore break; case 'checkout_rezClicked': case 'purchases_rezClicked': + case 'tester_rezClicked': + print("marketplace.js going to rez"); rezEntity(message.itemHref, message.itemType); break; case 'header_marketplaceImageClicked': @@ -1032,19 +1034,40 @@ var selectionDisplay = null; // for gridTool.js to ignore // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. var onWalletScreen = false; var onCommerceScreen = false; + var onMarketplaceItemTesterScreen = false; function onTabletScreenChanged(type, url) { onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; - var onCommerceScreenNow = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH - || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); + var onCommerceScreenNow = type === "QML" && ( + url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || + url === MARKETPLACE_PURCHASES_QML_PATH || + url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); + var onMarketplaceItemTesterScreenNow = ( + url.indexOf(MARKETPLACE_ITEM_TESTER_QML_PATH) !== -1 || + url === MARKETPLACE_ITEM_TESTER_QML_PATH); - if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { // exiting wallet or commerce screen + if ((!onWalletScreenNow && onWalletScreen) || + (!onCommerceScreenNow && onCommerceScreen) || + (!onMarketplaceItemTesterScreenNow && onMarketplaceScreen) + ) { + // exiting wallet, commerce, or marketplace item tester screen maybeEnableHMDPreview(); } onCommerceScreen = onCommerceScreenNow; onWalletScreen = onWalletScreenNow; - wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); + onMarketplaceItemTesterScreen = onMarketplaceItemTesterScreenNow; + + print("wire event bridge " + (onMarketplaceScreen || + onCommerceScreen || + onWalletScreen || + onMarketplaceItemTesterScreen)); + + wireEventBridge( + onMarketplaceScreen || + onCommerceScreen || + onWalletScreen || + onMarketplaceItemTesterScreen); if (url === MARKETPLACE_PURCHASES_QML_PATH) { sendToQml({ From c1cc031bd0c7ddcadb5215bb50ec264f81d8411c Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Thu, 20 Sep 2018 14:50:27 -0700 Subject: [PATCH 32/56] Remove cruft and refactor --- .../MarketplaceItemTester.qml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 9927c072db..37d2f2c170 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -45,8 +45,9 @@ Rectangle { function installResourceObj(resourceObj) { if ("application" == resourceObj["assetType"]) { Commerce.installApp(resourceObj["resource"]); + } else { + print("Cannot install resource object type " + resourceObj["assetType"]); } - // XXX support other asset types here } function addAllInstalledAppsToList() { @@ -63,14 +64,16 @@ Rectangle { return httpPattern.test(resource) ? resource : "file:///" + resource; } - function rezEntity(itemHref, itemType) { - + function rezEntity(resource, entityType) { + sendToScript({ + method: 'tester_rezClicked', + itemHref: toUrl(resource), + itemType: entityType}); } Component.onCompleted: { // On startup, list includes all tester-installed assets. addAllInstalledAppsToList(); - // XXX support other asset types here } ListView { @@ -99,21 +102,18 @@ Rectangle { urlHandler.handleUrl("hifi://localhost/0,0,0"); Commerce.replaceContentSet(toUrl(resource), ""); break; - case "entity or wearable": - print("going to rez " + toUrl(resource)); - sendToScript({ - method: 'tester_rezClicked', - itemHref: toUrl(resource), - itemType: "entity"}); + case "entity": + case "wearable": + rezEntity(resource, assetType); break; + default: + print("Marketplace item tester unsupported assetType " + assetType); } - // XXX support other resource types here. }, "trash": function(){ if ("application" == assetType) { Commerce.uninstallApp(resource); } - // XXX support other resource types here. resourceListModel.remove(index); } } From f9c25f2f32220e4f22969b9d65e8750f82774499 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Thu, 20 Sep 2018 16:36:06 -0700 Subject: [PATCH 33/56] Allow user to select asset type in marketplace item tester --- .../MarketplaceItemTester.qml | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 37d2f2c170..559b6cb29c 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -12,6 +12,7 @@ // import QtQuick 2.5 +import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.0 import QtQuick.Layouts 1.1 @@ -83,6 +84,7 @@ Rectangle { anchors.rightMargin: 12 model: resourceListModel spacing: 5 + interactive: false delegate: RowLayout { anchors.left: parent.left @@ -138,12 +140,25 @@ Rectangle { } } - Text { - text: assetType - font.pointSize: 10 + ComboBox { + id: comboBox + Layout.preferredWidth: root.width * .2 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + + model: [ + "application", + "avatar", + "content set", + "entity", + "wearable", + "unknown" + ] + + currentIndex: ("entity or wearable" == assetType) ? model.indexOf("unknown") : model.indexOf(assetType) + + Component.onCompleted: { + onActivated.connect(function() { assetType = currentText; }); + } } Repeater { @@ -159,7 +174,7 @@ Rectangle { MouseArea { anchors.fill: parent onClicked: { - actions[modelData.name](resource, assetType); + actions[modelData.name](resource, comboBox.currentText); } } } From 86e502637e581d6cdd9e5e6cc48cb37215891dd7 Mon Sep 17 00:00:00 2001 From: Seth Alves <seth.alves@gmail.com> Date: Sun, 16 Sep 2018 12:18:48 -0700 Subject: [PATCH 34/56] Make Settings thread safe --- interface/src/avatar/MyAvatar.cpp | 211 +++++++++++------------- interface/src/avatar/MyAvatar.h | 18 ++ libraries/shared/src/SettingManager.cpp | 105 ++++++++++-- libraries/shared/src/SettingManager.h | 21 ++- 4 files changed, 224 insertions(+), 131 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c47cfdb383..df7ec93b6a 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -91,6 +91,8 @@ const float MIN_SCALE_CHANGED_DELTA = 0.001f; const int MODE_READINGS_RING_BUFFER_SIZE = 500; const float CENTIMETERS_PER_METER = 100.0f; +const QString AVATAR_SETTINGS_GROUP_NAME { "Avatar" }; + MyAvatar::MyAvatar(QThread* thread) : Avatar(thread), _yawSpeed(YAW_SPEED_DEFAULT), @@ -118,7 +120,22 @@ MyAvatar::MyAvatar(QThread* thread) : _goToOrientation(), _prevShouldDrawHead(true), _audioListenerMode(FROM_HEAD), - _hmdAtRestDetector(glm::vec3(0), glm::quat()) + _hmdAtRestDetector(glm::vec3(0), glm::quat()), + _dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND), + _headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f), + _scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale), + _yawSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "yawSpeed", _yawSpeed), + _pitchSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "pitchSpeed", _pitchSpeed), + _fullAvatarURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "fullAvatarURL", + AvatarData::defaultFullAvatarModelUrl()), + _fullAvatarModelNameSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "fullAvatarModelName", _fullAvatarModelName), + _animGraphURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "animGraphURL", QUrl("")), + _displayNameSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "displayName", ""), + _collisionSoundURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "collisionSoundURL", QUrl(_collisionSoundURL)), + _useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn), + _userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT), + _flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD), + _avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", _flyingPrefHMD) { _clientTraitsHandler = std::unique_ptr<ClientTraitsHandler>(new ClientTraitsHandler(this)); @@ -1135,88 +1152,80 @@ void MyAvatar::restoreRoleAnimation(const QString& role) { } void MyAvatar::saveAvatarUrl() { - Settings settings; - settings.beginGroup("Avatar"); - if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid() ) { - settings.setValue("fullAvatarURL", - _fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ? - "" : - _fullAvatarURLFromPreferences.toString()); + if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid()) { + _fullAvatarURLSetting.set(_fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ? + "" : + _fullAvatarURLFromPreferences.toString()); + } +} + +void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex) { + // The original Settings interface saved avatar-entity array data like this: + // Avatar/avatarEntityData/size: 5 + // Avatar/avatarEntityData/1/id: ... + // Avatar/avatarEntityData/1/properties: ... + // ... + // Avatar/avatarEntityData/5/id: ... + // Avatar/avatarEntityData/5/properties: ... + // + // Create Setting::Handles to mimic this. + + while (_avatarEntityIDSettings.size() <= avatarEntityIndex) { + Setting::Handle<QUuid> idHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" + << QString::number(avatarEntityIndex + 1) << "id", QUuid()); + _avatarEntityIDSettings.push_back(idHandle); + Setting::Handle<QByteArray> dataHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" + << QString::number(avatarEntityIndex + 1) << "properties", QByteArray()); + _avatarEntityDataSettings.push_back(dataHandle); } - settings.endGroup(); } void MyAvatar::saveData() { - Settings settings; - settings.beginGroup("Avatar"); - - settings.setValue("dominantHand", _dominantHand); - settings.setValue("headPitch", getHead()->getBasePitch()); - - settings.setValue("scale", _targetScale); - - settings.setValue("yawSpeed", _yawSpeed); - settings.setValue("pitchSpeed", _pitchSpeed); + _dominantHandSetting.set(_dominantHand); + _headPitchSetting.set(getHead()->getBasePitch()); + _scaleSetting.set(_targetScale); + _yawSpeedSetting.set(_yawSpeed); + _pitchSpeedSetting.set(_pitchSpeed); // only save the fullAvatarURL if it has not been overwritten on command line // (so the overrideURL is not valid), or it was overridden _and_ we specified // --replaceAvatarURL (so _saveAvatarOverrideUrl is true) if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid() ) { - settings.setValue("fullAvatarURL", - _fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ? - "" : - _fullAvatarURLFromPreferences.toString()); + _fullAvatarURLSetting.set(_fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ? + "" : + _fullAvatarURLFromPreferences.toString()); } - settings.setValue("fullAvatarModelName", _fullAvatarModelName); - + _fullAvatarModelNameSetting.set(_fullAvatarModelName); QUrl animGraphUrl = _prefOverrideAnimGraphUrl.get(); - settings.setValue("animGraphURL", animGraphUrl); + _animGraphURLSetting.set(animGraphUrl); + _displayNameSetting.set(_displayName); + _collisionSoundURLSetting.set(_collisionSoundURL); + _useSnapTurnSetting.set(_useSnapTurn); + _userHeightSetting.set(getUserHeight()); + _flyingHMDSetting.set(getFlyingHMDPref()); - settings.beginWriteArray("attachmentData"); - for (int i = 0; i < _attachmentData.size(); i++) { - settings.setArrayIndex(i); - const AttachmentData& attachment = _attachmentData.at(i); - settings.setValue("modelURL", attachment.modelURL); - settings.setValue("jointName", attachment.jointName); - settings.setValue("translation_x", attachment.translation.x); - settings.setValue("translation_y", attachment.translation.y); - settings.setValue("translation_z", attachment.translation.z); - glm::vec3 eulers = safeEulerAngles(attachment.rotation); - settings.setValue("rotation_x", eulers.x); - settings.setValue("rotation_y", eulers.y); - settings.setValue("rotation_z", eulers.z); - settings.setValue("scale", attachment.scale); - settings.setValue("isSoft", attachment.isSoft); - } - settings.endArray(); - - settings.remove("avatarEntityData"); - settings.beginWriteArray("avatarEntityData"); - int avatarEntityIndex = 0; auto hmdInterface = DependencyManager::get<HMDScriptingInterface>(); _avatarEntitiesLock.withReadLock([&] { - for (auto entityID : _avatarEntityData.keys()) { - if (hmdInterface->getCurrentTabletFrameID() == entityID) { - // don't persist the tablet between domains / sessions - continue; - } + QList<QUuid> avatarEntityIDs = _avatarEntityData.keys(); + unsigned int avatarEntityCount = avatarEntityIDs.size(); + unsigned int previousAvatarEntityCount = _avatarEntityCountSetting.get(0); + resizeAvatarEntitySettingHandles(std::max<unsigned int>(avatarEntityCount, previousAvatarEntityCount)); + _avatarEntityCountSetting.set(avatarEntityCount); - settings.setArrayIndex(avatarEntityIndex); - settings.setValue("id", entityID); - settings.setValue("properties", _avatarEntityData.value(entityID)); + unsigned int avatarEntityIndex = 0; + for (auto entityID : avatarEntityIDs) { + _avatarEntityIDSettings[avatarEntityIndex].set(entityID); + _avatarEntityDataSettings[avatarEntityIndex].set(_avatarEntityData.value(entityID)); avatarEntityIndex++; } + + // clean up any left-over (due to the list shrinking) slots + for (; avatarEntityIndex < previousAvatarEntityCount; avatarEntityIndex++) { + _avatarEntityIDSettings[avatarEntityIndex].remove(); + _avatarEntityDataSettings[avatarEntityIndex].remove(); + } }); - settings.endArray(); - - settings.setValue("displayName", _displayName); - settings.setValue("collisionSoundURL", _collisionSoundURL); - settings.setValue("useSnapTurn", _useSnapTurn); - settings.setValue("userHeight", getUserHeight()); - settings.setValue("flyingHMD", getFlyingHMDPref()); - - settings.endGroup(); } float loadSetting(Settings& settings, const QString& name, float defaultValue) { @@ -1314,66 +1323,36 @@ void MyAvatar::setEnableInverseKinematics(bool isEnabled) { } void MyAvatar::loadData() { - Settings settings; - settings.beginGroup("Avatar"); + getHead()->setBasePitch(_headPitchSetting.get()); - getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f)); + _yawSpeed = _yawSpeedSetting.get(_yawSpeed); + _pitchSpeed = _pitchSpeedSetting.get(_pitchSpeed); - _yawSpeed = loadSetting(settings, "yawSpeed", _yawSpeed); - _pitchSpeed = loadSetting(settings, "pitchSpeed", _pitchSpeed); - - _prefOverrideAnimGraphUrl.set(QUrl(settings.value("animGraphURL", "").toString())); - _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl(); - _fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString(); + _prefOverrideAnimGraphUrl.set(_prefOverrideAnimGraphUrl.get().toString()); + _fullAvatarURLFromPreferences = _fullAvatarURLSetting.get(QUrl(AvatarData::defaultFullAvatarModelUrl())); + _fullAvatarModelName = _fullAvatarModelNameSetting.get(DEFAULT_FULL_AVATAR_MODEL_NAME).toString(); useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - int attachmentCount = settings.beginReadArray("attachmentData"); - for (int i = 0; i < attachmentCount; i++) { - settings.setArrayIndex(i); - AttachmentData attachment; - attachment.modelURL = settings.value("modelURL").toUrl(); - attachment.jointName = settings.value("jointName").toString(); - attachment.translation.x = loadSetting(settings, "translation_x", 0.0f); - attachment.translation.y = loadSetting(settings, "translation_y", 0.0f); - attachment.translation.z = loadSetting(settings, "translation_z", 0.0f); - glm::vec3 eulers; - eulers.x = loadSetting(settings, "rotation_x", 0.0f); - eulers.y = loadSetting(settings, "rotation_y", 0.0f); - eulers.z = loadSetting(settings, "rotation_z", 0.0f); - attachment.rotation = glm::quat(eulers); - attachment.scale = loadSetting(settings, "scale", 1.0f); - attachment.isSoft = settings.value("isSoft").toBool(); - // old attachments are stored and loaded/converted later when rig is ready - _oldAttachmentData.append(attachment); - } - settings.endArray(); - - int avatarEntityCount = settings.beginReadArray("avatarEntityData"); + int avatarEntityCount = _avatarEntityCountSetting.get(0); for (int i = 0; i < avatarEntityCount; i++) { - settings.setArrayIndex(i); - QUuid entityID = settings.value("id").toUuid(); + resizeAvatarEntitySettingHandles(i); // QUuid entityID = QUuid::createUuid(); // generate a new ID - QByteArray properties = settings.value("properties").toByteArray(); + QUuid entityID = _avatarEntityIDSettings[i].get(QUuid()); + QByteArray properties = _avatarEntityDataSettings[i].get(); updateAvatarEntity(entityID, properties); } - settings.endArray(); - if (avatarEntityCount == 0) { - // HACK: manually remove empty 'avatarEntityData' else legacy data may persist in settings file - settings.remove("avatarEntityData"); - } // Flying preferences must be loaded before calling setFlyingEnabled() Setting::Handle<bool> firstRunVal { Settings::firstRun, true }; - setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool()); + setFlyingHMDPref(firstRunVal.get() ? false : _flyingHMDSetting.get()); setFlyingEnabled(getFlyingEnabled()); - setDisplayName(settings.value("displayName").toString()); - setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); - setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); - setDominantHand(settings.value("dominantHand", _dominantHand).toString().toLower()); - setUserHeight(settings.value("userHeight", DEFAULT_AVATAR_HEIGHT).toDouble()); - settings.endGroup(); + setDisplayName(_displayNameSetting.get()); + setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString()); + setSnapTurn(_useSnapTurnSetting.get()); + setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower()); + setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT)); setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible)); _follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing)); @@ -1442,6 +1421,7 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString& return attachment; } + int MyAvatar::parseDataFromBuffer(const QByteArray& buffer) { qCDebug(interfaceapp) << "Error: ignoring update packet for MyAvatar" << " packetLength = " << buffer.size(); @@ -2899,10 +2879,7 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings } // Set avatar current scale - Settings settings; - settings.beginGroup("Avatar"); - _targetScale = loadSetting(settings, "scale", 1.0f); - + _targetScale = _scaleSetting.get(); // clamp the desired _targetScale by the domain limits NOW, don't try to gracefully animate. Because // this might cause our avatar to become embedded in the terrain. _targetScale = getDomainLimitedScale(); @@ -2914,7 +2891,6 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings setModelScale(_targetScale); rebuildCollisionShape(); - settings.endGroup(); _haveReceivedHeightLimitsFromDomain = true; } @@ -2925,10 +2901,7 @@ void MyAvatar::leaveDomain() { } void MyAvatar::saveAvatarScale() { - Settings settings; - settings.beginGroup("Avatar"); - settings.setValue("scale", _targetScale); - settings.endGroup(); + _scaleSetting.set(_targetScale); } void MyAvatar::clearScaleRestriction() { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 139f1f6ea2..1dc0b3cd40 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -550,6 +550,7 @@ public: float getHMDRollControlRate() const { return _hmdRollControlRate; } // get/set avatar data + void resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex); void saveData(); void loadData(); @@ -1806,6 +1807,23 @@ private: bool _haveReceivedHeightLimitsFromDomain { false }; int _disableHandTouchCount { 0 }; + + Setting::Handle<QString> _dominantHandSetting; + Setting::Handle<float> _headPitchSetting; + Setting::Handle<float> _scaleSetting; + Setting::Handle<float> _yawSpeedSetting; + Setting::Handle<float> _pitchSpeedSetting; + Setting::Handle<QUrl> _fullAvatarURLSetting; + Setting::Handle<QUrl> _fullAvatarModelNameSetting; + Setting::Handle<QUrl> _animGraphURLSetting; + Setting::Handle<QString> _displayNameSetting; + Setting::Handle<QUrl> _collisionSoundURLSetting; + Setting::Handle<bool> _useSnapTurnSetting; + Setting::Handle<float> _userHeightSetting; + Setting::Handle<bool> _flyingHMDSetting; + Setting::Handle<int> _avatarEntityCountSetting; + std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings; + std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/libraries/shared/src/SettingManager.cpp b/libraries/shared/src/SettingManager.cpp index 2e0850255a..e5920b785e 100644 --- a/libraries/shared/src/SettingManager.cpp +++ b/libraries/shared/src/SettingManager.cpp @@ -52,7 +52,7 @@ namespace Setting { if (_pendingChanges.contains(key) && _pendingChanges[key] != UNSET_VALUE) { loadedValue = _pendingChanges[key]; } else { - loadedValue = value(key); + loadedValue = _qSettings.value(key); } if (loadedValue.isValid()) { handle->setVariant(loadedValue); @@ -92,32 +92,115 @@ namespace Setting { } void Manager::saveAll() { - bool forceSync = false; withWriteLock([&] { + bool forceSync = false; for (auto key : _pendingChanges.keys()) { auto newValue = _pendingChanges[key]; - auto savedValue = value(key, UNSET_VALUE); + auto savedValue = _qSettings.value(key, UNSET_VALUE); if (newValue == savedValue) { continue; } + forceSync = true; if (newValue == UNSET_VALUE || !newValue.isValid()) { - forceSync = true; - remove(key); + _qSettings.remove(key); } else { - forceSync = true; - setValue(key, newValue); + _qSettings.setValue(key, newValue); } } _pendingChanges.clear(); - }); - if (forceSync) { - sync(); - } + if (forceSync) { + _qSettings.sync(); + } + }); // Restart timer if (_saveTimer) { _saveTimer->start(); } } + + QString Manager::fileName() const { + return resultWithReadLock<QString>([&] { + return _qSettings.fileName(); + }); + } + + void Manager::remove(const QString &key) { + withWriteLock([&] { + _qSettings.remove(key); + }); + } + + QStringList Manager::childGroups() const { + return resultWithReadLock<QStringList>([&] { + return _qSettings.childGroups(); + }); + } + + QStringList Manager::childKeys() const { + return resultWithReadLock<QStringList>([&] { + return _qSettings.childKeys(); + }); + } + + QStringList Manager::allKeys() const { + return resultWithReadLock<QStringList>([&] { + return _qSettings.allKeys(); + }); + } + + bool Manager::contains(const QString &key) const { + return resultWithReadLock<bool>([&] { + return _qSettings.contains(key); + }); + } + + int Manager::beginReadArray(const QString &prefix) { + return resultWithReadLock<int>([&] { + return _qSettings.beginReadArray(prefix); + }); + } + + void Manager::beginGroup(const QString &prefix) { + withWriteLock([&] { + _qSettings.beginGroup(prefix); + }); + } + + void Manager::beginWriteArray(const QString &prefix, int size) { + withWriteLock([&] { + _qSettings.beginWriteArray(prefix, size); + }); + } + + void Manager::endArray() { + withWriteLock([&] { + _qSettings.endArray(); + }); + } + + void Manager::endGroup() { + withWriteLock([&] { + _qSettings.endGroup(); + }); + } + + void Manager::setArrayIndex(int i) { + withWriteLock([&] { + _qSettings.setArrayIndex(i); + }); + } + + void Manager::setValue(const QString &key, const QVariant &value) { + withWriteLock([&] { + _qSettings.setValue(key, value); + }); + } + + QVariant Manager::value(const QString &key, const QVariant &defaultValue) const { + return resultWithReadLock<QVariant>([&] { + return _qSettings.value(key, defaultValue); + }); + } } diff --git a/libraries/shared/src/SettingManager.h b/libraries/shared/src/SettingManager.h index 6696a1ecf4..49f3bece4b 100644 --- a/libraries/shared/src/SettingManager.h +++ b/libraries/shared/src/SettingManager.h @@ -23,12 +23,28 @@ namespace Setting { class Interface; - class Manager : public QSettings, public ReadWriteLockable, public Dependency { + class Manager : public QObject, public ReadWriteLockable, public Dependency { Q_OBJECT public: void customDeleter() override; + // thread-safe proxies into QSettings + QString fileName() const; + void remove(const QString &key); + QStringList childGroups() const; + QStringList childKeys() const; + QStringList allKeys() const; + bool contains(const QString &key) const; + int beginReadArray(const QString &prefix); + void beginGroup(const QString &prefix); + void beginWriteArray(const QString &prefix, int size = -1); + void endArray(); + void endGroup(); + void setArrayIndex(int i); + void setValue(const QString &key, const QVariant &value); + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + protected: ~Manager(); void registerHandle(Interface* handle); @@ -52,6 +68,9 @@ namespace Setting { friend class Interface; friend void cleanupSettingsSaveThread(); friend void setupSettingsSaveThread(); + + + QSettings _qSettings; }; } From d4450ac7801b043cf885c77a61a1c6db5ce21a2a Mon Sep 17 00:00:00 2001 From: Dante Ruiz <danteruiz102@gmail.com> Date: Fri, 21 Sep 2018 15:59:00 -0700 Subject: [PATCH 35/56] fix equip and keeping new functionallity --- .../controllers/controllerModules/highlightNearbyEntities.js | 2 +- scripts/system/controllers/controllerModules/inEditMode.js | 2 +- scripts/system/controllers/controllerModules/inVREditMode.js | 2 +- .../controllers/controllerModules/nearActionGrabEntity.js | 2 +- .../controllers/controllerModules/nearGrabHyperLinkEntity.js | 2 +- .../controllers/controllerModules/nearParentGrabEntity.js | 2 +- scripts/system/controllers/controllerModules/nearTrigger.js | 2 +- .../controllers/controllerModules/webSurfaceLaserInput.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js index 3a33082f64..bc09ebee7a 100644 --- a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js +++ b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js @@ -37,7 +37,7 @@ this.highlightedEntities = []; this.parameters = dispatcherUtils.makeDispatcherModuleParameters( - 120, + 480, this.hand === dispatcherUtils.RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 2bdd89f141..d590545532 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -29,7 +29,7 @@ Script.include("/~/system/libraries/utils.js"); this.reticleMaxY; this.parameters = makeDispatcherModuleParameters( - 200, + 160, this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], [], 100, diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index 02863cf935..7b78d5e1c4 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -21,7 +21,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.disableModules = false; var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed. this.parameters = makeDispatcherModuleParameters( - 240, // Not too high otherwise the tablet laser doesn't work. + 200, // Not too high otherwise the tablet laser doesn't work. this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 27c1b458b8..a8de76aebd 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -26,7 +26,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.hapticTargetID = null; this.parameters = makeDispatcherModuleParameters( - 140, + 500, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js index 366fcd3032..962ae89bb9 100644 --- a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js +++ b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js @@ -21,7 +21,7 @@ this.hyperlink = ""; this.parameters = makeDispatcherModuleParameters( - 125, + 485, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index f805dbf60e..cc88371441 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -57,7 +57,7 @@ Script.include("/~/system/libraries/controllers.js"); this.cloneAllowed = true; this.parameters = makeDispatcherModuleParameters( - 140, + 500, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index f1126dedc3..6a9cd9fbcd 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -29,7 +29,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.startSent = false; this.parameters = makeDispatcherModuleParameters( - 120, + 480, this.hand === RIGHT_HAND ? ["rightHandTrigger", "rightHand"] : ["leftHandTrigger", "leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 4e36355621..2412e2fa1c 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -121,7 +121,7 @@ Script.include("/~/system/libraries/controllers.js"); controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; - if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) { + if ((allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) && !this.grabModuleWantsNearbyOverlay(controllerData)) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. From d772cdb34a1491196381752b682dcffbabdb7a1f Mon Sep 17 00:00:00 2001 From: SamGondelman <samuel_gondelman@alumni.brown.edu> Date: Mon, 24 Sep 2018 10:16:35 -0700 Subject: [PATCH 36/56] please --- libraries/render-utils/src/Model.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index b9ed43c339..ab6507b29c 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1553,14 +1553,13 @@ void Model::setBlendedVertices(int blendNumber, const QVector<glm::vec3>& vertic for (int i = 0; i < fbxGeometry.meshes.size(); i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); auto meshNormalsAndTangents = _normalsAndTangents.find(i); - if (mesh.blendshapes.isEmpty() || meshNormalsAndTangents == _normalsAndTangents.end()) { + const auto& buffer = _blendedVertexBuffers.find(i); + if (mesh.blendshapes.isEmpty() || meshNormalsAndTangents == _normalsAndTangents.end() || buffer == _blendedVertexBuffers.end()) { continue; } const auto vertexCount = mesh.vertices.size(); const auto verticesSize = vertexCount * sizeof(glm::vec3); - const auto& buffer = _blendedVertexBuffers.find(i); - assert(buffer != _blendedVertexBuffers.end()); buffer->second->resize(mesh.vertices.size() * sizeof(glm::vec3) + meshNormalsAndTangents->second.size() * sizeof(NormalType)); buffer->second->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3)); buffer->second->setSubData(verticesSize, meshNormalsAndTangents->second.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType)); From b31837168d15d4e5090b41ff03eef443ca34c97f Mon Sep 17 00:00:00 2001 From: Stephen Birarda <commit@birarda.com> Date: Mon, 24 Sep 2018 11:27:20 -0700 Subject: [PATCH 37/56] fix bad lock, optimize some operations, clarify comment --- interface/src/avatar/AvatarManager.cpp | 4 +++- interface/src/avatar/AvatarManager.h | 4 +++- libraries/avatars/src/AvatarHashMap.cpp | 2 +- libraries/avatars/src/AvatarHashMap.h | 3 +-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index d92a5c81da..0fdd246d7b 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -464,6 +464,8 @@ void AvatarManager::clearOtherAvatars() { { QWriteLocker locker(&_hashLock); + removedAvatars.reserve(_avatarHash.size()); + auto avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value()); @@ -484,7 +486,7 @@ void AvatarManager::clearOtherAvatars() { void AvatarManager::deleteAllAvatars() { assert(_avatarsToChangeInPhysics.empty()); - QReadLocker locker(&_hashLock); + QWriteLocker locker(&_hashLock); AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { auto avatar = std::static_pointer_cast<OtherAvatar>(avatarIterator.value()); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 407b3c50de..9c4287728d 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -205,7 +205,9 @@ private: AvatarSharedPointer newSharedAvatar() override; - // must not be called while holding the hash lock + // called only from the AvatarHashMap thread - cannot be called while this thread holds the + // hash lock, since handleRemovedAvatar needs a write lock on the entity tree and the entity tree + // frequently grabs a read lock on the hash to get a given avatar by ID void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 2ee51cca17..01557e307e 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -73,7 +73,7 @@ std::vector<AvatarSharedPointer> AvatarReplicas::takeReplicas(const QUuid& paren if (it != _replicasMap.end()) { // take a copy of the replica shared pointers for this parent - replicas = it->second; + replicas.swap(it->second); // erase the replicas for this parent from our map _replicasMap.erase(it); diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 724dd1deac..c2cb448e52 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -180,8 +180,7 @@ protected: bool& isNew); virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason); - - // must not be called while holding the hash lock + virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); AvatarHash _avatarHash; From ad73cb3996c08a3bb43b80bac7c2c487727be26a Mon Sep 17 00:00:00 2001 From: SamGondelman <samuel_gondelman@alumni.brown.edu> Date: Mon, 24 Sep 2018 14:02:35 -0700 Subject: [PATCH 38/56] fix avatar highlighting --- .../render-utils/src/HighlightEffect.cpp | 45 ++++++++++--------- libraries/render-utils/src/HighlightEffect.h | 2 - 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 11326b1120..bcac31dd5a 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -37,6 +37,8 @@ namespace gr { #define OUTLINE_STENCIL_MASK 1 +extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); + HighlightRessources::HighlightRessources() { } @@ -180,6 +182,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); auto maskSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); + auto maskSkinnedDQPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned().withDualQuatSkinned()); // Setup camera, projection and viewport for all items batch.setViewportTransform(args->_viewport); @@ -187,14 +190,17 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c batch.setProjectionJitter(jitter.x, jitter.y); batch.setViewTransform(viewMat); - std::vector<ShapeKey> skinnedShapeKeys{}; + std::vector<ShapeKey> skinnedShapeKeys; + std::vector<ShapeKey> skinnedDQShapeKeys; // Iterate through all inShapes and render the unskinned args->_shapePipeline = maskPipeline; batch.setPipeline(maskPipeline->pipeline); for (const auto& items : inShapes) { itemBounds.insert(itemBounds.end(), items.second.begin(), items.second.end()); - if (items.first.isSkinned()) { + if (items.first.isSkinned() && items.first.isDualQuatSkinned()) { + skinnedDQShapeKeys.push_back(items.first); + } else if (items.first.isSkinned()) { skinnedShapeKeys.push_back(items.first); } else { renderItems(renderContext, items.second); @@ -202,10 +208,21 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c } // Reiterate to render the skinned - args->_shapePipeline = maskSkinnedPipeline; - batch.setPipeline(maskSkinnedPipeline->pipeline); - for (const auto& key : skinnedShapeKeys) { - renderItems(renderContext, inShapes.at(key)); + if (skinnedShapeKeys.size() > 0) { + args->_shapePipeline = maskSkinnedPipeline; + batch.setPipeline(maskSkinnedPipeline->pipeline); + for (const auto& key : skinnedShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } + } + + // Reiterate to render the DQ skinned + if (skinnedDQShapeKeys.size() > 0) { + args->_shapePipeline = maskSkinnedDQPipeline; + batch.setPipeline(maskSkinnedDQPipeline->pipeline); + for (const auto& key : skinnedDQShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } } args->_shapePipeline = nullptr; @@ -488,7 +505,7 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setColorWriteMask(false, false, false, false); - initMaskPipelines(*shapePlumber, state); + initZPassPipelines(*shapePlumber, state); } auto sharedParameters = std::make_shared<HighlightSharedParameters>(); @@ -548,16 +565,4 @@ const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const const auto selectedMetasAndOpaques = task.addJob<SelectItems>("OpaqueSelection", selectMetaAndOpaqueInput); const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, selectionName).asVarying(); return task.addJob<SelectItems>("TransparentSelection", selectItemInput); -} - -void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) { - gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(shader::render_utils::program::model_shadow); - shapePlumber.addPipeline( - ShapeKey::Filter::Builder().withoutSkinned(), - modelProgram, state); - - gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(shader::render_utils::program::skin_model_shadow); - shapePlumber.addPipeline( - ShapeKey::Filter::Builder().withSkinned(), - skinProgram, state); -} +} \ No newline at end of file diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h index 64a97a549e..32668c1ab6 100644 --- a/libraries/render-utils/src/HighlightEffect.h +++ b/libraries/render-utils/src/HighlightEffect.h @@ -208,8 +208,6 @@ public: void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); private: - - static void initMaskPipelines(render::ShapePlumber& plumber, gpu::StatePointer state); static const render::Varying addSelectItemJobs(JobModel& task, const render::Varying& selectionName, const RenderFetchCullSortTask::BucketList& items); }; From 72fc686ec1c1172d72947864c948d169ecbb23fc Mon Sep 17 00:00:00 2001 From: Brad Davis <bdavis@saintandreas.org> Date: Mon, 24 Sep 2018 14:52:13 -0700 Subject: [PATCH 39/56] Fix timer managmement during shutdown --- interface/src/Application.cpp | 6 +++-- libraries/audio-client/src/AudioClient.cpp | 27 ++++++++++++++++--- libraries/audio-client/src/AudioClient.h | 2 ++ .../audio-client/src/AudioPeakValues.cpp | 6 +++++ 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 46cebc1661..4eac967428 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1759,10 +1759,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Make sure we don't time out during slow operations at startup updateHeartbeat(); - QTimer* settingsTimer = new QTimer(); moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{ - connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{ + // This needs to run on the settings thread, so we need to pass the `settingsTimer` as the + // receiver object, otherwise it will run on the application thread and trigger a warning + // about trying to kill the timer on the main thread. + connect(qApp, &Application::beforeAboutToQuit, settingsTimer, [this, settingsTimer]{ // Disconnect the signal from the save settings QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings); // Stop the settings timer diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 3a33eccc8a..6af74bc8e8 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -101,6 +101,13 @@ QList<QAudioDeviceInfo> getAvailableDevices(QAudio::Mode mode) { // now called from a background thread, to keep blocking operations off the audio thread void AudioClient::checkDevices() { + // Make sure we're not shutting down + Lock timerMutex(_checkDevicesMutex); + // If we HAVE shut down after we were queued, but prior to execution, early exit + if (nullptr == _checkDevicesTimer) { + return; + } + auto inputDevices = getAvailableDevices(QAudio::AudioInput); auto outputDevices = getAvailableDevices(QAudio::AudioOutput); @@ -278,8 +285,8 @@ void AudioClient::customDeleter() { _shouldRestartInputSetup = false; #endif stop(); - _checkDevicesTimer->stop(); - _checkPeakValuesTimer->stop(); + //_checkDevicesTimer->stop(); + //_checkPeakValuesTimer->stop(); deleteLater(); } @@ -648,12 +655,26 @@ void AudioClient::start() { } void AudioClient::stop() { - qCDebug(audioclient) << "AudioClient::stop(), requesting switchInputToAudioDevice() to shut down"; switchInputToAudioDevice(QAudioDeviceInfo(), true); qCDebug(audioclient) << "AudioClient::stop(), requesting switchOutputToAudioDevice() to shut down"; switchOutputToAudioDevice(QAudioDeviceInfo(), true); + + // Stop triggering the checks + QObject::disconnect(_checkPeakValuesTimer, &QTimer::timeout, nullptr, nullptr); + QObject::disconnect(_checkDevicesTimer, &QTimer::timeout, nullptr, nullptr); + + // Destruction of the pointers will occur when the parent object (this) is destroyed) + { + Lock lock(_checkDevicesMutex); + _checkDevicesTimer = nullptr; + } + { + Lock lock(_checkPeakValuesMutex); + _checkPeakValuesTimer = nullptr; + } + #if defined(Q_OS_ANDROID) _checkInputTimer.stop(); disconnect(&_checkInputTimer, &QTimer::timeout, 0, 0); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 4640d7c045..f5fee6184c 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -432,7 +432,9 @@ private: bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop? #endif + Mutex _checkDevicesMutex; QTimer* _checkDevicesTimer { nullptr }; + Mutex _checkPeakValuesMutex; QTimer* _checkPeakValuesTimer { nullptr }; bool _isRecording { false }; diff --git a/libraries/audio-client/src/AudioPeakValues.cpp b/libraries/audio-client/src/AudioPeakValues.cpp index 0b8921a117..a50567da7f 100644 --- a/libraries/audio-client/src/AudioPeakValues.cpp +++ b/libraries/audio-client/src/AudioPeakValues.cpp @@ -40,6 +40,12 @@ void release(IAudioClient* audioClient) { } void AudioClient::checkPeakValues() { + // Guard against running during shutdown + Lock timerMutex(_checkPeakValuesMutex); + if (nullptr == _checkPeakValuesTimer) { + return; + } + // prepare the windows environment CoInitialize(NULL); From 69d529936299331669c7913f5a4ca3f13b646be3 Mon Sep 17 00:00:00 2001 From: Wayne Chen <wayne@highfidelity.io> Date: Mon, 24 Sep 2018 15:22:46 -0700 Subject: [PATCH 40/56] patching fix for previous commits --- interface/src/Application.cpp | 13 +++--- interface/src/Application.h | 2 +- interface/src/ConnectionMonitor.cpp | 22 +++------- interface/src/ConnectionMonitor.h | 5 +-- .../scripting/WindowScriptingInterface.cpp | 8 ++++ .../src/scripting/WindowScriptingInterface.h | 4 ++ libraries/networking/src/DomainHandler.cpp | 43 ++++++++++++------- libraries/networking/src/DomainHandler.h | 8 +++- 8 files changed, 61 insertions(+), 44 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 46cebc1661..a9aa9077b0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3498,13 +3498,14 @@ bool Application::isServerlessMode() const { } void Application::setIsInterstitialMode(bool interstitialMode) { - Settings settings; - bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool(); - if (_interstitialMode != interstitialMode && enableInterstitial) { - _interstitialMode = interstitialMode; + bool enableInterstitial = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled(); + if (enableInterstitial) { + if (_interstitialMode != interstitialMode) { + _interstitialMode = interstitialMode; - DependencyManager::get<AudioClient>()->setAudioPaused(_interstitialMode); - DependencyManager::get<AvatarManager>()->setMyAvatarDataPacketsPaused(_interstitialMode); + DependencyManager::get<AudioClient>()->setAudioPaused(_interstitialMode); + DependencyManager::get<AvatarManager>()->setMyAvatarDataPacketsPaused(_interstitialMode); + } } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 3bebc60480..eedbdb7622 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -432,7 +432,7 @@ public slots: void setIsServerlessMode(bool serverlessDomain); void loadServerlessDomain(QUrl domainURL, bool errorDomain = false); - void setIsInterstitialMode(bool interstialMode); + void setIsInterstitialMode(bool interstitialMode); void updateVerboseLogging(); diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index ca90d039f4..e86061b090 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -23,8 +23,6 @@ // should be longer to allow the application to initialize. static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000; static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000; -static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000; -static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000; void ConnectionMonitor::init() { // Connect to domain disconnected message @@ -39,18 +37,15 @@ void ConnectionMonitor::init() { _timer.setSingleShot(true); if (!domainHandler.isConnected()) { - if (_enableInterstitialMode.get()) { - _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); - } else { - _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); - } + _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } connect(&_timer, &QTimer::timeout, this, [this]() { // set in a timeout error - if (_enableInterstitialMode.get()) { + bool enableInterstitial = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled(); + if (enableInterstitial) { qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; - emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); + emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, "", 5); } else { qDebug() << "ConnectionMonitor: Showing connection failure window"; DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(true); @@ -59,16 +54,13 @@ void ConnectionMonitor::init() { } void ConnectionMonitor::startTimer() { - if (_enableInterstitialMode.get()) { - _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); - } else { - _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); - } + _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } void ConnectionMonitor::stopTimer() { _timer.stop(); - if (!_enableInterstitialMode.get()) { + bool enableInterstitial = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled(); + if (!enableInterstitial) { DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(false); } } diff --git a/interface/src/ConnectionMonitor.h b/interface/src/ConnectionMonitor.h index f0589a3b8c..2fda6ef7cd 100644 --- a/interface/src/ConnectionMonitor.h +++ b/interface/src/ConnectionMonitor.h @@ -15,8 +15,6 @@ #include <QObject> #include <QTimer> -#include <SettingHandle.h> - class QUrl; class QString; @@ -26,7 +24,7 @@ public: void init(); signals: - void setRedirectErrorState(QUrl errorURL, int reasonCode); + void setRedirectErrorState(QUrl errorURL, QString reasonMessage = "", int reasonCode = -1, const QString& extraInfo = ""); private slots: void startTimer(); @@ -34,7 +32,6 @@ private slots: private: QTimer _timer; - Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false }; }; #endif // hifi_ConnectionMonitor_h diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index e3ae65aee1..d4eb37e0aa 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -180,6 +180,14 @@ void WindowScriptingInterface::setPreviousBrowseAssetLocation(const QString& loc Setting::Handle<QVariant>(LAST_BROWSE_ASSETS_LOCATION_SETTING).set(location); } +bool WindowScriptingInterface::getInterstitialModeEnabled() const { + return DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled(); +} + +void WindowScriptingInterface::setInterstitialModeEnabled(bool enableInterstitialMode) { + DependencyManager::get<NodeList>()->getDomainHandler().setInterstitialModeEnabled(enableInterstitialMode); +} + bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) { auto offscreenUi = DependencyManager::get<OffscreenUi>(); return offscreenUi->isPointOnDesktopWindow(point); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 3827406729..ddd7159f23 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -49,6 +49,7 @@ class WindowScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int innerHeight READ getInnerHeight) Q_PROPERTY(int x READ getX) Q_PROPERTY(int y READ getY) + Q_PROPERTY(bool interstitialModeEnabled READ getInterstitialModeEnabled WRITE setInterstitialModeEnabled) public: WindowScriptingInterface(); @@ -758,6 +759,9 @@ private: QString getPreviousBrowseAssetLocation() const; void setPreviousBrowseAssetLocation(const QString& location); + bool getInterstitialModeEnabled() const; + void setInterstitialModeEnabled(bool enableInterstitialMode); + void ensureReticleVisible() const; int createMessageBox(QString title, QString text, int buttons, int defaultButton); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index b2f118c5c8..df34a1fb59 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -15,6 +15,10 @@ #include <PathUtils.h> +#include <shared/QtHelpers.h> + +#include <QThread> + #include <QtCore/QJsonDocument> #include <QtCore/QDataStream> @@ -134,6 +138,18 @@ void DomainHandler::hardReset() { _pendingPath.clear(); } +bool DomainHandler::getInterstitialModeEnabled() const { + return _interstitialModeSettingLock.resultWithReadLock<bool>([&] { + return _enableInterstitialMode.get(); + }); +} + +void DomainHandler::setInterstitialModeEnabled(bool enableInterstitialMode) { + _interstitialModeSettingLock.withWriteLock([&] { + _enableInterstitialMode.set(enableInterstitialMode); + }); +} + void DomainHandler::setErrorDomainURL(const QUrl& url) { _errorDomainURL = url; return; @@ -340,11 +356,15 @@ void DomainHandler::loadedErrorDomain(std::map<QString, QString> namedPaths) { DependencyManager::get<AddressManager>()->goToViewpointForPath(viewpoint, QString()); } -void DomainHandler::setRedirectErrorState(QUrl errorUrl, int reasonCode) { - _errorDomainURL = errorUrl; +void DomainHandler::setRedirectErrorState(QUrl errorUrl, QString reasonMessage, int reasonCode, const QString& extraInfo) { _lastDomainConnectionError = reasonCode; - _isInErrorState = true; - emit redirectToErrorDomainURL(_errorDomainURL); + if (getInterstitialModeEnabled()) { + _errorDomainURL = errorUrl; + _isInErrorState = true; + emit redirectToErrorDomainURL(_errorDomainURL); + } else { + emit domainConnectionRefused(reasonMessage, reasonCode, extraInfo); + } } void DomainHandler::requestDomainSettings() { @@ -486,18 +506,9 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo); #else - if (_enableInterstitialMode.get()) { - if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) { - // ingest the error - this is a "hard" connection refusal. - setRedirectErrorState(_errorDomainURL, (int)reasonCode); - } else { - emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo); - } - _lastDomainConnectionError = (int)reasonCode; - } else { - emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo); - } - #endif + // ingest the error - this is a "hard" connection refusal. + setRedirectErrorState(_errorDomainURL, reasonMessage, (int)reasonCode, extraInfo); +#endif } auto accountManager = DependencyManager::get<AccountManager>(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 5fa920a554..e9ec20ba2e 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -19,6 +19,7 @@ #include <QtCore/QUrl> #include <QtNetwork/QHostInfo> +#include <shared/ReadWriteLockable.h> #include <SettingHandle.h> #include "HifiSockAddr.h" @@ -85,6 +86,8 @@ public: bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; } + bool getInterstitialModeEnabled() const; + void setInterstitialModeEnabled(bool enableInterstitialMode); void connectedToServerless(std::map<QString, QString> namedPaths); @@ -173,7 +176,7 @@ public slots: void processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message); // sets domain handler in error state. - void setRedirectErrorState(QUrl errorUrl, int reasonCode); + void setRedirectErrorState(QUrl errorUrl, QString reasonMessage = "", int reason = -1, const QString& extraInfo = ""); bool isInErrorState() { return _isInErrorState; } @@ -223,10 +226,11 @@ private: NetworkPeer _icePeer; bool _isConnected { false }; bool _isInErrorState { false }; - Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false }; QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; + mutable ReadWriteLockable _interstitialModeSettingLock; + Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false }; QSet<QString> _domainConnectionRefusals; bool _hasCheckedForAccessToken { false }; From 697f556a60211c48797f4bc5f685589e0a0ec229 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Mon, 24 Sep 2018 16:30:49 -0700 Subject: [PATCH 41/56] Persist list of marketplace items in test The list of resources between runs of the marketplace item tester now persist between runs of the tester. --- .../MarketplaceItemTester.qml | 54 +++++++++++++----- .../marketplaceItemTester/spinner.gif | Bin 0 -> 46135 bytes scripts/system/marketplaces/marketplaces.js | 52 ++++++++++++++++- 3 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 559b6cb29c..f2dbcc3f80 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -29,10 +29,35 @@ Rectangle { signal sendToScript(var message) HifiStylesUit.HifiConstants { id: hifi } - ListModel { id: resourceListModel } + ListModel { + id: resourceListModel + property var nextId: 0 + } color: hifi.colors.white + AnimatedImage { + id: spinner; + source: "spinner.gif" + width: 74; + height: width; + anchors.verticalCenter: parent.verticalCenter; + anchors.horizontalCenter: parent.horizontalCenter; + } + + function fromScript(message) { + switch (message.method) { + case "newResourceObjectInTest": + var resourceObject = message.resourceObject; + resourceListModel.append(resourceObject); + spinner.visible = false; + break; + case "marketplaceTestBackendIsAlive": + spinner.visible = false; + break; + } + } + function buildResourceObj(resource) { resource = resource.trim(); var assetType = (resource.match(/\.app\.json$/) ? "application" : @@ -40,14 +65,14 @@ Rectangle { resource.match(/\.json\.gz$/) ? "content set" : resource.match(/\.json$/) ? "entity or wearable" : "unknown"); - return { "resource": resource, "assetType": assetType }; + return { "id": resourceListModel.nextId++, + "resource": resource, + "assetType": assetType }; } function installResourceObj(resourceObj) { if ("application" == resourceObj["assetType"]) { Commerce.installApp(resourceObj["resource"]); - } else { - print("Cannot install resource object type " + resourceObj["assetType"]); } } @@ -72,11 +97,6 @@ Rectangle { itemType: entityType}); } - Component.onCompleted: { - // On startup, list includes all tester-installed assets. - addAllInstalledAppsToList(); - } - ListView { anchors.fill: parent anchors.leftMargin: 12 @@ -157,7 +177,13 @@ Rectangle { currentIndex: ("entity or wearable" == assetType) ? model.indexOf("unknown") : model.indexOf(assetType) Component.onCompleted: { - onActivated.connect(function() { assetType = currentText; }); + onCurrentIndexChanged.connect(function() { + assetType = model[currentIndex]; + sendToScript({ + method: 'tester_updateResourceObjectAssetType', + objectId: resourceListModel.get(index)["id"], + assetType: assetType }); + }); } } @@ -220,17 +246,17 @@ Rectangle { // ahead as though we are sure the present signal is one // we expect. if ("load file" == currentAction) { - print("disconnecting load file"); Window.browseChanged.disconnect(onResourceSelected); } else if ("load url" == currentAction) { - print("disconnecting load url"); Window.promptTextChanged.disconnect(onResourceSelected); } if (resource) { var resourceObj = buildResourceObj(resource); installResourceObj(resourceObj); - resourceListModel.append(resourceObj); - } + sendToScript({ + method: 'tester_newResourceObject', + resourceObject: resourceObj }); + } } Repeater { diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif b/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..00f75ae62fa59b28a7813ca00c4185390e3e51da GIT binary patch literal 46135 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�up~q*dFG9mryHju*(WVM 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 V-}`%i@9+J+zxVh4-rv9A{u}xRWtji~ literal 0 HcmV?d00001 diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 01c21044d2..a066143d45 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -800,6 +800,18 @@ var selectionDisplay = null; // for gridTool.js to ignore }, 150); } + function signalNewResourceObjectInTest(resourceObject) { + sendToQml({ + method: "newResourceObjectInTest", + resourceObject: resourceObject }); + } + + var resourceObjectsInTest = []; + function storeResourceObjectInTest(resourceObject) { + resourceObjectsInTest.push(resourceObject); + signalNewResourceObjectInTest(resourceObject); + } + // Function Name: fromQml() // // Description: @@ -856,9 +868,22 @@ var selectionDisplay = null; // for gridTool.js to ignore case 'checkout_rezClicked': case 'purchases_rezClicked': case 'tester_rezClicked': - print("marketplace.js going to rez"); rezEntity(message.itemHref, message.itemType); break; + case 'tester_newResourceObject': + storeResourceObjectInTest(message.resourceObject); + break; + case 'tester_updateResourceObjectAssetType': + var objectId = message.objectId; + for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) { + if (i in resourceObjectsInTest && + objectId === resourceObjectsInTest[i]["id"] + ) { + resourceObjectsInTest[i]["assetType"] = message.assetType; + break; + } + } + break; case 'header_marketplaceImageClicked': case 'purchases_backClicked': tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); @@ -1027,6 +1052,22 @@ var selectionDisplay = null; // for gridTool.js to ignore } } + function pushResourceObjectsInTest() { + var isQmlSignaled = false; + for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) { + if (i in resourceObjectsInTest) { + signalNewResourceObjectInTest(resourceObjectsInTest[i]); + isQmlSignaled = true; + } + } + // Be sure that the QML has heard from us, at least so that it + // can indicate to the user that all of the resoruce objects in + // test have been transmitted to it. + if (!isQmlSignaled) { + sendToQml({ method: "marketplaceTestBackendIsAlive" }); + } + } + // Function Name: onTabletScreenChanged() // // Description: @@ -1070,6 +1111,9 @@ var selectionDisplay = null; // for gridTool.js to ignore onMarketplaceItemTesterScreen); if (url === MARKETPLACE_PURCHASES_QML_PATH) { + // FIXME: There is a race condition here. The event bridge + // may not be up yet. Suggest Script.setTimeout(..., 750) to + // help avoid the condition. sendToQml({ method: 'updatePurchases', referrerURL: referrerURL, @@ -1103,6 +1147,12 @@ var selectionDisplay = null; // for gridTool.js to ignore method: 'inspectionCertificate_resetCert' }); } + + if (onMarketplaceItemTesterScreen) { + // Why? The QML event bridge, wired above, needs time to + // come up. + Script.setTimeout(pushResourceObjectsInTest, 750); + } } // From 7f189e4d10f87f7f6ae4bdbc259683928356a41b Mon Sep 17 00:00:00 2001 From: sabrina-shanman <sabrina@highfidelity.io> Date: Mon, 24 Sep 2018 17:15:56 -0700 Subject: [PATCH 42/56] Make visualPickResult used by Pointers a private copy --- libraries/pointers/src/Pointer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 031baece5f..bdf1250a8d 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -68,7 +68,7 @@ void Pointer::update(unsigned int pointerID) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { auto pickResult = getPrevPickResult(); - auto visualPickResult = getVisualPickResult(pickResult); + auto visualPickResult = getVisualPickResult(std::make_shared<PickResult>(*pickResult.get())); updateVisuals(visualPickResult); generatePointerEvents(pointerID, visualPickResult); }); From 5dbebd4aae4652fa0109d3da9e11f93cf85151a1 Mon Sep 17 00:00:00 2001 From: sabrina-shanman <sabrina@highfidelity.io> Date: Mon, 24 Sep 2018 17:36:24 -0700 Subject: [PATCH 43/56] Add comment explaining why visualPickResult is a copy --- libraries/pointers/src/Pointer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index bdf1250a8d..852a83c192 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -68,6 +68,7 @@ void Pointer::update(unsigned int pointerID) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { auto pickResult = getPrevPickResult(); + // Pointer needs its own PickResult object so it doesn't modify the cached pick result auto visualPickResult = getVisualPickResult(std::make_shared<PickResult>(*pickResult.get())); updateVisuals(visualPickResult); generatePointerEvents(pointerID, visualPickResult); From 0a43c17ce0d97458e57b972a92107806d5837be6 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Mon, 24 Sep 2018 21:15:08 -0700 Subject: [PATCH 44/56] Use entity-specific glyphs in marketplace item tester --- .../MarketplaceItemTester.qml | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index f2dbcc3f80..40e4f55f2c 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -188,13 +188,22 @@ Rectangle { } Repeater { - model: [ - { "name": "forward", "glyph": hifi.glyphs.forward, "size": 30 }, - { "name": "trash", "glyph": hifi.glyphs.trash, "size": 22} - ] + model: [ "forward", "trash" ] + HifiStylesUit.HiFiGlyphs { - text: modelData.glyph - size: modelData.size + property var glyphs: { + "application": hifi.glyphs.install, + "avatar": hifi.glyphs.avatar, + "content set": hifi.glyphs.globe, + "entity": hifi.glyphs.wand, + "trash": hifi.glyphs.trash, + "unknown": hifi.glyphs.circleSlash, + "wearable": hifi.glyphs.hat, + } + text: (("trash" == modelData) ? + glyphs.trash : + glyphs[comboBox.model[comboBox.currentIndex]]) + size: ("trash" == modelData) ? 22 : 30 color: hifi.colors.black horizontalAlignment: Text.AlignHCenter MouseArea { From ca1da9c830d8c3bab0a5ea65dda5cc4c3d1c3f32 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Mon, 24 Sep 2018 23:07:44 -0700 Subject: [PATCH 45/56] Implement delete-from-list functionality in marketplace item tester --- .../MarketplaceItemTester.qml | 8 +++++-- scripts/system/marketplaces/marketplaces.js | 24 +++++++------------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 0a852780b4..c962c84c31 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -136,6 +136,9 @@ Rectangle { if ("application" == assetType) { Commerce.uninstallApp(resource); } + sendToScript({ + method: "tester_deleteResourceObject", + objectId: resourceListModel.get(index).id}); resourceListModel.remove(index); } } @@ -174,13 +177,14 @@ Rectangle { "unknown" ] - currentIndex: ("entity or wearable" == assetType) ? model.indexOf("unknown") : model.indexOf(assetType) + currentIndex: (("entity or wearable" == assetType) ? + model.indexOf("unknown") : model.indexOf(assetType)) Component.onCompleted: { onCurrentIndexChanged.connect(function() { assetType = model[currentIndex]; sendToScript({ - method: 'tester_updateResourceObjectAssetType', + method: "tester_updateResourceObjectAssetType", objectId: resourceListModel.get(index)["id"], assetType: assetType }); }); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index c279e76b16..aba4ba5f7d 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -770,18 +770,13 @@ function maybeEnableHMDPreview() { }, UI_FADE_TIMEOUT_MS); } +var resourceObjectsInTest = []; function signalNewResourceObjectInTest(resourceObject) { ui.tablet.sendToQml({ method: "newResourceObjectInTest", resourceObject: resourceObject }); } -var resourceObjectsInTest = []; -function storeResourceObjectInTest(resourceObject) { - resourceObjectsInTest.push(resourceObject); - signalNewResourceObjectInTest(resourceObject); -} - var onQmlMessageReceived = function onQmlMessageReceived(message) { if (message.messageSrc === "HTML") { return; @@ -835,18 +830,15 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { rezEntity(message.itemHref, message.itemType); break; case 'tester_newResourceObject': - storeResourceObjectInTest(message.resourceObject); + var resourceObject = message.resourceObject; + resourceObjectsInTest[resourceObject.id] = resourceObject; + signalNewResourceObjectInTest(resourceObject); break; case 'tester_updateResourceObjectAssetType': - var objectId = message.objectId; - for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) { - if (i in resourceObjectsInTest && - objectId === resourceObjectsInTest[i]["id"] - ) { - resourceObjectsInTest[i]["assetType"] = message.assetType; - break; - } - } + resourceObjectsInTest[message.objectId].assetType = message.assetType; + break; + case 'tester_deleteResourceObject': + delete resourceObjectsInTest[message.objectId]; break; case 'header_marketplaceImageClicked': case 'purchases_backClicked': From ff83713365b4b4fa5c69f733506d656f2745a826 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Mon, 24 Sep 2018 23:57:48 -0700 Subject: [PATCH 46/56] Use persistent object id counter --- .../MarketplaceItemTester.qml | 17 +++++++-------- scripts/system/marketplaces/marketplaces.js | 21 ++++++++----------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index c962c84c31..a0ccf91baa 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -26,13 +26,11 @@ Rectangle { id: root property string installedApps + property var nextResourceObjectId: 0 signal sendToScript(var message) HifiStylesUit.HifiConstants { id: hifi } - ListModel { - id: resourceListModel - property var nextId: 0 - } + ListModel { id: resourceListModel } color: hifi.colors.white @@ -52,7 +50,8 @@ Rectangle { resourceListModel.append(resourceObject); spinner.visible = false; break; - case "marketplaceTestBackendIsAlive": + case "nextObjectIdInTest": + nextResourceObjectId = message.id; spinner.visible = false; break; } @@ -65,14 +64,14 @@ Rectangle { resource.match(/\.json\.gz$/) ? "content set" : resource.match(/\.json$/) ? "entity or wearable" : "unknown"); - return { "id": resourceListModel.nextId++, + return { "id": nextResourceObjectId++, "resource": resource, "assetType": assetType }; } function installResourceObj(resourceObj) { - if ("application" == resourceObj["assetType"]) { - Commerce.installApp(resourceObj["resource"]); + if ("application" == resourceObj.assetType) { + Commerce.installApp(resourceObj.resource); } } @@ -269,7 +268,7 @@ Rectangle { sendToScript({ method: 'tester_newResourceObject', resourceObject: resourceObj }); - } + } } Repeater { diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index aba4ba5f7d..2e0c05be43 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -980,19 +980,16 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { }; function pushResourceObjectsInTest() { - var isQmlSignaled = false; - for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) { - if (i in resourceObjectsInTest) { - signalNewResourceObjectInTest(resourceObjectsInTest[i]); - isQmlSignaled = true; - } - } - // Be sure that the QML has heard from us, at least so that it - // can indicate to the user that all of the resoruce objects in - // test have been transmitted to it. - if (!isQmlSignaled) { - ui.tablet.sendToQml({ method: "marketplaceTestBackendIsAlive" }); + var maxObjectId = -1; + for (var objectId in resourceObjectsInTest) { + signalNewResourceObjectInTest(resourceObjectsInTest[objectId]); + maxObjectId = (maxObjectId < objectId) ? parseInt(objectId) : maxObjectId; } + // N.B. Thinking about removing the following sendToQml? Be sure + // that the marketplace item tester QML has heard from us, at least + // so that it can indicate to the user that all of the resoruce + // objects in test have been transmitted to it. + ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxObjectId + 1 }); } // Function Name: onTabletScreenChanged() From eca31e7a994a5a0c7841e82eda987f99f972ba5c Mon Sep 17 00:00:00 2001 From: sabrina-shanman <sabrina@highfidelity.io> Date: Tue, 25 Sep 2018 09:20:57 -0700 Subject: [PATCH 47/56] Fix instantiating abstract class when creating visual pick result for pointers --- interface/src/raypick/LaserPointer.cpp | 5 +++++ interface/src/raypick/LaserPointer.h | 2 ++ interface/src/raypick/ParabolaPointer.cpp | 5 +++++ interface/src/raypick/ParabolaPointer.h | 2 ++ interface/src/raypick/StylusPointer.cpp | 5 +++++ interface/src/raypick/StylusPointer.h | 1 + libraries/pointers/src/Pointer.cpp | 2 +- libraries/pointers/src/Pointer.h | 1 + 8 files changed, 22 insertions(+), 1 deletion(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 5fbe3a90b5..64faf5f9bf 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -35,6 +35,11 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& } } +PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult); + return std::make_shared<RayPickResult>(*rayPickResult.get()); +} + QVariantMap LaserPointer::toVariantMap() const { QVariantMap qVariantMap; diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index c0ac3259d9..b391f60f85 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -47,6 +47,8 @@ public: static std::shared_ptr<StartEndRenderState> buildRenderState(const QVariantMap& propMap); protected: + PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override; + void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override; diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 888b3ddbe8..e7f54d2e97 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -30,6 +30,11 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& { } +PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + auto stylusPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult); + return std::make_shared<ParabolaPickResult>(*stylusPickResult.get()); +} + void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { auto renderState = std::static_pointer_cast<RenderState>(_renderStates[state]); if (renderState) { diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 526abe3b0d..8fb864c07b 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -102,6 +102,8 @@ public: static std::shared_ptr<StartEndRenderState> buildRenderState(const QVariantMap& propMap); protected: + virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override; + void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override; diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 06e3e52d21..7f05a706a4 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -147,6 +147,11 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { return false; } +PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + auto stylusPickResult = std::static_pointer_cast<StylusPickResult>(pickResult); + return std::make_shared<StylusPickResult>(*stylusPickResult.get()); +} + Pointer::PickedObject StylusPointer::getHoveredObject(const PickResultPointer& pickResult) { auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult); if (!stylusPickResult) { diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h index 4095acb529..ff60fd78e5 100644 --- a/interface/src/raypick/StylusPointer.h +++ b/interface/src/raypick/StylusPointer.h @@ -42,6 +42,7 @@ protected: Buttons getPressedButtons(const PickResultPointer& pickResult) override; bool shouldHover(const PickResultPointer& pickResult) override; bool shouldTrigger(const PickResultPointer& pickResult) override; + virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override; PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 852a83c192..26460cbdd7 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -69,7 +69,7 @@ void Pointer::update(unsigned int pointerID) { withReadLock([&] { auto pickResult = getPrevPickResult(); // Pointer needs its own PickResult object so it doesn't modify the cached pick result - auto visualPickResult = getVisualPickResult(std::make_shared<PickResult>(*pickResult.get())); + auto visualPickResult = getVisualPickResult(getPickResultCopy(pickResult)); updateVisuals(visualPickResult); generatePointerEvents(pointerID, visualPickResult); }); diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 4264a60079..173163374f 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -91,6 +91,7 @@ protected: virtual bool shouldHover(const PickResultPointer& pickResult) { return true; } virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; } + virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const = 0; virtual PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) { return pickResult; }; static const float POINTER_MOVE_DELAY; From 660bdf36d01b83996aacc93e9997eac41b70f463 Mon Sep 17 00:00:00 2001 From: sabrina-shanman <sabrina@highfidelity.io> Date: Tue, 25 Sep 2018 09:56:11 -0700 Subject: [PATCH 48/56] Check if pick result is null when building visual pick result --- interface/src/raypick/LaserPointer.cpp | 3 +++ interface/src/raypick/ParabolaPointer.cpp | 3 +++ interface/src/raypick/StylusPointer.cpp | 3 +++ 3 files changed, 9 insertions(+) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 64faf5f9bf..79bca0449f 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -36,6 +36,9 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& } PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + if (!pickResult) { + std::make_shared<RayPickResult>(); + } auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult); return std::make_shared<RayPickResult>(*rayPickResult.get()); } diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 4614b81cbb..ec4222d5f6 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -31,6 +31,9 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& } PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + if (!pickResult) { + std::make_shared<ParabolaPickResult>(); + } auto stylusPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult); return std::make_shared<ParabolaPickResult>(*stylusPickResult.get()); } diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 7f05a706a4..2742c68d1d 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -148,6 +148,9 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { } PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + if (!pickResult) { + std::make_shared<StylusPickResult>(); + } auto stylusPickResult = std::static_pointer_cast<StylusPickResult>(pickResult); return std::make_shared<StylusPickResult>(*stylusPickResult.get()); } From 6e83e96d1cff5c000fe30f22e052ec4815749c60 Mon Sep 17 00:00:00 2001 From: sabrina-shanman <sabrina@highfidelity.io> Date: Tue, 25 Sep 2018 12:05:47 -0700 Subject: [PATCH 49/56] Check type in Pointer::getPickResultCopy --- interface/src/raypick/LaserPointer.cpp | 4 ++-- interface/src/raypick/ParabolaPointer.cpp | 6 +++--- interface/src/raypick/StylusPointer.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 79bca0449f..577978cc88 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -36,10 +36,10 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& } PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const { - if (!pickResult) { + auto rayPickResult = std::dynamic_pointer_cast<RayPickResult>(pickResult); + if (!rayPickResult) { std::make_shared<RayPickResult>(); } - auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult); return std::make_shared<RayPickResult>(*rayPickResult.get()); } diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index ec4222d5f6..92b82fff7f 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -31,11 +31,11 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& } PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const { - if (!pickResult) { + auto parabolaPickResult = std::dynamic_pointer_cast<ParabolaPickResult>(pickResult); + if (!parabolaPickResult) { std::make_shared<ParabolaPickResult>(); } - auto stylusPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult); - return std::make_shared<ParabolaPickResult>(*stylusPickResult.get()); + return std::make_shared<ParabolaPickResult>(*parabolaPickResult.get()); } void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 2742c68d1d..0b44b2705d 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -148,10 +148,10 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { } PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const { - if (!pickResult) { + auto stylusPickResult = std::dynamic_pointer_cast<StylusPickResult>(pickResult); + if (!stylusPickResult) { std::make_shared<StylusPickResult>(); } - auto stylusPickResult = std::static_pointer_cast<StylusPickResult>(pickResult); return std::make_shared<StylusPickResult>(*stylusPickResult.get()); } From 55ab0a394b81706e7d5776cf253258e688d014a8 Mon Sep 17 00:00:00 2001 From: sabrina-shanman <sabrina@highfidelity.io> Date: Tue, 25 Sep 2018 12:21:00 -0700 Subject: [PATCH 50/56] Return std::make_shared --- interface/src/raypick/LaserPointer.cpp | 2 +- interface/src/raypick/ParabolaPointer.cpp | 2 +- interface/src/raypick/StylusPointer.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 577978cc88..3c66923b4e 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -38,7 +38,7 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const { auto rayPickResult = std::dynamic_pointer_cast<RayPickResult>(pickResult); if (!rayPickResult) { - std::make_shared<RayPickResult>(); + return std::make_shared<RayPickResult>(); } return std::make_shared<RayPickResult>(*rayPickResult.get()); } diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 92b82fff7f..33fa8738d9 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -33,7 +33,7 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const { auto parabolaPickResult = std::dynamic_pointer_cast<ParabolaPickResult>(pickResult); if (!parabolaPickResult) { - std::make_shared<ParabolaPickResult>(); + return std::make_shared<ParabolaPickResult>(); } return std::make_shared<ParabolaPickResult>(*parabolaPickResult.get()); } diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 0b44b2705d..b648e125bf 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -150,7 +150,7 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const { auto stylusPickResult = std::dynamic_pointer_cast<StylusPickResult>(pickResult); if (!stylusPickResult) { - std::make_shared<StylusPickResult>(); + return std::make_shared<StylusPickResult>(); } return std::make_shared<StylusPickResult>(*stylusPickResult.get()); } From 5adc4c2290905a37b9668447ee0cbb2cb092022f Mon Sep 17 00:00:00 2001 From: Brad Davis <bdavis@saintandreas.org> Date: Tue, 25 Sep 2018 13:17:49 -0700 Subject: [PATCH 51/56] Protect against null pointer usage in sound scripting wrapper --- libraries/audio/src/Sound.cpp | 7 +++++-- libraries/audio/src/Sound.h | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 67f9952771..da284f19a3 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -43,8 +43,11 @@ void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPo } } -SoundScriptingInterface::SoundScriptingInterface(SharedSoundPointer sound) : _sound(sound) { - QObject::connect(sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready); +SoundScriptingInterface::SoundScriptingInterface(const SharedSoundPointer& sound) : _sound(sound) { + // During shutdown we can sometimes get an empty sound pointer back + if (_sound) { + QObject::connect(_sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready); + } } Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) : diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 348600e4ae..a0544870d0 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -105,11 +105,11 @@ class SoundScriptingInterface : public QObject { Q_PROPERTY(float duration READ getDuration) public: - SoundScriptingInterface(SharedSoundPointer sound); - SharedSoundPointer getSound() { return _sound; } + SoundScriptingInterface(const SharedSoundPointer& sound); + const SharedSoundPointer& getSound() { return _sound; } - bool isReady() const { return _sound->isReady(); } - float getDuration() { return _sound->getDuration(); } + bool isReady() const { return _sound ? _sound->isReady() : false; } + float getDuration() { return _sound ? _sound->getDuration() : 0.0f; } /**jsdoc * Triggered when the sound has been downloaded and is ready to be played. From 15de0590e8ceaa44419781b4177b493d7851bfef Mon Sep 17 00:00:00 2001 From: Brad Davis <bdavis@saintandreas.org> Date: Tue, 25 Sep 2018 13:18:49 -0700 Subject: [PATCH 52/56] Cleaning up commented out code --- libraries/audio-client/src/AudioClient.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6af74bc8e8..68a68633e6 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -285,9 +285,6 @@ void AudioClient::customDeleter() { _shouldRestartInputSetup = false; #endif stop(); - //_checkDevicesTimer->stop(); - //_checkPeakValuesTimer->stop(); - deleteLater(); } From 4e7b03883f2731160c036ae59fbb0c98daa22612 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Tue, 25 Sep 2018 15:13:26 -0700 Subject: [PATCH 53/56] Use triple equals --- .../marketplaceItemTester/MarketplaceItemTester.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index a0ccf91baa..8dfb277197 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -132,7 +132,7 @@ Rectangle { } }, "trash": function(){ - if ("application" == assetType) { + if ("application" === assetType) { Commerce.uninstallApp(resource); } sendToScript({ @@ -176,7 +176,7 @@ Rectangle { "unknown" ] - currentIndex: (("entity or wearable" == assetType) ? + currentIndex: (("entity or wearable" === assetType) ? model.indexOf("unknown") : model.indexOf(assetType)) Component.onCompleted: { @@ -203,10 +203,10 @@ Rectangle { "unknown": hifi.glyphs.circleSlash, "wearable": hifi.glyphs.hat, } - text: (("trash" == modelData) ? + text: (("trash" === modelData) ? glyphs.trash : glyphs[comboBox.model[comboBox.currentIndex]]) - size: ("trash" == modelData) ? 22 : 30 + size: ("trash" === modelData) ? 22 : 30 color: hifi.colors.black horizontalAlignment: Text.AlignHCenter MouseArea { @@ -257,7 +257,7 @@ Rectangle { // Alas, there is nothing we can do about that so charge // ahead as though we are sure the present signal is one // we expect. - if ("load file" == currentAction) { + if ("load file" === currentAction) { Window.browseChanged.disconnect(onResourceSelected); } else if ("load url" == currentAction) { Window.promptTextChanged.disconnect(onResourceSelected); From 276125937c3ab37b8eec7905fd0678f302c50d60 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Tue, 25 Sep 2018 15:18:12 -0700 Subject: [PATCH 54/56] Eliminate double equals --- .../marketplaceItemTester/MarketplaceItemTester.qml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 8dfb277197..8f391f24c0 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -70,7 +70,7 @@ Rectangle { } function installResourceObj(resourceObj) { - if ("application" == resourceObj.assetType) { + if ("application" === resourceObj.assetType) { Commerce.installApp(resourceObj.resource); } } @@ -257,10 +257,13 @@ Rectangle { // Alas, there is nothing we can do about that so charge // ahead as though we are sure the present signal is one // we expect. - if ("load file" === currentAction) { - Window.browseChanged.disconnect(onResourceSelected); - } else if ("load url" == currentAction) { - Window.promptTextChanged.disconnect(onResourceSelected); + switch(currentAction) { + case "load file": + Window.browseChanged.disconnect(onResourceSelected); + break + case "load url": + Window.promptTextChanged.disconnect(onResourceSelected); + break; } if (resource) { var resourceObj = buildResourceObj(resource); From e6249e690e2a5ff951c02b76f2fb8ee102258fcb Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Tue, 25 Sep 2018 15:52:26 -0700 Subject: [PATCH 55/56] Remove duplicate line --- scripts/system/marketplaces/marketplaces.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 2e0c05be43..cc5ff99673 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -23,7 +23,6 @@ Script.include("/~/system/libraries/connectionUtils.js"); var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; -var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); From 28ec98be9e274d40edb8b83e0da89f6e1a92f10e Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian <kkurian@gmail.com> Date: Tue, 25 Sep 2018 19:24:46 -0700 Subject: [PATCH 56/56] Uninstall marketplace item tester on wallet shutdown --- scripts/system/commerce/wallet.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 2c64b35b00..b12191b00c 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -521,6 +521,14 @@ function installMarketplaceItemTester() { }); } +function uninstallMarketplaceItemTester() { + if (Menu.menuExists(DEVELOPER_MENU) && + Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL) + ) { + Menu.removeMenuItem(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL); + } +} + var BUTTON_NAME = "WALLET"; var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; var ui; @@ -555,6 +563,7 @@ function off() { function shutdown() { GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); deleteSendMoneyParticleEffect(); + uninstallMarketplaceItemTester(); off(); }