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