diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 639c9f003f..1b9aa4dc18 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -140,9 +140,6 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { auto orientation = myAvatar->getLocalOrientation(); _rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState); - // evaluate AnimGraph animation and update jointStates. - Model::updateRig(deltaTime, parentTransform); - Rig::EyeParameters eyeParams; eyeParams.eyeLookAt = lookAt; eyeParams.eyeSaccade = head->getSaccade(); @@ -153,6 +150,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { _rig->updateFromEyeParameters(eyeParams); + // evaluate AnimGraph animation and update jointStates. Parent::updateRig(deltaTime, parentTransform); } diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index 7700874d9a..79314ce49a 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -28,11 +28,15 @@ const int MAX_HISTORY_SIZE = 64; const QString COMMAND_STYLE = "color: #266a9b;"; const QString RESULT_SUCCESS_STYLE = "color: #677373;"; +const QString RESULT_INFO_STYLE = "color: #223bd1;"; +const QString RESULT_WARNING_STYLE = "color: #d13b22;"; const QString RESULT_ERROR_STYLE = "color: #d13b22;"; const QString GUTTER_PREVIOUS_COMMAND = "<"; const QString GUTTER_ERROR = "X"; +const QString JSConsole::_consoleFileName { "about:console" }; + JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) : QWidget(parent), _ui(new Ui::Console), @@ -77,6 +81,8 @@ void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) { } if (_scriptEngine != NULL) { disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint); + disconnect(_scriptEngine, &ScriptEngine::infoMessage, this, &JSConsole::handleInfo); + disconnect(_scriptEngine, &ScriptEngine::warningMessage, this, &JSConsole::handleWarning); disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError); if (_ownScriptEngine) { _scriptEngine->deleteLater(); @@ -84,10 +90,12 @@ void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) { } // if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine - _ownScriptEngine = scriptEngine == NULL; - _scriptEngine = _ownScriptEngine ? DependencyManager::get()->loadScript(QString(), false) : scriptEngine; + _ownScriptEngine = (scriptEngine == NULL); + _scriptEngine = _ownScriptEngine ? DependencyManager::get()->loadScript(_consoleFileName, false) : scriptEngine; connect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint); + connect(_scriptEngine, &ScriptEngine::infoMessage, this, &JSConsole::handleInfo); + connect(_scriptEngine, &ScriptEngine::warningMessage, this, &JSConsole::handleWarning); connect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError); } @@ -107,11 +115,10 @@ void JSConsole::executeCommand(const QString& command) { QScriptValue JSConsole::executeCommandInWatcher(const QString& command) { QScriptValue result; - static const QString filename = "JSConcole"; QMetaObject::invokeMethod(_scriptEngine, "evaluate", Qt::ConnectionType::BlockingQueuedConnection, Q_RETURN_ARG(QScriptValue, result), Q_ARG(const QString&, command), - Q_ARG(const QString&, filename)); + Q_ARG(const QString&, _consoleFileName)); return result; } @@ -134,16 +141,26 @@ void JSConsole::commandFinished() { resetCurrentCommandHistory(); } -void JSConsole::handleError(const QString& scriptName, const QString& message) { +void JSConsole::handleError(const QString& message, const QString& scriptName) { Q_UNUSED(scriptName); appendMessage(GUTTER_ERROR, "" + message.toHtmlEscaped() + ""); } -void JSConsole::handlePrint(const QString& scriptName, const QString& message) { +void JSConsole::handlePrint(const QString& message, const QString& scriptName) { Q_UNUSED(scriptName); appendMessage("", message); } +void JSConsole::handleInfo(const QString& message, const QString& scriptName) { + Q_UNUSED(scriptName); + appendMessage("", "" + message.toHtmlEscaped() + ""); +} + +void JSConsole::handleWarning(const QString& message, const QString& scriptName) { + Q_UNUSED(scriptName); + appendMessage("", "" + message.toHtmlEscaped() + ""); +} + void JSConsole::mouseReleaseEvent(QMouseEvent* event) { _ui->promptTextEdit->setFocus(); } diff --git a/interface/src/ui/JSConsole.h b/interface/src/ui/JSConsole.h index d5f5aff301..864f847071 100644 --- a/interface/src/ui/JSConsole.h +++ b/interface/src/ui/JSConsole.h @@ -47,8 +47,10 @@ protected: protected slots: void scrollToBottom(); void resizeTextInput(); - void handlePrint(const QString& scriptName, const QString& message); - void handleError(const QString& scriptName, const QString& message); + void handlePrint(const QString& message, const QString& scriptName); + void handleInfo(const QString& message, const QString& scriptName); + void handleWarning(const QString& message, const QString& scriptName); + void handleError(const QString& message, const QString& scriptName); void commandFinished(); private: @@ -66,6 +68,7 @@ private: bool _ownScriptEngine; QString _rootCommand; ScriptEngine* _scriptEngine; + static const QString _consoleFileName; }; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index b684aac89c..680e9129aa 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1097,28 +1097,27 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) { } void AudioClient::prepareLocalAudioInjectors() { - if (_outputPeriod == 0) { - return; - } - - int bufferCapacity = _localInjectorsStream.getSampleCapacity(); - if (_localToOutputResampler) { - // avoid overwriting the buffer, - // instead of failing on writes because the buffer is used as a lock-free pipe - bufferCapacity -= - _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL) * - AudioConstants::STEREO; - bufferCapacity += 1; - } - int samplesNeeded = std::numeric_limits::max(); while (samplesNeeded > 0) { - // lock for every write to avoid locking out the device callback - // this lock is intentional - the buffer is only lock-free in its use in the device callback - RecursiveLock lock(_localAudioMutex); + // unlock between every write to allow device switching + Lock lock(_localAudioMutex); + + // in case of a device switch, consider bufferCapacity volatile across iterations + if (_outputPeriod == 0) { + return; + } + + int bufferCapacity = _localInjectorsStream.getSampleCapacity(); + int maxOutputSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * AudioConstants::STEREO; + if (_localToOutputResampler) { + maxOutputSamples = + _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL) * + AudioConstants::STEREO; + } samplesNeeded = bufferCapacity - _localSamplesAvailable.load(std::memory_order_relaxed); - if (samplesNeeded <= 0) { + if (samplesNeeded < maxOutputSamples) { + // avoid overwriting the buffer to prevent losing frames break; } @@ -1168,16 +1167,18 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { memset(mixBuffer, 0, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO * sizeof(float)); for (AudioInjector* injector : _activeLocalAudioInjectors) { - if (injector->getLocalBuffer()) { + // the lock guarantees that injectorBuffer, if found, is invariant + AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer(); + if (injectorBuffer) { static const int HRTF_DATASET_INDEX = 1; int numChannels = injector->isAmbisonic() ? AudioConstants::AMBISONIC : (injector->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO); - qint64 bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; + size_t bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; // get one frame from the injector memset(_localScratchBuffer, 0, bytesToRead); - if (0 < injector->getLocalBuffer()->readData((char*)_localScratchBuffer, bytesToRead)) { + if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) { if (injector->isAmbisonic()) { @@ -1317,15 +1318,17 @@ void AudioClient::setIsStereoInput(bool isStereoInput) { } bool AudioClient::outputLocalInjector(AudioInjector* injector) { - Lock lock(_injectorsMutex); - if (injector->getLocalBuffer() && _audioInput ) { - // just add it to the vector of active local injectors, if - // not already there. - // Since this is invoked with invokeMethod, there _should_ be - // no reason to lock access to the vector of injectors. + AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer(); + if (injectorBuffer) { + // local injectors are on the AudioInjectorsThread, so we must guard access + Lock lock(_injectorsMutex); if (!_activeLocalAudioInjectors.contains(injector)) { qCDebug(audioclient) << "adding new injector"; _activeLocalAudioInjectors.append(injector); + + // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop()) + injectorBuffer->setParent(nullptr); + injectorBuffer->moveToThread(&_localAudioThread); } else { qCDebug(audioclient) << "injector exists in active list already"; } @@ -1333,7 +1336,7 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) { return true; } else { - // no local buffer or audio + // no local buffer return false; } } @@ -1452,7 +1455,7 @@ void AudioClient::outputNotify() { bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) { bool supportedFormat = false; - RecursiveLock lock(_localAudioMutex); + Lock lock(_localAudioMutex); _localSamplesAvailable.exchange(0, std::memory_order_release); // cleanup any previously initialized device @@ -1681,8 +1684,12 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { int injectorSamplesPopped = 0; { - RecursiveLock lock(_audio->_localAudioMutex); bool append = networkSamplesPopped > 0; + // this does not require a lock as of the only two functions adding to _localSamplesAvailable (samples count): + // - prepareLocalAudioInjectors will only increase samples count + // - switchOutputToAudioDevice will zero samples count + // stop the device, so that readData will exhaust the existing buffer or see a zeroed samples count + // and start the device, which can only see a zeroed samples count samplesRequested = std::min(samplesRequested, _audio->_localSamplesAvailable.load(std::memory_order_acquire)); if ((injectorSamplesPopped = _localInjectorsStream.appendSamples(mixBuffer, samplesRequested, append)) > 0) { _audio->_localSamplesAvailable.fetch_sub(injectorSamplesPopped, std::memory_order_release); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 139749e8e8..aaedee7456 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -96,8 +96,6 @@ public: using AudioPositionGetter = std::function; using AudioOrientationGetter = std::function; - using RecursiveMutex = std::recursive_mutex; - using RecursiveLock = std::unique_lock; using Mutex = std::mutex; using Lock = std::unique_lock; @@ -345,7 +343,7 @@ private: int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC]; float* _localOutputMixBuffer { NULL }; AudioInjectorsThread _localAudioThread; - RecursiveMutex _localAudioMutex; + Mutex _localAudioMutex; // for output audio (used by this thread) int _outputPeriod { 0 }; diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index d61b8d60ca..2e4611cd4e 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -33,7 +33,11 @@ public: PacketType packetType, QString codecName = QString("")); public slots: + // threadsafe + // moves injector->getLocalBuffer() to another thread (so removes its parent) + // take care to delete it when ~AudioInjector, as parenting Qt semantics will not work virtual bool outputLocalInjector(AudioInjector* injector) = 0; + virtual bool shouldLoopbackInjectors() { return false; } virtual void setIsStereoInput(bool stereo) = 0; diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 86cbc98d84..ce98225190 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -51,6 +51,10 @@ AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOpt { } +AudioInjector::~AudioInjector() { + deleteLocalBuffer(); +} + bool AudioInjector::stateHas(AudioInjectorState state) const { return (_state & state) == state; } @@ -87,11 +91,7 @@ void AudioInjector::finish() { emit finished(); - if (_localBuffer) { - _localBuffer->stop(); - _localBuffer->deleteLater(); - _localBuffer = NULL; - } + deleteLocalBuffer(); if (stateHas(AudioInjectorState::PendingDelete)) { // we've been asked to delete after finishing, trigger a deleteLater here @@ -163,7 +163,7 @@ bool AudioInjector::injectLocally() { if (_localAudioInterface) { if (_audioData.size() > 0) { - _localBuffer = new AudioInjectorLocalBuffer(_audioData, this); + _localBuffer = new AudioInjectorLocalBuffer(_audioData); _localBuffer->open(QIODevice::ReadOnly); _localBuffer->setShouldLoop(_options.loop); @@ -172,7 +172,8 @@ bool AudioInjector::injectLocally() { _localBuffer->setCurrentOffset(_currentSendOffset); // call this function on the AudioClient's thread - success = QMetaObject::invokeMethod(_localAudioInterface, "outputLocalInjector", Q_ARG(AudioInjector*, this)); + // this will move the local buffer's thread to the LocalInjectorThread + success = _localAudioInterface->outputLocalInjector(this); if (!success) { qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface"; @@ -185,6 +186,14 @@ bool AudioInjector::injectLocally() { return success; } +void AudioInjector::deleteLocalBuffer() { + if (_localBuffer) { + _localBuffer->stop(); + _localBuffer->deleteLater(); + _localBuffer = nullptr; + } +} + const uchar MAX_INJECTOR_VOLUME = packFloatGainToByte(1.0f); static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1; static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0; diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 7d57708738..a901c2520f 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -52,6 +52,7 @@ class AudioInjector : public QObject { public: AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions); AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions); + ~AudioInjector(); bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); } @@ -99,6 +100,7 @@ private: int64_t injectNextFrame(); bool inject(bool(AudioInjectorManager::*injection)(AudioInjector*)); bool injectLocally(); + void deleteLocalBuffer(); static AbstractAudioInterface* _localAudioInterface; diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.cpp b/libraries/audio/src/AudioInjectorLocalBuffer.cpp index 049adb0cc6..7834644baf 100644 --- a/libraries/audio/src/AudioInjectorLocalBuffer.cpp +++ b/libraries/audio/src/AudioInjectorLocalBuffer.cpp @@ -11,8 +11,7 @@ #include "AudioInjectorLocalBuffer.h" -AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArray, QObject* parent) : - QIODevice(parent), +AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArray) : _rawAudioArray(rawAudioArray), _shouldLoop(false), _isStopped(false), diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.h b/libraries/audio/src/AudioInjectorLocalBuffer.h index 07d8ae5b9f..673966ff09 100644 --- a/libraries/audio/src/AudioInjectorLocalBuffer.h +++ b/libraries/audio/src/AudioInjectorLocalBuffer.h @@ -19,7 +19,7 @@ class AudioInjectorLocalBuffer : public QIODevice { Q_OBJECT public: - AudioInjectorLocalBuffer(const QByteArray& rawAudioArray, QObject* parent); + AudioInjectorLocalBuffer(const QByteArray& rawAudioArray); void stop(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 8fcc859b62..e1e5dc4282 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -120,7 +120,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } // evaluate AnimGraph animation and update jointStates. - Model::updateRig(deltaTime, parentTransform); + Parent::updateRig(deltaTime, parentTransform); } void SkeletonModel::updateAttitude() { @@ -136,7 +136,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { if (fullUpdate) { setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients()); - Model::simulate(deltaTime, fullUpdate); + Parent::simulate(deltaTime, fullUpdate); // let rig compute the model offset glm::vec3 registrationPoint; @@ -144,7 +144,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { setOffset(registrationPoint); } } else { - Model::simulate(deltaTime, fullUpdate); + Parent::simulate(deltaTime, fullUpdate); } if (!isActive() || !_owningAvatar->isMyAvatar()) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h index 23af04e964..059dd245fd 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h @@ -23,6 +23,7 @@ using SkeletonModelWeakPointer = std::weak_ptr; /// A skeleton loaded from a model. class SkeletonModel : public CauterizedModel { + using Parent = CauterizedModel; Q_OBJECT public: diff --git a/libraries/networking/src/FingerprintUtils.cpp b/libraries/networking/src/FingerprintUtils.cpp index 1990d356b6..216e0f28dd 100644 --- a/libraries/networking/src/FingerprintUtils.cpp +++ b/libraries/networking/src/FingerprintUtils.cpp @@ -19,8 +19,8 @@ #include #ifdef Q_OS_WIN -#include -#include +#include +#include #endif //Q_OS_WIN #ifdef Q_OS_MAC @@ -30,6 +30,9 @@ #endif //Q_OS_MAC static const QString FALLBACK_FINGERPRINT_KEY = "fallbackFingerprint"; + +QUuid FingerprintUtils::_machineFingerprint { QUuid() }; + QString FingerprintUtils::getMachineFingerprintString() { QString uuidString; #ifdef Q_OS_LINUX @@ -47,122 +50,32 @@ QString FingerprintUtils::getMachineFingerprintString() { #endif //Q_OS_MAC #ifdef Q_OS_WIN - HRESULT hres; - IWbemLocator *pLoc = NULL; - - // initialize com. Interface already does, but other - // users of this lib don't necessarily do so. - hres = CoInitializeEx(0, COINIT_MULTITHREADED); - if (FAILED(hres)) { - qCDebug(networking) << "Failed to initialize COM library!"; - return uuidString; - } + HKEY cryptoKey; - // initialize WbemLocator - hres = CoCreateInstance( - CLSID_WbemLocator, - 0, - CLSCTX_INPROC_SERVER, - IID_IWbemLocator, (LPVOID *) &pLoc); + // try and open the key that contains the machine GUID + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", 0, KEY_READ, &cryptoKey) == ERROR_SUCCESS) { + DWORD type; + DWORD guidSize; - if (FAILED(hres)) { - qCDebug(networking) << "Failed to initialize WbemLocator"; - return uuidString; - } - - // Connect to WMI through the IWbemLocator::ConnectServer method - IWbemServices *pSvc = NULL; + const char* MACHINE_GUID_KEY = "MachineGuid"; - // Connect to the root\cimv2 namespace with - // the current user and obtain pointer pSvc - // to make IWbemServices calls. - hres = pLoc->ConnectServer( - _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace - NULL, // User name. NULL = current user - NULL, // User password. NULL = current - 0, // Locale. NULL indicates current - NULL, // Security flags. - 0, // Authority (for example, Kerberos) - 0, // Context object - &pSvc // pointer to IWbemServices proxy - ); + // try and retrieve the size of the GUID value + if (RegQueryValueEx(cryptoKey, MACHINE_GUID_KEY, NULL, &type, NULL, &guidSize) == ERROR_SUCCESS) { + // make sure that the value is a string + if (type == REG_SZ) { + // retrieve the machine GUID and return that as our UUID string + std::string machineGUID(guidSize / sizeof(char), '\0'); - if (FAILED(hres)) { - pLoc->Release(); - qCDebug(networking) << "Failed to connect to WMI"; - return uuidString; - } - - // Set security levels on the proxy - hres = CoSetProxyBlanket( - pSvc, // Indicates the proxy to set - RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx - RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx - NULL, // Server principal name - RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx - RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx - NULL, // client identity - EOAC_NONE // proxy capabilities - ); - - if (FAILED(hres)) { - pSvc->Release(); - pLoc->Release(); - qCDebug(networking) << "Failed to set security on proxy blanket"; - return uuidString; - } - - // Use the IWbemServices pointer to grab the Win32_BIOS stuff - IEnumWbemClassObject* pEnumerator = NULL; - hres = pSvc->ExecQuery( - bstr_t("WQL"), - bstr_t("SELECT * FROM Win32_ComputerSystemProduct"), - WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, - NULL, - &pEnumerator); - - if (FAILED(hres)) { - pSvc->Release(); - pLoc->Release(); - qCDebug(networking) << "query to get Win32_ComputerSystemProduct info"; - return uuidString; - } - - // Get the SerialNumber from the Win32_BIOS data - IWbemClassObject *pclsObj; - ULONG uReturn = 0; - - SHORT sRetStatus = -100; - - while (pEnumerator) { - HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); - - if(0 == uReturn){ - break; - } - - VARIANT vtProp; - - // Get the value of the Name property - hr = pclsObj->Get(L"UUID", 0, &vtProp, 0, 0); - if (!FAILED(hres)) { - switch (vtProp.vt) { - case VT_BSTR: - uuidString = QString::fromWCharArray(vtProp.bstrVal); - break; + if (RegQueryValueEx(cryptoKey, MACHINE_GUID_KEY, NULL, NULL, + reinterpret_cast(&machineGUID[0]), &guidSize) == ERROR_SUCCESS) { + uuidString = QString::fromStdString(machineGUID); + } } } - VariantClear(&vtProp); - pclsObj->Release(); + RegCloseKey(cryptoKey); } - pEnumerator->Release(); - // Cleanup - pSvc->Release(); - pLoc->Release(); - - qCDebug(networking) << "Windows BIOS UUID: " << uuidString; #endif //Q_OS_WIN return uuidString; @@ -171,29 +84,36 @@ QString FingerprintUtils::getMachineFingerprintString() { QUuid FingerprintUtils::getMachineFingerprint() { - QString uuidString = getMachineFingerprintString(); + if (_machineFingerprint.isNull()) { + QString uuidString = getMachineFingerprintString(); + + // now, turn into uuid. A malformed string will + // return QUuid() ("{00000...}"), which handles + // any errors in getting the string + QUuid uuid(uuidString); - // now, turn into uuid. A malformed string will - // return QUuid() ("{00000...}"), which handles - // any errors in getting the string - QUuid uuid(uuidString); - if (uuid == QUuid()) { - // if you cannot read a fallback key cuz we aren't saving them, just generate one for - // this session and move on - if (DependencyManager::get().isNull()) { - return QUuid::createUuid(); - } - // read fallback key (if any) - Settings settings; - uuid = QUuid(settings.value(FALLBACK_FINGERPRINT_KEY).toString()); - qCDebug(networking) << "read fallback maching fingerprint: " << uuid.toString(); if (uuid == QUuid()) { - // no fallback yet, set one - uuid = QUuid::createUuid(); - settings.setValue(FALLBACK_FINGERPRINT_KEY, uuid.toString()); - qCDebug(networking) << "no fallback machine fingerprint, setting it to: " << uuid.toString(); + // if you cannot read a fallback key cuz we aren't saving them, just generate one for + // this session and move on + if (DependencyManager::get().isNull()) { + return QUuid::createUuid(); + } + // read fallback key (if any) + Settings settings; + uuid = QUuid(settings.value(FALLBACK_FINGERPRINT_KEY).toString()); + qCDebug(networking) << "read fallback maching fingerprint: " << uuid.toString(); + + if (uuid == QUuid()) { + // no fallback yet, set one + uuid = QUuid::createUuid(); + settings.setValue(FALLBACK_FINGERPRINT_KEY, uuid.toString()); + qCDebug(networking) << "no fallback machine fingerprint, setting it to: " << uuid.toString(); + } } + + _machineFingerprint = uuid; } - return uuid; + + return _machineFingerprint; } diff --git a/libraries/networking/src/FingerprintUtils.h b/libraries/networking/src/FingerprintUtils.h index 572b150ec4..c4cb900a48 100644 --- a/libraries/networking/src/FingerprintUtils.h +++ b/libraries/networking/src/FingerprintUtils.h @@ -21,6 +21,7 @@ public: private: static QString getMachineFingerprintString(); + static QUuid _machineFingerprint; }; #endif // hifi_FingerprintUtils_h diff --git a/libraries/render-utils/src/CauterizedModel.h b/libraries/render-utils/src/CauterizedModel.h index 39bcedf639..dcff7bd12d 100644 --- a/libraries/render-utils/src/CauterizedModel.h +++ b/libraries/render-utils/src/CauterizedModel.h @@ -28,10 +28,10 @@ public: const std::unordered_set& getCauterizeBoneSet() const { return _cauterizeBoneSet; } void setCauterizeBoneSet(const std::unordered_set& boneSet) { _cauterizeBoneSet = boneSet; } - void deleteGeometry() override; - bool updateGeometry() override; + void deleteGeometry() override; + bool updateGeometry() override; - void createVisibleRenderItemSet() override; + void createVisibleRenderItemSet() override; void createCollisionRenderItemSet() override; virtual void updateClusterMatrices() override; @@ -41,7 +41,7 @@ public: protected: std::unordered_set _cauterizeBoneSet; - QVector _cauterizeMeshStates; + QVector _cauterizeMeshStates; bool _isCauterized { false }; bool _enableCauterization { false }; }; diff --git a/libraries/script-engine/src/Mat4.cpp b/libraries/script-engine/src/Mat4.cpp index 6676d0cde1..6965f43b32 100644 --- a/libraries/script-engine/src/Mat4.cpp +++ b/libraries/script-engine/src/Mat4.cpp @@ -11,7 +11,9 @@ #include #include +#include #include "ScriptEngineLogging.h" +#include "ScriptEngine.h" #include "Mat4.h" glm::mat4 Mat4::multiply(const glm::mat4& m1, const glm::mat4& m2) const { @@ -66,10 +68,12 @@ glm::vec3 Mat4::getUp(const glm::mat4& m) const { return glm::vec3(m[0][1], m[1][1], m[2][1]); } -void Mat4::print(const QString& label, const glm::mat4& m) const { - qCDebug(scriptengine) << qPrintable(label) << - "row0 =" << m[0][0] << "," << m[1][0] << "," << m[2][0] << "," << m[3][0] << - "row1 =" << m[0][1] << "," << m[1][1] << "," << m[2][1] << "," << m[3][1] << - "row2 =" << m[0][2] << "," << m[1][2] << "," << m[2][2] << "," << m[3][2] << - "row3 =" << m[0][3] << "," << m[1][3] << "," << m[2][3] << "," << m[3][3]; +void Mat4::print(const QString& label, const glm::mat4& m, bool transpose) const { + glm::dmat4 out = transpose ? glm::transpose(m) : m; + QString message = QString("%1 %2").arg(qPrintable(label)); + message = message.arg(glm::to_string(out).c_str()); + qCDebug(scriptengine) << message; + if (ScriptEngine* scriptEngine = qobject_cast(engine())) { + scriptEngine->print(message); + } } diff --git a/libraries/script-engine/src/Mat4.h b/libraries/script-engine/src/Mat4.h index 19bbbe178a..8b942874ee 100644 --- a/libraries/script-engine/src/Mat4.h +++ b/libraries/script-engine/src/Mat4.h @@ -16,9 +16,10 @@ #include #include +#include /// Scriptable Mat4 object. Used exclusively in the JavaScript API -class Mat4 : public QObject { +class Mat4 : public QObject, protected QScriptable { Q_OBJECT public slots: @@ -43,7 +44,7 @@ public slots: glm::vec3 getRight(const glm::mat4& m) const; glm::vec3 getUp(const glm::mat4& m) const; - void print(const QString& label, const glm::mat4& m) const; + void print(const QString& label, const glm::mat4& m, bool transpose = false) const; }; #endif // hifi_Mat4_h diff --git a/libraries/script-engine/src/Quat.cpp b/libraries/script-engine/src/Quat.cpp index 05002dcf5d..a6f7acffc8 100644 --- a/libraries/script-engine/src/Quat.cpp +++ b/libraries/script-engine/src/Quat.cpp @@ -15,7 +15,9 @@ #include #include +#include #include "ScriptEngineLogging.h" +#include "ScriptEngine.h" #include "Quat.h" quat Quat::normalize(const glm::quat& q) { @@ -114,8 +116,17 @@ float Quat::dot(const glm::quat& q1, const glm::quat& q2) { return glm::dot(q1, q2); } -void Quat::print(const QString& label, const glm::quat& q) { - qCDebug(scriptengine) << qPrintable(label) << q.x << "," << q.y << "," << q.z << "," << q.w; +void Quat::print(const QString& label, const glm::quat& q, bool asDegrees) { + QString message = QString("%1 %2").arg(qPrintable(label)); + if (asDegrees) { + message = message.arg(glm::to_string(glm::dvec3(safeEulerAngles(q))).c_str()); + } else { + message = message.arg(glm::to_string(glm::dquat(q)).c_str()); + } + qCDebug(scriptengine) << message; + if (ScriptEngine* scriptEngine = qobject_cast(engine())) { + scriptEngine->print(message); + } } bool Quat::equal(const glm::quat& q1, const glm::quat& q2) { diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index ee3ab9aa7c..3b3a6fde7c 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -18,6 +18,7 @@ #include #include +#include /**jsdoc * A Quaternion @@ -30,7 +31,7 @@ */ /// Scriptable interface a Quaternion helper class object. Used exclusively in the JavaScript API -class Quat : public QObject { +class Quat : public QObject, protected QScriptable { Q_OBJECT public slots: @@ -58,7 +59,7 @@ public slots: glm::quat slerp(const glm::quat& q1, const glm::quat& q2, float alpha); glm::quat squad(const glm::quat& q1, const glm::quat& q2, const glm::quat& s1, const glm::quat& s2, float h); float dot(const glm::quat& q1, const glm::quat& q2); - void print(const QString& label, const glm::quat& q); + void print(const QString& label, const glm::quat& q, bool asDegrees = false); bool equal(const glm::quat& q1, const glm::quat& q2); glm::quat cancelOutRollAndPitch(const glm::quat& q); glm::quat cancelOutRoll(const glm::quat& q); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c904062507..8bbb9a3e2c 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -105,11 +105,11 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) { } message += context->argument(i).toString(); } - qCDebug(scriptengineScript).noquote() << "script:print()<<" << message; // noquote() so that \n is treated as newline + qCDebug(scriptengineScript).noquote() << message; // noquote() so that \n is treated as newline - // FIXME - this approach neeeds revisiting. print() comes here, which ends up calling Script.print? - engine->globalObject().property("Script").property("print") - .call(engine->nullValue(), QScriptValueList({ message })); + if (ScriptEngine *scriptEngine = qobject_cast(engine)) { + scriptEngine->print(message); + } return QScriptValue(); } @@ -472,6 +472,11 @@ void ScriptEngine::scriptInfoMessage(const QString& message) { emit infoMessage(message, getFilename()); } +void ScriptEngine::scriptPrintedMessage(const QString& message) { + qCDebug(scriptengine) << message; + emit printedMessage(message, getFilename()); +} + // Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of // callAnimationStateHandler requires that the type be registered. // These two are meaningful, if we ever do want to use them... diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 5ea8d052e9..6188f24d71 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -221,6 +221,7 @@ public: void scriptErrorMessage(const QString& message); void scriptWarningMessage(const QString& message); void scriptInfoMessage(const QString& message); + void scriptPrintedMessage(const QString& message); int getNumRunningEntityScripts() const; bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 88b0e0b7b5..2076657288 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -453,7 +453,8 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL (scriptFilename.scheme() != "http" && scriptFilename.scheme() != "https" && scriptFilename.scheme() != "atp" && - scriptFilename.scheme() != "file")) { + scriptFilename.scheme() != "file" && + scriptFilename.scheme() != "about")) { // deal with a "url" like c:/something scriptUrl = normalizeScriptURL(QUrl::fromLocalFile(scriptFilename.toString())); } else { @@ -472,7 +473,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL }, Qt::QueuedConnection); - if (scriptFilename.isEmpty()) { + if (scriptFilename.isEmpty() || !scriptUrl.isValid()) { launchScriptEngine(scriptEngine); } else { // connect to the appropriate signals of this script engine diff --git a/libraries/script-engine/src/ScriptUUID.cpp b/libraries/script-engine/src/ScriptUUID.cpp index 6a52f4f6ca..ee15f1a760 100644 --- a/libraries/script-engine/src/ScriptUUID.cpp +++ b/libraries/script-engine/src/ScriptUUID.cpp @@ -14,6 +14,7 @@ #include #include "ScriptEngineLogging.h" +#include "ScriptEngine.h" #include "ScriptUUID.h" QUuid ScriptUUID::fromString(const QString& s) { @@ -36,6 +37,11 @@ bool ScriptUUID::isNull(const QUuid& id) { return id.isNull(); } -void ScriptUUID::print(const QString& lable, const QUuid& id) { - qCDebug(scriptengine) << qPrintable(lable) << id.toString(); +void ScriptUUID::print(const QString& label, const QUuid& id) { + QString message = QString("%1 %2").arg(qPrintable(label)); + message = message.arg(id.toString()); + qCDebug(scriptengine) << message; + if (ScriptEngine* scriptEngine = qobject_cast(engine())) { + scriptEngine->print(message); + } } diff --git a/libraries/script-engine/src/ScriptUUID.h b/libraries/script-engine/src/ScriptUUID.h index db94b5082b..221f9c46f0 100644 --- a/libraries/script-engine/src/ScriptUUID.h +++ b/libraries/script-engine/src/ScriptUUID.h @@ -15,9 +15,10 @@ #define hifi_ScriptUUID_h #include +#include /// Scriptable interface for a UUID helper class object. Used exclusively in the JavaScript API -class ScriptUUID : public QObject { +class ScriptUUID : public QObject, protected QScriptable { Q_OBJECT public slots: @@ -26,7 +27,7 @@ public slots: QUuid generate(); bool isEqual(const QUuid& idA, const QUuid& idB); bool isNull(const QUuid& id); - void print(const QString& lable, const QUuid& id); + void print(const QString& label, const QUuid& id); }; #endif // hifi_ScriptUUID_h diff --git a/libraries/script-engine/src/Vec3.cpp b/libraries/script-engine/src/Vec3.cpp index 6c8f618500..a156f56d96 100644 --- a/libraries/script-engine/src/Vec3.cpp +++ b/libraries/script-engine/src/Vec3.cpp @@ -14,20 +14,26 @@ #include #include +#include #include "ScriptEngineLogging.h" #include "NumericalConstants.h" #include "Vec3.h" +#include "ScriptEngine.h" float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) { float radians = glm::orientedAngle(glm::normalize(v1), glm::normalize(v2), glm::normalize(v3)); return glm::degrees(radians); } - -void Vec3::print(const QString& lable, const glm::vec3& v) { - qCDebug(scriptengine) << qPrintable(lable) << v.x << "," << v.y << "," << v.z; +void Vec3::print(const QString& label, const glm::vec3& v) { + QString message = QString("%1 %2").arg(qPrintable(label)); + message = message.arg(glm::to_string(glm::dvec3(v)).c_str()); + qCDebug(scriptengine) << message; + if (ScriptEngine* scriptEngine = qobject_cast(engine())) { + scriptEngine->print(message); + } } bool Vec3::withinEpsilon(const glm::vec3& v1, const glm::vec3& v2, float epsilon) { diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index b3a3dc3035..c7179a80c0 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -17,6 +17,7 @@ #include #include +#include #include "GLMHelpers.h" @@ -48,7 +49,7 @@ */ /// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API -class Vec3 : public QObject { +class Vec3 : public QObject, protected QScriptable { Q_OBJECT Q_PROPERTY(glm::vec3 UNIT_X READ UNIT_X CONSTANT) Q_PROPERTY(glm::vec3 UNIT_Y READ UNIT_Y CONSTANT) diff --git a/scripts/developer/libraries/jasmine/hifi-boot.js b/scripts/developer/libraries/jasmine/hifi-boot.js index 772dd8c17e..405b692d66 100644 --- a/scripts/developer/libraries/jasmine/hifi-boot.js +++ b/scripts/developer/libraries/jasmine/hifi-boot.js @@ -20,9 +20,10 @@ print('Tests completed with ' + errorCount + ' ' + ERROR + '.'); } - if (pending.length) + if (pending.length) { print ('disabled:
   '+ pending.join('
   ')+'
'); + } print('Tests completed in ' + (endTime - startTime) + 'ms.'); }; this.suiteStarted = function(obj) { diff --git a/scripts/developer/tests/printTest.js b/scripts/developer/tests/printTest.js new file mode 100644 index 0000000000..c1fe6ec745 --- /dev/null +++ b/scripts/developer/tests/printTest.js @@ -0,0 +1,39 @@ +/* eslint-env jasmine */ + +// this test generates sample print, Script.print, etc. output + +main(); + +function main() { + // to match with historical behavior, Script.print(message) output only triggers + // the printedMessage signal (and therefore doesn't show up in the application log) + Script.print('[Script.print] hello world'); + + // the rest of these should show up in both the application log and signaled print handlers + print('[print]', 'hello', 'world'); + + // note: these trigger the equivalent of an emit + Script.printedMessage('[Script.printedMessage] hello world', '{filename}'); + Script.infoMessage('[Script.infoMessage] hello world', '{filename}'); + Script.warningMessage('[Script.warningMessage] hello world', '{filename}'); + Script.errorMessage('[Script.errorMessage] hello world', '{filename}'); + + { + Vec3.print('[Vec3.print]', Vec3.HALF); + + var q = Quat.fromPitchYawRollDegrees(45, 45, 45); + Quat.print('[Quat.print]', q); + Quat.print('[Quat.print (euler)]', q, true); + + function vec4(x,y,z,w) { + return { x: x, y: y, z: z, w: w }; + } + var m = Mat4.createFromColumns( + vec4(1,2,3,4), vec4(5,6,7,8), vec4(9,10,11,12), vec4(13,14,15,16) + ); + Mat4.print('[Mat4.print (col major)]', m); + Mat4.print('[Mat4.print (row major)]', m, true); + + Uuid.print('[Uuid.print]', Uuid.fromString(Uuid.toString(0))); + } +} diff --git a/scripts/developer/tests/unit_tests/avatarUnitTests.js b/scripts/developer/tests/unit_tests/avatarUnitTests.js index 7032b5f5e6..4ab8556ab7 100644 --- a/scripts/developer/tests/unit_tests/avatarUnitTests.js +++ b/scripts/developer/tests/unit_tests/avatarUnitTests.js @@ -1,5 +1,7 @@ +/* eslint-env jasmine */ // Art3mis +// eslint-disable-next-line max-len var DEFAULT_AVATAR_URL = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/e76946cc-c272-4adf-9bb6-02cde0a4b57d/8fd984ea6fe1495147a3303f87fa6e23.fst?1460131758"; var ORIGIN = {x: 0, y: 0, z: 0}; @@ -8,6 +10,15 @@ var ROT_IDENT = {x: 0, y: 0, z: 0, w: 1}; describe("MyAvatar", function () { + // backup/restore current skeletonModelURL + beforeAll(function() { + this.oldURL = MyAvatar.skeletonModelURL; + }); + + afterAll(function() { + MyAvatar.skeletonModelURL = this.oldURL; + }); + // reload the avatar from scratch before each test. beforeEach(function (done) { MyAvatar.skeletonModelURL = DEFAULT_AVATAR_URL; @@ -20,12 +31,12 @@ describe("MyAvatar", function () { MyAvatar.position = ORIGIN; MyAvatar.orientation = ROT_IDENT; // give the avatar 1/2 a second to settle on the ground in the idle pose. - Script.setTimeout(function () { + Script.setTimeout(function () { done(); }, 500); } }, 500); - }); + }, 10000 /* timeout -- allow time to download avatar*/); // makes the assumption that there is solid ground somewhat underneath the avatar. it("position and orientation getters", function () { diff --git a/scripts/developer/tests/unit_tests/bindUnitTest.js b/scripts/developer/tests/unit_tests/bindUnitTest.js index 609487a30b..3407490a04 100644 --- a/scripts/developer/tests/unit_tests/bindUnitTest.js +++ b/scripts/developer/tests/unit_tests/bindUnitTest.js @@ -1,3 +1,5 @@ +/* eslint-env jasmine */ + Script.include('../../../system/libraries/utils.js'); describe('Bind', function() { diff --git a/scripts/developer/tests/unit_tests/entityUnitTests.js b/scripts/developer/tests/unit_tests/entityUnitTests.js index 1372676901..f2c4b4871f 100644 --- a/scripts/developer/tests/unit_tests/entityUnitTests.js +++ b/scripts/developer/tests/unit_tests/entityUnitTests.js @@ -1,3 +1,5 @@ +/* eslint-env jasmine */ + describe('Entity', function() { var center = Vec3.sum( MyAvatar.position, @@ -19,6 +21,14 @@ describe('Entity', function() { }, }; + it('serversExist', function() { + expect(Entities.serversExist()).toBe(true); + }); + + it('canRezTmp', function() { + expect(Entities.canRezTmp()).toBe(true); + }); + beforeEach(function() { boxEntity = Entities.addEntity(boxProps); }); @@ -62,4 +72,4 @@ describe('Entity', function() { props = Entities.getEntityProperties(boxEntity); expect(props.lastEdited).toBeGreaterThan(prevLastEdited); }); -}); \ No newline at end of file +}); diff --git a/scripts/developer/tests/unit_tests/testRunner.js b/scripts/developer/tests/unit_tests/testRunner.js index 31d83cd986..7ce55b1874 100644 --- a/scripts/developer/tests/unit_tests/testRunner.js +++ b/scripts/developer/tests/unit_tests/testRunner.js @@ -1,13 +1,30 @@ +/* eslint-env jasmine */ + // Include testing library Script.include('../../libraries/jasmine/jasmine.js'); -Script.include('../../libraries/jasmine/hifi-boot.js') +Script.include('../../libraries/jasmine/hifi-boot.js'); // Include unit tests -// FIXME: Figure out why jasmine done() is not working. -// Script.include('avatarUnitTests.js'); +Script.include('avatarUnitTests.js'); Script.include('bindUnitTest.js'); Script.include('entityUnitTests.js'); +describe("jasmine internal tests", function() { + it('should support async .done()', function(done) { + var start = new Date; + Script.setTimeout(function() { + expect((new Date - start)/1000).toBeCloseTo(0.5, 1); + done(); + }, 500); + }); + // jasmine pending test + xit('disabled test', function() { + expect(false).toBe(true); + }); +}); + +// invoke Script.stop (after any async tests complete) +jasmine.getEnv().addReporter({ jasmineDone: Script.stop }); + // Run the tests jasmine.getEnv().execute(); -Script.stop(); diff --git a/tutorial/Changelog.md b/tutorial/Changelog.md new file mode 100644 index 0000000000..bd923b6841 --- /dev/null +++ b/tutorial/Changelog.md @@ -0,0 +1,3 @@ + * home-tutorial-34 + * Update tutorial to only start if `HMD.active` + * Update builder's grid to use "Good - Sub-meshes" for collision shape type