diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 0ba83864c4..bea677aeb6 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -138,7 +138,6 @@ void Agent::handleJurisdictionPacket(QSharedPointer message, Sh void Agent::handleAudioPacket(QSharedPointer message) { _receivedAudioStream.parseData(*message); - _lastReceivedAudioLoudness = _receivedAudioStream.getNextOutputFrameLoudness(); _receivedAudioStream.clearBuffer(); } @@ -323,12 +322,14 @@ void Agent::scriptRequestFinished() { request->deleteLater(); } + void Agent::executeScript() { _scriptEngine = std::unique_ptr(new ScriptEngine(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload)); _scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do // setup an Avatar for the script to use auto scriptedAvatar = DependencyManager::get(); + connect(_scriptEngine.get(), SIGNAL(update(float)), scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection); scriptedAvatar->setForceFaceTrackerConnected(true); @@ -338,11 +339,33 @@ void Agent::executeScript() { // give this AvatarData object to the script engine _scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data()); + auto player = DependencyManager::get(); + connect(player.data(), &recording::Deck::playbackStateChanged, [=] { + if (player->isPlaying()) { + auto recordingInterface = DependencyManager::get(); + if (recordingInterface->getPlayFromCurrentLocation()) { + scriptedAvatar->setRecordingBasis(); + } + } else { + scriptedAvatar->clearRecordingBasis(); + } + }); using namespace recording; static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME); - // FIXME how to deal with driving multiple avatars locally? Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [this, scriptedAvatar](Frame::ConstPointer frame) { + + auto recordingInterface = DependencyManager::get(); + bool useFrameSkeleton = recordingInterface->getPlayerUseSkeletonModel(); + + // FIXME - the ability to switch the avatar URL is not actually supported when playing back from a recording + if (!useFrameSkeleton) { + static std::once_flag warning; + std::call_once(warning, [] { + qWarning() << "Recording.setPlayerUseSkeletonModel(false) is not currently supported."; + }); + } + AvatarData::fromFrame(frame->data, *scriptedAvatar); }); @@ -352,8 +375,11 @@ void Agent::executeScript() { const QByteArray& audio = frame->data; static quint16 audioSequenceNumber{ 0 }; Transform audioTransform; + + auto headOrientation = scriptedAvatar->getHeadOrientation(); audioTransform.setTranslation(scriptedAvatar->getPosition()); - audioTransform.setRotation(scriptedAvatar->getOrientation()); + audioTransform.setRotation(headOrientation); + QByteArray encodedBuffer; if (_encoder) { _encoder->encode(audio, encodedBuffer); @@ -537,7 +563,10 @@ void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) { } void Agent::processAgentAvatarAudio() { - if (_isAvatar && (_isListeningToAudioStream || _avatarSound)) { + auto recordingInterface = DependencyManager::get(); + bool isPlayingRecording = recordingInterface->isPlaying(); + + if (_isAvatar && ((_isListeningToAudioStream && !isPlayingRecording) || _avatarSound)) { // if we have an avatar audio stream then send it out to our audio-mixer auto scriptedAvatar = DependencyManager::get(); bool silentFrame = true; diff --git a/assignment-client/src/AssignmentClient.h b/assignment-client/src/AssignmentClient.h index 5147bfca98..28464bc222 100644 --- a/assignment-client/src/AssignmentClient.h +++ b/assignment-client/src/AssignmentClient.h @@ -27,6 +27,7 @@ public: QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort, quint16 assignmentMonitorPort); ~AssignmentClient(); + private slots: void sendAssignmentRequest(); void assignmentCompleted(); diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 7f4593910e..930b3946c6 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -11,9 +11,13 @@ #include "EntityScriptServer.h" +#include + #include #include +#include #include +#include #include #include #include @@ -24,12 +28,29 @@ #include #include -#include "ClientServerUtils.h" +#include "EntityScriptServerLogging.h" #include "../entities/AssignmentParentFinder.h" +using Mutex = std::mutex; +using Lock = std::lock_guard; + +static std::mutex logBufferMutex; +static std::string logBuffer; + +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { + auto logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message); + + if (!logMessage.isEmpty()) { + Lock lock(logBufferMutex); + logBuffer.append(logMessage.toStdString() + '\n'); + } +} + int EntityScriptServer::_entitiesScriptEngineCount = 0; EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssignment(message) { + qInstallMessageHandler(messageHandler); + DependencyManager::get()->setPacketSender(&_entityEditSender); ResourceManager::init(); @@ -57,6 +78,17 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket"); packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket"); + packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket"); + + static const int LOG_INTERVAL = MSECS_PER_SECOND / 10; + auto timer = new QTimer(this); + timer->setInterval(LOG_INTERVAL); + connect(timer, &QTimer::timeout, this, &EntityScriptServer::pushLogs); + timer->start(); +} + +EntityScriptServer::~EntityScriptServer() { + qInstallMessageHandler(LogHandler::verboseMessageHandler); } static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server"; @@ -68,7 +100,7 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointerread(NUM_BYTES_RFC4122_UUID)); if (_entityViewer.getTree() && !_shuttingDown) { - qDebug() << "Reloading: " << entityID; + qCDebug(entity_script_server) << "Reloading: " << entityID; _entitiesScriptEngine->unloadEntityScript(entityID); checkAndCallPreload(entityID, true); } @@ -100,6 +132,99 @@ void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer(); + + auto& domainHandler = nodeList->getDomainHandler(); + const QJsonObject& settingsObject = domainHandler.getSettingsObject(); + + static const QString ENTITY_SCRIPT_SERVER_SETTINGS_KEY = "entity_script_server"; + + if (!settingsObject.contains(ENTITY_SCRIPT_SERVER_SETTINGS_KEY)) { + qWarning() << "Received settings from the domain-server with no entity_script_server section."; + return; + } + + auto entityScriptServerSettings = settingsObject[ENTITY_SCRIPT_SERVER_SETTINGS_KEY].toObject(); + + static const QString MAX_ENTITY_PPS_OPTION = "max_total_entity_pps"; + static const QString ENTITY_PPS_PER_SCRIPT = "entity_pps_per_script"; + + if (!entityScriptServerSettings.contains(MAX_ENTITY_PPS_OPTION) || !entityScriptServerSettings.contains(ENTITY_PPS_PER_SCRIPT)) { + qWarning() << "Received settings from the domain-server with no max_total_entity_pps or entity_pps_per_script properties."; + return; + } + + _maxEntityPPS = std::max(0, entityScriptServerSettings[MAX_ENTITY_PPS_OPTION].toInt()); + _entityPPSPerScript = std::max(0, entityScriptServerSettings[ENTITY_PPS_PER_SCRIPT].toInt()); + + qDebug() << QString("Received entity script server settings, Max Entity PPS: %1, Entity PPS Per Entity Script: %2") + .arg(_maxEntityPPS).arg(_entityPPSPerScript); +} + +void EntityScriptServer::updateEntityPPS() { + int numRunningScripts = _entitiesScriptEngine->getNumRunningEntityScripts(); + int pps; + if (std::numeric_limits::max() / _entityPPSPerScript < numRunningScripts) { + qWarning() << QString("Integer multiplaction would overflow, clamping to maxint: %1 * %2").arg(numRunningScripts).arg(_entityPPSPerScript); + pps = std::numeric_limits::max(); + pps = std::min(_maxEntityPPS, pps); + } else { + pps = _entityPPSPerScript * numRunningScripts; + pps = std::min(_maxEntityPPS, pps); + } + _entityEditSender.setPacketsPerSecond(pps); + qDebug() << QString("Updating entity PPS to: %1 @ %2 PPS per script = %3 PPS").arg(numRunningScripts).arg(_entityPPSPerScript).arg(pps); +} + +void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer message, SharedNodePointer senderNode) { + // These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them + // about each other. + bool enable = false; + message->readPrimitive(&enable); + + auto senderUUID = senderNode->getUUID(); + auto it = _logListeners.find(senderUUID); + + if (enable && senderNode->getCanRez()) { + if (it == std::end(_logListeners)) { + _logListeners.insert(senderUUID); + qCInfo(entity_script_server) << "Node" << senderUUID << "subscribed to log stream"; + } + } else { + if (it != std::end(_logListeners)) { + _logListeners.erase(it); + qCInfo(entity_script_server) << "Node" << senderUUID << "unsubscribed from log stream"; + } + } +} + +void EntityScriptServer::pushLogs() { + std::string buffer; + { + Lock lock(logBufferMutex); + std::swap(logBuffer, buffer); + } + + if (buffer.empty()) { + return; + } + if (_logListeners.empty()) { + return; + } + + auto nodeList = DependencyManager::get(); + for (auto uuid : _logListeners) { + auto node = nodeList->nodeWithUUID(uuid); + if (node && node->getActiveSocket()) { + auto packet = NLPacketList::create(PacketType::EntityServerScriptLog, QByteArray(), true, true); + packet->write(buffer.data(), buffer.size()); + nodeList->sendPacketList(std::move(packet), *node); + } + } +} + void EntityScriptServer::run() { // make sure we request our script once the agent connects to the domain auto nodeList = DependencyManager::get(); @@ -114,6 +239,9 @@ void EntityScriptServer::run() { connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init); messagesThread->start(); + DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); + connect(&domainHandler, &DomainHandler::settingsReceived, this, &EntityScriptServer::handleSettings); + // make sure we hear about connected nodes so we can grab an ATP script if a request is pending connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &EntityScriptServer::nodeActivated); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &EntityScriptServer::nodeKilled); @@ -154,9 +282,67 @@ void EntityScriptServer::run() { connect(tree, &EntityTree::entityServerScriptChanging, this, &EntityScriptServer::entityServerScriptChanging, Qt::QueuedConnection); } +void EntityScriptServer::cleanupOldKilledListeners() { + auto threshold = usecTimestampNow() - 5 * USECS_PER_SECOND; + using ValueType = std::pair; + auto it = std::remove_if(std::begin(_killedListeners), std::end(_killedListeners), [&](ValueType value) { + return value.second < threshold; + }); + _killedListeners.erase(it, std::end(_killedListeners)); +} + void EntityScriptServer::nodeActivated(SharedNodePointer activatedNode) { - if (activatedNode->getType() == NodeType::AudioMixer) { - negotiateAudioFormat(); + switch (activatedNode->getType()) { + case NodeType::AudioMixer: + negotiateAudioFormat(); + break; + case NodeType::Agent: { + auto activatedNodeUUID = activatedNode->getUUID(); + using ValueType = std::pair; + auto it = std::find_if(std::begin(_killedListeners), std::end(_killedListeners), [&](ValueType value) { + return value.first == activatedNodeUUID; + }); + if (it != std::end(_killedListeners)) { + _killedListeners.erase(it); + _logListeners.insert(activatedNodeUUID); + } + break; + } + default: + // Do nothing + break; + } +} + +void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) { + switch (killedNode->getType()) { + case NodeType::EntityServer: { + if (!_shuttingDown) { + if (_entitiesScriptEngine) { + _entitiesScriptEngine->unloadAllEntityScripts(); + _entitiesScriptEngine->stop(); + } + + resetEntitiesScriptEngine(); + + _entityViewer.clear(); + } + break; + } + case NodeType::Agent: { + cleanupOldKilledListeners(); + + auto killedNodeUUID = killedNode->getUUID(); + auto it = _logListeners.find(killedNodeUUID); + if (it != std::end(_logListeners)) { + _logListeners.erase(killedNodeUUID); + _killedListeners.emplace_back(killedNodeUUID, usecTimestampNow()); + } + break; + } + default: + // Do nothing + break; } } @@ -188,7 +374,7 @@ void EntityScriptServer::handleSelectedAudioFormat(QSharedPointergetName()) { _codec = plugin; _encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO); - qDebug() << "Selected Codec Plugin:" << _codec.get(); + qCDebug(entity_script_server) << "Selected Codec Plugin:" << _codec.get(); break; } } @@ -232,7 +418,9 @@ void EntityScriptServer::resetEntitiesScriptEngine() { newEngine->runInThread(); DependencyManager::get()->setEntitiesScriptEngine(newEngine.data()); + disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS); _entitiesScriptEngine.swap(newEngine); + connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS); } @@ -288,26 +476,13 @@ void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, const QString scriptUrl = entity->getServerScripts(); if (!scriptUrl.isEmpty()) { scriptUrl = ResourceManager::normalizeURL(scriptUrl); - qDebug() << "Loading entity server script" << scriptUrl << "for" << entityID; + qCDebug(entity_script_server) << "Loading entity server script" << scriptUrl << "for" << entityID; ScriptEngine::loadEntityScript(_entitiesScriptEngine, entityID, scriptUrl, reload); } } } } -void EntityScriptServer::nodeKilled(SharedNodePointer killedNode) { - if (!_shuttingDown && killedNode->getType() == NodeType::EntityServer) { - if (_entitiesScriptEngine) { - _entitiesScriptEngine->unloadAllEntityScripts(); - _entitiesScriptEngine->stop(); - } - - resetEntitiesScriptEngine(); - - _entityViewer.clear(); - } -} - void EntityScriptServer::sendStatsPacket() { } diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h index 9ea2d43aec..a468e62958 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -12,7 +12,11 @@ #ifndef hifi_EntityScriptServer_h #define hifi_EntityScriptServer_h +#include +#include + #include +#include #include #include @@ -20,11 +24,15 @@ #include #include +static const int DEFAULT_MAX_ENTITY_PPS = 9000; +static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900; + class EntityScriptServer : public ThreadedAssignment { Q_OBJECT public: EntityScriptServer(ReceivedMessage& message); + ~EntityScriptServer(); virtual void aboutToFinish() override; @@ -42,6 +50,13 @@ private slots: void handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode); void handleEntityScriptGetStatusPacket(QSharedPointer message, SharedNodePointer senderNode); + void handleSettings(); + void updateEntityPPS(); + + void handleEntityServerScriptLogPacket(QSharedPointer message, SharedNodePointer senderNode); + + void pushLogs(); + private: void negotiateAudioFormat(); void selectAudioFormat(const QString& selectedCodecName); @@ -55,6 +70,8 @@ private: void entityServerScriptChanging(const EntityItemID& entityID, const bool reload); void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false); + void cleanupOldKilledListeners(); + bool _shuttingDown { false }; static int _entitiesScriptEngineCount; @@ -62,6 +79,12 @@ private: EntityEditPacketSender _entityEditSender; EntityTreeHeadlessViewer _entityViewer; + int _maxEntityPPS { DEFAULT_MAX_ENTITY_PPS }; + int _entityPPSPerScript { DEFAULT_ENTITY_PPS_PER_SCRIPT }; + + std::set _logListeners; + std::vector> _killedListeners; + QString _selectedCodecName; CodecPluginPointer _codec; Encoder* _encoder { nullptr }; diff --git a/assignment-client/src/scripts/EntityScriptServerLogging.cpp b/assignment-client/src/scripts/EntityScriptServerLogging.cpp new file mode 100644 index 0000000000..7d033d8c9c --- /dev/null +++ b/assignment-client/src/scripts/EntityScriptServerLogging.cpp @@ -0,0 +1,14 @@ +// +// EntityScriptServerLogging.cpp +// assignment-client/src/scripts +// +// Created by Clement on 2/2/17. +// Copyright 2015 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 "EntityScriptServerLogging.h" + +Q_LOGGING_CATEGORY(entity_script_server, "hifi.entity-script-server") \ No newline at end of file diff --git a/assignment-client/src/scripts/EntityScriptServerLogging.h b/assignment-client/src/scripts/EntityScriptServerLogging.h new file mode 100644 index 0000000000..efb0c70cd0 --- /dev/null +++ b/assignment-client/src/scripts/EntityScriptServerLogging.h @@ -0,0 +1,19 @@ +// +// EntityScriptServerLogging.h +// assignment-client/src/scripts +// +// Created by Clement on 2/2/17. +// Copyright 2015 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_EntityScriptServerLogging_h +#define hifi_EntityScriptServerLogging_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(entity_script_server) + +#endif // hifi_EntityScriptServerLogging_h \ No newline at end of file diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index d27d068f84..045af9dc09 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -178,7 +178,7 @@ { "name": "maximum_user_capacity_redirect_location", "label": "Redirect to Location on Maximum Capacity", - "help": "Is there another domain, you'd like to redirect clients to when the maximum number of avatars are connected.", + "help": "The location to redirect users to when the maximum number of avatars are connected.", "placeholder": "", "default": "", "advanced": false @@ -822,6 +822,29 @@ } ] }, + { + "name": "entity_script_server", + "label": "Entity Script Server (ESS)", + "assignment-types": [5], + "settings": [ + { + "name": "entity_pps_per_script", + "label": "Entity PPS per script", + "help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.
Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.", + "default": 900, + "type": "int", + "advanced": true + }, + { + "name": "max_total_entity_pps", + "label": "Maximum Total Entity PPS", + "help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.
Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.", + "default": 9000, + "type": "int", + "advanced": true + } + ] + }, { "name": "avatars", "label": "Avatars", diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index c9e91c8666..04a3f560b6 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -22,7 +22,7 @@ "to": "Actions.Up", "filters": [ - { "type": "deadZone", "min": 0.95 }, + { "type": "deadZone", "min": 0.6 }, "invert" ] }, diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 086c1d15d2..faf37d5366 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -105,6 +105,14 @@ Item { visible: root.expanded text: "Asset Mbps In/Out: " + root.assetMbpsIn.toFixed(2) + "/" + root.assetMbpsOut.toFixed(2) } + StatText { + visible: root.expanded + text: "Fully Simulated Avatars: " + root.fullySimulatedAvatarCount + } + StatText { + visible: root.expanded + text: "Partially Simulated Avatars: " + root.partiallySimulatedAvatarCount + } } } @@ -217,7 +225,10 @@ Item { text: " Batch: " + root.batchFrameTime.toFixed(1) + " ms" } StatText { - text: " GPU: " + root.gpuFrameTime.toFixed(1) + " ms" + text: " GPU: " + root.gpuFrameTime.toFixed(1) + " ms" + } + StatText { + text: " Avatar: " + root.avatarSimulationTime.toFixed(1) + " ms" } StatText { text: "Triangles: " + root.triangles + diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 876be740cd..f6f7e88d0c 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -129,7 +129,7 @@ Rectangle { property int dropSamples: 9; property int dropSpread: 0; DropShadow { - visible: showPlace && desktop.gradientsSupported; + visible: showPlace && (desktop ? desktop.gradientsSupported : false) source: place; anchors.fill: place; horizontalOffset: dropHorizontalOffset; diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 923b09b9ef..0c7104fba5 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -16,6 +16,8 @@ import QtQuick.Controls 1.4 import "../styles-uit" import "../controls-uit" as HifiControls +// references HMD, Users, UserActivityLogger from root context + Rectangle { id: pal // Size @@ -26,7 +28,7 @@ Rectangle { // Properties property int myCardHeight: 90 property int rowHeight: 70 - property int actionButtonWidth: 75 + property int actionButtonWidth: 55 property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring. @@ -35,7 +37,9 @@ Rectangle { // Keep a local list of per-avatar gainSliderValueDBs. Far faster than fetching this data from the server. // NOTE: if another script modifies the per-avatar gain, this value won't be accurate! property var gainSliderValueDB: ({}); - + + HifiConstants { id: hifi } + // The letterbox used for popup messages LetterboxMessage { id: letterboxMessage @@ -54,8 +58,8 @@ Rectangle { property bool punctuationMode: false id: palContainer // Size - width: pal.width - 50 - height: pal.height - 50 + width: pal.width - 10 + height: pal.height - 10 // Style color: pal.color // Anchors @@ -397,7 +401,7 @@ Rectangle { width: 20 height: 28 anchors.right: adminTab.right - anchors.rightMargin: 31 + hifi.dimensions.scrollbarBackgroundWidth + anchors.rightMargin: 10 + hifi.dimensions.scrollbarBackgroundWidth anchors.top: adminTab.top anchors.topMargin: 2 RalewayRegular { @@ -422,6 +426,8 @@ Rectangle { onExited: adminHelpText.color = hifi.colors.redHighlight } } + } + HifiControls.Keyboard { id: keyboard raised: myCard.currentlyEditingDisplayName && HMD.active @@ -432,7 +438,7 @@ Rectangle { right: parent.right } } - } + // Timer used when selecting table rows that aren't yet present in the model // (i.e. when selecting avatars using edit.js or sphere overlays) Timer { diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml new file mode 100644 index 0000000000..952a1f7faa --- /dev/null +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -0,0 +1,555 @@ +// +// TabletAddressDialog.qml +// +// Created by Dante Ruiz on 2016/07/16 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 +import QtQuick 2.4 +import QtGraphicalEffects 1.0 +import "../../controls" +import "../../styles" +import "../../windows" +import "../" +import "../toolbars" +import "../../styles-uit" as HifiStyles +import "../../controls-uit" as HifiControls + +Item { + id: root + HifiConstants { id: hifi } + HifiStyles.HifiConstants { id: hifiStyleConstants } + + width: parent.width + height: parent.height + + property var allStories: []; + property int cardWidth: 370; + property int cardHeight: 320; + property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; + + + Component.onCompleted: { + fillDestinations(); + updateLocationText(); + root.parentChanged.connect(center); + center(); + } + Component.onDestruction: { + root.parentChanged.disconnect(center); + } + + function center() { + // Explicitly center in order to avoid warnings at shutdown + anchors.centerIn = parent; + } + + + function resetAfterTeleport() { + //storyCardFrame.shown = root.shown = false; + } + function goCard(targetString) { + if (0 !== targetString.indexOf('hifi://')) { + return; + } + addressLine.text = targetString; + toggleOrGo(true); + clearAddressLineTimer.start(); + } + + property bool isCursorVisible: false // Override default cursor visibility. + + + AddressBarDialog { + id: addressBarDialog + + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + + width: parent.width + height: parent.height + + anchors { + right: parent.right + left: parent.left + top: parent.top + bottom: parent.bottom + } + + onMetaverseServerUrlChanged: updateLocationTextTimer.start(); + Rectangle { + id: topBar + height: 90 + gradient: Gradient { + GradientStop { + position: 0 + color: "#2b2b2b" + + } + + GradientStop { + position: 1 + color: "#1e1e1e" + } + } + + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.topMargin: 0 + anchors.top: parent.top + + Row { + id: thing + spacing: 2 * hifi.layout.spacing + + anchors { + top: parent.top; + left: parent.left + } + + TextButton { + id: allTab; + text: "ALL"; + property string includeActions: 'snapshot, concurrency'; + selected: allTab === selectedTab; + action: tabSelect; + } + + TextButton { + id: placeTab; + text: "PLACES"; + property string includeActions: 'concurrency'; + selected: placeTab === selectedTab; + action: tabSelect; + + } + + TextButton { + id: snapTab; + text: "SNAP"; + property string includeActions: 'snapshot'; + selected: snapTab === selectedTab; + action: tabSelect; + } + } + + } + + Rectangle { + id: bgMain + gradient: Gradient { + GradientStop { + position: 0 + color: "#2b2b2b" + + } + + GradientStop { + position: 1 + color: "#0f212e" + } + } + + + anchors.bottom: backgroundImage.top + anchors.bottomMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.top: topBar.bottom + anchors.topMargin: 0 + + ListModel { id: suggestions } + + ListView { + id: scroll + + property int stackedCardShadowHeight: 10; + clip: true + spacing: 14 + anchors { + bottom: parent.bottom + top: parent.top + left: parent.left + right: parent.right + leftMargin: 50 + } + model: suggestions + orientation: ListView.Vertical + + delegate: Card { + width: cardWidth; + height: cardHeight; + goFunction: goCard; + userName: model.username; + placeName: model.place_name; + hifiUrl: model.place_name + model.path; + thumbnail: model.thumbnail_url; + imageUrl: model.image_url; + action: model.action; + timestamp: model.created_at; + onlineUsers: model.online_users; + storyId: model.metaverseId; + drillDownToPlace: model.drillDownToPlace; + shadowHeight: scroll.stackedCardShadowHeight; + hoverThunk: function () { scroll.currentIndex = index; } + unhoverThunk: function () { scroll.currentIndex = -1; } + } + + highlightMoveDuration: -1; + highlightMoveVelocity: -1; + highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.blueHighlight; z: 1; } + } + } + + Rectangle { + id: backgroundImage + width: 480 + height: 70 + + gradient: Gradient { + GradientStop { + position: 0 + color: "#c2ced8" + + } + + GradientStop { + position: 1 + color: "#c2ced8" + } + } + + anchors { + bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom + right: parent.right + left: parent.left + } + + + ToolbarButton { + id: homeButton + imageURL: "../../../images/home.svg" + onClicked: { + addressBarDialog.loadHome(); + root.shown = false; + } + anchors { + left: parent.left + leftMargin: homeButton.width / 2 + verticalCenter: parent.verticalCenter + } + } + property int inputAreaHeight: 70 + property int inputAreaStep: (height - inputAreaHeight) / 2 + + HifiStyles.RalewayLight { + id: notice; + font.pixelSize: hifi.fonts.pixelSize * 0.50; + anchors { + top: parent.top + topMargin: parent.inputAreaStep + 12 + left: addressLine.left + right: addressLine.right + } + } + HifiStyles.FiraSansRegular { + id: location; + font.pixelSize: addressLine.font.pixelSize; + color: "gray"; + clip: true; + anchors.fill: addressLine; + visible: addressLine.text.length === 0 + } + + TextInput { + id: addressLine + focus: true + anchors { + bottom: parent.bottom + left: homeButton.right + right: parent.right + leftMargin: homeButton.width + rightMargin: homeButton.width / 2 + topMargin: parent.inputAreaStep + (2 * hifi.layout.spacing) + bottomMargin: parent.inputAreaStep + } + font.pixelSize: hifi.fonts.pixelSize * 0.75 + cursorVisible: false + onTextChanged: { + filterChoicesByText(); + updateLocationText(text.length > 0); + if (!isCursorVisible && text.length > 0) { + isCursorVisible = true; + cursorVisible = true; + } + } + onAccepted: { + addressBarDialog.keyboardEnabled = false; + } + onActiveFocusChanged: { + cursorVisible = isCursorVisible && focus; + } + MouseArea { + // If user clicks in address bar show cursor to indicate ability to enter address. + anchors.fill: parent + onClicked: { + isCursorVisible = true; + //parent.cursorVisible = true; + parent.forceActiveFocus(); + addressBarDialog.keyboardEnabled = HMD.active + tabletRoot.playButtonClickSound(); + } + } + } + } + + Timer { + // Delay updating location text a bit to avoid flicker of content and so that connection status is valid. + id: updateLocationTextTimer + running: false + interval: 500 // ms + repeat: false + onTriggered: updateLocationText(false); + } + + Timer { + // Delay clearing address line so as to avoid flicker of "not connected" being displayed after entering an address. + id: clearAddressLineTimer + running: false + interval: 100 // ms + repeat: false + onTriggered: { + addressLine.text = ""; + isCursorVisible = false; + } + } + + + HifiControls.Keyboard { + id: keyboard + raised: parent.keyboardEnabled + numeric: parent.punctuationMode + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + } + + } + + function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects. + // TODO: make available to other .qml. + var request = new XMLHttpRequest(); + // QT bug: apparently doesn't handle onload. Workaround using readyState. + request.onreadystatechange = function () { + var READY_STATE_DONE = 4; + var HTTP_OK = 200; + if (request.readyState >= READY_STATE_DONE) { + var error = (request.status !== HTTP_OK) && request.status.toString() + ':' + request.statusText, + response = !error && request.responseText, + contentType = !error && request.getResponseHeader('content-type'); + if (!error && contentType.indexOf('application/json') === 0) { + try { + response = JSON.parse(response); + } catch (e) { + error = e; + } + } + cb(error, response); + } + }; + request.open("GET", url, true); + request.send(); + } + + function identity(x) { + return x; + } + + function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey + if (!error && (data.status === 'success')) { + return; + } + if (!error) { // Create a message from the data + error = data.status + ': ' + data.error; + } + if (typeof(error) === 'string') { // Make a proper Error object + error = new Error(error); + } + error.message += ' in ' + url; // Include the url. + cb(error); + return true; + } + + + function resolveUrl(url) { + return (url.indexOf('/') === 0) ? (addressBarDialog.metaverseServerUrl + url) : url; + } + + function makeModelData(data) { // create a new obj from data + // ListModel elements will only ever have those properties that are defined by the first obj that is added. + // So here we make sure that we have all the properties we need, regardless of whether it is a place data or user story. + var name = data.place_name, + tags = data.tags || [data.action, data.username], + description = data.description || "", + thumbnail_url = data.thumbnail_url || ""; + return { + place_name: name, + username: data.username || "", + path: data.path || "", + created_at: data.created_at || "", + action: data.action || "", + thumbnail_url: resolveUrl(thumbnail_url), + image_url: resolveUrl(data.details.image_url), + + metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. + + tags: tags, + description: description, + online_users: data.details.concurrency || 0, + drillDownToPlace: false, + + searchText: [name].concat(tags, description || []).join(' ').toUpperCase() + } + } + function suggestable(place) { + if (place.action === 'snapshot') { + return true; + } + return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + } + property var selectedTab: allTab; + function tabSelect(textButton) { + selectedTab = textButton; + fillDestinations(); + } + property var placeMap: ({}); + function addToSuggestions(place) { + var collapse = allTab.selected && (place.action !== 'concurrency'); + if (collapse) { + var existing = placeMap[place.place_name]; + if (existing) { + existing.drillDownToPlace = true; + return; + } + } + suggestions.append(place); + if (collapse) { + placeMap[place.place_name] = suggestions.get(suggestions.count - 1); + } else if (place.action === 'concurrency') { + suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). + } + } + property int requestId: 0; + function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model + var options = [ + 'now=' + new Date().toISOString(), + 'include_actions=' + selectedTab.includeActions, + 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), + 'require_online=true', + 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), + 'page=' + pageNumber + ]; + var url = metaverseBase + 'user_stories?' + options.join('&'); + var thisRequestId = ++requestId; + getRequest(url, function (error, data) { + if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { + return; + } + var stories = data.user_stories.map(function (story) { // explicit single-argument function + return makeModelData(story, url); + }); + allStories = allStories.concat(stories); + stories.forEach(makeFilteredPlaceProcessor()); + if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now + return getUserStoryPage(pageNumber + 1, cb); + } + cb(); + }); + } + function makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches + var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity), + data = allStories; + function matches(place) { + if (!words.length) { + return suggestable(place); + } + return words.every(function (word) { + return place.searchText.indexOf(word) >= 0; + }); + } + return function (place) { + if (matches(place)) { + addToSuggestions(place); + } + }; + } + function filterChoicesByText() { + suggestions.clear(); + placeMap = {}; + allStories.forEach(makeFilteredPlaceProcessor()); + } + + function fillDestinations() { + allStories = []; + suggestions.clear(); + placeMap = {}; + getUserStoryPage(1, function (error) { + console.log('user stories query', error || 'ok', allStories.length); + }); + } + + function updateLocationText(enteringAddress) { + if (enteringAddress) { + notice.text = "Go to a place, @user, path or network address"; + notice.color = hifiStyleConstants.colors.baseGrayHighlight; + } else { + notice.text = AddressManager.isConnected ? "Your location:" : "Not Connected"; + notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.baseGrayHighlight : hifiStyleConstants.colors.redHighlight; + // Display hostname, which includes ip address, localhost, and other non-placenames. + location.text = (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''); + } + } + + onVisibleChanged: { + updateLocationText(false); + if (visible) { + addressLine.forceActiveFocus(); + fillDestinations(); + } + } + + function toggleOrGo(fromSuggestions) { + if (addressLine.text !== "") { + addressBarDialog.loadAddress(addressLine.text, fromSuggestions) + } + root.shown = false; + } + + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + root.shown = false + clearAddressLineTimer.start(); + event.accepted = true + break + case Qt.Key_Enter: + case Qt.Key_Return: + toggleOrGo() + clearAddressLineTimer.start(); + event.accepted = true + break + } + } +} diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index cfda92e774..0260bd6a01 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -18,6 +18,16 @@ Item { loader.item.scriptURL = injectedJavaScriptUrl; } + // used to send a message from qml to interface script. + signal sendToScript(var message); + + // used to receive messages from interface script + function fromScript(message) { + if (loader.item.hasOwnProperty("fromScript")) { + loader.item.fromScript(message); + } + } + SoundEffect { id: buttonClickSound volume: 0.1 @@ -55,6 +65,9 @@ Item { } }); } + if (loader.item.hasOwnProperty("sendToScript")) { + loader.item.sendToScript.connect(tabletRoot.sendToScript); + } loader.item.forceActiveFocus(); } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cfbaee7ade..1441ae9001 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -41,6 +41,7 @@ #include +#include #include #include @@ -62,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -174,7 +176,6 @@ #include "FrameTimingsScriptingInterface.h" #include #include -#include // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -436,6 +437,7 @@ bool setupEssentials(int& argc, char** argv) { } DependencyManager::set(); + PROFILE_SET_THREAD_NAME("Main Thread"); #if defined(Q_OS_WIN) // Select appropriate audio DLL @@ -518,6 +520,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); return previousSessionCrashed; } @@ -5522,6 +5525,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri auto recordingInterface = DependencyManager::get(); scriptEngine->registerGlobalObject("Recording", recordingInterface.data()); + auto entityScriptServerLog = DependencyManager::get(); + scriptEngine->registerGlobalObject("EntityScriptServerLog", entityScriptServerLog.data()); + // connect this script engines printedMessage signal to the global ScriptEngines these various messages connect(scriptEngine, &ScriptEngine::printedMessage, DependencyManager::get().data(), &ScriptEngines::onPrintedMessage); connect(scriptEngine, &ScriptEngine::errorMessage, DependencyManager::get().data(), &ScriptEngines::onErrorMessage); @@ -6282,6 +6288,17 @@ void Application::toggleLogDialog() { } } +void Application::toggleEntityScriptServerLogDialog() { + if (! _entityScriptServerLogDialog) { + _entityScriptServerLogDialog = new EntityScriptServerLogDialog(nullptr); + } + + if (_entityScriptServerLogDialog->isVisible()) { + _entityScriptServerLogDialog->hide(); + } else { + _entityScriptServerLogDialog->show(); + } +} void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) { postLambdaEvent([notify, includeAnimated, aspectRatio, this] { diff --git a/interface/src/Application.h b/interface/src/Application.h index 05ff5beb48..6072acac93 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -62,6 +62,7 @@ #include "scripting/DialogsManagerScriptingInterface.h" #include "ui/ApplicationOverlay.h" #include "ui/BandwidthDialog.h" +#include "ui/EntityScriptServerLogDialog.h" #include "ui/LodToolsDialog.h" #include "ui/LogDialog.h" #include "ui/OctreeStatsDialog.h" @@ -314,6 +315,7 @@ public slots: Q_INVOKABLE void loadDialog(); Q_INVOKABLE void loadScriptURLDialog() const; void toggleLogDialog(); + void toggleEntityScriptServerLogDialog(); void toggleRunningScriptsWidget() const; Q_INVOKABLE void showAssetServerWidget(QString filePath = ""); @@ -566,6 +568,7 @@ private: NodeToOctreeSceneStats _octreeServerSceneStats; ControllerScriptingInterface* _controllerScriptingInterface{ nullptr }; QPointer _logDialog; + QPointer _entityScriptServerLogDialog; FileLogger* _logger; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 870d60fdf3..acf97ad5f7 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -702,7 +702,14 @@ Menu::Menu() { // Developer > Log... addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, - qApp, SLOT(toggleLogDialog())); + qApp, SLOT(toggleLogDialog())); + auto essLogAction = addActionToQMenuAndActionHash(developerMenu, MenuOption::EntityScriptServerLog, 0, + qApp, SLOT(toggleEntityScriptServerLogDialog())); + QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { + auto nodeList = DependencyManager::get(); + essLogAction->setEnabled(nodeList->getThisNodeCanRez()); + }); + essLogAction->setEnabled(nodeList->getThisNodeCanRez()); action = addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)..."); connect(action, &QAction::triggered, [] { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index e2a368db2d..1b2564735b 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -98,6 +98,7 @@ namespace MenuOption { const QString EchoServerAudio = "Echo Server Audio"; const QString EnableCharacterController = "Enable avatar collisions"; const QString EnableInverseKinematics = "Enable Inverse Kinematics"; + const QString EntityScriptServerLog = "Entity Script Server Log"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; const QString ExpandMyAvatarTiming = "Expand /myAvatar"; const QString ExpandOtherAvatarTiming = "Expand /otherAvatar"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 64e82f63da..ab97f563f6 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -307,12 +307,22 @@ bool Avatar::shouldDie() const { void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "simulate"); + + _simulationRate.increment(); + if (inView) { + _simulationInViewRate.increment(); + } + + PerformanceTimer perfTimer("simulate"); { PROFILE_RANGE(simulation, "updateJoints"); if (inView && _hasNewJointData) { _skeletonModel->getRig()->copyJointsFromJointData(_jointData); + _jointDataSimulationRate.increment(); + _skeletonModel->simulate(deltaTime, true); + _skeletonModelSimulationRate.increment(); locationChanged(); // joints changed, so if there are any children, update them. _hasNewJointData = false; @@ -328,6 +338,7 @@ void Avatar::simulate(float deltaTime, bool inView) { } else { // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. _skeletonModel->simulate(deltaTime, false); + _skeletonModelSimulationRate.increment(); } } @@ -357,6 +368,21 @@ void Avatar::simulate(float deltaTime, bool inView) { } } +float Avatar::getSimulationRate(const QString& rateName) const { + if (rateName == "") { + return _simulationRate.rate(); + } else if (rateName == "avatar") { + return _simulationRate.rate(); + } else if (rateName == "avatarInView") { + return _simulationInViewRate.rate(); + } else if (rateName == "skeletonModel") { + return _skeletonModelSimulationRate.rate(); + } else if (rateName == "jointData") { + return _jointDataSimulationRate.rate(); + } + return 0.0f; +} + bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const { const float HEAD_SPHERE_RADIUS = 0.1f; glm::vec3 theirLookAt = dynamic_pointer_cast(avatar)->getHead()->getLookAtPosition(); @@ -1010,7 +1036,7 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { void Avatar::setModelURLFinished(bool success) { if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) { - qDebug() << "Using default after failing to load Avatar model: " << _skeletonModelURL; + qCWarning(interfaceapp) << "Using default after failing to load Avatar model: " << _skeletonModelURL; // call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that // we don't redo this every time we receive an identity packet from the avatar with the bad url. QMetaObject::invokeMethod(_skeletonModel.get(), "setURL", diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 8f2b0817c1..5c05702e92 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -182,6 +182,8 @@ public: void animateScaleChanges(float deltaTime); void setTargetScale(float targetScale) override; + Q_INVOKABLE float getSimulationRate(const QString& rateName = QString("")) const; + public slots: // FIXME - these should be migrated to use Pose data instead @@ -259,6 +261,13 @@ protected: void addToScene(AvatarSharedPointer self); void ensureInScene(AvatarSharedPointer self); + // Some rate tracking support + RateCounter<> _simulationRate; + RateCounter<> _simulationInViewRate; + RateCounter<> _skeletonModelSimulationRate; + RateCounter<> _jointDataSimulationRate; + + private: uint64_t _lastRenderUpdateTime { 0 }; int _leftPointerGeometryID { 0 }; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index df3164e6fc..c3fc974365 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -132,11 +132,23 @@ void AvatarManager::updateMyAvatar(float deltaTime) { Q_LOGGING_CATEGORY(trace_simulation_avatar, "trace.simulation.avatar"); -float AvatarManager::getAvatarDataRate(const QUuid& sessionID, const QString& rateName) { +float AvatarManager::getAvatarDataRate(const QUuid& sessionID, const QString& rateName) const { auto avatar = getAvatarBySessionID(sessionID); - return avatar->getDataRate(rateName); + return avatar ? avatar->getDataRate(rateName) : 0.0f; } +float AvatarManager::getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName) const { + auto avatar = getAvatarBySessionID(sessionID); + return avatar ? avatar->getUpdateRate(rateName) : 0.0f; +} + +float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName) const { + auto avatar = std::static_pointer_cast(getAvatarBySessionID(sessionID)); + return avatar ? avatar->getSimulationRate(rateName) : 0.0f; +} + + + class AvatarPriority { public: AvatarPriority(AvatarSharedPointer a, float p) : avatar(a), priority(p) {} @@ -218,6 +230,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const uint64_t MAX_UPDATE_BUDGET = 2000; // usec uint64_t renderExpiry = startTime + RENDER_UPDATE_BUDGET; uint64_t maxExpiry = startTime + MAX_UPDATE_BUDGET; + + int fullySimulatedAvatars = 0; + int partiallySimulatedAvatars = 0; while (!sortedAvatars.empty()) { const AvatarPriority& sortData = sortedAvatars.top(); const auto& avatar = std::static_pointer_cast(sortData.avatar); @@ -246,11 +261,13 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { avatar->simulate(deltaTime, inView); avatar->updateRenderItem(pendingChanges); avatar->setLastRenderUpdateTime(startTime); + fullySimulatedAvatars++; } else if (now < maxExpiry) { // we've spent most of our time budget, but we still simulate() the avatar as it if were out of view // --> some avatars may freeze until their priority trickles up const bool inView = false; avatar->simulate(deltaTime, inView); + partiallySimulatedAvatars++; } else { // we've spent ALL of our time budget --> bail on the rest of the avatar updates // --> some scale or fade animations may glitch @@ -259,6 +276,10 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } sortedAvatars.pop(); } + + _avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC; + _fullySimulatedAvatars = fullySimulatedAvatars; + _partiallySimulatedAvatars = partiallySimulatedAvatars; qApp->getMain3DScene()->enqueuePendingChanges(pendingChanges); simulateAvatarFades(deltaTime); @@ -492,7 +513,7 @@ void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) { } -AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) { +AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) const { if (sessionID == AVATAR_SELF_ID || sessionID == _myAvatar->getSessionUUID()) { return _myAvatar; } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 787d6f2d83..4d503842b9 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -41,7 +41,11 @@ public: void init(); std::shared_ptr getMyAvatar() { return _myAvatar; } - AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) override; + AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) const override; + + int getFullySimulatedAvatars() const { return _fullySimulatedAvatars; } + int getPartiallySimulatedAvatars() const { return _partiallySimulatedAvatars; } + float getAvatarSimulationTime() const { return _avatarSimulationTime; } void updateMyAvatar(float deltaTime); void updateOtherAvatars(float deltaTime); @@ -69,7 +73,10 @@ public: void handleOutgoingChanges(const VectorOfMotionStates& motionStates); void handleCollisionEvents(const CollisionEvents& collisionEvents); - Q_INVOKABLE float getAvatarDataRate(const QUuid& sessionID, const QString& rateName = QString("")); + Q_INVOKABLE float getAvatarDataRate(const QUuid& sessionID, const QString& rateName = QString("")) const; + Q_INVOKABLE float getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName = QString("")) const; + Q_INVOKABLE float getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName = QString("")) const; + Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude = QScriptValue(), const QScriptValue& avatarIdsToDiscard = QScriptValue()); @@ -109,6 +116,9 @@ private: VectorOfMotionStates _motionStatesToRemoveFromPhysics; RateCounter<> _myAvatarSendRate; + int _fullySimulatedAvatars { 0 }; + int _partiallySimulatedAvatars { 0 }; + float _avatarSimulationTime { 0.0f }; }; diff --git a/interface/src/avatar/CauterizedModel.cpp b/interface/src/avatar/CauterizedModel.cpp index 02107e9d24..f6b470f024 100644 --- a/interface/src/avatar/CauterizedModel.cpp +++ b/interface/src/avatar/CauterizedModel.cpp @@ -116,7 +116,7 @@ void CauterizedModel::updateClusterMatrices() { for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); auto jointMatrix = _rig->getJointTransform(cluster.jointIndex); -#if GLM_ARCH & GLM_ARCH_SSE2 +#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MACOS) glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix; glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out); state.clusterMatrices[j] = out; @@ -155,7 +155,7 @@ void CauterizedModel::updateClusterMatrices() { if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) { jointMatrix = cauterizeMatrix; } -#if GLM_ARCH & GLM_ARCH_SSE2 +#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MACOS) glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix; glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out); state.clusterMatrices[j] = out; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d4815b35c6..1915046f72 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -795,6 +795,11 @@ void MyAvatar::saveData() { } settings.endArray(); + if (_avatarEntityData.size() == 0) { + // HACK: manually remove empty 'avatarEntityData' else deleted avatarEntityData may show up in settings file + settings.remove("avatarEntityData"); + } + settings.beginWriteArray("avatarEntityData"); int avatarEntityIndex = 0; auto hmdInterface = DependencyManager::get(); @@ -931,6 +936,10 @@ void MyAvatar::loadData() { updateAvatarEntity(entityID, properties); } settings.endArray(); + if (avatarEntityCount == 0) { + // HACK: manually remove empty 'avatarEntityData' else legacy data may persist in settings file + settings.remove("avatarEntityData"); + } setAvatarEntityDataChanged(true); setDisplayName(settings.value("displayName").toString()); @@ -1165,7 +1174,6 @@ void MyAvatar::clearJointsData() { } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { - Avatar::setSkeletonModelURL(skeletonModelURL); render::ScenePointer scene = qApp->getMain3DScene(); _skeletonModel->setVisibleInScene(true, scene); diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp index 22a33ae858..a8901365e5 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -134,3 +134,9 @@ void TestScriptingInterface::startTraceEvent(QString name) { void TestScriptingInterface::endTraceEvent(QString name) { tracing::traceEvent(trace_test(), name, tracing::DurationEnd); } + +void TestScriptingInterface::profileRange(const QString& name, QScriptValue fn) { + PROFILE_RANGE(script, name); + fn.call(); +} + diff --git a/interface/src/scripting/TestScriptingInterface.h b/interface/src/scripting/TestScriptingInterface.h index 9539493b51..73b8f0ac93 100644 --- a/interface/src/scripting/TestScriptingInterface.h +++ b/interface/src/scripting/TestScriptingInterface.h @@ -13,6 +13,8 @@ #include #include +class QScriptValue; + class TestScriptingInterface : public QObject { Q_OBJECT @@ -69,6 +71,7 @@ public slots: void endTraceEvent(QString name); + Q_INVOKABLE void profileRange(const QString& name, QScriptValue function); private: bool waitForCondition(qint64 maxWaitMs, std::function condition); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 52f7d723eb..9c1aedf7a0 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -16,6 +16,8 @@ #include +#include + #include "Application.h" #include "DomainHandler.h" #include "MainWindow.h" @@ -147,6 +149,15 @@ void WindowScriptingInterface::setPreviousBrowseLocation(const QString& location Setting::Handle(LAST_BROWSE_LOCATION_SETTING).set(location); } +/// Makes sure that the reticle is visible, use this in blocking forms that require a reticle and +/// might be in same thread as a script that sets the reticle to invisible +void WindowScriptingInterface::ensureReticleVisible() const { + auto compositorHelper = DependencyManager::get(); + if (!compositorHelper->getReticleVisible()) { + compositorHelper->setReticleVisible(true); + } +} + /// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current /// working directory. /// \param const QString& title title of the window @@ -154,6 +165,7 @@ void WindowScriptingInterface::setPreviousBrowseLocation(const QString& location /// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` /// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` QScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) { + ensureReticleVisible(); QString path = directory; if (path.isEmpty()) { path = getPreviousBrowseLocation(); @@ -175,6 +187,7 @@ QScriptValue WindowScriptingInterface::browse(const QString& title, const QStrin /// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` /// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` QScriptValue WindowScriptingInterface::save(const QString& title, const QString& directory, const QString& nameFilter) { + ensureReticleVisible(); QString path = directory; if (path.isEmpty()) { path = getPreviousBrowseLocation(); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 6cc6c7b715..60d24d50df 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -83,6 +83,8 @@ private: QString getPreviousBrowseLocation() const; void setPreviousBrowseLocation(const QString& location); + void ensureReticleVisible() const; + int createMessageBox(QString title, QString text, int buttons, int defaultButton); QHash _messageBoxes; int _lastMessageBoxID{ -1 }; diff --git a/interface/src/ui/BaseLogDialog.cpp b/interface/src/ui/BaseLogDialog.cpp new file mode 100644 index 0000000000..47cea9c26a --- /dev/null +++ b/interface/src/ui/BaseLogDialog.cpp @@ -0,0 +1,135 @@ +// +// BaseLogDialog.cpp +// interface/src/ui +// +// Created by Clement Brisset on 1/31/17. +// 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 "BaseLogDialog.h" + +#include +#include +#include +#include +#include + +#include + +const int TOP_BAR_HEIGHT = 46; +const int INITIAL_WIDTH = 720; +const int INITIAL_HEIGHT = 480; +const int MINIMAL_WIDTH = 570; +const int SEARCH_BUTTON_LEFT = 25; +const int SEARCH_BUTTON_WIDTH = 20; +const int SEARCH_TEXT_WIDTH = 240; +const QColor HIGHLIGHT_COLOR = QColor("#3366CC"); + +class KeywordHighlighter : public QSyntaxHighlighter { +public: + KeywordHighlighter(QTextDocument* parent = nullptr); + QString keyword; + +protected: + void highlightBlock(const QString& text) override; + +private: + QTextCharFormat keywordFormat; + +}; + +BaseLogDialog::BaseLogDialog(QWidget* parent) : QDialog(parent, Qt::Window) { + setWindowTitle("Base Log"); + setAttribute(Qt::WA_DeleteOnClose); + + QFile styleSheet(PathUtils::resourcesPath() + "styles/log_dialog.qss"); + if (styleSheet.open(QIODevice::ReadOnly)) { + QDir::setCurrent(PathUtils::resourcesPath()); + setStyleSheet(styleSheet.readAll()); + } + + initControls(); + + resize(INITIAL_WIDTH, INITIAL_HEIGHT); + setMinimumWidth(MINIMAL_WIDTH); +} + +BaseLogDialog::~BaseLogDialog() { + deleteLater(); +} + +void BaseLogDialog::initControls() { + _searchButton = new QPushButton(this); + // set object name for css styling + _searchButton->setObjectName("searchButton"); + _leftPad = SEARCH_BUTTON_LEFT; + _searchButton->setGeometry(_leftPad, ELEMENT_MARGIN, SEARCH_BUTTON_WIDTH, ELEMENT_HEIGHT); + _leftPad += SEARCH_BUTTON_WIDTH; + _searchButton->show(); + connect(_searchButton, SIGNAL(clicked()), SLOT(handleSearchButton())); + + _searchTextBox = new QLineEdit(this); + // disable blue outline in Mac + _searchTextBox->setAttribute(Qt::WA_MacShowFocusRect, false); + _searchTextBox->setGeometry(_leftPad, ELEMENT_MARGIN, SEARCH_TEXT_WIDTH, ELEMENT_HEIGHT); + _leftPad += SEARCH_TEXT_WIDTH + CHECKBOX_MARGIN; + _searchTextBox->show(); + connect(_searchTextBox, SIGNAL(textChanged(QString)), SLOT(handleSearchTextChanged(QString))); + + _logTextBox = new QPlainTextEdit(this); + _logTextBox->setReadOnly(true); + _logTextBox->show(); + _highlighter = new KeywordHighlighter(_logTextBox->document()); + +} + +void BaseLogDialog::showEvent(QShowEvent* event) { + showLogData(); +} + +void BaseLogDialog::resizeEvent(QResizeEvent* event) { + _logTextBox->setGeometry(0, TOP_BAR_HEIGHT, width(), height() - TOP_BAR_HEIGHT); +} + +void BaseLogDialog::appendLogLine(QString logLine) { + if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) { + _logTextBox->appendPlainText(logLine.trimmed()); + } +} + +void BaseLogDialog::handleSearchButton() { + _searchTextBox->setFocus(); +} + +void BaseLogDialog::handleSearchTextChanged(QString searchText) { + _searchTerm = searchText; + _highlighter->keyword = searchText; + _highlighter->rehighlight(); +} + +void BaseLogDialog::showLogData() { + _logTextBox->clear(); + _logTextBox->appendPlainText(getCurrentLog()); + _logTextBox->ensureCursorVisible(); +} + +KeywordHighlighter::KeywordHighlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) { + keywordFormat.setForeground(HIGHLIGHT_COLOR); +} + +void KeywordHighlighter::highlightBlock(const QString& text) { + if (keyword.isNull() || keyword.isEmpty()) { + return; + } + + int index = text.indexOf(keyword, 0, Qt::CaseInsensitive); + int length = keyword.length(); + + while (index >= 0) { + setFormat(index, length, keywordFormat); + index = text.indexOf(keyword, index + length, Qt::CaseInsensitive); + } +} diff --git a/interface/src/ui/BaseLogDialog.h b/interface/src/ui/BaseLogDialog.h new file mode 100644 index 0000000000..72fe83cd82 --- /dev/null +++ b/interface/src/ui/BaseLogDialog.h @@ -0,0 +1,60 @@ +// +// BaseLogDialog.h +// interface/src/ui +// +// Created by Clement Brisset on 1/31/17. +// 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_BaseLogDialog_h +#define hifi_BaseLogDialog_h + +#include + +const int ELEMENT_MARGIN = 7; +const int ELEMENT_HEIGHT = 32; +const int CHECKBOX_MARGIN = 12; +const int CHECKBOX_WIDTH = 140; + +class QPushButton; +class QLineEdit; +class QPlainTextEdit; +class KeywordHighlighter; + +class BaseLogDialog : public QDialog { + Q_OBJECT + +public: + BaseLogDialog(QWidget* parent); + ~BaseLogDialog(); + +public slots: + void appendLogLine(QString logLine); + +private slots: + void handleSearchButton(); + void handleSearchTextChanged(QString text); + +protected: + int _leftPad { 0 }; + + void resizeEvent(QResizeEvent* event) override; + void showEvent(QShowEvent* event) override; + virtual QString getCurrentLog() = 0; + +private: + QPushButton* _searchButton { nullptr }; + QLineEdit* _searchTextBox { nullptr }; + QPlainTextEdit* _logTextBox { nullptr }; + QString _searchTerm; + KeywordHighlighter* _highlighter { nullptr }; + + void initControls(); + void showLogData(); +}; + + +#endif // hifi_BaseLogDialog_h diff --git a/interface/src/ui/EntityScriptServerLogDialog.cpp b/interface/src/ui/EntityScriptServerLogDialog.cpp new file mode 100644 index 0000000000..e8ee3ee522 --- /dev/null +++ b/interface/src/ui/EntityScriptServerLogDialog.cpp @@ -0,0 +1,23 @@ +// +// EntityScriptServerLogDialog.cpp +// interface/src/ui +// +// Created by Clement Brisset on 1/31/17. +// 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 "EntityScriptServerLogDialog.h" + +#include + +EntityScriptServerLogDialog::EntityScriptServerLogDialog(QWidget* parent) : BaseLogDialog(parent) { + setWindowTitle("Entity Script Server Log"); + + + auto client = DependencyManager::get(); + QObject::connect(client.data(), &EntityScriptServerLogClient::receivedNewLogLines, + this, &EntityScriptServerLogDialog::appendLogLine); +} diff --git a/interface/src/ui/EntityScriptServerLogDialog.h b/interface/src/ui/EntityScriptServerLogDialog.h new file mode 100644 index 0000000000..a5451b6159 --- /dev/null +++ b/interface/src/ui/EntityScriptServerLogDialog.h @@ -0,0 +1,27 @@ +// +// EntityScriptServerLogDialog.h +// interface/src/ui +// +// Created by Clement Brisset on 1/31/17. +// 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_EntityScriptServerLogDialog_h +#define hifi_EntityScriptServerLogDialog_h + +#include "BaseLogDialog.h" + +class EntityScriptServerLogDialog : public BaseLogDialog { + Q_OBJECT + +public: + EntityScriptServerLogDialog(QWidget* parent = nullptr); + +protected: + QString getCurrentLog() override { return QString(); }; +}; + +#endif // hifi_EntityScriptServerLogDialog_h diff --git a/interface/src/ui/LogDialog.cpp b/interface/src/ui/LogDialog.cpp index 1e56d0cfd9..cce7879e94 100644 --- a/interface/src/ui/LogDialog.cpp +++ b/interface/src/ui/LogDialog.cpp @@ -9,90 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdouble-promotion" -#endif +#include "LogDialog.h" -#include -#include +#include +#include -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif +#include -#include -#include - -#include "Application.h" -#include "ui/LogDialog.h" - -const int TOP_BAR_HEIGHT = 46; -const int INITIAL_WIDTH = 720; -const int MINIMAL_WIDTH = 570; -const int ELEMENT_MARGIN = 7; -const int ELEMENT_HEIGHT = 32; -const int SEARCH_BUTTON_LEFT = 25; -const int SEARCH_BUTTON_WIDTH = 20; -const int SEARCH_TEXT_WIDTH = 240; -const int CHECKBOX_MARGIN = 12; -const int CHECKBOX_WIDTH = 140; const int REVEAL_BUTTON_WIDTH = 122; -const float INITIAL_HEIGHT_RATIO = 0.6f; -const QString HIGHLIGHT_COLOR = "#3366CC"; - -int qTextCursorMeta = qRegisterMetaType("QTextCursor"); -int qTextBlockMeta = qRegisterMetaType("QTextBlock"); - -LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : QDialog(parent, Qt::Window) { +LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLogDialog(parent) { _logger = logger; setWindowTitle("Log"); - setAttribute(Qt::WA_DeleteOnClose); - - QFile styleSheet(PathUtils::resourcesPath() + "styles/log_dialog.qss"); - if (styleSheet.open(QIODevice::ReadOnly)) { - QDir::setCurrent(PathUtils::resourcesPath()); - setStyleSheet(styleSheet.readAll()); - } - - initControls(); - - QDesktopWidget desktop; - QRect screen = desktop.screenGeometry(); - resize(INITIAL_WIDTH, static_cast(screen.height() * INITIAL_HEIGHT_RATIO)); - move(screen.center() - rect().center()); - setMinimumWidth(MINIMAL_WIDTH); - - connect(_logger, SIGNAL(logReceived(QString)), this, SLOT(appendLogLine(QString)), Qt::QueuedConnection); -} - -LogDialog::~LogDialog() { - deleteLater(); -} - -void LogDialog::initControls() { - - int left; - _searchButton = new QPushButton(this); - // set object name for css styling - _searchButton->setObjectName("searchButton"); - left = SEARCH_BUTTON_LEFT; - _searchButton->setGeometry(left, ELEMENT_MARGIN, SEARCH_BUTTON_WIDTH, ELEMENT_HEIGHT); - left += SEARCH_BUTTON_WIDTH; - _searchButton->show(); - connect(_searchButton, SIGNAL(clicked()), SLOT(handleSearchButton())); - - _searchTextBox = new QLineEdit(this); - // disable blue outline in Mac - _searchTextBox->setAttribute(Qt::WA_MacShowFocusRect, false); - _searchTextBox->setGeometry(left, ELEMENT_MARGIN, SEARCH_TEXT_WIDTH, ELEMENT_HEIGHT); - left += SEARCH_TEXT_WIDTH + CHECKBOX_MARGIN; - _searchTextBox->show(); - connect(_searchTextBox, SIGNAL(textChanged(QString)), SLOT(handleSearchTextChanged(QString))); _extraDebuggingBox = new QCheckBox("Extra debugging", this); - _extraDebuggingBox->setGeometry(left, ELEMENT_MARGIN, CHECKBOX_WIDTH, ELEMENT_HEIGHT); + _extraDebuggingBox->setGeometry(_leftPad, ELEMENT_MARGIN, CHECKBOX_WIDTH, ELEMENT_HEIGHT); if (_logger->extraDebugging()) { _extraDebuggingBox->setCheckState(Qt::Checked); } @@ -105,72 +36,25 @@ void LogDialog::initControls() { _revealLogButton->show(); connect(_revealLogButton, SIGNAL(clicked()), SLOT(handleRevealButton())); - _logTextBox = new QPlainTextEdit(this); - _logTextBox->setReadOnly(true); - _logTextBox->show(); - _highlighter = new KeywordHighlighter(_logTextBox->document()); - + connect(_logger, SIGNAL(logReceived(QString)), this, SLOT(appendLogLine(QString)), Qt::QueuedConnection); } -void LogDialog::showEvent(QShowEvent*) { - showLogData(); -} - -void LogDialog::resizeEvent(QResizeEvent*) { - _logTextBox->setGeometry(0, TOP_BAR_HEIGHT, width(), height() - TOP_BAR_HEIGHT); +void LogDialog::resizeEvent(QResizeEvent* event) { + BaseLogDialog::resizeEvent(event); _revealLogButton->setGeometry(width() - ELEMENT_MARGIN - REVEAL_BUTTON_WIDTH, ELEMENT_MARGIN, REVEAL_BUTTON_WIDTH, ELEMENT_HEIGHT); } -void LogDialog::appendLogLine(QString logLine) { - if (isVisible()) { - if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) { - _logTextBox->appendPlainText(logLine.trimmed()); - } - } -} - -void LogDialog::handleSearchButton() { - _searchTextBox->setFocus(); -} - void LogDialog::handleRevealButton() { _logger->locateLog(); } -void LogDialog::handleExtraDebuggingCheckbox(const int state) { +void LogDialog::handleExtraDebuggingCheckbox(int state) { _logger->setExtraDebugging(state != 0); } -void LogDialog::handleSearchTextChanged(const QString searchText) { - _searchTerm = searchText; - _highlighter->keyword = searchText; - showLogData(); -} - -void LogDialog::showLogData() { - _logTextBox->clear(); - _logTextBox->insertPlainText(_logger->getLogData()); - _logTextBox->ensureCursorVisible(); -} - -KeywordHighlighter::KeywordHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent), keywordFormat() { - keywordFormat.setForeground(QColor(HIGHLIGHT_COLOR)); -} - -void KeywordHighlighter::highlightBlock(const QString &text) { - - if (keyword.isNull() || keyword.isEmpty()) { - return; - } - - int index = text.indexOf(keyword, 0, Qt::CaseInsensitive); - int length = keyword.length(); - - while (index >= 0) { - setFormat(index, length, keywordFormat); - index = text.indexOf(keyword, index + length, Qt::CaseInsensitive); - } +QString LogDialog::getCurrentLog() { + return _logger->getLogData(); } diff --git a/interface/src/ui/LogDialog.h b/interface/src/ui/LogDialog.h index 1493a43f01..c8794b57ea 100644 --- a/interface/src/ui/LogDialog.h +++ b/interface/src/ui/LogDialog.h @@ -12,64 +12,32 @@ #ifndef hifi_LogDialog_h #define hifi_LogDialog_h -#include -#include -#include -#include -#include -#include -#include +#include "BaseLogDialog.h" -#include +class QCheckBox; +class QPushButton; +class QResizeEvent; +class AbstractLoggerInterface; -class KeywordHighlighter : public QSyntaxHighlighter { +class LogDialog : public BaseLogDialog { Q_OBJECT public: - KeywordHighlighter(QTextDocument *parent = 0); - QString keyword; - -protected: - void highlightBlock(const QString &text) override; - -private: - QTextCharFormat keywordFormat; - -}; - -class LogDialog : public QDialog { - Q_OBJECT - -public: - LogDialog(QWidget*, AbstractLoggerInterface*); - ~LogDialog(); - -public slots: - void appendLogLine(QString logLine); + LogDialog(QWidget* parent, AbstractLoggerInterface* logger); private slots: - void handleSearchButton(); void handleRevealButton(); - void handleExtraDebuggingCheckbox(const int); - void handleSearchTextChanged(const QString); + void handleExtraDebuggingCheckbox(int); protected: - void resizeEvent(QResizeEvent*) override; - void showEvent(QShowEvent*) override; - + void resizeEvent(QResizeEvent* event) override; + QString getCurrentLog() override; + private: - QPushButton* _searchButton; - QLineEdit* _searchTextBox; QCheckBox* _extraDebuggingBox; QPushButton* _revealLogButton; - QPlainTextEdit* _logTextBox; - QString _searchTerm; - KeywordHighlighter* _highlighter; AbstractLoggerInterface* _logger; - - void initControls(); - void showLogData(); }; #endif // hifi_LogDialog_h diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index ff0028322c..e82f99bed2 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -121,6 +121,8 @@ void Stats::updateStats(bool force) { auto avatarManager = DependencyManager::get(); // we need to take one avatar out so we don't include ourselves STAT_UPDATE(avatarCount, avatarManager->size() - 1); + STAT_UPDATE(fullySimulatedAvatarCount, avatarManager->getFullySimulatedAvatars()); + STAT_UPDATE(partiallySimulatedAvatarCount, avatarManager->getPartiallySimulatedAvatars()); STAT_UPDATE(serverCount, (int)nodeList->size()); STAT_UPDATE(framerate, qApp->getFps()); if (qApp->getActiveDisplayPlugin()) { @@ -306,6 +308,8 @@ void Stats::updateStats(bool force) { // Update Frame timing (in ms) STAT_UPDATE(gpuFrameTime, (float)gpuContext->getFrameTimerGPUAverage()); STAT_UPDATE(batchFrameTime, (float)gpuContext->getFrameTimerBatchAverage()); + STAT_UPDATE(avatarSimulationTime, (float)avatarManager->getAvatarSimulationTime()); + STAT_UPDATE(gpuBuffers, (int)gpu::Context::getBufferGPUCount()); STAT_UPDATE(gpuBufferMemory, (int)BYTES_TO_MB(gpu::Context::getBufferGPUMemoryUsage())); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 3fe851494c..f501f4b09a 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -49,6 +49,8 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, simrate, 0) STATS_PROPERTY(int, avatarSimrate, 0) STATS_PROPERTY(int, avatarCount, 0) + STATS_PROPERTY(int, fullySimulatedAvatarCount, 0) + STATS_PROPERTY(int, partiallySimulatedAvatarCount, 0) STATS_PROPERTY(int, packetInCount, 0) STATS_PROPERTY(int, packetOutCount, 0) STATS_PROPERTY(float, mbpsIn, 0) @@ -111,6 +113,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, gpuFreeMemory, 0) STATS_PROPERTY(float, gpuFrameTime, 0) STATS_PROPERTY(float, batchFrameTime, 0) + STATS_PROPERTY(float, avatarSimulationTime, 0) public: static Stats* getInstance(); @@ -156,6 +159,8 @@ signals: void simrateChanged(); void avatarSimrateChanged(); void avatarCountChanged(); + void fullySimulatedAvatarCountChanged(); + void partiallySimulatedAvatarCountChanged(); void packetInCountChanged(); void packetOutCountChanged(); void mbpsInChanged(); @@ -216,6 +221,7 @@ signals: void gpuFreeMemoryChanged(); void gpuFrameTimeChanged(); void batchFrameTimeChanged(); + void avatarSimulationTimeChanged(); void rectifiedTextureCountChanged(); void decimatedTextureCountChanged(); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index f33ef24c0d..ddf380d0b2 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -23,14 +23,20 @@ #include #include #include +#include #include #include #include #include #include +#include +#include #include #include #include +#include +#include "scripting/AccountScriptingInterface.h" +#include "scripting/HMDScriptingInterface.h" static const float DPI = 30.47f; static const float INCHES_TO_METERS = 1.0f / 39.3701f; @@ -149,10 +155,17 @@ void Web3DOverlay::loadSourceURL() { _webSurface->load(_url, [&](QQmlContext* context, QObject* obj) {}); _webSurface->resume(); + _webSurface->getRootContext()->setContextProperty("Users", DependencyManager::get().data()); + _webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get().data()); + _webSurface->getRootContext()->setContextProperty("UserActivityLogger", DependencyManager::get().data()); + if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); auto flags = tabletScriptingInterface->getFlags(); _webSurface->getRootContext()->setContextProperty("offscreenFlags", flags); + _webSurface->getRootContext()->setContextProperty("AddressManager", DependencyManager::get().data()); + _webSurface->getRootContext()->setContextProperty("Account", AccountScriptingInterface::getInstance()); + _webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get().data()); tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data()); // Override min fps for tablet UI, for silky smooth scrolling diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index b25140d0a8..af060429af 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -283,14 +283,20 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += sizeof(packetStateFlags); if (hasAvatarGlobalPosition) { + auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); data->globalPosition[0] = _globalPosition.x; data->globalPosition[1] = _globalPosition.y; data->globalPosition[2] = _globalPosition.z; destinationBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); + + int numBytes = destinationBuffer - startSection; + + _globalPositionRateOutbound.increment(numBytes); } if (hasAvatarBoundingBox) { + auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); data->avatarDimensions[0] = _globalBoundingBoxDimensions.x; @@ -302,36 +308,56 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent data->boundOriginOffset[2] = _globalBoundingBoxOffset.z; destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); + + int numBytes = destinationBuffer - startSection; + _avatarBoundingBoxRateOutbound.increment(numBytes); } if (hasAvatarOrientation) { + auto startSection = destinationBuffer; auto localOrientation = getLocalOrientation(); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation); + + int numBytes = destinationBuffer - startSection; + _avatarOrientationRateOutbound.increment(numBytes); } if (hasAvatarScale) { + auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); auto scale = getDomainLimitedScale(); packFloatRatioToTwoByte((uint8_t*)(&data->scale), scale); destinationBuffer += sizeof(AvatarDataPacket::AvatarScale); + + int numBytes = destinationBuffer - startSection; + _avatarScaleRateOutbound.increment(numBytes); } if (hasLookAtPosition) { + auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); auto lookAt = _headData->getLookAtPosition(); data->lookAtPosition[0] = lookAt.x; data->lookAtPosition[1] = lookAt.y; data->lookAtPosition[2] = lookAt.z; destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition); + + int numBytes = destinationBuffer - startSection; + _lookAtPositionRateOutbound.increment(numBytes); } if (hasAudioLoudness) { + auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); data->audioLoudness = packFloatGainToByte(_headData->getAudioLoudness() / AUDIO_LOUDNESS_SCALE); destinationBuffer += sizeof(AvatarDataPacket::AudioLoudness); + + int numBytes = destinationBuffer - startSection; + _audioLoudnessRateOutbound.increment(numBytes); } if (hasSensorToWorldMatrix) { + auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); packOrientationQuatToSixBytes(data->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix)); @@ -341,9 +367,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent data->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1]; data->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2]; destinationBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix); + + int numBytes = destinationBuffer - startSection; + _sensorToWorldRateOutbound.increment(numBytes); } if (hasAdditionalFlags) { + auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); uint8_t flags { 0 }; @@ -370,27 +400,39 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } data->flags = flags; destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags); + + int numBytes = destinationBuffer - startSection; + _additionalFlagsRateOutbound.increment(numBytes); } if (hasAvatarLocalPosition) { + auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); auto localPosition = getLocalPosition(); data->localPosition[0] = localPosition.x; data->localPosition[1] = localPosition.y; data->localPosition[2] = localPosition.z; destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition); + + int numBytes = destinationBuffer - startSection; + _localPositionRateOutbound.increment(numBytes); } if (hasParentInfo) { + auto startSection = destinationBuffer; auto parentInfo = reinterpret_cast(destinationBuffer); QByteArray referentialAsBytes = parentID.toRfc4122(); memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size()); parentInfo->parentJointIndex = _parentJointIndex; destinationBuffer += sizeof(AvatarDataPacket::ParentInfo); + + int numBytes = destinationBuffer - startSection; + _parentInfoRateOutbound.increment(numBytes); } // If it is connected, pack up the data if (hasFaceTrackerInfo) { + auto startSection = destinationBuffer; auto faceTrackerInfo = reinterpret_cast(destinationBuffer); faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; @@ -403,10 +445,14 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // followed by a variable number of float coefficients memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), _headData->_blendshapeCoefficients.size() * sizeof(float)); destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); + + int numBytes = destinationBuffer - startSection; + _faceTrackerRateOutbound.increment(numBytes); } // If it is connected, pack up the data if (hasJointData) { + auto startSection = destinationBuffer; QReadLocker readLock(&_jointDataLock); // joint rotation data @@ -554,6 +600,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent << (destinationBuffer - startPosition); } #endif + + int numBytes = destinationBuffer - startSection; + _jointDataRateOutbound.increment(numBytes); } int avatarDataSize = destinationBuffer - startPosition; @@ -670,6 +719,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); int numBytesRead = sourceBuffer - startSection; _globalPositionRate.increment(numBytesRead); + _globalPositionUpdateRate.increment(); // if we don't have a parent, make sure to also set our local position if (!hasParent()) { @@ -698,6 +748,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); int numBytesRead = sourceBuffer - startSection; _avatarBoundingBoxRate.increment(numBytesRead); + _avatarBoundingBoxUpdateRate.increment(); } if (hasAvatarOrientation) { @@ -713,6 +764,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } int numBytesRead = sourceBuffer - startSection; _avatarOrientationRate.increment(numBytesRead); + _avatarOrientationUpdateRate.increment(); } if (hasAvatarScale) { @@ -732,6 +784,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::AvatarScale); int numBytesRead = sourceBuffer - startSection; _avatarScaleRate.increment(numBytesRead); + _avatarScaleUpdateRate.increment(); } if (hasLookAtPosition) { @@ -750,6 +803,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::LookAtPosition); int numBytesRead = sourceBuffer - startSection; _lookAtPositionRate.increment(numBytesRead); + _lookAtPositionUpdateRate.increment(); } if (hasAudioLoudness) { @@ -770,6 +824,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _headData->setAudioLoudness(audioLoudness); int numBytesRead = sourceBuffer - startSection; _audioLoudnessRate.increment(numBytesRead); + _audioLoudnessUpdateRate.increment(); } if (hasSensorToWorldMatrix) { @@ -790,6 +845,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix); int numBytesRead = sourceBuffer - startSection; _sensorToWorldRate.increment(numBytesRead); + _sensorToWorldUpdateRate.increment(); } if (hasAdditionalFlags) { @@ -833,6 +889,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } int numBytesRead = sourceBuffer - startSection; _additionalFlagsRate.increment(numBytesRead); + _additionalFlagsUpdateRate.increment(); } // FIXME -- make sure to handle the existance of a parent vs a change in the parent... @@ -855,7 +912,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { int numBytesRead = sourceBuffer - startSection; _parentInfoRate.increment(numBytesRead); - } else { + _parentInfoUpdateRate.increment(); + } + else { // FIXME - this aint totally right, for switching to parent/no-parent _parentID = QUuid(); } @@ -877,6 +936,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition); int numBytesRead = sourceBuffer - startSection; _localPositionRate.increment(numBytesRead); + _localPositionUpdateRate.increment(); } if (hasFaceTrackerInfo) { @@ -899,6 +959,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += coefficientsSize; int numBytesRead = sourceBuffer - startSection; _faceTrackerRate.increment(numBytesRead); + _faceTrackerUpdateRate.increment(); } if (hasJointData) { @@ -991,17 +1052,19 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { int numBytesRead = sourceBuffer - startSection; _jointDataRate.increment(numBytesRead); + _jointDataUpdateRate.increment(); } int numBytesRead = sourceBuffer - startPosition; _averageBytesReceived.updateAverage(numBytesRead); _parseBufferRate.increment(numBytesRead); + _parseBufferUpdateRate.increment(); return numBytesRead; } -float AvatarData::getDataRate(const QString& rateName) { +float AvatarData::getDataRate(const QString& rateName) const { if (rateName == "") { return _parseBufferRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "globalPosition") { @@ -1028,10 +1091,64 @@ float AvatarData::getDataRate(const QString& rateName) { return _faceTrackerRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "jointData") { return _jointDataRate.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "globalPositionOutbound") { + return _globalPositionRateOutbound.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "localPositionOutbound") { + return _localPositionRateOutbound.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "avatarBoundingBoxOutbound") { + return _avatarBoundingBoxRateOutbound.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "avatarOrientationOutbound") { + return _avatarOrientationRateOutbound.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "avatarScaleOutbound") { + return _avatarScaleRateOutbound.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "lookAtPositionOutbound") { + return _lookAtPositionRateOutbound.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "audioLoudnessOutbound") { + return _audioLoudnessRateOutbound.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "sensorToWorkMatrixOutbound") { + return _sensorToWorldRateOutbound.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "additionalFlagsOutbound") { + return _additionalFlagsRateOutbound.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "parentInfoOutbound") { + return _parentInfoRateOutbound.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "faceTrackerOutbound") { + return _faceTrackerRateOutbound.rate() / BYTES_PER_KILOBIT; + } else if (rateName == "jointDataOutbound") { + return _jointDataRateOutbound.rate() / BYTES_PER_KILOBIT; } return 0.0f; } +float AvatarData::getUpdateRate(const QString& rateName) const { + if (rateName == "") { + return _parseBufferUpdateRate.rate(); + } else if (rateName == "globalPosition") { + return _globalPositionUpdateRate.rate(); + } else if (rateName == "localPosition") { + return _localPositionUpdateRate.rate(); + } else if (rateName == "avatarBoundingBox") { + return _avatarBoundingBoxUpdateRate.rate(); + } else if (rateName == "avatarOrientation") { + return _avatarOrientationUpdateRate.rate(); + } else if (rateName == "avatarScale") { + return _avatarScaleUpdateRate.rate(); + } else if (rateName == "lookAtPosition") { + return _lookAtPositionUpdateRate.rate(); + } else if (rateName == "audioLoudness") { + return _audioLoudnessUpdateRate.rate(); + } else if (rateName == "sensorToWorkMatrix") { + return _sensorToWorldUpdateRate.rate(); + } else if (rateName == "additionalFlags") { + return _additionalFlagsUpdateRate.rate(); + } else if (rateName == "parentInfo") { + return _parentInfoUpdateRate.rate(); + } else if (rateName == "faceTracker") { + return _faceTrackerUpdateRate.rate(); + } else if (rateName == "jointData") { + return _jointDataUpdateRate.rate(); + } + return 0.0f; +} int AvatarData::getAverageBytesReceivedPerSecond() const { return lrint(_averageBytesReceived.getAverageSampleValuePerSecond()); @@ -1842,8 +1959,7 @@ QJsonObject AvatarData::toJson() const { return root; } -void AvatarData::fromJson(const QJsonObject& json) { - +void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { int version; if (json.contains(JSON_AVATAR_VERSION)) { version = json[JSON_AVATAR_VERSION].toInt(); @@ -1865,7 +1981,7 @@ void AvatarData::fromJson(const QJsonObject& json) { if (json.contains(JSON_AVATAR_BODY_MODEL)) { auto bodyModelURL = json[JSON_AVATAR_BODY_MODEL].toString(); - if (bodyModelURL != getSkeletonModelURL().toString()) { + if (useFrameSkeleton && bodyModelURL != getSkeletonModelURL().toString()) { setSkeletonModelURL(bodyModelURL); } } @@ -1958,8 +2074,9 @@ QByteArray AvatarData::toFrame(const AvatarData& avatar) { } -void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result) { +void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result, bool useFrameSkeleton) { QJsonDocument doc = QJsonDocument::fromBinaryData(frameData); + #ifdef WANT_JSON_DEBUG { QJsonObject obj = doc.object(); @@ -1967,7 +2084,7 @@ void AvatarData::fromFrame(const QByteArray& frameData, AvatarData& result) { qCDebug(avatars).noquote() << QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Indented); } #endif - result.fromJson(doc.object()); + result.fromJson(doc.object(), useFrameSkeleton); } float AvatarData::getBodyYaw() const { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 52cf81798e..01cab8b93a 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -329,7 +329,7 @@ public: static const QString FRAME_NAME; - static void fromFrame(const QByteArray& frameData, AvatarData& avatar); + static void fromFrame(const QByteArray& frameData, AvatarData& avatar, bool useFrameSkeleton = true); static QByteArray toFrame(const AvatarData& avatar); AvatarData(); @@ -380,8 +380,27 @@ public: void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time. virtual void updateAttitude() {} // Tell skeleton mesh about changes - glm::quat getHeadOrientation() const { return _headData->getOrientation(); } - void setHeadOrientation(const glm::quat& orientation) { _headData->setOrientation(orientation); } + glm::quat getHeadOrientation() { + lazyInitHeadData(); + return _headData->getOrientation(); + } + void setHeadOrientation(const glm::quat& orientation) { + if (_headData) { + _headData->setOrientation(orientation); + } + } + + void setLookAtPosition(const glm::vec3& lookAtPosition) { + if (_headData) { + _headData->setLookAtPosition(lookAtPosition); + } + } + + void setBlendshapeCoefficients(const QVector& blendshapeCoefficients) { + if (_headData) { + _headData->setBlendshapeCoefficients(blendshapeCoefficients); + } + } // access to Head().set/getMousePitch (degrees) float getHeadPitch() const { return _headData->getBasePitch(); } @@ -513,7 +532,7 @@ public: TransformPointer getRecordingBasis() const; void setRecordingBasis(TransformPointer recordingBasis = TransformPointer()); QJsonObject toJson() const; - void fromJson(const QJsonObject& json); + void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); glm::vec3 getClientGlobalPosition() { return _globalPosition; } glm::vec3 getGlobalBoundingBoxCorner() { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; } @@ -528,7 +547,8 @@ public: Q_INVOKABLE glm::mat4 getControllerLeftHandMatrix() const; Q_INVOKABLE glm::mat4 getControllerRightHandMatrix() const; - float getDataRate(const QString& rateName = QString("")); + Q_INVOKABLE float getDataRate(const QString& rateName = QString("")) const; + Q_INVOKABLE float getUpdateRate(const QString& rateName = QString("")) const; int getJointCount() { return _jointData.size(); } @@ -596,7 +616,7 @@ protected: bool _forceFaceTrackerConnected; bool _hasNewJointData; // set in AvatarData, cleared in Avatar - HeadData* _headData; + HeadData* _headData { nullptr }; QUrl _skeletonModelURL; bool _firstSkeletonCheck { true }; @@ -644,7 +664,7 @@ protected: quint64 _lastToByteArray { 0 }; // tracks the last time we did a toByteArray - // Some rate data for incoming data + // Some rate data for incoming data in bytes RateCounter<> _parseBufferRate; RateCounter<> _globalPositionRate; RateCounter<> _localPositionRate; @@ -659,6 +679,35 @@ protected: RateCounter<> _faceTrackerRate; RateCounter<> _jointDataRate; + // Some rate data for incoming data updates + RateCounter<> _parseBufferUpdateRate; + RateCounter<> _globalPositionUpdateRate; + RateCounter<> _localPositionUpdateRate; + RateCounter<> _avatarBoundingBoxUpdateRate; + RateCounter<> _avatarOrientationUpdateRate; + RateCounter<> _avatarScaleUpdateRate; + RateCounter<> _lookAtPositionUpdateRate; + RateCounter<> _audioLoudnessUpdateRate; + RateCounter<> _sensorToWorldUpdateRate; + RateCounter<> _additionalFlagsUpdateRate; + RateCounter<> _parentInfoUpdateRate; + RateCounter<> _faceTrackerUpdateRate; + RateCounter<> _jointDataUpdateRate; + + // Some rate data for outgoing data + RateCounter<> _globalPositionRateOutbound; + RateCounter<> _localPositionRateOutbound; + RateCounter<> _avatarBoundingBoxRateOutbound; + RateCounter<> _avatarOrientationRateOutbound; + RateCounter<> _avatarScaleRateOutbound; + RateCounter<> _lookAtPositionRateOutbound; + RateCounter<> _audioLoudnessRateOutbound; + RateCounter<> _sensorToWorldRateOutbound; + RateCounter<> _additionalFlagsRateOutbound; + RateCounter<> _parentInfoRateOutbound; + RateCounter<> _faceTrackerRateOutbound; + RateCounter<> _jointDataRateOutbound; + glm::vec3 _globalBoundingBoxDimensions; glm::vec3 _globalBoundingBoxOffset; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 9d43bf438b..00c515a635 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -90,7 +90,7 @@ AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, return avatar; } -AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) { +AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) const { QReadLocker locker(&_hashLock); if (_avatarHash.contains(sessionUUID)) { return _avatarHash.value(sessionUUID); diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index eae4026bfc..dd097aef22 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -40,7 +40,7 @@ public: Q_INVOKABLE QVector getAvatarIdentifiers(); Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID); - virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) { return findAvatar(sessionID); } + virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) const { return findAvatar(sessionID); } int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters); signals: @@ -65,7 +65,7 @@ protected: virtual AvatarSharedPointer newSharedAvatar(); virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); - virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID); // uses a QReadLocker on the hashLock + virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason); virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); @@ -73,7 +73,7 @@ protected: AvatarHash _avatarHash; // "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock. // If you read from a different thread, you must read-lock the _hashLock. (Scripted write access is not supported). - QReadWriteLock _hashLock; + mutable QReadWriteLock _hashLock; private: QUuid _lastOwnerSessionUUID; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 574c574f66..cf6b39812a 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -118,6 +118,8 @@ public: virtual void run() override { + PROFILE_SET_THREAD_NAME("Present Thread"); + // FIXME determine the best priority balance between this and the main thread... // It may be dependent on the display plugin being used, since VR plugins should // have higher priority on rendering (although we could say that the Oculus plugin diff --git a/libraries/entities/src/EntityScriptServerLogClient.cpp b/libraries/entities/src/EntityScriptServerLogClient.cpp new file mode 100644 index 0000000000..4405af5b1b --- /dev/null +++ b/libraries/entities/src/EntityScriptServerLogClient.cpp @@ -0,0 +1,85 @@ +// +// EntityScriptServerLogClient.cpp +// interface/src +// +// Created by Clement Brisset on 2/1/17. +// 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 "EntityScriptServerLogClient.h" + +EntityScriptServerLogClient::EntityScriptServerLogClient() { + auto nodeList = DependencyManager::get(); + auto& packetReceiver = nodeList->getPacketReceiver(); + packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket"); + + QObject::connect(nodeList.data(), &NodeList::nodeActivated, this, &EntityScriptServerLogClient::nodeActivated); + QObject::connect(nodeList.data(), &NodeList::nodeKilled, this, &EntityScriptServerLogClient::nodeKilled); + + QObject::connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptServerLogClient::canRezChanged); +} + +void EntityScriptServerLogClient::connectNotify(const QMetaMethod& signal) { + // This needs to be delayed or "receivers()" will return completely inconsistent values + QMetaObject::invokeMethod(this, "connectionsChanged", Qt::QueuedConnection); +} + +void EntityScriptServerLogClient::disconnectNotify(const QMetaMethod& signal) { + // This needs to be delayed or "receivers()" will return completely inconsistent values + QMetaObject::invokeMethod(this, "connectionsChanged", Qt::QueuedConnection); +} + +void EntityScriptServerLogClient::connectionsChanged() { + auto numReceivers = receivers(SIGNAL(receivedNewLogLines(QString))); + if (!_subscribed && numReceivers > 0) { + enableToEntityServerScriptLog(DependencyManager::get()->getThisNodeCanRez()); + } else if (_subscribed && numReceivers == 0) { + enableToEntityServerScriptLog(false); + } +} + +void EntityScriptServerLogClient::enableToEntityServerScriptLog(bool enable) { + auto nodeList = DependencyManager::get(); + + if (auto node = nodeList->soloNodeOfType(NodeType::EntityScriptServer)) { + auto packet = NLPacket::create(PacketType::EntityServerScriptLog, sizeof(bool), true); + packet->writePrimitive(enable); + nodeList->sendPacket(std::move(packet), *node); + + if (_subscribed != enable) { + if (enable) { + emit receivedNewLogLines("====================== Subscribded to the Entity Script Server's log ======================"); + } else { + emit receivedNewLogLines("==================== Unsubscribded from the Entity Script Server's log ===================="); + } + } + _subscribed = enable; + } +} + +void EntityScriptServerLogClient::handleEntityServerScriptLogPacket(QSharedPointer message, SharedNodePointer senderNode) { + emit receivedNewLogLines(QString::fromUtf8(message->readAll())); +} + +void EntityScriptServerLogClient::nodeActivated(SharedNodePointer activatedNode) { + if (_subscribed && activatedNode->getType() == NodeType::EntityScriptServer) { + _subscribed = false; + enableToEntityServerScriptLog(DependencyManager::get()->getThisNodeCanRez()); + } +} + +void EntityScriptServerLogClient::nodeKilled(SharedNodePointer killedNode) { + if (killedNode->getType() == NodeType::EntityScriptServer) { + emit receivedNewLogLines("====================== Connection to the Entity Script Server lost ======================"); + } +} + +void EntityScriptServerLogClient::canRezChanged(bool canRez) { + auto numReceivers = receivers(SIGNAL(receivedNewLogLines(QString))); + if (numReceivers > 0) { + enableToEntityServerScriptLog(canRez); + } +} diff --git a/libraries/entities/src/EntityScriptServerLogClient.h b/libraries/entities/src/EntityScriptServerLogClient.h new file mode 100644 index 0000000000..22245e2a4e --- /dev/null +++ b/libraries/entities/src/EntityScriptServerLogClient.h @@ -0,0 +1,46 @@ +// +// EntityScriptServerLogClient.h +// interface/src +// +// Created by Clement Brisset on 2/1/17. +// 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_EntityScriptServerLogClient_h +#define hifi_EntityScriptServerLogClient_h + +#include + +#include + +class EntityScriptServerLogClient : public QObject, public Dependency { + Q_OBJECT + +public: + EntityScriptServerLogClient(); + +signals: + void receivedNewLogLines(QString logLines); + +protected: + void connectNotify(const QMetaMethod& signal) override; + void disconnectNotify(const QMetaMethod& signal) override; + +private slots: + void enableToEntityServerScriptLog(bool enable); + void handleEntityServerScriptLogPacket(QSharedPointer message, SharedNodePointer senderNode); + + void nodeActivated(SharedNodePointer activatedNode); + void nodeKilled(SharedNodePointer killedNode); + void canRezChanged(bool canRez); + + void connectionsChanged(); + +private: + bool _subscribed { false }; +}; + +#endif // hifi_EntityScriptServerLogClient_h diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index b42ca4045d..d96b19394e 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -27,6 +27,7 @@ #include "ZoneEntityItem.h" #include "WebEntityItem.h" #include +#include EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) : @@ -178,6 +179,8 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) { + PROFILE_RANGE(script_entities, __FUNCTION__); + _activityTracking.addedEntityCount++; EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); @@ -259,6 +262,8 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit } EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties) { + PROFILE_RANGE(script_entities, __FUNCTION__); + EntityItemProperties results; if (_entityTree) { _entityTree->withReadLock([&] { @@ -305,6 +310,8 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit } QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { + PROFILE_RANGE(script_entities, __FUNCTION__); + _activityTracking.editedEntityCount++; EntityItemProperties properties = scriptSideProperties; @@ -456,6 +463,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } void EntityScriptingInterface::deleteEntity(QUuid id) { + PROFILE_RANGE(script_entities, __FUNCTION__); + _activityTracking.deletedEntityCount++; EntityItemID entityID(id); @@ -511,6 +520,8 @@ void EntityScriptingInterface::setEntitiesScriptEngine(EntitiesScriptEngineProvi } void EntityScriptingInterface::callEntityMethod(QUuid id, const QString& method, const QStringList& params) { + PROFILE_RANGE(script_entities, __FUNCTION__); + std::lock_guard lock(_entitiesScriptEngineLock); if (_entitiesScriptEngine) { EntityItemID entityID{ id }; @@ -519,6 +530,8 @@ void EntityScriptingInterface::callEntityMethod(QUuid id, const QString& method, } QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const { + PROFILE_RANGE(script_entities, __FUNCTION__); + EntityItemID result; if (_entityTree) { EntityItemPointer closestEntity; @@ -542,6 +555,8 @@ void EntityScriptingInterface::dumpTree() const { } QVector EntityScriptingInterface::findEntities(const glm::vec3& center, float radius) const { + PROFILE_RANGE(script_entities, __FUNCTION__); + QVector result; if (_entityTree) { QVector entities; @@ -557,6 +572,8 @@ QVector EntityScriptingInterface::findEntities(const glm::vec3& center, f } QVector EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const { + PROFILE_RANGE(script_entities, __FUNCTION__); + QVector result; if (_entityTree) { QVector entities; @@ -573,6 +590,8 @@ QVector EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corn } QVector EntityScriptingInterface::findEntitiesInFrustum(QVariantMap frustum) const { + PROFILE_RANGE(script_entities, __FUNCTION__); + QVector result; const QString POSITION_PROPERTY = "position"; @@ -616,6 +635,7 @@ QVector EntityScriptingInterface::findEntitiesInFrustum(QVariantMap frust RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { + PROFILE_RANGE(script_entities, __FUNCTION__); QVector entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); QVector entitiesToDiscard = qVectorEntityItemIDFromScriptValue(entityIdsToDiscard); @@ -723,6 +743,8 @@ RayToEntityIntersectionResult::RayToEntityIntersectionResult() : } QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) { + PROFILE_RANGE(script_entities, __FUNCTION__); + QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); obj.setProperty("accurate", value.accurate); @@ -770,6 +792,8 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c } void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& value) { + PROFILE_RANGE(script_entities, __FUNCTION__); + value.intersects = object.property("intersects").toVariant().toBool(); value.accurate = object.property("accurate").toVariant().toBool(); QScriptValue entityIDValue = object.property("entityID"); @@ -807,6 +831,8 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra bool EntityScriptingInterface::setVoxels(QUuid entityID, std::function actor) { + PROFILE_RANGE(script_entities, __FUNCTION__); + if (!_entityTree) { return false; } @@ -831,6 +857,8 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID, } bool EntityScriptingInterface::setPoints(QUuid entityID, std::function actor) { + PROFILE_RANGE(script_entities, __FUNCTION__); + if (!_entityTree) { return false; } @@ -870,18 +898,24 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function& points) { + PROFILE_RANGE(script_entities, __FUNCTION__); + EntityItemPointer entity = static_cast(_entityTree->findEntityByEntityItemID(entityID)); if (!entity) { qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID; @@ -913,6 +951,8 @@ bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector(_entityTree->findEntityByEntityItemID(entityID)); if (!entity) { qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID; @@ -988,6 +1028,8 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, const QUuid& entityID, const QVariantMap& arguments) { + PROFILE_RANGE(script_entities, __FUNCTION__); + QUuid actionID = QUuid::createUuid(); auto actionFactory = DependencyManager::get(); bool success = false; @@ -1019,6 +1061,8 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments) { + PROFILE_RANGE(script_entities, __FUNCTION__); + return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { bool success = entity->updateAction(simulation, actionID, arguments); if (success) { @@ -1029,6 +1073,8 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& } bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& actionID) { + PROFILE_RANGE(script_entities, __FUNCTION__); + bool success = false; actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { success = entity->removeAction(simulation, actionID); @@ -1042,6 +1088,8 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& } QVector EntityScriptingInterface::getActionIDs(const QUuid& entityID) { + PROFILE_RANGE(script_entities, __FUNCTION__); + QVector result; actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { QList actionIDs = entity->getActionIDs(); @@ -1052,6 +1100,8 @@ QVector EntityScriptingInterface::getActionIDs(const QUuid& entityID) { } QVariantMap EntityScriptingInterface::getActionArguments(const QUuid& entityID, const QUuid& actionID) { + PROFILE_RANGE(script_entities, __FUNCTION__); + QVariantMap result; actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { result = entity->getActionArguments(actionID); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 2e6544fbdb..fcaef90527 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -537,6 +537,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS FBXGeometry* geometryPtr = new FBXGeometry; FBXGeometry& geometry = *geometryPtr; + geometry.originalURL = url; + float unitScaleFactor = 1.0f; glm::vec3 ambientColor; QString hifiGlobalNodeID; diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index b0e34dd674..5910b8d312 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -288,6 +288,7 @@ class FBXGeometry { public: using Pointer = std::shared_ptr; + QString originalURL; QString author; QString applicationName; ///< the name of the application that generated the model diff --git a/libraries/fbx/src/FBXReader_Node.cpp b/libraries/fbx/src/FBXReader_Node.cpp index 435c4d830b..d814f58dab 100644 --- a/libraries/fbx/src/FBXReader_Node.cpp +++ b/libraries/fbx/src/FBXReader_Node.cpp @@ -62,7 +62,10 @@ template QVariant readBinaryArray(QDataStream& in, int& position) { position += sizeof(T) * arrayLength; in.readRawData(arrayData.data(), arrayData.size()); } - memcpy(&values[0], arrayData.constData(), arrayData.size()); + + if (arrayData.size() > 0) { + memcpy(&values[0], arrayData.constData(), arrayData.size()); + } } else { values.reserve(arrayLength); const unsigned int DEFLATE_ENCODING = 1; diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 8af115ebcb..447b9d56aa 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -604,6 +604,9 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::functionsetParentItem(_quickWindow->contentItem()); @@ -952,4 +955,13 @@ void OffscreenQmlSurface::emitWebEvent(const QVariant& message) { } } +void OffscreenQmlSurface::sendToQml(const QVariant& message) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "emitQmlEvent", Qt::QueuedConnection, Q_ARG(QVariant, message)); + } else if (_rootItem) { + // call fromScript method on qml root + QMetaObject::invokeMethod(_rootItem, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); + } +} + #include "OffscreenQmlSurface.moc" diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index f6168e7b6d..efd35fce8b 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -107,6 +107,11 @@ signals: void scriptEventReceived(const QVariant& message); void webEventReceived(const QVariant& message); + // qml event bridge +public slots: + void sendToQml(const QVariant& message); +signals: + void fromQml(QVariant message); protected: bool filterEnabled(QObject* originalDestination, QEvent* event) const; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index dbfe8fe730..c51f468908 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -328,12 +328,12 @@ void GLBackend::render(const Batch& batch) { void GLBackend::syncCache() { + PROFILE_RANGE(render_gpu_gl_detail, __FUNCTION__); + syncTransformStateCache(); syncPipelineStateCache(); syncInputStateCache(); syncOutputStateCache(); - - glEnable(GL_LINE_SMOOTH); } #ifdef GPU_STEREO_DRAWCALL_DOUBLED diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp index 81d38c3339..8aab6abaa9 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp @@ -1,4 +1,4 @@ -// +// // GLBackendPipeline.cpp // libraries/gpu/src/gpu // diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp index 6f8c229184..a7d4a7ff7c 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp @@ -28,11 +28,8 @@ void GLBackend::resetPipelineState(State::Signature nextSignature) { } } } -} - -void GLBackend::syncPipelineStateCache() { - State::Data state; + // force a few states regardless glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Point size is always on @@ -43,6 +40,25 @@ void GLBackend::syncPipelineStateCache() { // Default line width accross the board glLineWidth(1.0f); + glEnable(GL_LINE_SMOOTH); + +} + +void GLBackend::syncPipelineStateCache() { + State::Data state; + + // force a few states regardless + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + + // Point size is always on + // FIXME CORE + //glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); + glEnable(GL_PROGRAM_POINT_SIZE_EXT); + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); + + // Default line width accross the board + glLineWidth(1.0f); + glEnable(GL_LINE_SMOOTH); getCurrentGLState(state); State::Signature signature = State::evalSignature(state); diff --git a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp index df9153d43e..9dac2986e3 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp @@ -151,6 +151,7 @@ bool GLTextureTransferHelper::process() { #endif return true; } + PROFILE_COUNTER_IF_CHANGED(render_gpu_gl, "transferringTextures", int, (int) _transferringTextures.size()) static auto lastReport = usecTimestampNow(); auto now = usecTimestampNow(); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index b540a403c7..6948a045a2 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -297,6 +297,7 @@ void GL45Texture::startTransfer() { } bool GL45Texture::continueTransfer() { + PROFILE_RANGE(render_gpu_gl, "continueTransfer") size_t maxFace = GL_TEXTURE_CUBE_MAP == _target ? CUBE_NUM_FACES : 1; for (uint8_t face = 0; face < maxFace; ++face) { for (uint16_t mipLevel = _minMip; mipLevel <= _maxMip; ++mipLevel) { @@ -306,6 +307,8 @@ bool GL45Texture::continueTransfer() { _sparseInfo.allocatedPages += _sparseInfo.getPageCount(size); } if (_gpuObject.isStoredMipFaceAvailable(mipLevel, face)) { + PROFILE_RANGE_EX(render_gpu_gl, "texSubImage", 0x0000ffff, (size.x * size.y * maxFace / 1024)); + auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); if (GL_TEXTURE_2D == _target) { @@ -379,6 +382,8 @@ void GL45Texture::stripToMip(uint16_t newMinMip) { return; } + PROFILE_RANGE(render_gpu_gl, "GL45Texture::stripToMip"); + auto mipLevels = usedMipLevels(); { Lock lock(texturesByMipCountsMutex); @@ -420,29 +425,35 @@ void GL45Texture::stripToMip(uint16_t newMinMip) { auto newLevels = usedMipLevels(); // Create and setup the new texture (allocate) - glCreateTextures(_target, 1, &const_cast(_id)); - glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); - glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); - Vec3u newDimensions = _gpuObject.evalMipDimensions(_mipOffset); - glTextureStorage2D(_id, newLevels, _internalFormat, newDimensions.x, newDimensions.y); + { + Vec3u newDimensions = _gpuObject.evalMipDimensions(_mipOffset); + PROFILE_RANGE_EX(render_gpu_gl, "Re-Allocate", 0xff0000ff, (newDimensions.x * newDimensions.y)); + + glCreateTextures(_target, 1, &const_cast(_id)); + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); + glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); + glTextureStorage2D(_id, newLevels, _internalFormat, newDimensions.x, newDimensions.y); + } // Copy the contents of the old texture to the new - GLuint fbo { 0 }; - glCreateFramebuffers(1, &fbo); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - for (uint16 targetMip = _minMip; targetMip <= _maxMip; ++targetMip) { - uint16 sourceMip = targetMip + mipDelta; - Vec3u mipDimensions = _gpuObject.evalMipDimensions(targetMip + _mipOffset); - for (GLenum target : getFaceTargets(_target)) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, oldId, sourceMip); - (void)CHECK_GL_ERROR(); - glCopyTextureSubImage2D(_id, targetMip, 0, 0, 0, 0, mipDimensions.x, mipDimensions.y); - (void)CHECK_GL_ERROR(); + { + PROFILE_RANGE(render_gpu_gl, "Blit"); + // Preferred path only available in 4.3 + for (uint16 targetMip = _minMip; targetMip <= _maxMip; ++targetMip) { + uint16 sourceMip = targetMip + mipDelta; + Vec3u mipDimensions = _gpuObject.evalMipDimensions(targetMip + _mipOffset); + for (GLenum target : getFaceTargets(_target)) { + glCopyImageSubData( + oldId, target, sourceMip, 0, 0, 0, + _id, target, targetMip, 0, 0, 0, + mipDimensions.x, mipDimensions.y, 1 + ); + (void)CHECK_GL_ERROR(); + } } + + glDeleteTextures(1, &oldId); } - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); - glDeleteTextures(1, &oldId); } // Re-sync the sampler to force access to the new mip level diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index 5903be11d3..db87950e5a 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -17,7 +17,6 @@ #include #include "ModelLogging.h" - using namespace model; using namespace gpu; @@ -142,8 +141,9 @@ const QImage TextureUsage::process2DImageColor(const QImage& srcImage, bool& val validAlpha = (numOpaques != NUM_PIXELS); } - if (!validAlpha && image.format() != QImage::Format_RGB888) { - image = image.convertToFormat(QImage::Format_RGB888); + // Force all the color images to be rgba32bits + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); } return image; @@ -200,7 +200,7 @@ const QImage& image, bool isLinear, bool doCompress) { } } -#define CPU_MIPMAPS 0 +#define CPU_MIPMAPS 1 void generateMips(gpu::Texture* texture, QImage& image, gpu::Element formatMip) { #if CPU_MIPMAPS @@ -286,15 +286,16 @@ gpu::Texture* TextureUsage::createNormalTextureFromNormalImage(const QImage& src PROFILE_RANGE(resource_parse, "createNormalTextureFromNormalImage"); QImage image = processSourceImage(srcImage, false); - if (image.format() != QImage::Format_RGB888) { - image = image.convertToFormat(QImage::Format_RGB888); + // Make sure the normal map source image is RGBA32 + if (image.format() != QImage::Format_RGBA8888) { + image = image.convertToFormat(QImage::Format_RGBA8888); } gpu::Texture* theTexture = nullptr; if ((image.width() > 0) && (image.height() > 0)) { - - gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); - gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); + + gpu::Element formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); + gpu::Element formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); theTexture->setSource(srcImageName); @@ -330,7 +331,8 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm const double pStrength = 2.0; int width = image.width(); int height = image.height(); - QImage result(width, height, QImage::Format_RGB888); + // THe end result image for normal map is RGBA32 even though the A is not used + QImage result(width, height, QImage::Format_RGBA8888); for (int i = 0; i < width; i++) { const int iNextClamped = clampPixelCoordinate(i + 1, width - 1); @@ -378,8 +380,8 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm gpu::Texture* theTexture = nullptr; if ((image.width() > 0) && (image.height() > 0)) { - gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); - gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); + gpu::Element formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); + gpu::Element formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA); theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); theTexture->setSource(srcImageName); @@ -398,8 +400,8 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromImage(const QImage& srcIma image = image.convertToFormat(QImage::Format_RGB888); } } else { - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); + if (image.format() != QImage::Format_RGBA8888) { + image = image.convertToFormat(QImage::Format_RGBA8888); } } @@ -433,8 +435,8 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromGlossImage(const QImage& s image = image.convertToFormat(QImage::Format_RGB888); } } else { - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); + if (image.format() != QImage::Format_RGBA8888) { + image = image.convertToFormat(QImage::Format_RGBA8888); } } @@ -472,8 +474,8 @@ gpu::Texture* TextureUsage::createMetallicTextureFromImage(const QImage& srcImag image = image.convertToFormat(QImage::Format_RGB888); } } else { - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); + if (image.format() != QImage::Format_RGBA8888) { + image = image.convertToFormat(QImage::Format_RGBA8888); } } @@ -762,8 +764,8 @@ gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcIm gpu::Texture* theTexture = nullptr; if ((srcImage.width() > 0) && (srcImage.height() > 0)) { QImage image = processSourceImage(srcImage, true); - if (image.format() != QImage::Format_RGB888) { - image = image.convertToFormat(QImage::Format_RGB888); + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); } gpu::Element formatGPU; diff --git a/libraries/networking/src/EntityScriptClient.cpp b/libraries/networking/src/EntityScriptClient.cpp index 604a82fcca..a2c01312e6 100644 --- a/libraries/networking/src/EntityScriptClient.cpp +++ b/libraries/networking/src/EntityScriptClient.cpp @@ -162,4 +162,4 @@ void EntityScriptClient::forceFailureOfPendingRequests(SharedNodePointer node) { messageMapIt->second.clear(); } } -} \ No newline at end of file +} diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 88b5ec19ad..e1197bf8a5 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -111,7 +111,8 @@ public: EntityScriptGetStatusReply, ReloadEntityServerScript, EntityPhysics, - LAST_PACKET_TYPE = EntityPhysics + EntityServerScriptLog, + LAST_PACKET_TYPE = EntityServerScriptLog }; }; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index e1627f2fd6..665e063a6e 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1178,7 +1178,7 @@ void Model::updateClusterMatrices() { for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); auto jointMatrix = _rig->getJointTransform(cluster.jointIndex); -#if GLM_ARCH & GLM_ARCH_SSE2 +#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MACOS) glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix; glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out); state.clusterMatrices[j] = out; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 2191d45d45..6e99ed0b14 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -64,6 +64,8 @@ #include "ScriptEngines.h" #include "TabletScriptingInterface.h" +#include + #include "MIDIEvent.h" static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3"; @@ -863,6 +865,10 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi } void ScriptEngine::run() { + auto filenameParts = _fileNameString.split("/"); + auto name = filenameParts.size() > 0 ? filenameParts[filenameParts.size() - 1] : "unknown"; + PROFILE_SET_THREAD_NAME("Script: " + name); + if (DependencyManager::get()->isStopped()) { return; // bail early - avoid setting state in init(), as evaluate() will bail too } @@ -923,7 +929,10 @@ void ScriptEngine::run() { bool processedEvents = false; while (!_isFinished && clock::now() < sleepUntil) { - QCoreApplication::processEvents(); // before we sleep again, give events a chance to process + { + PROFILE_RANGE(script, "processEvents-sleep"); + QCoreApplication::processEvents(); // before we sleep again, give events a chance to process + } processedEvents = true; // If after processing events, we're past due, exit asap @@ -938,6 +947,8 @@ void ScriptEngine::run() { std::this_thread::sleep_until(smallSleepUntil); } + PROFILE_RANGE(script, "ScriptMainLoop"); + #ifdef SCRIPT_DELAY_DEBUG { auto actuallySleptUntil = clock::now(); @@ -961,6 +972,7 @@ void ScriptEngine::run() { // Only call this if we didn't processEvents as part of waiting for next frame if (!processedEvents) { + PROFILE_RANGE(script, "processEvents"); QCoreApplication::processEvents(); } @@ -985,7 +997,10 @@ void ScriptEngine::run() { float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND; if (!_isFinished) { auto preUpdate = clock::now(); - emit update(deltaTime); + { + PROFILE_RANGE(script, "ScriptUpdate"); + emit update(deltaTime); + } auto postUpdate = clock::now(); auto elapsed = (postUpdate - preUpdate); totalUpdates += std::chrono::duration_cast(elapsed); @@ -1393,6 +1408,16 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin } } +int ScriptEngine::getNumRunningEntityScripts() const { + int sum = 0; + for (auto& st : _entityScripts) { + if (st.status == RUNNING) { + ++sum; + } + } + return sum; +} + bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const { auto it = _entityScripts.constFind(entityID); if (it == _entityScripts.constEnd()) { @@ -1456,6 +1481,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co newDetails.status = ERROR_LOADING_SCRIPT; newDetails.errorInfo = "Failed to load script"; _entityScripts[entityID] = newDetails; + emit entityScriptDetailsUpdated(); return; } @@ -1467,6 +1493,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co newDetails.status = ERROR_RUNNING_SCRIPT; newDetails.errorInfo = "Bad syntax"; _entityScripts[entityID] = newDetails; + emit entityScriptDetailsUpdated(); return; // done processing script } @@ -1497,6 +1524,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co newDetails.status = ERROR_RUNNING_SCRIPT; newDetails.errorInfo = exceptionMessage; _entityScripts[entityID] = newDetails; + emit entityScriptDetailsUpdated(); return; } @@ -1523,6 +1551,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co newDetails.status = ERROR_RUNNING_SCRIPT; newDetails.errorInfo = "Could not find constructor"; _entityScripts[entityID] = newDetails; + emit entityScriptDetailsUpdated(); return; // done processing script } @@ -1544,6 +1573,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co newDetails.lastModified = lastModified; newDetails.definingSandboxURL = sandboxURL; _entityScripts[entityID] = newDetails; + emit entityScriptDetailsUpdated(); if (isURL) { setParentURL(""); @@ -1575,6 +1605,7 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { } _entityScripts.remove(entityID); stopAllTimersForEntityScript(entityID); + emit entityScriptDetailsUpdated(); } } @@ -1596,6 +1627,7 @@ void ScriptEngine::unloadAllEntityScripts() { } } _entityScripts.clear(); + emit entityScriptDetailsUpdated(); #ifdef DEBUG_ENGINE_STATE qCDebug(scriptengine) << "---- CURRENT STATE OF ENGINE: --------------------------"; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 32d81b9511..3fc79aca9c 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -199,6 +199,7 @@ public: void scriptWarningMessage(const QString& message); void scriptInfoMessage(const QString& message); + int getNumRunningEntityScripts() const; bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; public slots: @@ -222,6 +223,10 @@ signals: void reloadScript(const QString& scriptName, bool isUserLoaded); void doneRunning(); + // Emitted when an entity script is added or removed, or when the status of an entity + // script is updated (goes from RUNNING to ERROR_RUNNING_SCRIPT, for example) + void entityScriptDetailsUpdated(); + protected: void init(); diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 2414ec469f..71f0073ead 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -183,6 +183,18 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr _qmlTabletRoot = qmlTabletRoot; if (_qmlTabletRoot && _qmlOffscreenSurface) { QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant))); + + // forward qml surface events to interface js + connect(dynamic_cast(_qmlOffscreenSurface), &OffscreenQmlSurface::fromQml, [this](QVariant message) { + if (message.canConvert()) { + emit fromQml(qvariant_cast(message).toVariant()); + } else if (message.canConvert()) { + emit fromQml(message.toString()); + } else { + qWarning() << "fromQml: Unsupported message type " << message; + } + }); + gotoHomeScreen(); QMetaObject::invokeMethod(_qmlTabletRoot, "setUsername", Q_ARG(const QVariant&, QVariant(getUsername()))); @@ -197,6 +209,7 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr } else { removeButtonsFromHomeScreen(); _state = State::Uninitialized; + emit screenChanged(QVariant("Closed"), QVariant("")); } } @@ -208,10 +221,21 @@ void TabletProxy::gotoMenuScreen() { QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen()), Qt::DirectConnection); QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL))); _state = State::Menu; + emit screenChanged(QVariant("Menu"), QVariant(VRMENU_SOURCE_URL)); } } } +void TabletProxy::loadQMLSource(const QVariant& path) { + if (_qmlTabletRoot) { + if (_state != State::QML) { + removeButtonsFromHomeScreen(); + QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, path)); + _state = State::QML; + emit screenChanged(QVariant("QML"), path); + } + } +} void TabletProxy::gotoHomeScreen() { if (_qmlTabletRoot) { if (_state != State::Home) { @@ -220,6 +244,7 @@ void TabletProxy::gotoHomeScreen() { QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound"); _state = State::Home; + emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); } } } @@ -236,6 +261,7 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS if (_state != State::Web) { QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL))); _state = State::Web; + emit screenChanged(QVariant("Web"), QVariant(url)); } QMetaObject::invokeMethod(_qmlTabletRoot, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); @@ -298,6 +324,12 @@ void TabletProxy::emitScriptEvent(QVariant msg) { } } +void TabletProxy::sendToQml(QVariant msg) { + if (_qmlOffscreenSurface) { + QMetaObject::invokeMethod(_qmlOffscreenSurface, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg)); + } +} + void TabletProxy::addButtonsToHomeScreen() { auto tablet = getQmlTablet(); if (!tablet) { diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index 4fe2c44c10..93f5bcf6ba 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -89,6 +89,8 @@ public: Q_INVOKABLE void gotoWebScreen(const QString& url); Q_INVOKABLE void gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl); + Q_INVOKABLE void loadQMLSource(const QVariant& path); + /**jsdoc * Creates a new button, adds it to this and returns it. * @function TabletProxy#addButton @@ -120,6 +122,13 @@ public: */ Q_INVOKABLE void emitScriptEvent(QVariant msg); + /**jsdoc + * Used to send an event to the qml embedded in the tablet + * @function TabletProxy#sendToQml + * @param msg {object|string} + */ + Q_INVOKABLE void sendToQml(QVariant msg); + Q_INVOKABLE bool onHomeScreen(); QObject* getTabletSurface(); @@ -137,6 +146,22 @@ signals: */ void webEventReceived(QVariant msg); + /**jsdoc + * Signaled when this tablet receives an event from the qml embedded in the tablet + * @function TabletProxy#fromQml + * @param msg {object|string} + * @returns {Signal} + */ + void fromQml(QVariant msg); + + /**jsdoc + * Signales when this tablet screen changes. + * @function TabletProxy#screenChanged + * @param type {string} - "Home", "Web", "Menu", "QML", "Closed" + * @param url {string} - only valid for Web and QML. + */ + void screenChanged(QVariant type, QVariant url); + private slots: void addButtonsToHomeScreen(); void addButtonsToMenuScreen(); @@ -149,7 +174,7 @@ protected: QQuickItem* _qmlTabletRoot { nullptr }; QObject* _qmlOffscreenSurface { nullptr }; - enum class State { Uninitialized, Home, Web, Menu }; + enum class State { Uninitialized, Home, Web, Menu, QML }; State _state { State::Uninitialized }; }; diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index 8e2c372bb6..e40ef814ea 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -177,7 +177,7 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont prefixString.append(QString(" [%1]").arg(_targetName)); } - QString logMessage = QString("%1 %2").arg(prefixString, message.split("\n").join("\n" + prefixString + " ")); + QString logMessage = QString("%1 %2").arg(prefixString, message.split('\n').join('\n' + prefixString + " ")); fprintf(stdout, "%s\n", qPrintable(logMessage)); return logMessage; } diff --git a/libraries/shared/src/Profile.cpp b/libraries/shared/src/Profile.cpp index f23c17c6be..7a8a8f0570 100644 --- a/libraries/shared/src/Profile.cpp +++ b/libraries/shared/src/Profile.cpp @@ -10,6 +10,7 @@ Q_LOGGING_CATEGORY(trace_app, "trace.app") Q_LOGGING_CATEGORY(trace_app_detail, "trace.app.detail") +Q_LOGGING_CATEGORY(trace_metadata, "trace.metadata") Q_LOGGING_CATEGORY(trace_network, "trace.network") Q_LOGGING_CATEGORY(trace_parse, "trace.parse") Q_LOGGING_CATEGORY(trace_render, "trace.render") @@ -18,6 +19,8 @@ Q_LOGGING_CATEGORY(trace_render_gpu, "trace.render.gpu") Q_LOGGING_CATEGORY(trace_resource, "trace.resource") Q_LOGGING_CATEGORY(trace_resource_network, "trace.resource.network") Q_LOGGING_CATEGORY(trace_resource_parse, "trace.resource.parse") +Q_LOGGING_CATEGORY(trace_script, "trace.script") +Q_LOGGING_CATEGORY(trace_script_entities, "trace.script.entities") Q_LOGGING_CATEGORY(trace_simulation, "trace.simulation") Q_LOGGING_CATEGORY(trace_simulation_detail, "trace.simulation.detail") Q_LOGGING_CATEGORY(trace_simulation_animation, "trace.simulation.animation") diff --git a/libraries/shared/src/Profile.h b/libraries/shared/src/Profile.h index da786526a7..ee09298deb 100644 --- a/libraries/shared/src/Profile.h +++ b/libraries/shared/src/Profile.h @@ -16,6 +16,7 @@ // When profiling something that may happen many times per frame, use a xxx_detail category so that they may easily be filtered out of trace results Q_DECLARE_LOGGING_CATEGORY(trace_app) Q_DECLARE_LOGGING_CATEGORY(trace_app_detail) +Q_DECLARE_LOGGING_CATEGORY(trace_metadata) Q_DECLARE_LOGGING_CATEGORY(trace_network) Q_DECLARE_LOGGING_CATEGORY(trace_render) Q_DECLARE_LOGGING_CATEGORY(trace_render_detail) @@ -23,6 +24,8 @@ Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu) Q_DECLARE_LOGGING_CATEGORY(trace_resource) Q_DECLARE_LOGGING_CATEGORY(trace_resource_parse) Q_DECLARE_LOGGING_CATEGORY(trace_resource_network) +Q_DECLARE_LOGGING_CATEGORY(trace_script) +Q_DECLARE_LOGGING_CATEGORY(trace_script_entities) Q_DECLARE_LOGGING_CATEGORY(trace_simulation) Q_DECLARE_LOGGING_CATEGORY(trace_simulation_detail) Q_DECLARE_LOGGING_CATEGORY(trace_simulation_animation) @@ -69,6 +72,10 @@ inline void counter(const QLoggingCategory& category, const QString& name, const } } +inline void metadata(const QString& metadataType, const QVariantMap& args) { + tracing::traceEvent(trace_metadata(), metadataType, tracing::Metadata, "", args); +} + #define PROFILE_RANGE(category, name) Duration profileRangeThis(trace_##category(), name); #define PROFILE_RANGE_EX(category, name, argbColor, payload, ...) Duration profileRangeThis(trace_##category(), name, argbColor, (uint64_t)payload, ##__VA_ARGS__); #define PROFILE_RANGE_BEGIN(category, rangeId, name, argbColor) rangeId = Duration::beginRange(trace_##category(), name, argbColor) @@ -78,6 +85,7 @@ inline void counter(const QLoggingCategory& category, const QString& name, const #define PROFILE_COUNTER_IF_CHANGED(category, name, type, value) { static type lastValue = 0; type newValue = value; if (newValue != lastValue) { counter(trace_##category(), name, { { name, newValue }}); lastValue = newValue; } } #define PROFILE_COUNTER(category, name, ...) counter(trace_##category(), name, ##__VA_ARGS__); #define PROFILE_INSTANT(category, name, ...) instant(trace_##category(), name, ##__VA_ARGS__); +#define PROFILE_SET_THREAD_NAME(threadName) metadata("thread_name", { { "name", threadName } }); #define SAMPLE_PROFILE_RANGE(chance, category, name, ...) if (randFloat() <= chance) { PROFILE_RANGE(category, name); } #define SAMPLE_PROFILE_RANGE_EX(chance, category, name, ...) if (randFloat() <= chance) { PROFILE_RANGE_EX(category, name, argbColor, payload, ##__VA_ARGS__); } diff --git a/libraries/shared/src/SettingHandle.cpp b/libraries/shared/src/SettingHandle.cpp index a04a7c3f61..2bfc904c0a 100644 --- a/libraries/shared/src/SettingHandle.cpp +++ b/libraries/shared/src/SettingHandle.cpp @@ -28,9 +28,10 @@ QString Settings::fileName() const { } void Settings::remove(const QString& key) { - if (key == "" || _manager->contains(key)) { - _manager->remove(key); - } + // NOTE: you can't check _manager->contains(key) here because it will return 'false' + // when the key is a valid child group with child keys. + // However, calling remove() without checking will do the right thing. + _manager->remove(key); } QStringList Settings::childGroups() const { @@ -46,6 +47,7 @@ QStringList Settings::allKeys() const { } bool Settings::contains(const QString& key) const { + // NOTE: this will return 'false' if key is a valid child group with child keys. return _manager->contains(key); } diff --git a/libraries/shared/src/Trace.cpp b/libraries/shared/src/Trace.cpp index e4ad70d912..1e3d490a9c 100644 --- a/libraries/shared/src/Trace.cpp +++ b/libraries/shared/src/Trace.cpp @@ -132,6 +132,9 @@ void Tracer::serialize(const QString& originalPath) { { std::lock_guard guard(_eventsMutex); currentEvents.swap(_events); + for (auto& event : _metadataEvents) { + currentEvents.push_back(event); + } } // If the file exists and we can't remove it, fail early @@ -194,33 +197,53 @@ void Tracer::serialize(const QString& originalPath) { #endif } -void Tracer::traceEvent(const QLoggingCategory& category, - const QString& name, EventType type, - qint64 timestamp, qint64 processID, qint64 threadID, - const QString& id, - const QVariantMap& args, const QVariantMap& extra) { +void Tracer::traceEvent(const QLoggingCategory& category, + const QString& name, EventType type, + qint64 timestamp, qint64 processID, qint64 threadID, + const QString& id, + const QVariantMap& args, const QVariantMap& extra) { std::lock_guard guard(_eventsMutex); - if (!_enabled) { + + // We always want to store metadata events even if tracing is not enabled so that when + // tracing is enabled we will be able to associate that metadata with that trace. + // Metadata events should be used sparingly - as of 12/30/16 the Chrome Tracing + // spec only supports thread+process metadata, so we should only expect to see metadata + // events created when a new thread or process is created. + if (!_enabled && type != Metadata) { return; } - _events.push_back({ - id, - name, - type, - timestamp, - processID, - threadID, - category, - args, - extra - }); + if (type == Metadata) { + _metadataEvents.push_back({ + id, + name, + type, + timestamp, + processID, + threadID, + category, + args, + extra + }); + } else { + _events.push_back({ + id, + name, + type, + timestamp, + processID, + threadID, + category, + args, + extra + }); + } } void Tracer::traceEvent(const QLoggingCategory& category, const QString& name, EventType type, const QString& id, const QVariantMap& args, const QVariantMap& extra) { - if (!_enabled) { + if (!_enabled && type != Metadata) { return; } diff --git a/libraries/shared/src/Trace.h b/libraries/shared/src/Trace.h index ee4f28f0ce..93e2c6c4c2 100644 --- a/libraries/shared/src/Trace.h +++ b/libraries/shared/src/Trace.h @@ -97,6 +97,7 @@ private: bool _enabled { false }; std::list _events; + std::list _metadataEvents; std::mutex _eventsMutex; }; diff --git a/script-archive/acScripts/BetterClientSimulationBotFromRecording.js b/script-archive/acScripts/BetterClientSimulationBotFromRecording.js new file mode 100644 index 0000000000..fafb761729 --- /dev/null +++ b/script-archive/acScripts/BetterClientSimulationBotFromRecording.js @@ -0,0 +1,183 @@ +// +// BetterClientSimulationBotFromRecording.js +// examples +// +// Created by Brad Hefta-Gaub on 2/6/17. +// 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 +// + + +var WANT_DEBUGGING = false; + +randFloat = function(low, high) { + return low + Math.random() * (high - low); +} + +var AVATARS_ARRAY = [ + "http://mpassets.highfidelity.com/0c2c264b-2fd2-46a4-bf80-de681881f66b-v1/F_MotRac.fst", + "http://mpassets.highfidelity.com/bd80a6d7-7173-489e-87c6-f7ee56e65530-v1/M_RetFut.fst", + "http://mpassets.highfidelity.com/47c8d706-d486-4c2d-afcc-70d4e1e25117-v1/M_RetSpaSuit.fst", + "http://mpassets.highfidelity.com/548d0792-0bac-4933-bbfc-57d71912d77e-v1/M_OutMer.fst", + "http://mpassets.highfidelity.com/13277c09-892f-4a5e-b9a5-8994a37d68bf-v1/F_WasWar.fst", + "http://mpassets.highfidelity.com/2d384111-0f0e-42e2-b800-66bfcab4aefb-v1/F_VooQue.fst", + "http://mpassets.highfidelity.com/57e4d1cd-9f52-4c95-9051-326f9bb114ea-v1/F_SteAvi.fst", + "http://mpassets.highfidelity.com/da2ad4cd-47d4-41da-b764-41f39ff77e30-v1/F_JerGir.fst", + "http://mpassets.highfidelity.com/96c747ab-f71b-44ee-8eb9-d19fc9593dda-v1/F_CatBur.fst", + "http://mpassets.highfidelity.com/ede82c38-c66e-4f67-9e0b-0bb0782db18f-v1/M_WesOut.fst", + "http://mpassets.highfidelity.com/8872ae86-a763-4db3-8373-d27514c1481e-v1/M_VinAvi.fst", + "http://mpassets.highfidelity.com/faf505f1-4fd1-4ed2-8909-816af246c48f-v1/M_VicGen.fst", + "http://mpassets.highfidelity.com/d807a7d2-5122-4436-a6f9-3173c94d1c49-v1/M_SuaGen.fst", + "http://mpassets.highfidelity.com/1dd41735-06f4-45a3-9ec0-d05215ace77b-v1/M_MarSen.fst", + "http://mpassets.highfidelity.com/2cad3894-8ab3-4ba5-a723-0234f93fbd6a-v1/M_BowBea.fst", + "http://mpassets.highfidelity.com/cf0eb1be-9ec7-4756-8eaf-ac8f3ec09eba-v1/F_ClaDef.fst", + "http://mpassets.highfidelity.com/0cedeca3-c1a4-4be9-9fd5-dad716afcc7e-v1/F_Cyria.fst", + "http://mpassets.highfidelity.com/dc55803b-9215-47dd-9408-eb835dac4082-v1/F_ParGir.fst", + "http://mpassets.highfidelity.com/775a8fb3-cfe7-494d-b603-a0a2d6910e55-v1/F_VinCov.fst", + "http://mpassets.highfidelity.com/eba0d8f8-aa72-4a6b-ab64-4d3fd4695b20-v1/F_VogHei.fst", + "http://mpassets.highfidelity.com/4f400c78-38f9-42af-b03b-11b5451d41b9-v1/M_MidRog.fst", + "http://mpassets.highfidelity.com/ad774d79-13f1-46e2-87c9-de49a261b264-v1/F_GunSli.fst", + "http://mpassets.highfidelity.com/5acbaefa-5455-49a2-8d40-89d12aa393ca-v1/M_KniWol.fst", + "http://mpassets.highfidelity.com/aaa1b0a8-3e1b-492a-9aee-600e5dc907db-v1/F_RetSciSuit.fst", + "http://mpassets.highfidelity.com/d8da10b6-25c1-40e2-9a66-369316c722d7-v1/F_AniSuit.fst", + "http://mpassets.highfidelity.com/f3fbb9f4-e159-49ed-ac32-03af9056b17e-v1/matthew.fst", + "http://mpassets.highfidelity.com/0c954ba0-4d87-4353-b65e-c45509f85658-v1/priscilla.fst", + "http://mpassets.highfidelity.com/e76946cc-c272-4adf-9bb6-02cde0a4b57d-v1/9e8c5c42a0cbd436962d6bd36f032ab3.fst", + "http://mpassets.highfidelity.com/72e083ee-194d-4113-9c61-0591d8257493-v1/skeleton_Rigged.fst", + "http://mpassets.highfidelity.com/f14bf7c9-49a1-4249-988a-0a577ed78957-v1/beingOfLight.fst", + "http://mpassets.highfidelity.com/1b7e1e7c-6c0b-4f20-9cd0-1d5ccedae620-v1/bb64e937acf86447f6829767e958073c.fst", + "http://mpassets.highfidelity.com/67d7c7aa-c300-4d03-85f4-86480130eaa5-v1/F_StarCrew.fst", + "http://mpassets.highfidelity.com/d293ef06-c659-467a-9288-c3cbaff0372a-v1/arya_avatar.fst", + "http://mpassets.highfidelity.com/faf249d5-12a8-48e2-a08e-fb0c33087011-v1/F_Ranger.fst", + "http://mpassets.highfidelity.com/b4502145-15eb-4023-b7d6-a81c5cbf6abf-v1/F_FitTra.fst", + "http://mpassets.highfidelity.com/548d0792-0bac-4933-bbfc-57d71912d77e-v1/M_OutMer.fst", + "http://mpassets.highfidelity.com/caa61e5d-5629-4165-81d8-6a7eb55e942d-v1/F_DeaSur.fst", + "http://mpassets.highfidelity.com/2cad3894-8ab3-4ba5-a723-0234f93fbd6a-v1/M_BowBea.fst", + "http://mpassets.highfidelity.com/fd4fa45a-9d2a-463e-a484-f9d1b3bba724-v1/M_BeaWar.fst", + "http://mpassets.highfidelity.com/367a5b60-8a92-4d56-a152-a00f3086f02b-v1/M_Espio.fst", + "http://mpassets.highfidelity.com/ab466729-31da-4b4c-a33c-366f7c1d38e5-v1/M_MMAFig.fst", + "http://mpassets.highfidelity.com/b0795a0c-493d-4abd-b4cc-5f32e6d6df46-v1/M_SalMer.fst", + "http://mpassets.highfidelity.com/0a1d44bf-a988-4199-b29e-a532ab85a2e8-v1/M_StaShi.fst", + "http://mpassets.highfidelity.com/d807a7d2-5122-4436-a6f9-3173c94d1c49-v1/M_SuaGen.fst", + "http://mpassets.highfidelity.com/cb20212c-36f2-4d41-bdad-132361ca6ff4-v1/M_TreTee.fst", + "http://mpassets.highfidelity.com/830988dc-619a-4e88-96e1-a19fa0aaa30f-v1/M_UrbEnf.fst", + "http://mpassets.highfidelity.com/faf505f1-4fd1-4ed2-8909-816af246c48f-v1/M_VicGen.fst", + "http://mpassets.highfidelity.com/883ac86f-dd29-4676-8bda-7dd52fb6465f-v1/M_WasWan.fst", + "http://mpassets.highfidelity.com/ede82c38-c66e-4f67-9e0b-0bb0782db18f-v1/M_WesOut.fst", + "http://mpassets.highfidelity.com/04c9a1e9-0390-4a7f-b6c6-5f135c19e3fb-v1/F_ArmTro.fst", + "http://mpassets.highfidelity.com/e863348f-a777-4f36-86e6-af6e65ffa161-v1/F_BloSam.fst", + "http://mpassets.highfidelity.com/cf0eb1be-9ec7-4756-8eaf-ac8f3ec09eba-v1/F_ClaDef.fst", + "http://mpassets.highfidelity.com/0cedeca3-c1a4-4be9-9fd5-dad716afcc7e-v1/F_Cyria.fst", + "http://mpassets.highfidelity.com/da2ad4cd-47d4-41da-b764-41f39ff77e30-v1/F_JerGir.fst", + "http://mpassets.highfidelity.com/534d42f8-ec13-4145-929f-5c8facac2fb7-v1/F_LegFig.fst", + "http://mpassets.highfidelity.com/dc55803b-9215-47dd-9408-eb835dac4082-v1/F_ParGir.fst", + "http://mpassets.highfidelity.com/f823e831-d8c4-4191-a3bd-427e406e69f9-v1/F_Shinjuku.fst", + "http://mpassets.highfidelity.com/eba0d8f8-aa72-4a6b-ab64-4d3fd4695b20-v1/F_VogHei.fst", + "http://mpassets.highfidelity.com/13277c09-892f-4a5e-b9a5-8994a37d68bf-v1/F_WasWar.fst", + "http://mpassets.highfidelity.com/9b589fbb-59e4-47a9-8b3f-bf8d3a0bd1d8-v1/M_LawSur.fst", + "http://mpassets.highfidelity.com/4f400c78-38f9-42af-b03b-11b5451d41b9-v1/M_MidRog.fst", + "http://mpassets.highfidelity.com/c90d755d-0456-48fd-b98c-09c4d85cd481-v1/M_MouOff.fst", + "http://mpassets.highfidelity.com/c2ed3b9a-b3a9-4424-9fd2-8a798209f32b-v1/M_PerTra.fst", + "http://mpassets.highfidelity.com/c48928ac-7657-41f4-bbdc-9b47385736ab-v1/M_SpaMar.fst", + "http://mpassets.highfidelity.com/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73-v1/4618d52e711fbb34df442b414da767bb.fst", + "http://mpassets.highfidelity.com/c85c497d-c87b-42b1-9bbf-5405e05a0ad3-v1/M_ArmSol.fst", + "http://mpassets.highfidelity.com/1dd41735-06f4-45a3-9ec0-d05215ace77b-v1/M_MarSen.fst", + "http://mpassets.highfidelity.com/bd80a6d7-7173-489e-87c6-f7ee56e65530-v1/M_RetFut.fst", + "http://mpassets.highfidelity.com/8872ae86-a763-4db3-8373-d27514c1481e-v1/M_VinAvi.fst", + "http://mpassets.highfidelity.com/f798d926-9a9e-481a-b298-af0e45451252-v1/F_Assassin.fst", + "http://mpassets.highfidelity.com/ad774d79-13f1-46e2-87c9-de49a261b264-v1/F_GunSli.fst", + "http://mpassets.highfidelity.com/aaa1b0a8-3e1b-492a-9aee-600e5dc907db-v1/F_RetSciSuit.fst" + ]; + + +var AVATAR_URL = AVATARS_ARRAY[Math.floor(Math.random() * AVATARS_ARRAY.length)]; +print("RANDOM AVATAR SELECTED:" + AVATAR_URL); + +var RECORDINGS_ARRAY = [ + "http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/waiting6.hfr", + "http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/waiting7.hfr", + "http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/waiting10.hfr", + "http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/bot1.hfr", + "http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/bot2.hfr", + "http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/bot3.hfr", + "http://hifi-content.s3.amazonaws.com/DomainContent/Event%20/NPC%27s/bot4.hfr" + ]; + +var RECORDING_URL = RECORDINGS_ARRAY[Math.floor(Math.random() * RECORDINGS_ARRAY.length)]; +print("RANDOM RECORDING SELECTED:" + RECORDING_URL); + +// not quite what I want... +var LOCATIONS_ARRAY = [ + { min_x: 97.0, max_x: 103.0, y:-0.6, min_z: 30.8, max_z: 40 }, + { min_x: 92.7, max_x: 106.6, y:-0.3, min_z: 43 , max_z: 43 }, + { min_x: 92.7, max_x: 106.6, y: 0.3, min_z: 45 , max_z: 45 }, + { min_x: 92.7, max_x: 106.6, y: 1 , min_z: 47 , max_z: 47 }, + { min_x: 92.7, max_x: 106.6, y: 1.7, min_z: 51.9, max_z: 51.9 }, +]; + +var LOCATION_PARAMS = LOCATIONS_ARRAY[Math.floor(Math.random() * LOCATIONS_ARRAY.length)]; + +var LOCATION = { x: randFloat(LOCATION_PARAMS.min_x, LOCATION_PARAMS.max_x), y: LOCATION_PARAMS.y, z: randFloat(LOCATION_PARAMS.min_z, LOCATION_PARAMS.max_z) }; + +Vec3.print("RANDOM LOCATION SELECTED:", LOCATION); + +var playFromCurrentLocation = true; +var loop = true; + + +// Set position here if playFromCurrentLocation is true +Avatar.position = LOCATION; +Avatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0); +Avatar.scale = 1.0; +Agent.isAvatar = true; + +// make the agent "listen" to the audio stream to cause additional audio-mixer load, technically this isn't needed when you're playing a recording +// but if you switch to a non-recording bot, you will need this, so we can leave this. +Agent.isListeningToAudioStream = true; +Avatar.skeletonModelURL = AVATAR_URL; // FIXME - currently setting an avatar while playing a recording doesn't work it will be ignored + +Recording.loadRecording(RECORDING_URL); + +count = 300; // This is necessary to wait for the audio mixer to connect +function update(event) { + if (count > 0) { + count--; + return; + } + if (count == 0) { + Recording.setPlayFromCurrentLocation(playFromCurrentLocation); + Recording.setPlayerLoop(loop); + Recording.setPlayerUseDisplayName(true); + Recording.setPlayerUseAttachments(true); + Recording.setPlayerUseHeadModel(false); + Recording.setPlayerUseSkeletonModel(false); // FIXME - this would allow you to override the recording avatar, but that's not currently working + Recording.startPlaying(); + Vec3.print("Playing from ", Avatar.position); + count--; + } else if (WANT_DEBUGGING) { + count = 100; + Vec3.print("Avatar at: ", Avatar.position); + Quat.print("Avatar head orientation: ", Avatar.headOrientation); + print("outbound:" + +" GP: " + Avatar.getDataRate("globalPositionOutbound").toFixed(2) + "\n" + +" LP: " + Avatar.getDataRate("localPositionOutbound").toFixed(2) + "\n" + +" BB: " + Avatar.getDataRate("avatarBoundingBoxOutbound").toFixed(2) + "\n" + +" AO: " + Avatar.getDataRate("avatarOrientationOutbound").toFixed(2) + "\n" + +" AS: " + Avatar.getDataRate("avatarScaleOutbound").toFixed(2) + "\n" + +" LA: " + Avatar.getDataRate("lookAtPositionOutbound").toFixed(2) + "\n" + +" AL: " + Avatar.getDataRate("audioLoudnessOutbound").toFixed(2) + "\n" + +" SW: " + Avatar.getDataRate("sensorToWorkMatrixOutbound").toFixed(2) + "\n" + +" AF: " + Avatar.getDataRate("additionalFlagsOutbound").toFixed(2) + "\n" + +" PI: " + Avatar.getDataRate("parentInfoOutbound").toFixed(2) + "\n" + +" FT: " + Avatar.getDataRate("faceTrackerOutbound").toFixed(2) + "\n" + +" JD: " + Avatar.getDataRate("jointDataOutbound").toFixed(2)); + } + + if (!Recording.isPlaying()) { + Script.update.disconnect(update); + } +} + +Script.update.connect(update); diff --git a/scripts/developer/debugging/debugAvatarMixer.js b/scripts/developer/debugging/debugAvatarMixer.js index 6c0a935b70..ebd43fc2f0 100644 --- a/scripts/developer/debugging/debugAvatarMixer.js +++ b/scripts/developer/debugging/debugAvatarMixer.js @@ -60,19 +60,26 @@ function updateOverlays() { var overlayPosition = avatar.getJointPosition("Head"); overlayPosition.y += 1.05; - var text = " All: " + AvatarManager.getAvatarDataRate(avatarID).toFixed(2) + "\n" - +" GP: " + AvatarManager.getAvatarDataRate(avatarID,"globalPosition").toFixed(2) + "\n" - +" LP: " + AvatarManager.getAvatarDataRate(avatarID,"localPosition").toFixed(2) + "\n" - +" BB: " + AvatarManager.getAvatarDataRate(avatarID,"avatarBoundingBox").toFixed(2) + "\n" - +" AO: " + AvatarManager.getAvatarDataRate(avatarID,"avatarOrientation").toFixed(2) + "\n" - +" AS: " + AvatarManager.getAvatarDataRate(avatarID,"avatarScale").toFixed(2) + "\n" - +" LA: " + AvatarManager.getAvatarDataRate(avatarID,"lookAtPosition").toFixed(2) + "\n" - +" AL: " + AvatarManager.getAvatarDataRate(avatarID,"audioLoudness").toFixed(2) + "\n" - +" SW: " + AvatarManager.getAvatarDataRate(avatarID,"sensorToWorkMatrix").toFixed(2) + "\n" - +" AF: " + AvatarManager.getAvatarDataRate(avatarID,"additionalFlags").toFixed(2) + "\n" - +" PI: " + AvatarManager.getAvatarDataRate(avatarID,"parentInfo").toFixed(2) + "\n" - +" FT: " + AvatarManager.getAvatarDataRate(avatarID,"faceTracker").toFixed(2) + "\n" - +" JD: " + AvatarManager.getAvatarDataRate(avatarID,"jointData").toFixed(2); + var text = avatarID + "\n" + +"--- Data from Mixer ---\n" + +"All: " + AvatarManager.getAvatarDataRate(avatarID).toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID).toFixed(2) + "hz)" + "\n" + +" GP: " + AvatarManager.getAvatarDataRate(avatarID,"globalPosition").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"globalPosition").toFixed(2) + "hz)" + "\n" + +" LP: " + AvatarManager.getAvatarDataRate(avatarID,"localPosition").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"localPosition").toFixed(2) + "hz)" + "\n" + +" BB: " + AvatarManager.getAvatarDataRate(avatarID,"avatarBoundingBox").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"avatarBoundingBox").toFixed(2) + "hz)" + "\n" + +" AO: " + AvatarManager.getAvatarDataRate(avatarID,"avatarOrientation").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"avatarOrientation").toFixed(2) + "hz)" + "\n" + +" AS: " + AvatarManager.getAvatarDataRate(avatarID,"avatarScale").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"avatarScale").toFixed(2) + "hz)" + "\n" + +" LA: " + AvatarManager.getAvatarDataRate(avatarID,"lookAtPosition").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"lookAtPosition").toFixed(2) + "hz)" + "\n" + +" AL: " + AvatarManager.getAvatarDataRate(avatarID,"audioLoudness").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"audioLoudness").toFixed(2) + "hz)" + "\n" + +" SW: " + AvatarManager.getAvatarDataRate(avatarID,"sensorToWorkMatrix").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"sensorToWorkMatrix").toFixed(2) + "hz)" + "\n" + +" AF: " + AvatarManager.getAvatarDataRate(avatarID,"additionalFlags").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"additionalFlags").toFixed(2) + "hz)" + "\n" + +" PI: " + AvatarManager.getAvatarDataRate(avatarID,"parentInfo").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"parentInfo").toFixed(2) + "hz)" + "\n" + +" FT: " + AvatarManager.getAvatarDataRate(avatarID,"faceTracker").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"faceTracker").toFixed(2) + "hz)" + "\n" + +" JD: " + AvatarManager.getAvatarDataRate(avatarID,"jointData").toFixed(2) + "kbps (" + AvatarManager.getAvatarUpdateRate(avatarID,"jointData").toFixed(2) + "hz)" + "\n" + +"--- Simulation ---\n" + +"All: " + AvatarManager.getAvatarSimulationRate(avatarID,"avatar").toFixed(2) + "hz \n" + +" inView: " + AvatarManager.getAvatarSimulationRate(avatarID,"avatarInView").toFixed(2) + "hz \n" + +" SM: " + AvatarManager.getAvatarSimulationRate(avatarID,"skeletonModel").toFixed(2) + "hz \n" + +" JD: " + AvatarManager.getAvatarSimulationRate(avatarID,"jointData").toFixed(2) + "hz \n" if (avatarID in debugOverlays) { // keep the overlay above the current position of this avatar @@ -85,8 +92,8 @@ function updateOverlays() { var newOverlay = Overlays.addOverlay("text3d", { position: overlayPosition, dimensions: { - x: 1, - y: 13 * 0.13 + x: 1.25, + y: 19 * 0.13 }, lineHeight: 0.1, font:{size:0.1}, diff --git a/scripts/developer/debugging/essDebugWindow.js b/scripts/developer/debugging/essDebugWindow.js new file mode 100644 index 0000000000..ef52694184 --- /dev/null +++ b/scripts/developer/debugging/essDebugWindow.js @@ -0,0 +1,28 @@ +// +// essDebugWindow.js +// +// Created by Clement Brisset on 2/2/17. +// 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +// Set up the qml ui +var qml = Script.resolvePath('debugWindow.qml'); +var window = new OverlayWindow({ + title: 'Entity Script Server Log Window', + source: qml, + width: 400, height: 900, +}); +window.setPosition(25, 50); +window.closed.connect(function() { Script.stop(); }); + +EntityScriptServerLog.receivedNewLogLines.connect(function(message) { + window.sendToQml(message.trim()); +}); + + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 087460deab..527a9cfc2b 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -26,6 +26,7 @@ Script.include("/~/system/libraries/controllers.js"); // // add lines where the hand ray picking is happening // + var WANT_DEBUG = false; var WANT_DEBUG_STATE = false; var WANT_DEBUG_SEARCH_NAME = null; @@ -752,6 +753,7 @@ function MyController(hand) { this.previouslyUnhooked = {}; this.shouldScale = false; + this.isScalingAvatar = false; // handPosition is where the avatar's hand appears to be, in-world. this.getHandPosition = function () { @@ -824,11 +826,7 @@ function MyController(hand) { this.update = function(deltaTime, timestamp) { this.updateSmoothedTrigger(); - // If both trigger and grip buttons squeezed and nothing is held, rescale my avatar! - if (this.hand === RIGHT_HAND && this.state === STATE_SEARCHING && - this.getOtherHandController().state === STATE_SEARCHING) { - this.maybeScaleMyAvatar(); - } + this.maybeScaleMyAvatar(); if (this.ignoreInput()) { @@ -2595,22 +2593,29 @@ function MyController(hand) { }; this.maybeScaleMyAvatar = function() { - if (!myAvatarScalingEnabled) { + if (!myAvatarScalingEnabled || this.shouldScale || this.hand === LEFT_HAND) { + // If scaling disabled, or if we are currently scaling an entity, don't scale avatar + // and only rescale avatar for one hand (so we're not doing it twice) return; } - if (!this.shouldScale) { + // Only scale avatar if both triggers and grips are squeezed + var tryingToScale = this.secondarySqueezed() && this.getOtherHandController().secondarySqueezed() && + this.triggerSmoothedSqueezed() && this.getOtherHandController().triggerSmoothedSqueezed(); + + + if (!this.isScalingAvatar) { // If both secondary triggers squeezed, start scaling - if (this.secondarySqueezed() && this.getOtherHandController().secondarySqueezed()) { + if (tryingToScale) { this.scalingStartDistance = Vec3.length(Vec3.subtract(this.getHandPosition(), this.getOtherHandController().getHandPosition())); this.scalingStartAvatarScale = MyAvatar.scale; - this.shouldScale = true; + this.isScalingAvatar = true; } - } else if (!this.secondarySqueezed() || !this.getOtherHandController().secondarySqueezed()) { - this.shouldScale = false; + } else if (!tryingToScale) { + this.isScalingAvatar = false; } - if (this.shouldScale) { + if (this.isScalingAvatar) { var scalingCurrentDistance = Vec3.length(Vec3.subtract(this.getHandPosition(), this.getOtherHandController().getHandPosition())); var newAvatarScale = (scalingCurrentDistance / this.scalingStartDistance) * this.scalingStartAvatarScale; diff --git a/scripts/system/nameTag.js b/scripts/system/nameTag.js new file mode 100644 index 0000000000..e25db69064 --- /dev/null +++ b/scripts/system/nameTag.js @@ -0,0 +1,112 @@ +"use strict"; + +/*jslint vars: true, plusplus: true*/ +/*global Entities, Script, Quat, Vec3, MyAvatar, print*/ +// nameTag.js +// +// Created by Triplelexx on 17/01/31 +// Copyright 2017 High Fidelity, Inc. +// +// Running the script creates a text entity that will hover over the user's head showing their display name. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +const CLIENTONLY = false; +const NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; +const ENTITY_CHECK_INTERVAL = 5000; // ms = 5 seconds +const STARTUP_DELAY = 2000; // ms = 2 second +const OLD_AGE = 3500; // we recreate the entity if older than this time in seconds +const TTL = 2; // time to live in seconds if script is not running +const HEIGHT_ABOVE_HEAD = 0.2; +const HEAD_OFFSET = -0.025; +const SIZE_Y = 0.075; +const LETTER_OFFSET = 0.03; // arbitrary value to dynamically change width, could be more accurate by detecting characters +const LINE_HEIGHT = 0.05; + +var nameTagEntityID = NULL_UUID; +var lastCheckForEntity = 0; + +// create the name tag entity after a brief delay +Script.setTimeout(function() { + addNameTag(); +}, STARTUP_DELAY); + +function addNameTag() { + var nameTagPosition = Vec3.sum(MyAvatar.getHeadPosition(), Vec3.multiply(HEAD_OFFSET, Quat.getFront(MyAvatar.orientation))); + nameTagPosition.y += HEIGHT_ABOVE_HEAD; + var nameTagProperties = { + name: MyAvatar.displayName + ' Name Tag', + type: 'Text', + text: MyAvatar.displayName, + lineHeight: LINE_HEIGHT, + parentID: MyAvatar.sessionUUID, + dimensions: dimensionsFromName(), + position: nameTagPosition + } + nameTagEntityID = Entities.addEntity(nameTagProperties, CLIENTONLY); +} + +function updateNameTag() { + var nameTagProps = Entities.getEntityProperties(nameTagEntityID); + var nameTagPosition = Vec3.sum(MyAvatar.getHeadPosition(), Vec3.multiply(HEAD_OFFSET, Quat.getFront(MyAvatar.orientation))); + nameTagPosition.y += HEIGHT_ABOVE_HEAD; + + Entities.editEntity(nameTagEntityID, { + position: nameTagPosition, + dimensions: dimensionsFromName(), + // lifetime is in seconds we add TTL on top of the next poll time + lifetime: Math.round(nameTagProps.age) + (ENTITY_CHECK_INTERVAL / 1000) + TTL, + text: MyAvatar.displayName + }); +}; + +function deleteNameTag() { + if(nameTagEntityID !== NULL_UUID) { + Entities.deleteEntity(nameTagEntityID); + nameTagEntityID = NULL_UUID; + } +} + +function dimensionsFromName() { + return { + x: LETTER_OFFSET * MyAvatar.displayName.length, + y: SIZE_Y, + z: 0.0 + } +}; + +// cleanup on ending +Script.scriptEnding.connect(cleanup); +function cleanup() { + deleteNameTag(); +} + +Script.update.connect(update); +function update() { + // if no entity we return + if(nameTagEntityID == NULL_UUID) { + return; + } + + if(Date.now() - lastCheckForEntity > ENTITY_CHECK_INTERVAL) { + checkForEntity(); + lastCheckForEntity = Date.now(); + } +} + +function checkForEntity() { + var nameTagProps = Entities.getEntityProperties(nameTagEntityID); + // it is possible for the age to not be a valid number, we check for this and return accordingly + if(nameTagProps.age == -1) { + return; + } + + // it's too old or we receive undefined make a new one, otherwise update + if(nameTagProps.age > OLD_AGE || nameTagProps.age == undefined) { + deleteNameTag(); + addNameTag(); + } else { + updateNameTag(); + } +} diff --git a/scripts/system/pal.js b/scripts/system/pal.js index adbde0ef5c..2e07a2d431 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -203,8 +203,7 @@ var pal = new OverlayWindow({ height: 640, visible: false }); -pal.fromQml.connect(function (message) { // messages are {method, params}, like json-rpc. See also sendToQml. - print('From PAL QML:', JSON.stringify(message)); +function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. switch (message.method) { case 'selected': selectedIds = message.params; @@ -259,7 +258,15 @@ pal.fromQml.connect(function (message) { // messages are {method, params}, like default: print('Unrecognized message from Pal.qml:', JSON.stringify(message)); } -}); +} + +function sendToQml(message) { + if (Settings.getValue("HUDUIEnabled")) { + pal.sendToQml(message); + } else { + tablet.sendToQml(message); + } +} // // Main operations. @@ -298,10 +305,10 @@ function populateUserList(selectData) { data.push(avatarPalDatum); print('PAL data:', JSON.stringify(avatarPalDatum)); }); - pal.sendToQml({ method: 'users', params: data }); + sendToQml({ method: 'users', params: data }); if (selectData) { selectData[2] = true; - pal.sendToQml({ method: 'select', params: selectData }); + sendToQml({ method: 'select', params: selectData }); } } @@ -322,7 +329,7 @@ function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { } print('Username Data:', JSON.stringify(data)); // Ship the data off to QML - pal.sendToQml({ method: 'updateUsername', params: data }); + sendToQml({ method: 'updateUsername', params: data }); } var pingPong = true; @@ -396,7 +403,7 @@ function handleClick(pickRay) { ExtendedOverlay.applyPickRay(pickRay, function (overlay) { // Don't select directly. Tell qml, who will give us back a list of ids. var message = {method: 'select', params: [[overlay.key], !overlay.selected, false]}; - pal.sendToQml(message); + sendToQml(message); return true; }); } @@ -492,6 +499,7 @@ if (Settings.getValue("HUDUIEnabled")) { visible: true, alpha: 0.9 }); + pal.fromQml.connect(fromQml); } else { tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ @@ -499,7 +507,9 @@ if (Settings.getValue("HUDUIEnabled")) { icon: "icons/tablet-icons/people-i.svg", sortOrder: 7 }); + tablet.fromQml.connect(fromQml); } + var isWired = false; var audioTimer; var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too) @@ -518,10 +528,26 @@ function off() { Users.requestsDomainListData = false; } function onClicked() { - if (!pal.visible) { + if (Settings.getValue("HUDUIEnabled")) { + if (!pal.visible) { + Users.requestsDomainListData = true; + populateUserList(); + pal.raise(); + isWired = true; + Script.update.connect(updateOverlays); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); + } else { + off(); + } + pal.setVisible(!pal.visible); + } else { + tablet.loadQMLSource("../Pal.qml"); Users.requestsDomainListData = true; populateUserList(); - pal.raise(); isWired = true; Script.update.connect(updateOverlays); Controller.mousePressEvent.connect(handleMouseEvent); @@ -529,10 +555,7 @@ function onClicked() { triggerMapping.enable(); triggerPressMapping.enable(); audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); - } else { - off(); } - pal.setVisible(!pal.visible); } // @@ -550,7 +573,7 @@ function receiveMessage(channel, messageString, senderID) { if (!pal.visible) { onClicked(); } - pal.sendToQml(message); // Accepts objects, not just strings. + sendToQml(message); // Accepts objects, not just strings. break; default: print('Unrecognized PAL message', messageString); @@ -607,13 +630,13 @@ function createAudioInterval(interval) { var userId = id || 0; param[userId] = level; }); - pal.sendToQml({method: 'updateAudioLevel', params: param}); + sendToQml({method: 'updateAudioLevel', params: param}); }, interval); } function avatarDisconnected(nodeID) { // remove from the pal list - pal.sendToQml({method: 'avatarDisconnected', params: [nodeID]}); + sendToQml({method: 'avatarDisconnected', params: [nodeID]}); } // // Button state. @@ -624,11 +647,20 @@ function onVisibleChanged() { button.clicked.connect(onClicked); pal.visibleChanged.connect(onVisibleChanged); pal.closed.connect(off); + +if (!Settings.getValue("HUDUIEnabled")) { + tablet.screenChanged.connect(function (type, url) { + if (type !== "QML" || url !== "../Pal.qml") { + off(); + } + }); +} + Users.usernameFromIDReply.connect(usernameFromIDReply); Users.avatarDisconnected.connect(avatarDisconnected); function clearLocalQMLDataAndClosePAL() { - pal.sendToQml({ method: 'clearLocalQMLData' }); + sendToQml({ method: 'clearLocalQMLData' }); if (pal.visible) { onClicked(); // Close the PAL } diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js new file mode 100644 index 0000000000..1a3fbab3ea --- /dev/null +++ b/scripts/system/tablet-goto.js @@ -0,0 +1,64 @@ +"use strict"; + +// +// goto.js +// scripts/system/ +// +// Created by Dante Ruiz on 8 February 2017 +// Copyright 2016 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 +// + +(function() { // BEGIN LOCAL_SCOPE + var gotoQmlSource = "TabletAddressDialog.qml"; + var button; + var buttonName = "GOTO"; + var toolBar = null; + var tablet = null; + function onAddressBarShown(visible) { + if (toolBar) { + button.editProperties({isActive: visible}); + } + } + + function onClicked(){ + if (toolBar) { + DialogsManager.toggleAddressBar(); + } else { + tablet.loadQMLSource(gotoQmlSource); + } + } + if (Settings.getValue("HUDUIEnabled")) { + toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + button = toolBar.addButton({ + objectName: buttonName, + imageURL: Script.resolvePath("assets/images/tools/directory.svg"), + visible: true, + alpha: 0.9 + }); + } else { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/goto-i.svg", + text: buttonName, + sortOrder: 8 + }); + } + + button.clicked.connect(onClicked); + DialogsManager.addressBarShown.connect(onAddressBarShown); + + Script.scriptEnding.connect(function () { + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + if (toolBar) { + toolBar.removeButton(buttonName); + } + DialogsManager.addressBarShown.disconnect(onAddressBarShown); + }); + +}()); // END LOCAL_SCOPE diff --git a/unpublishedScripts/marketplace/stopwatch/models/Stopwatch-min-hand.fbx b/unpublishedScripts/marketplace/stopwatch/models/Stopwatch-min-hand.fbx new file mode 100644 index 0000000000..b504da13bd Binary files /dev/null and b/unpublishedScripts/marketplace/stopwatch/models/Stopwatch-min-hand.fbx differ diff --git a/unpublishedScripts/marketplace/stopwatch/models/Stopwatch-sec-hand.fbx b/unpublishedScripts/marketplace/stopwatch/models/Stopwatch-sec-hand.fbx new file mode 100644 index 0000000000..12aa85f126 Binary files /dev/null and b/unpublishedScripts/marketplace/stopwatch/models/Stopwatch-sec-hand.fbx differ diff --git a/unpublishedScripts/marketplace/stopwatch/models/Stopwatch.fbx b/unpublishedScripts/marketplace/stopwatch/models/Stopwatch.fbx new file mode 100644 index 0000000000..073544875e Binary files /dev/null and b/unpublishedScripts/marketplace/stopwatch/models/Stopwatch.fbx differ diff --git a/unpublishedScripts/marketplace/stopwatch/chime.wav b/unpublishedScripts/marketplace/stopwatch/sounds/chime.wav similarity index 100% rename from unpublishedScripts/marketplace/stopwatch/chime.wav rename to unpublishedScripts/marketplace/stopwatch/sounds/chime.wav diff --git a/unpublishedScripts/marketplace/stopwatch/tick.wav b/unpublishedScripts/marketplace/stopwatch/sounds/tick.wav similarity index 100% rename from unpublishedScripts/marketplace/stopwatch/tick.wav rename to unpublishedScripts/marketplace/stopwatch/sounds/tick.wav diff --git a/unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js b/unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js index 4203db37fa..e72f949163 100644 --- a/unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js +++ b/unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js @@ -8,34 +8,39 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var positionToSpawn = Vec3.sum(MyAvatar.position, Quat.getFront(MyAvatar.rotation)); +var forward = Quat.getFront(MyAvatar.orientation); +Vec3.print("Forward: ", forward); +var positionToSpawn = Vec3.sum(MyAvatar.position, Vec3.multiply(3, forward)); +var scale = 0.5; +positionToSpawn.y += 0.5; var stopwatchID = Entities.addEntity({ type: "Model", name: "stopwatch/base", position: positionToSpawn, - modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Stopwatch.fbx", - dimensions: {"x":4.129462242126465,"y":1.058512806892395,"z":5.773681640625} + modelURL: Script.resolvePath("models/Stopwatch.fbx"), + dimensions: Vec3.multiply(scale, {"x":4.129462242126465,"y":1.058512806892395,"z":5.773681640625}), + rotation: Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollDegrees(90, 0, 0)) }); var secondHandID = Entities.addEntity({ type: "Model", name: "stopwatch/seconds", parentID: stopwatchID, - localPosition: {"x":-0.004985813982784748,"y":0.39391064643859863,"z":0.8312804698944092}, - dimensions: {"x":0.14095762372016907,"y":0.02546107769012451,"z":1.6077008247375488}, + localPosition: Vec3.multiply(scale, {"x":-0.004985813982784748,"y":0.39391064643859863,"z":0.8312804698944092}), + dimensions: Vec3.multiply(scale, {"x":0.14095762372016907,"y":0.02546107769012451,"z":1.6077008247375488}), registrationPoint: {"x":0.5,"y":0.5,"z":1}, - modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Stopwatch-sec-hand.fbx", + modelURL: Script.resolvePath("models/Stopwatch-sec-hand.fbx"), }); var minuteHandID = Entities.addEntity({ type: "Model", name: "stopwatch/minutes", parentID: stopwatchID, - localPosition: {"x":-0.0023056098725646734,"y":0.3308190703392029,"z":0.21810021996498108}, - dimensions: {"x":0.045471154153347015,"y":0.015412690117955208,"z":0.22930574417114258}, + localPosition: Vec3.multiply(scale, {"x":-0.0023056098725646734,"y":0.3308190703392029,"z":0.21810021996498108}), + dimensions: Vec3.multiply(scale, {"x":0.045471154153347015,"y":0.015412690117955208,"z":0.22930574417114258}), registrationPoint: {"x":0.5,"y":0.5,"z":1}, - modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Stopwatch-min-hand.fbx", + modelURL: Script.resolvePath("models/Stopwatch-min-hand.fbx"), }); Entities.editEntity(stopwatchID, { @@ -46,3 +51,5 @@ Entities.editEntity(stopwatchID, { script: Script.resolvePath("stopwatchClient.js"), serverScripts: Script.resolvePath("stopwatchServer.js") }); + +Script.stop() diff --git a/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js b/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js index b923d6af88..8b36b48cde 100644 --- a/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js +++ b/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js @@ -17,11 +17,11 @@ self.secondHandID = null; self.minuteHandID = null; - self.tickSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/huffman/tick.wav"); + self.tickSound = SoundCache.getSound(Script.resolvePath("sounds/tick.wav")); self.tickInjector = null; self.tickIntervalID = null; - self.chimeSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/huffman/chime.wav"); + self.chimeSound = SoundCache.getSound(Script.resolvePath("sounds/chime.wav")); self.preload = function(entityID) { print("Preloading stopwatch: ", entityID);