diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index be23dcfa25..a0c80453e0 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -371,25 +371,39 @@ void Agent::executeScript() { using namespace recording; static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::getAudioFrameName()); Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &scriptedAvatar](Frame::ConstPointer frame) { - const QByteArray& audio = frame->data; static quint16 audioSequenceNumber{ 0 }; - Transform audioTransform; + QByteArray audio(frame->data); + + if (_isNoiseGateEnabled) { + static int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; + _noiseGate.gateSamples(reinterpret_cast(audio.data()), numSamples); + } + + computeLoudness(&audio, scriptedAvatar); + + // the codec needs a flush frame before sending silent packets, so + // do not send one if the gate closed in this block (eventually this can be crossfaded). + auto packetType = PacketType::MicrophoneAudioNoEcho; + if (scriptedAvatar->getAudioLoudness() == 0.0f && !_noiseGate.closedInLastBlock()) { + packetType = PacketType::SilentAudioFrame; + } + + Transform audioTransform; auto headOrientation = scriptedAvatar->getHeadOrientation(); audioTransform.setTranslation(scriptedAvatar->getPosition()); audioTransform.setRotation(headOrientation); - computeLoudness(&audio, scriptedAvatar); - QByteArray encodedBuffer; if (_encoder) { _encoder->encode(audio, encodedBuffer); } else { encodedBuffer = audio; } + AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, audioTransform, scriptedAvatar->getPosition(), glm::vec3(0), - PacketType::MicrophoneAudioNoEcho, _selectedCodecName); + packetType, _selectedCodecName); }); auto avatarHashMap = DependencyManager::set(); @@ -483,6 +497,14 @@ void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } +void Agent::setIsNoiseGateEnabled(bool isNoiseGateEnabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setIsNoiseGateEnabled", Q_ARG(bool, isNoiseGateEnabled)); + return; + } + _isNoiseGateEnabled = isNoiseGateEnabled; +} + void Agent::setIsAvatar(bool isAvatar) { // this must happen on Agent's main thread if (QThread::currentThread() != thread()) { diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 0ce7b71d5d..620ac8e047 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -29,6 +29,7 @@ #include +#include "AudioNoiseGate.h" #include "MixedAudioStream.h" #include "avatars/ScriptableAvatar.h" @@ -38,6 +39,7 @@ class Agent : public ThreadedAssignment { Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar) Q_PROPERTY(bool isPlayingAvatarSound READ isPlayingAvatarSound) Q_PROPERTY(bool isListeningToAudioStream READ isListeningToAudioStream WRITE setIsListeningToAudioStream) + Q_PROPERTY(bool isNoiseGateEnabled READ isNoiseGateEnabled WRITE setIsNoiseGateEnabled) Q_PROPERTY(float lastReceivedAudioLoudness READ getLastReceivedAudioLoudness) Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) @@ -52,6 +54,9 @@ public: bool isListeningToAudioStream() const { return _isListeningToAudioStream; } void setIsListeningToAudioStream(bool isListeningToAudioStream); + bool isNoiseGateEnabled() const { return _isNoiseGateEnabled; } + void setIsNoiseGateEnabled(bool isNoiseGateEnabled); + float getLastReceivedAudioLoudness() const { return _lastReceivedAudioLoudness; } QUuid getSessionUUID() const; @@ -106,6 +111,9 @@ private: QTimer* _avatarIdentityTimer = nullptr; QHash _outgoingScriptAudioSequenceNumbers; + AudioNoiseGate _noiseGate; + bool _isNoiseGateEnabled { false }; + CodecPluginPointer _codec; QString _selectedCodecName; Encoder* _encoder { nullptr }; diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 47071b10b7..f674ab006c 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -455,13 +455,13 @@ void EntityScriptServer::addingEntity(const EntityItemID& entityID) { void EntityScriptServer::deletingEntity(const EntityItemID& entityID) { if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) { - _entitiesScriptEngine->unloadEntityScript(entityID); + _entitiesScriptEngine->unloadEntityScript(entityID, true); } } void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, const bool reload) { if (_entityViewer.getTree() && !_shuttingDown) { - _entitiesScriptEngine->unloadEntityScript(entityID); + _entitiesScriptEngine->unloadEntityScript(entityID, true); checkAndCallPreload(entityID, reload); } } diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 661a6213b8..d6b57b450a 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -246,10 +246,13 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList _agentPermissions[editorKey]->set(NodePermissions::Permission::canAdjustLocks); } - QList> permissionsSets; - permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get(); + std::list> permissionsSets{ + _standardAgentPermissions.get(), + _agentPermissions.get() + }; foreach (auto permissionsSet, permissionsSets) { - foreach (NodePermissionsKey userKey, permissionsSet.keys()) { + for (auto entry : permissionsSet) { + const auto& userKey = entry.first; if (onlyEditorsAreRezzers) { if (permissionsSet[userKey]->can(NodePermissions::Permission::canAdjustLocks)) { permissionsSet[userKey]->set(NodePermissions::Permission::canRezPermanentEntities); @@ -300,7 +303,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList } QVariantMap& DomainServerSettingsManager::getDescriptorsMap() { - static const QString DESCRIPTORS{ "descriptors" }; auto& settingsMap = getSettingsMap(); @@ -1355,18 +1357,12 @@ QStringList DomainServerSettingsManager::getAllKnownGroupNames() { // extract all the group names from the group-permissions and group-forbiddens settings QSet result; - QHashIterator i(_groupPermissions.get()); - while (i.hasNext()) { - i.next(); - NodePermissionsKey key = i.key(); - result += key.first; + for (const auto& entry : _groupPermissions.get()) { + result += entry.first.first; } - QHashIterator j(_groupForbiddens.get()); - while (j.hasNext()) { - j.next(); - NodePermissionsKey key = j.key(); - result += key.first; + for (const auto& entry : _groupForbiddens.get()) { + result += entry.first.first; } return result.toList(); @@ -1377,20 +1373,17 @@ bool DomainServerSettingsManager::setGroupID(const QString& groupName, const QUu _groupIDs[groupName.toLower()] = groupID; _groupNames[groupID] = groupName; - QHashIterator i(_groupPermissions.get()); - while (i.hasNext()) { - i.next(); - NodePermissionsPointer perms = i.value(); + + for (const auto& entry : _groupPermissions.get()) { + auto& perms = entry.second; if (perms->getID().toLower() == groupName.toLower() && !perms->isGroup()) { changed = true; perms->setGroupID(groupID); } } - QHashIterator j(_groupForbiddens.get()); - while (j.hasNext()) { - j.next(); - NodePermissionsPointer perms = j.value(); + for (const auto& entry : _groupForbiddens.get()) { + auto& perms = entry.second; if (perms->getID().toLower() == groupName.toLower() && !perms->isGroup()) { changed = true; perms->setGroupID(groupID); diff --git a/interface/resources/styles/log_dialog.qss b/interface/resources/styles/log_dialog.qss index 1fc4df0717..d3ae4e0a00 100644 --- a/interface/resources/styles/log_dialog.qss +++ b/interface/resources/styles/log_dialog.qss @@ -1,6 +1,6 @@ QPlainTextEdit { - font-family: Inconsolata, Lucida Console, Andale Mono, Monaco; + font-family: Inconsolata, Consolas, Courier New, monospace; font-size: 16px; padding-left: 28px; padding-top: 7px; @@ -11,7 +11,7 @@ QPlainTextEdit { } QLineEdit { - font-family: Inconsolata, Lucida Console, Andale Mono, Monaco; + font-family: Inconsolata, Consolas, Courier New, monospace; padding-left: 7px; background-color: #CCCCCC; border-width: 0; diff --git a/interface/src/avatar/CauterizedMeshPartPayload.cpp b/interface/src/avatar/CauterizedMeshPartPayload.cpp index c8ec90dcee..c11f92083b 100644 --- a/interface/src/avatar/CauterizedMeshPartPayload.cpp +++ b/interface/src/avatar/CauterizedMeshPartPayload.cpp @@ -20,55 +20,28 @@ using namespace render; CauterizedMeshPartPayload::CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) : ModelMeshPartPayload(model, meshIndex, partIndex, shapeIndex, transform, offsetTransform) {} -void CauterizedMeshPartPayload::updateTransformForSkinnedCauterizedMesh(const Transform& transform, - const QVector& clusterMatrices, - const QVector& cauterizedClusterMatrices) { - _transform = transform; - _cauterizedTransform = transform; - - if (clusterMatrices.size() > 0) { - _worldBound = AABox(); - for (auto& clusterMatrix : clusterMatrices) { - AABox clusterBound = _localBound; - clusterBound.transform(clusterMatrix); - _worldBound += clusterBound; - } - - _worldBound.transform(transform); - if (clusterMatrices.size() == 1) { - _transform = _transform.worldTransform(Transform(clusterMatrices[0])); - if (cauterizedClusterMatrices.size() != 0) { - _cauterizedTransform = _cauterizedTransform.worldTransform(Transform(cauterizedClusterMatrices[0])); - } else { - _cauterizedTransform = _transform; - } - } - } else { - _worldBound = _localBound; - _worldBound.transform(_drawTransform); - } +void CauterizedMeshPartPayload::updateTransformForCauterizedMesh( + const Transform& renderTransform, + const gpu::BufferPointer& buffer) { + _cauterizedTransform = renderTransform; + _cauterizedClusterBuffer = buffer; } void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { // Still relying on the raw data from the model - const Model::MeshState& state = _model->getMeshState(_meshIndex); SkeletonModel* skeleton = static_cast(_model); bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE) && skeleton->getEnableCauterization(); - if (state.clusterBuffer) { - if (useCauterizedMesh) { - const Model::MeshState& cState = skeleton->getCauterizeMeshState(_meshIndex); - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, cState.clusterBuffer); - } else { - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer); + if (useCauterizedMesh) { + if (_cauterizedClusterBuffer) { + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _cauterizedClusterBuffer); + } + batch.setModelTransform(_cauterizedTransform); + } else { + if (_clusterBuffer) { + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer); } batch.setModelTransform(_transform); - } else { - if (useCauterizedMesh) { - batch.setModelTransform(_cauterizedTransform); - } else { - batch.setModelTransform(_transform); - } } } diff --git a/interface/src/avatar/CauterizedMeshPartPayload.h b/interface/src/avatar/CauterizedMeshPartPayload.h index f4319ead6f..dc88e950c1 100644 --- a/interface/src/avatar/CauterizedMeshPartPayload.h +++ b/interface/src/avatar/CauterizedMeshPartPayload.h @@ -17,12 +17,13 @@ class CauterizedMeshPartPayload : public ModelMeshPartPayload { public: CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); - void updateTransformForSkinnedCauterizedMesh(const Transform& transform, - const QVector& clusterMatrices, - const QVector& cauterizedClusterMatrices); + + void updateTransformForCauterizedMesh(const Transform& renderTransform, const gpu::BufferPointer& buffer); void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override; + private: + gpu::BufferPointer _cauterizedClusterBuffer; Transform _cauterizedTransform; }; diff --git a/interface/src/avatar/CauterizedModel.cpp b/interface/src/avatar/CauterizedModel.cpp index 1ca87a498a..d8db83fbb7 100644 --- a/interface/src/avatar/CauterizedModel.cpp +++ b/interface/src/avatar/CauterizedModel.cpp @@ -26,8 +26,8 @@ CauterizedModel::~CauterizedModel() { } void CauterizedModel::deleteGeometry() { - Model::deleteGeometry(); - _cauterizeMeshStates.clear(); + Model::deleteGeometry(); + _cauterizeMeshStates.clear(); } bool CauterizedModel::updateGeometry() { @@ -41,7 +41,7 @@ bool CauterizedModel::updateGeometry() { _cauterizeMeshStates.append(state); } } - return needsFullUpdate; + return needsFullUpdate; } void CauterizedModel::createVisibleRenderItemSet() { @@ -86,13 +86,13 @@ void CauterizedModel::createVisibleRenderItemSet() { } } } else { - Model::createVisibleRenderItemSet(); + Model::createVisibleRenderItemSet(); } } void CauterizedModel::createCollisionRenderItemSet() { // Temporary HACK: use base class method for now - Model::createCollisionRenderItemSet(); + Model::createCollisionRenderItemSet(); } void CauterizedModel::updateClusterMatrices() { @@ -122,8 +122,8 @@ void CauterizedModel::updateClusterMatrices() { state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4), (const gpu::Byte*) state.clusterMatrices.constData()); } - } - } + } + } // as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty. if (!_cauterizeBoneSet.empty()) { @@ -191,6 +191,9 @@ void CauterizedModel::updateRenderItems() { return; } + // lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box. + self->updateClusterMatrices(); + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); Transform modelTransform; @@ -209,15 +212,22 @@ void CauterizedModel::updateRenderItems() { if (data._model && data._model->isLoaded()) { // Ensure the model geometry was not reset between frames if (deleteGeometryCounter == data._model->getGeometryCounter()) { - // lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box. - data._model->updateClusterMatrices(); - - // update the model transform and bounding box for this render item. + // this stuff identical to what happens in regular Model const Model::MeshState& state = data._model->getMeshState(data._meshIndex); + Transform renderTransform = modelTransform; + if (state.clusterMatrices.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0])); + } + data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer); + + // this stuff for cauterized mesh CauterizedModel* cModel = static_cast(data._model); - assert(data._meshIndex < cModel->_cauterizeMeshStates.size()); - const Model::MeshState& cState = cModel->_cauterizeMeshStates.at(data._meshIndex); - data.updateTransformForSkinnedCauterizedMesh(modelTransform, state.clusterMatrices, cState.clusterMatrices); + const Model::MeshState& cState = cModel->getCauterizeMeshState(data._meshIndex); + renderTransform = modelTransform; + if (cState.clusterMatrices.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(cState.clusterMatrices[0])); + } + data.updateTransformForCauterizedMesh(renderTransform, cState.clusterBuffer); } } }); diff --git a/interface/src/ui/BaseLogDialog.cpp b/interface/src/ui/BaseLogDialog.cpp index 7e0027e0a8..571d3ac403 100644 --- a/interface/src/ui/BaseLogDialog.cpp +++ b/interface/src/ui/BaseLogDialog.cpp @@ -28,17 +28,23 @@ const int SEARCH_BUTTON_LEFT = 25; const int SEARCH_BUTTON_WIDTH = 20; const int SEARCH_TOGGLE_BUTTON_WIDTH = 50; const int SEARCH_TEXT_WIDTH = 240; +const int TIME_STAMP_LENGTH = 16; +const int FONT_WEIGHT = 75; const QColor HIGHLIGHT_COLOR = QColor("#3366CC"); +const QColor BOLD_COLOR = QColor("#445c8c"); +const QString BOLD_PATTERN = "\\[\\d*\\/.*:\\d*:\\d*\\]"; -class KeywordHighlighter : public QSyntaxHighlighter { +class Highlighter : public QSyntaxHighlighter { public: - KeywordHighlighter(QTextDocument* parent = nullptr); + Highlighter(QTextDocument* parent = nullptr); + void setBold(int indexToBold); QString keyword; protected: void highlightBlock(const QString& text) override; private: + QTextCharFormat boldFormat; QTextCharFormat keywordFormat; }; @@ -89,7 +95,7 @@ void BaseLogDialog::initControls() { _leftPad += SEARCH_TOGGLE_BUTTON_WIDTH + BUTTON_MARGIN; _searchPrevButton->show(); connect(_searchPrevButton, SIGNAL(clicked()), SLOT(toggleSearchPrev())); - + _searchNextButton = new QPushButton(this); _searchNextButton->setObjectName("searchNextButton"); _searchNextButton->setGeometry(_leftPad, ELEMENT_MARGIN, SEARCH_TOGGLE_BUTTON_WIDTH, ELEMENT_HEIGHT); @@ -101,9 +107,8 @@ void BaseLogDialog::initControls() { _logTextBox = new QPlainTextEdit(this); _logTextBox->setReadOnly(true); _logTextBox->show(); - _highlighter = new KeywordHighlighter(_logTextBox->document()); + _highlighter = new Highlighter(_logTextBox->document()); connect(_logTextBox, SIGNAL(selectionChanged()), SLOT(updateSelection())); - } void BaseLogDialog::showEvent(QShowEvent* event) { @@ -116,7 +121,9 @@ void BaseLogDialog::resizeEvent(QResizeEvent* event) { void BaseLogDialog::appendLogLine(QString logLine) { if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) { + int indexToBold = _logTextBox->document()->characterCount(); _logTextBox->appendPlainText(logLine.trimmed()); + _highlighter->setBold(indexToBold); } } @@ -128,7 +135,7 @@ void BaseLogDialog::handleSearchTextChanged(QString searchText) { if (searchText.isEmpty()) { return; } - + QTextCursor cursor = _logTextBox->textCursor(); if (cursor.hasSelection()) { QString selectedTerm = cursor.selectedText(); @@ -136,16 +143,16 @@ void BaseLogDialog::handleSearchTextChanged(QString searchText) { return; } } - + cursor.setPosition(0, QTextCursor::MoveAnchor); _logTextBox->setTextCursor(cursor); bool foundTerm = _logTextBox->find(searchText); - + if (!foundTerm) { cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); _logTextBox->setTextCursor(cursor); } - + _searchTerm = searchText; _highlighter->keyword = searchText; _highlighter->rehighlight(); @@ -175,6 +182,7 @@ void BaseLogDialog::showLogData() { _logTextBox->clear(); _logTextBox->appendPlainText(getCurrentLog()); _logTextBox->ensureCursorVisible(); + _highlighter->rehighlight(); } void BaseLogDialog::updateSelection() { @@ -187,16 +195,28 @@ void BaseLogDialog::updateSelection() { } } -KeywordHighlighter::KeywordHighlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) { +Highlighter::Highlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) { + boldFormat.setFontWeight(FONT_WEIGHT); + boldFormat.setForeground(BOLD_COLOR); keywordFormat.setForeground(HIGHLIGHT_COLOR); } -void KeywordHighlighter::highlightBlock(const QString& text) { +void Highlighter::highlightBlock(const QString& text) { + QRegExp expression(BOLD_PATTERN); + + int index = text.indexOf(expression, 0); + + while (index >= 0) { + int length = expression.matchedLength(); + setFormat(index, length, boldFormat); + index = text.indexOf(expression, index + length); + } + if (keyword.isNull() || keyword.isEmpty()) { return; } - int index = text.indexOf(keyword, 0, Qt::CaseInsensitive); + index = text.indexOf(keyword, 0, Qt::CaseInsensitive); int length = keyword.length(); while (index >= 0) { @@ -204,3 +224,7 @@ void KeywordHighlighter::highlightBlock(const QString& text) { index = text.indexOf(keyword, index + length, Qt::CaseInsensitive); } } + +void Highlighter::setBold(int indexToBold) { + setFormat(indexToBold, TIME_STAMP_LENGTH, boldFormat); +} diff --git a/interface/src/ui/BaseLogDialog.h b/interface/src/ui/BaseLogDialog.h index d097010bae..e18d23937f 100644 --- a/interface/src/ui/BaseLogDialog.h +++ b/interface/src/ui/BaseLogDialog.h @@ -23,7 +23,7 @@ const int BUTTON_MARGIN = 8; class QPushButton; class QLineEdit; class QPlainTextEdit; -class KeywordHighlighter; +class Highlighter; class BaseLogDialog : public QDialog { Q_OBJECT @@ -56,7 +56,7 @@ private: QPushButton* _searchPrevButton { nullptr }; QPushButton* _searchNextButton { nullptr }; QString _searchTerm; - KeywordHighlighter* _highlighter { nullptr }; + Highlighter* _highlighter { nullptr }; void initControls(); void showLogData(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 27e00b47c6..c6bd70a0cb 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -940,7 +940,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { if (_tree && !_shuttingDown && _entitiesScriptEngine) { - _entitiesScriptEngine->unloadEntityScript(entityID); + _entitiesScriptEngine->unloadEntityScript(entityID, true); } forceRecheckEntities(); // reset our state to force checking our inside/outsideness of entities diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 5d2755f9b5..6fa005e360 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -13,18 +13,31 @@ #define hifi_NodePermissions_h #include +#include #include #include #include #include - +#include +#include #include "GroupRank.h" class NodePermissions; using NodePermissionsPointer = std::shared_ptr; -using NodePermissionsKey = QPair; // name, rankID +using NodePermissionsKey = std::pair; // name, rankID using NodePermissionsKeyList = QList>; +namespace std { + template<> + struct hash { + size_t operator()(const NodePermissionsKey& key) const { + size_t result = qHash(key.first); + result <<= 32; + result |= qHash(key.second); + return result; + } + }; +} class NodePermissions { public: @@ -100,27 +113,40 @@ public: NodePermissionsMap() { } NodePermissionsPointer& operator[](const NodePermissionsKey& key) { NodePermissionsKey dataKey(key.first.toLower(), key.second); - if (!_data.contains(dataKey)) { + if (0 == _data.count(dataKey)) { _data[dataKey] = NodePermissionsPointer(new NodePermissions(key)); } return _data[dataKey]; } NodePermissionsPointer operator[](const NodePermissionsKey& key) const { - return _data.value(NodePermissionsKey(key.first.toLower(), key.second)); + NodePermissionsPointer result; + auto itr = _data.find(NodePermissionsKey(key.first.toLower(), key.second)); + if (_data.end() != itr) { + result = itr->second; + } + return result; } bool contains(const NodePermissionsKey& key) const { - return _data.contains(NodePermissionsKey(key.first.toLower(), key.second)); + return 0 != _data.count(NodePermissionsKey(key.first.toLower(), key.second)); } - bool contains(const QString& keyFirst, QUuid keySecond) const { - return _data.contains(NodePermissionsKey(keyFirst.toLower(), keySecond)); + bool contains(const QString& keyFirst, const QUuid& keySecond) const { + return 0 != _data.count(NodePermissionsKey(keyFirst.toLower(), keySecond)); } - QList keys() const { return _data.keys(); } - QHash get() { return _data; } + + QList keys() const { + QList result; + for (const auto& entry : _data) { + result.push_back(entry.first); + } + return result; + } + + const std::unordered_map& get() { return _data; } void clear() { _data.clear(); } - void remove(const NodePermissionsKey& key) { _data.remove(key); } + void remove(const NodePermissionsKey& key) { _data.erase(key); } private: - QHash _data; + std::unordered_map _data; }; diff --git a/libraries/networking/src/udt/PacketQueue.cpp b/libraries/networking/src/udt/PacketQueue.cpp index bb20982ca4..9560f2f187 100644 --- a/libraries/networking/src/udt/PacketQueue.cpp +++ b/libraries/networking/src/udt/PacketQueue.cpp @@ -15,6 +15,10 @@ using namespace udt; +PacketQueue::PacketQueue() { + _channels.emplace_back(new std::list()); +} + MessageNumber PacketQueue::getNextMessageNumber() { static const MessageNumber MAX_MESSAGE_NUMBER = MessageNumber(1) << MESSAGE_NUMBER_SIZE; _currentMessageNumber = (_currentMessageNumber + 1) % MAX_MESSAGE_NUMBER; @@ -24,7 +28,7 @@ MessageNumber PacketQueue::getNextMessageNumber() { bool PacketQueue::isEmpty() const { LockGuard locker(_packetsLock); // Only the main channel and it is empty - return (_channels.size() == 1) && _channels.front().empty(); + return (_channels.size() == 1) && _channels.front()->empty(); } PacketQueue::PacketPointer PacketQueue::takePacket() { @@ -34,19 +38,19 @@ PacketQueue::PacketPointer PacketQueue::takePacket() { } // Find next non empty channel - if (_channels[nextIndex()].empty()) { + if (_channels[nextIndex()]->empty()) { nextIndex(); } auto& channel = _channels[_currentIndex]; - Q_ASSERT(!channel.empty()); + Q_ASSERT(!channel->empty()); // Take front packet - auto packet = std::move(channel.front()); - channel.pop_front(); + auto packet = std::move(channel->front()); + channel->pop_front(); // Remove now empty channel (Don't remove the main channel) - if (channel.empty() && _currentIndex != 0) { - channel.swap(_channels.back()); + if (channel->empty() && _currentIndex != 0) { + channel->swap(*_channels.back()); _channels.pop_back(); --_currentIndex; } @@ -61,7 +65,7 @@ unsigned int PacketQueue::nextIndex() { void PacketQueue::queuePacket(PacketPointer packet) { LockGuard locker(_packetsLock); - _channels.front().push_back(std::move(packet)); + _channels.front()->push_back(std::move(packet)); } void PacketQueue::queuePacketList(PacketListPointer packetList) { @@ -70,5 +74,6 @@ void PacketQueue::queuePacketList(PacketListPointer packetList) { } LockGuard locker(_packetsLock); - _channels.push_back(std::move(packetList->_packets)); + _channels.emplace_back(new std::list()); + _channels.back()->swap(packetList->_packets); } diff --git a/libraries/networking/src/udt/PacketQueue.h b/libraries/networking/src/udt/PacketQueue.h index 69784fd8db..2b3d3a4b5b 100644 --- a/libraries/networking/src/udt/PacketQueue.h +++ b/libraries/networking/src/udt/PacketQueue.h @@ -30,10 +30,11 @@ class PacketQueue { using LockGuard = std::lock_guard; using PacketPointer = std::unique_ptr; using PacketListPointer = std::unique_ptr; - using Channel = std::list; + using Channel = std::unique_ptr>; using Channels = std::vector; public: + PacketQueue(); void queuePacket(PacketPointer packet); void queuePacketList(PacketListPointer packetList); @@ -49,7 +50,7 @@ private: MessageNumber _currentMessageNumber { 0 }; mutable Mutex _packetsLock; // Protects the packets to be sent. - Channels _channels = Channels(1); // One channel per packet list + Main channel + Channels _channels; // One channel per packet list + Main channel unsigned int _currentIndex { 0 }; }; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 5b3d285b47..41a1bb4c74 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -372,19 +372,12 @@ void ModelMeshPartPayload::notifyLocationChanged() { } -void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const QVector& clusterMatrices) { - _transform = transform; - - if (clusterMatrices.size() > 0) { - _worldBound = _adjustedLocalBound; - _worldBound.transform(_transform); - if (clusterMatrices.size() == 1) { - _transform = _transform.worldTransform(Transform(clusterMatrices[0])); - } - } else { - _worldBound = _localBound; - _worldBound.transform(_transform); - } +void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform, + const gpu::BufferPointer& buffer) { + _transform = renderTransform; + _worldBound = _adjustedLocalBound; + _worldBound.transform(boundTransform); + _clusterBuffer = buffer; } ItemKey ModelMeshPartPayload::getKey() const { @@ -532,9 +525,8 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const { void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { // Still relying on the raw data from the model - const Model::MeshState& state = _model->getMeshState(_meshIndex); - if (state.clusterBuffer) { - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer); + if (_clusterBuffer) { + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer); } batch.setModelTransform(_transform); } @@ -590,8 +582,6 @@ void ModelMeshPartPayload::render(RenderArgs* args) const { auto locations = args->_pipeline->locations; assert(locations); - // Bind the model transform and the skinCLusterMatrices if needed - _model->updateClusterMatrices(); bindTransform(batch, locations, args->_renderMode); //Bind the index buffer and vertex buffer and Blend shapes if needed diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index c585c95025..ef74011c40 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -89,8 +89,9 @@ public: typedef Payload::DataPointer Pointer; void notifyLocationChanged() override; - void updateTransformForSkinnedMesh(const Transform& transform, - const QVector& clusterMatrices); + void updateTransformForSkinnedMesh(const Transform& renderTransform, + const Transform& boundTransform, + const gpu::BufferPointer& buffer); float computeFadeAlpha() const; @@ -108,6 +109,7 @@ public: void computeAdjustedLocalBound(const QVector& clusterMatrices); + gpu::BufferPointer _clusterBuffer; Model* _model; int _meshIndex; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 48c1d29b68..c584b0bc21 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -227,6 +227,10 @@ void Model::updateRenderItems() { return; } + // lazy update of cluster matrices used for rendering. + // We need to update them here so we can correctly update the bounding box. + self->updateClusterMatrices(); + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); uint32_t deleteGeometryCounter = self->_deleteGeometryCounter; @@ -240,12 +244,12 @@ void Model::updateRenderItems() { Transform modelTransform = data._model->getTransform(); modelTransform.setScale(glm::vec3(1.0f)); - // lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box. - data._model->updateClusterMatrices(); - - // update the model transform and bounding box for this render item. - const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex); - data.updateTransformForSkinnedMesh(modelTransform, state.clusterMatrices); + const Model::MeshState& state = data._model->getMeshState(data._meshIndex); + Transform renderTransform = modelTransform; + if (state.clusterMatrices.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0])); + } + data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer); } } }); @@ -1048,7 +1052,7 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { } void Model::computeMeshPartLocalBounds() { - for (auto& part : _modelMeshRenderItemsSet) { + for (auto& part : _modelMeshRenderItemsSet) { assert(part->_meshIndex < _modelMeshRenderItemsSet.size()); const Model::MeshState& state = _meshStates.at(part->_meshIndex); part->computeAdjustedLocalBound(state.clusterMatrices); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index e1a71b4a16..0b5f09a4df 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -543,16 +543,6 @@ void ScriptEngine::init() { auto entityScriptingInterface = DependencyManager::get(); entityScriptingInterface->init(); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) { - if (_entityScripts.contains(entityID)) { - if (isEntityScriptRunning(entityID)) { - qCWarning(scriptengine) << "deletingEntity while entity script is still running!" << entityID; - } - _entityScripts.remove(entityID); - emit entityScriptDetailsUpdated(); - } - }); - // register various meta-types registerMetaTypes(this); @@ -1850,7 +1840,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co processDeferredEntityLoads(entityScript, entityID); } -void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { +void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::unloadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " @@ -1858,7 +1848,8 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { #endif QMetaObject::invokeMethod(this, "unloadEntityScript", - Q_ARG(const EntityItemID&, entityID)); + Q_ARG(const EntityItemID&, entityID), + Q_ARG(bool, shouldRemoveFromMap)); return; } #ifdef THREAD_DEBUGGING @@ -1873,7 +1864,12 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { } else { qCDebug(scriptengine) << "unload called while !running" << entityID << oldDetails.status; } - if (oldDetails.status != EntityScriptStatus::UNLOADED) { + + if (shouldRemoveFromMap) { + // this was a deleted entity, we've been asked to remove it from the map + _entityScripts.remove(entityID); + emit entityScriptDetailsUpdated(); + } else if (oldDetails.status != EntityScriptStatus::UNLOADED) { EntityScriptDetails newDetails; newDetails.status = EntityScriptStatus::UNLOADED; newDetails.lastModified = QDateTime::currentMSecsSinceEpoch(); @@ -1881,6 +1877,7 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { newDetails.scriptText = oldDetails.scriptText; setEntityScriptDetails(entityID, newDetails); } + stopAllTimersForEntityScript(entityID); { // FIXME: shouldn't have to do this here, but currently something seems to be firing unloads moments after firing initial load requests diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index b988ccfe90..681499779c 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -171,7 +171,7 @@ public: return _entityScripts.contains(entityID) && _entityScripts[entityID].status == EntityScriptStatus::RUNNING; } Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload); - Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID); // will call unload method + Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method Q_INVOKABLE void unloadAllEntityScripts(); Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params = QStringList()) override; diff --git a/scripts/tutorials/entity_scripts/sit.js b/scripts/tutorials/entity_scripts/sit.js index 2ba19231e0..ffce196572 100644 --- a/scripts/tutorials/entity_scripts/sit.js +++ b/scripts/tutorials/entity_scripts/sit.js @@ -2,7 +2,6 @@ Script.include("/~/system/libraries/utils.js"); var SETTING_KEY = "com.highfidelity.avatar.isSitting"; - var ROLE = "fly"; var ANIMATION_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/animations/sitting_idle.fbx"; var ANIMATION_FPS = 30; var ANIMATION_FIRST_FRAME = 1; @@ -10,23 +9,25 @@ var RELEASE_KEYS = ['w', 'a', 's', 'd', 'UP', 'LEFT', 'DOWN', 'RIGHT']; var RELEASE_TIME = 500; // ms var RELEASE_DISTANCE = 0.2; // meters - var MAX_IK_ERROR = 20; - var DESKTOP_UI_CHECK_INTERVAL = 250; + var MAX_IK_ERROR = 30; + var DESKTOP_UI_CHECK_INTERVAL = 100; var DESKTOP_MAX_DISTANCE = 5; var SIT_DELAY = 25 + var MAX_RESET_DISTANCE = 0.5 this.entityID = null; this.timers = {}; this.animStateHandlerID = null; + this.interval = null; this.preload = function(entityID) { this.entityID = entityID; } this.unload = function() { - if (MyAvatar.sessionUUID === this.getSeatUser()) { - this.sitUp(this.entityID); + if (Settings.getValue(SETTING_KEY) === this.entityID) { + this.sitUp(); } - if (this.interval) { + if (this.interval !== null) { Script.clearInterval(this.interval); this.interval = null; } @@ -34,42 +35,60 @@ } this.setSeatUser = function(user) { - var userData = Entities.getEntityProperties(this.entityID, ["userData"]).userData; - userData = JSON.parse(userData); + try { + var userData = Entities.getEntityProperties(this.entityID, ["userData"]).userData; + userData = JSON.parse(userData); - if (user) { - userData.seat.user = user; - } else { - delete userData.seat.user; + if (user !== null) { + userData.seat.user = user; + } else { + delete userData.seat.user; + } + + Entities.editEntity(this.entityID, { + userData: JSON.stringify(userData) + }); + } catch (e) { + // Do Nothing } - - Entities.editEntity(this.entityID, { - userData: JSON.stringify(userData) - }); } this.getSeatUser = function() { - var properties = Entities.getEntityProperties(this.entityID, ["userData", "position"]); - var userData = JSON.parse(properties.userData); + try { + var properties = Entities.getEntityProperties(this.entityID, ["userData", "position"]); + var userData = JSON.parse(properties.userData); - if (userData.seat.user && userData.seat.user !== MyAvatar.sessionUUID) { - var avatar = AvatarList.getAvatar(userData.seat.user); - if (avatar && Vec3.distance(avatar.position, properties.position) > RELEASE_DISTANCE) { - return null; + // If MyAvatar return my uuid + if (userData.seat.user === MyAvatar.sessionUUID) { + return userData.seat.user; } + + + // If Avatar appears to be sitting + if (userData.seat.user) { + var avatar = AvatarList.getAvatar(userData.seat.user); + if (avatar && (Vec3.distance(avatar.position, properties.position) < RELEASE_DISTANCE)) { + return userData.seat.user; + } + } + } catch (e) { + // Do nothing } - return userData.seat.user; + + // Nobody on the seat + return null; } + // Is the seat used this.checkSeatForAvatar = function() { var seatUser = this.getSeatUser(); - var avatarIdentifiers = AvatarList.getAvatarIdentifiers(); - for (var i in avatarIdentifiers) { - var avatar = AvatarList.getAvatar(avatarIdentifiers[i]); - if (avatar && avatar.sessionUUID === seatUser) { - return true; - } + + // If MyAvatar appears to be sitting + if (seatUser === MyAvatar.sessionUUID) { + var properties = Entities.getEntityProperties(this.entityID, ["position"]); + return Vec3.distance(MyAvatar.position, properties.position) < RELEASE_DISTANCE; } - return false; + + return seatUser !== null; } this.sitDown = function() { @@ -78,40 +97,50 @@ return; } - this.setSeatUser(MyAvatar.sessionUUID); - var previousValue = Settings.getValue(SETTING_KEY); Settings.setValue(SETTING_KEY, this.entityID); + this.setSeatUser(MyAvatar.sessionUUID); if (previousValue === "") { MyAvatar.characterControllerEnabled = false; MyAvatar.hmdLeanRecenterEnabled = false; - MyAvatar.overrideRoleAnimation(ROLE, ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME); + var ROLES = MyAvatar.getAnimationRoles(); + for (i in ROLES) { + MyAvatar.overrideRoleAnimation(ROLES[i], ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME); + } MyAvatar.resetSensorsAndBody(); } - var that = this; - Script.setTimeout(function() { - var properties = Entities.getEntityProperties(that.entityID, ["position", "rotation"]); - var index = MyAvatar.getJointIndex("Hips"); - MyAvatar.pinJoint(index, properties.position, properties.rotation); + var properties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]); + var index = MyAvatar.getJointIndex("Hips"); + MyAvatar.pinJoint(index, properties.position, properties.rotation); - that.animStateHandlerID = MyAvatar.addAnimationStateHandler(function(properties) { - return { headType: 0 }; - }, ["headType"]); - Script.update.connect(that, that.update); - Controller.keyPressEvent.connect(that, that.keyPressed); - Controller.keyReleaseEvent.connect(that, that.keyReleased); - for (var i in RELEASE_KEYS) { - Controller.captureKeyEvents({ text: RELEASE_KEYS[i] }); - } - }, SIT_DELAY); + this.animStateHandlerID = MyAvatar.addAnimationStateHandler(function(properties) { + return { headType: 0 }; + }, ["headType"]); + Script.update.connect(this, this.update); + Controller.keyPressEvent.connect(this, this.keyPressed); + Controller.keyReleaseEvent.connect(this, this.keyReleased); + for (var i in RELEASE_KEYS) { + Controller.captureKeyEvents({ text: RELEASE_KEYS[i] }); + } } this.sitUp = function() { - this.setSeatUser(null); + MyAvatar.removeAnimationStateHandler(this.animStateHandlerID); + Script.update.disconnect(this, this.update); + Controller.keyPressEvent.disconnect(this, this.keyPressed); + Controller.keyReleaseEvent.disconnect(this, this.keyReleased); + for (var i in RELEASE_KEYS) { + Controller.releaseKeyEvents({ text: RELEASE_KEYS[i] }); + } + this.setSeatUser(null); if (Settings.getValue(SETTING_KEY) === this.entityID) { - MyAvatar.restoreRoleAnimation(ROLE); + Settings.setValue(SETTING_KEY, ""); + var ROLES = MyAvatar.getAnimationRoles(); + for (i in ROLES) { + MyAvatar.restoreRoleAnimation(ROLES[i]); + } MyAvatar.characterControllerEnabled = true; MyAvatar.hmdLeanRecenterEnabled = true; @@ -124,16 +153,6 @@ MyAvatar.bodyPitch = 0.0; MyAvatar.bodyRoll = 0.0; }, SIT_DELAY); - - Settings.setValue(SETTING_KEY, ""); - } - - MyAvatar.removeAnimationStateHandler(this.animStateHandlerID); - Script.update.disconnect(this, this.update); - Controller.keyPressEvent.disconnect(this, this.keyPressed); - Controller.keyReleaseEvent.disconnect(this, this.keyReleased); - for (var i in RELEASE_KEYS) { - Controller.releaseKeyEvents({ text: RELEASE_KEYS[i] }); } } @@ -183,15 +202,22 @@ } } - this.update = function(dt) { if (MyAvatar.sessionUUID === this.getSeatUser()) { - var properties = Entities.getEntityProperties(this.entityID, ["position"]); + var properties = Entities.getEntityProperties(this.entityID); var avatarDistance = Vec3.distance(MyAvatar.position, properties.position); var ikError = MyAvatar.getIKErrorOnLastSolve(); if (avatarDistance > RELEASE_DISTANCE || ikError > MAX_IK_ERROR) { print("IK error: " + ikError + ", distance from chair: " + avatarDistance); - this.sitUp(this.entityID); + + // Move avatar in front of the chair to avoid getting stuck in collision hulls + if (avatarDistance < MAX_RESET_DISTANCE) { + var offset = { x: 0, y: 1.0, z: -0.5 - properties.dimensions.z * properties.registrationPoint.z }; + var position = Vec3.sum(properties.position, Vec3.multiplyQbyV(properties.rotation, offset)); + MyAvatar.position = position; + } + + this.sitUp(); } } } @@ -203,6 +229,18 @@ if (RELEASE_KEYS.indexOf(event.text) !== -1) { var that = this; this.timers[event.text] = Script.setTimeout(function() { + delete that.timers[event.text]; + + var properties = Entities.getEntityProperties(that.entityID); + var avatarDistance = Vec3.distance(MyAvatar.position, properties.position); + + // Move avatar in front of the chair to avoid getting stuck in collision hulls + if (avatarDistance < MAX_RESET_DISTANCE) { + var offset = { x: 0, y: 1.0, z: -0.5 - properties.dimensions.z * properties.registrationPoint.z }; + var position = Vec3.sum(properties.position, Vec3.multiplyQbyV(properties.rotation, offset)); + MyAvatar.position = position; + } + that.sitUp(); }, RELEASE_TIME); } @@ -223,7 +261,7 @@ } this.hoverEnterEntity = function(event) { - if (isInEditMode() || (MyAvatar.sessionUUID === this.getSeatUser())) { + if (isInEditMode() || this.interval !== null) { return; } @@ -239,18 +277,18 @@ }, DESKTOP_UI_CHECK_INTERVAL); } this.hoverLeaveEntity = function(event) { - if (this.interval) { + if (this.interval !== null) { Script.clearInterval(this.interval); this.interval = null; } this.cleanupOverlay(); } - this.clickDownOnEntity = function () { - if (isInEditMode() || (MyAvatar.sessionUUID === this.getSeatUser())) { + this.clickDownOnEntity = function (id, event) { + if (isInEditMode()) { return; } - if (this.canSitDesktop()) { + if (event.isPrimaryButton && this.canSitDesktop()) { this.sitDown(); } } diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index a85a112bf5..8dc993e6fe 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -17,3 +17,5 @@ set_target_properties(ac-client PROPERTIES FOLDER "Tools") add_subdirectory(skeleton-dump) set_target_properties(skeleton-dump PROPERTIES FOLDER "Tools") +add_subdirectory(atp-get) +set_target_properties(atp-get PROPERTIES FOLDER "Tools") diff --git a/tools/atp-get/CMakeLists.txt b/tools/atp-get/CMakeLists.txt new file mode 100644 index 0000000000..b1646dc023 --- /dev/null +++ b/tools/atp-get/CMakeLists.txt @@ -0,0 +1,3 @@ +set(TARGET_NAME atp-get) +setup_hifi_project(Core Widgets) +link_hifi_libraries(shared networking) diff --git a/tools/atp-get/src/ATPGetApp.cpp b/tools/atp-get/src/ATPGetApp.cpp new file mode 100644 index 0000000000..30054fffea --- /dev/null +++ b/tools/atp-get/src/ATPGetApp.cpp @@ -0,0 +1,269 @@ +// +// ATPGetApp.cpp +// tools/atp-get/src +// +// Created by Seth Alves on 2017-3-15 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ATPGetApp.h" + +ATPGetApp::ATPGetApp(int argc, char* argv[]) : + QCoreApplication(argc, argv) +{ + // parse command-line + QCommandLineParser parser; + parser.setApplicationDescription("High Fidelity ATP-Get"); + + const QCommandLineOption helpOption = parser.addHelpOption(); + + const QCommandLineOption verboseOutput("v", "verbose output"); + parser.addOption(verboseOutput); + + const QCommandLineOption domainAddressOption("d", "domain-server address", "127.0.0.1"); + parser.addOption(domainAddressOption); + + const QCommandLineOption cacheSTUNOption("s", "cache stun-server response"); + parser.addOption(cacheSTUNOption); + + const QCommandLineOption listenPortOption("listenPort", "listen port", QString::number(INVALID_PORT)); + parser.addOption(listenPortOption); + + + if (!parser.parse(QCoreApplication::arguments())) { + qCritical() << parser.errorText() << endl; + parser.showHelp(); + Q_UNREACHABLE(); + } + + if (parser.isSet(helpOption)) { + parser.showHelp(); + Q_UNREACHABLE(); + } + + _verbose = parser.isSet(verboseOutput); + if (!_verbose) { + QLoggingCategory::setFilterRules("qt.network.ssl.warning=false"); + + const_cast(&networking())->setEnabled(QtDebugMsg, false); + const_cast(&networking())->setEnabled(QtInfoMsg, false); + const_cast(&networking())->setEnabled(QtWarningMsg, false); + + const_cast(&shared())->setEnabled(QtDebugMsg, false); + const_cast(&shared())->setEnabled(QtInfoMsg, false); + const_cast(&shared())->setEnabled(QtWarningMsg, false); + } + + + QStringList filenames = parser.positionalArguments(); + if (filenames.empty() || filenames.size() > 2) { + qDebug() << "give remote url and optional local filename as arguments"; + parser.showHelp(); + Q_UNREACHABLE(); + } + + _url = QUrl(filenames[0]); + if (_url.scheme() != "atp") { + qDebug() << "url should start with atp:"; + parser.showHelp(); + Q_UNREACHABLE(); + } + + if (filenames.size() == 2) { + _localOutputFile = filenames[1]; + } + + QString domainServerAddress = "127.0.0.1:40103"; + if (parser.isSet(domainAddressOption)) { + domainServerAddress = parser.value(domainAddressOption); + } + + if (_verbose) { + qDebug() << "domain-server address is" << domainServerAddress; + } + + int listenPort = INVALID_PORT; + if (parser.isSet(listenPortOption)) { + listenPort = parser.value(listenPortOption).toInt(); + } + + Setting::init(); + DependencyManager::registerInheritance(); + + DependencyManager::set([&]{ return QString("Mozilla/5.0 (HighFidelityATPGet)"); }); + DependencyManager::set(); + DependencyManager::set(NodeType::Agent, listenPort); + + + auto nodeList = DependencyManager::get(); + + // start the nodeThread so its event loop is running + QThread* nodeThread = new QThread(this); + nodeThread->setObjectName("NodeList Thread"); + nodeThread->start(); + + // make sure the node thread is given highest priority + nodeThread->setPriority(QThread::TimeCriticalPriority); + + // setup a timer for domain-server check ins + QTimer* domainCheckInTimer = new QTimer(nodeList.data()); + connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); + domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); + + // put the NodeList and datagram processing on the node thread + nodeList->moveToThread(nodeThread); + + const DomainHandler& domainHandler = nodeList->getDomainHandler(); + + connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); + // connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); + // connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); + connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ATPGetApp::domainConnectionRefused); + + connect(nodeList.data(), &NodeList::nodeAdded, this, &ATPGetApp::nodeAdded); + connect(nodeList.data(), &NodeList::nodeKilled, this, &ATPGetApp::nodeKilled); + connect(nodeList.data(), &NodeList::nodeActivated, this, &ATPGetApp::nodeActivated); + // connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID); + // connect(nodeList.data(), &NodeList::uuidChanged, this, &ATPGetApp::setSessionUUID); + connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &ATPGetApp::notifyPacketVersionMismatch); + + nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer + << NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer); + + DependencyManager::get()->handleLookupString(domainServerAddress, false); + + auto assetClient = DependencyManager::set(); + assetClient->init(); + + QTimer* doTimer = new QTimer(this); + doTimer->setSingleShot(true); + connect(doTimer, &QTimer::timeout, this, &ATPGetApp::timedOut); + doTimer->start(4000); +} + +ATPGetApp::~ATPGetApp() { +} + + +void ATPGetApp::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { + qDebug() << "domainConnectionRefused"; +} + +void ATPGetApp::domainChanged(const QString& domainHostname) { + if (_verbose) { + qDebug() << "domainChanged"; + } +} + +void ATPGetApp::nodeAdded(SharedNodePointer node) { + if (_verbose) { + qDebug() << "node added: " << node->getType(); + } +} + +void ATPGetApp::nodeActivated(SharedNodePointer node) { + if (node->getType() == NodeType::AssetServer) { + lookup(); + } +} + +void ATPGetApp::nodeKilled(SharedNodePointer node) { + qDebug() << "nodeKilled"; +} + +void ATPGetApp::timedOut() { + finish(1); +} + +void ATPGetApp::notifyPacketVersionMismatch() { + if (_verbose) { + qDebug() << "packet version mismatch"; + } + finish(1); +} + +void ATPGetApp::lookup() { + + auto path = _url.path(); + qDebug() << "path is " << path; + + auto request = DependencyManager::get()->createGetMappingRequest(path); + QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable { + auto result = request->getError(); + if (result == GetMappingRequest::NotFound) { + qDebug() << "not found"; + } else if (result == GetMappingRequest::NoError) { + qDebug() << "found, hash is " << request->getHash(); + download(request->getHash()); + } else { + qDebug() << "error -- " << request->getError() << " -- " << request->getErrorString(); + } + request->deleteLater(); + }); + request->start(); +} + +void ATPGetApp::download(AssetHash hash) { + auto assetClient = DependencyManager::get(); + auto assetRequest = new AssetRequest(hash); + + connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) mutable { + Q_ASSERT(request->getState() == AssetRequest::Finished); + + if (request->getError() == AssetRequest::Error::NoError) { + QString data = QString::fromUtf8(request->getData()); + if (_localOutputFile == "") { + QTextStream cout(stdout); + cout << data; + } else { + QFile outputHandle(_localOutputFile); + if (outputHandle.open(QIODevice::ReadWrite)) { + QTextStream stream( &outputHandle ); + stream << data; + } else { + qDebug() << "couldn't open output file:" << _localOutputFile; + } + } + } + + request->deleteLater(); + finish(0); + }); + + assetRequest->start(); +} + +void ATPGetApp::finish(int exitCode) { + auto nodeList = DependencyManager::get(); + + // send the domain a disconnect packet, force stoppage of domain-server check-ins + nodeList->getDomainHandler().disconnect(); + nodeList->setIsShuttingDown(true); + + // tell the packet receiver we're shutting down, so it can drop packets + nodeList->getPacketReceiver().setShouldDropPackets(true); + + QThread* nodeThread = DependencyManager::get()->thread(); + // remove the NodeList from the DependencyManager + DependencyManager::destroy(); + // ask the node thread to quit and wait until it is done + nodeThread->quit(); + nodeThread->wait(); + + QCoreApplication::exit(exitCode); +} diff --git a/tools/atp-get/src/ATPGetApp.h b/tools/atp-get/src/ATPGetApp.h new file mode 100644 index 0000000000..5507d2aa62 --- /dev/null +++ b/tools/atp-get/src/ATPGetApp.h @@ -0,0 +1,52 @@ +// +// ATPGetApp.h +// tools/atp-get/src +// +// Created by Seth Alves on 2017-3-15 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#ifndef hifi_ATPGetApp_h +#define hifi_ATPGetApp_h + +#include +#include +#include +#include +#include +#include +#include +#include + + +class ATPGetApp : public QCoreApplication { + Q_OBJECT +public: + ATPGetApp(int argc, char* argv[]); + ~ATPGetApp(); + +private slots: + void domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo); + void domainChanged(const QString& domainHostname); + void nodeAdded(SharedNodePointer node); + void nodeActivated(SharedNodePointer node); + void nodeKilled(SharedNodePointer node); + void notifyPacketVersionMismatch(); + +private: + NodeList* _nodeList; + void timedOut(); + void lookup(); + void download(AssetHash hash); + void finish(int exitCode); + bool _verbose; + + QUrl _url; + QString _localOutputFile; +}; + +#endif // hifi_ATPGetApp_h diff --git a/tools/atp-get/src/main.cpp b/tools/atp-get/src/main.cpp new file mode 100644 index 0000000000..bddf30c666 --- /dev/null +++ b/tools/atp-get/src/main.cpp @@ -0,0 +1,31 @@ +// +// main.cpp +// tools/atp-get/src +// +// Created by Seth Alves on 2017-3-15 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +#include +#include +#include +#include + +#include + +#include "ATPGetApp.h" + +using namespace std; + +int main(int argc, char * argv[]) { + QCoreApplication::setApplicationName(BuildInfo::AC_CLIENT_SERVER_NAME); + QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); + QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); + QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + + ATPGetApp app(argc, argv); + + return app.exec(); +}