diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index d61a1a49b7..1944d23654 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -50,6 +50,7 @@ #include "entities/AssignmentParentFinder.h" #include "RecordingScriptingInterface.h" #include "AbstractAudioInterface.h" +#include "AgentScriptingInterface.h" static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; @@ -450,7 +451,7 @@ void Agent::executeScript() { packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket"); // register ourselves to the script engine - _scriptEngine->registerGlobalObject("Agent", this); + _scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this)); _scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get().data()); _scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 0fc3fbe1f9..b55dea89be 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -33,19 +33,6 @@ #include "entities/EntityTreeHeadlessViewer.h" #include "avatars/ScriptableAvatar.h" -/**jsdoc - * @namespace Agent - * - * @hifi-assignment-client - * - * @property {boolean} isAvatar - * @property {boolean} isPlayingAvatarSound Read-only. - * @property {boolean} isListeningToAudioStream - * @property {boolean} isNoiseGateEnabled - * @property {number} lastReceivedAudioLoudness Read-only. - * @property {Uuid} sessionUUID Read-only. - */ - class Agent : public ThreadedAssignment { Q_OBJECT @@ -73,28 +60,11 @@ public: virtual void aboutToFinish() override; public slots: - /**jsdoc - * @function Agent.run - * @deprecated This function is being removed from the API. - */ void run() override; - /**jsdoc - * @function Agent.playAvatarSound - * @param {object} avatarSound - */ void playAvatarSound(SharedSoundPointer avatarSound); - - /**jsdoc - * @function Agent.setIsAvatar - * @param {boolean} isAvatar - */ - void setIsAvatar(bool isAvatar); - /**jsdoc - * @function Agent.isAvatar - * @returns {boolean} - */ + void setIsAvatar(bool isAvatar); bool isAvatar() const { return _isAvatar; } private slots: diff --git a/assignment-client/src/AgentScriptingInterface.cpp b/assignment-client/src/AgentScriptingInterface.cpp new file mode 100644 index 0000000000..3000c2b96f --- /dev/null +++ b/assignment-client/src/AgentScriptingInterface.cpp @@ -0,0 +1,17 @@ +// +// AgentScriptingInterface.cpp +// assignment-client/src +// +// Created by Thijs Wenker on 7/23/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AgentScriptingInterface.h" + +AgentScriptingInterface::AgentScriptingInterface(Agent* agent) : + QObject(agent), + _agent(agent) +{ } diff --git a/assignment-client/src/AgentScriptingInterface.h b/assignment-client/src/AgentScriptingInterface.h new file mode 100644 index 0000000000..9fa7688778 --- /dev/null +++ b/assignment-client/src/AgentScriptingInterface.h @@ -0,0 +1,80 @@ +// +// AgentScriptingInterface.h +// assignment-client/src +// +// Created by Thijs Wenker on 7/23/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#ifndef hifi_AgentScriptingInterface_h +#define hifi_AgentScriptingInterface_h + +#include + +#include "Agent.h" + +/**jsdoc + * @namespace Agent + * + * @hifi-assignment-client + * + * @property {boolean} isAvatar + * @property {boolean} isPlayingAvatarSound Read-only. + * @property {boolean} isListeningToAudioStream + * @property {boolean} isNoiseGateEnabled + * @property {number} lastReceivedAudioLoudness Read-only. + * @property {Uuid} sessionUUID Read-only. + */ +class AgentScriptingInterface : public QObject { + Q_OBJECT + Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar) + Q_PROPERTY(bool isPlayingAvatarSound READ isPlayingAvatarSound) + Q_PROPERTY(bool isListeningToAudioStream READ isListeningToAudioStream WRITE setIsListeningToAudioStream) + Q_PROPERTY(bool isNoiseGateEnabled READ isNoiseGateEnabled WRITE setIsNoiseGateEnabled) + Q_PROPERTY(float lastReceivedAudioLoudness READ getLastReceivedAudioLoudness) + Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) + +public: + AgentScriptingInterface(Agent* agent); + + bool isPlayingAvatarSound() const { return _agent->isPlayingAvatarSound(); } + + bool isListeningToAudioStream() const { return _agent->isListeningToAudioStream(); } + void setIsListeningToAudioStream(bool isListeningToAudioStream) const { _agent->setIsListeningToAudioStream(isListeningToAudioStream); } + + bool isNoiseGateEnabled() const { return _agent->isNoiseGateEnabled(); } + void setIsNoiseGateEnabled(bool isNoiseGateEnabled) const { _agent->setIsNoiseGateEnabled(isNoiseGateEnabled); } + + float getLastReceivedAudioLoudness() const { return _agent->getLastReceivedAudioLoudness(); } + QUuid getSessionUUID() const { return _agent->getSessionUUID(); } + +public slots: + /**jsdoc + * @function Agent.setIsAvatar + * @param {boolean} isAvatar + */ + void setIsAvatar(bool isAvatar) const { _agent->setIsAvatar(isAvatar); } + + /**jsdoc + * @function Agent.isAvatar + * @returns {boolean} + */ + bool isAvatar() const { return _agent->isAvatar(); } + + /**jsdoc + * @function Agent.playAvatarSound + * @param {object} avatarSound + */ + void playAvatarSound(SharedSoundPointer avatarSound) const { _agent->playAvatarSound(avatarSound); } + +private: + Agent* _agent; + +}; + + +#endif // hifi_AgentScriptingInterface_h diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index a88a584c63..10e71062ac 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -244,7 +244,7 @@ Rectangle { var avatarSettings = { dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', collisionsEnabled : settings.avatarCollisionsOn, - animGraphUrl : settings.avatarAnimationJSON, + animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, collisionSoundUrl : settings.avatarCollisionSoundUrl }; diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 6884d2e1f6..915213508c 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -145,6 +145,22 @@ Rectangle { } pal.sendToScript({method: 'refreshNearby', params: params}); } + function refreshConnections() { + var flickable = connectionsUserModel.flickable; + connectionsRefreshScrollTimer.oldY = flickable.contentY; + flickable.contentY = 0; + connectionsUserModel.getFirstPage('delayRefresh', function () { + connectionsRefreshScrollTimer.start(); + }); + } + Timer { + id: connectionsRefreshScrollTimer; + interval: 500; + property real oldY: 0; + onTriggered: { + connectionsUserModel.flickable.contentY = oldY; + } + } Rectangle { id: palTabContainer; @@ -276,7 +292,10 @@ Rectangle { id: reloadConnections; width: reloadConnections.height; glyph: hifi.glyphs.reload; - onClicked: connectionsUserModel.getFirstPage('delayRefresh'); + onClicked: { + pal.sendToScript({method: 'refreshConnections'}); + refreshConnections(); + } } } // "CONNECTIONS" text @@ -1209,6 +1228,9 @@ Rectangle { case 'clearLocalQMLData': ignored = {}; break; + case 'refreshConnections': + refreshConnections(); + break; case 'avatarDisconnected': var sessionID = message.params[0]; delete ignored[sessionID]; diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index f996bdfd03..e1b55866c2 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -20,7 +20,8 @@ Rectangle { property real scaleValue: scaleSlider.value / 10 property alias dominantHandIsLeft: leftHandRadioButton.checked property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked - property alias avatarAnimationJSON: avatarAnimationUrlInputText.text + property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text + property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text property real avatarScaleBackup; @@ -45,6 +46,7 @@ Rectangle { } avatarAnimationJSON = settings.animGraphUrl; + avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; avatarCollisionSoundUrl = settings.collisionSoundUrl; visible = true; diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 988502dd91..542145904f 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -93,7 +93,7 @@ ListModel { property int totalPages: 0; property int totalEntries: 0; // Check consistency and call processPage. - function handlePage(error, response) { + function handlePage(error, response, cb) { var processed; console.debug('handlePage', listModelName, additionalFirstPageRequested, error, JSON.stringify(response)); function fail(message) { @@ -134,7 +134,9 @@ ListModel { if (additionalFirstPageRequested) { console.debug('deferred getFirstPage', listModelName); additionalFirstPageRequested = false; - getFirstPage('delayedClear'); + getFirstPage('delayedClear', cb); + } else if (cb) { + cb(); } } function debugView(label) { @@ -147,7 +149,7 @@ ListModel { // Override either http or getPage. property var http; // An Item that has a request function. - property var getPage: function () { // Any override MUST call handlePage(), above, even if results empty. + property var getPage: function (cb) { // Any override MUST call handlePage(), above, even if results empty. if (!http) { return console.warn("Neither http nor getPage was set for", listModelName); } // If it is a path starting with slash, add the metaverseServer domain. var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint; @@ -165,12 +167,12 @@ ListModel { var parametersSeparator = /\?/.test(url) ? '&' : '?'; url = url + parametersSeparator + parameters.join('&'); console.debug('getPage', listModelName, currentPageToRetrieve); - http.request({uri: url}, handlePage); + http.request({uri: url}, cb ? function (error, result) { handlePage(error, result, cb); } : handlePage); } // Start the show by retrieving data according to `getPage()`. // It can be custom-defined by this item's Parent. - property var getFirstPage: function (delayClear) { + property var getFirstPage: function (delayClear, cb) { if (requestPending) { console.debug('deferring getFirstPage', listModelName); additionalFirstPageRequested = true; @@ -180,7 +182,7 @@ ListModel { resetModel(); requestPending = true; console.debug("getFirstPage", listModelName, currentPageToRetrieve); - getPage(); + getPage(cb); } property bool additionalFirstPageRequested: false; property bool requestPending: false; // For de-bouncing getNextPage. diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e87dcc85cd..27a52fd0b2 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2062,6 +2062,8 @@ void MyAvatar::initAnimGraph() { graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json"); } + emit animGraphUrlChanged(graphUrl); + _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 004a965c28..793b4aa158 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -178,6 +178,10 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene withWriteLock([&] { + if (_contentType == ContentType::NoContent) { + return; + } + // This work must be done on the main thread // If we couldn't create a new web surface, exit if (!hasWebSurface() && !buildWebSurface(entity)) { @@ -311,13 +315,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { }); } else if (_contentType == ContentType::QmlContent) { _webSurface->load(_lastSourceUrl); - } else if (_contentType == ContentType::NoContent) { - // Show empty white panel - _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty(URL_PROPERTY, ""); - }); } - _fadeStartTime = usecTimestampNow(); _webSurface->resume(); diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 9372cfa667..a1737641c1 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -29,34 +29,16 @@ public: void addPacketStatsAndSendStatsPacket(QJsonObject statsObject); public slots: - // JSDoc: Overridden in Agent.h. /// threaded run of assignment virtual void run() = 0; - /**jsdoc - * @function Agent.stop - * @deprecated This function is being removed from the API. - */ Q_INVOKABLE virtual void stop() { setFinished(true); } - /**jsdoc - * @function Agent.sendStatsPacket - * @deprecated This function is being removed from the API. - */ virtual void sendStatsPacket(); - /**jsdoc - * @function Agent.clearQueuedCheckIns - * @deprecated This function is being removed from the API. - */ void clearQueuedCheckIns() { _numQueuedCheckIns = 0; } signals: - /**jsdoc - * @function Agent.finished - * @returns {Signal} - * @deprecated This function is being removed from the API. - */ void finished(); protected: @@ -68,10 +50,6 @@ protected: int _numQueuedCheckIns { 0 }; protected slots: - /**jsdoc - * @function Agent.domainSettingsRequestFailed - * @deprecated This function is being removed from the API. - */ void domainSettingsRequestFailed(); private slots: diff --git a/scripts/developer/tests/agentAPITest.js b/scripts/developer/tests/agentAPITest.js new file mode 100644 index 0000000000..b7d21efbdf --- /dev/null +++ b/scripts/developer/tests/agentAPITest.js @@ -0,0 +1,55 @@ +// agentAPITest.js +// scripts/developer/tests +// +// Created by Thijs Wenker on 7/23/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var SOUND_DATA = { url: "http://hifi-content.s3.amazonaws.com/howard/sounds/piano1.wav" }; + +// getSound function from crowd-agent.js +function getSound(data, callback) { // callback(sound) when downloaded (which may be immediate). + var sound = SoundCache.getSound(data.url); + if (sound.downloaded) { + return callback(sound); + } + function onDownloaded() { + sound.ready.disconnect(onDownloaded); + callback(sound); + } + sound.ready.connect(onDownloaded); +} + + +function agentAPITest() { + console.warn('Agent.isAvatar =', Agent.isAvatar); + + Agent.isAvatar = true; + console.warn('Agent.isAvatar =', Agent.isAvatar); + + console.warn('Agent.isListeningToAudioStream =', Agent.isListeningToAudioStream); + + Agent.isListeningToAudioStream = true; + console.warn('Agent.isListeningToAudioStream =', Agent.isListeningToAudioStream); + + console.warn('Agent.isNoiseGateEnabled =', Agent.isNoiseGateEnabled); + + Agent.isNoiseGateEnabled = true; + console.warn('Agent.isNoiseGateEnabled =', Agent.isNoiseGateEnabled); + console.warn('Agent.lastReceivedAudioLoudness =', Agent.lastReceivedAudioLoudness); + console.warn('Agent.sessionUUID =', Agent.sessionUUID); + + getSound(SOUND_DATA, function (sound) { + console.warn('Agent.isPlayingAvatarSound =', Agent.isPlayingAvatarSound); + Agent.playAvatarSound(sound); + console.warn('Agent.isPlayingAvatarSound =', Agent.isPlayingAvatarSound); + }); +} + +if (Script.context === "agent") { + agentAPITest(); +} else { + console.error('This script should be run as agent script. EXITING.'); +} diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 5c7cb93aa6..03b7b3969d 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -67,7 +67,8 @@ function getMyAvatarSettings() { dominantHand: MyAvatar.getDominantHand(), collisionsEnabled : MyAvatar.getCollisionsEnabled(), collisionSoundUrl : MyAvatar.collisionSoundURL, - animGraphUrl : MyAvatar.getAnimGraphUrl(), + animGraphUrl: MyAvatar.getAnimGraphUrl(), + animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), } } @@ -142,9 +143,14 @@ function onNewCollisionSoundUrl(url) { } function onAnimGraphUrlChanged(url) { - if(currentAvatarSettings.animGraphUrl !== url) { + if (currentAvatarSettings.animGraphUrl !== url) { currentAvatarSettings.animGraphUrl = url; - sendToQml({'method' : 'settingChanged', 'name' : 'animGraphUrl', 'value' : url}) + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl }) + + if (currentAvatarSettings.animGraphOverrideUrl !== MyAvatar.getAnimGraphOverrideUrl()) { + currentAvatarSettings.animGraphOverrideUrl = MyAvatar.getAnimGraphOverrideUrl(); + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl', 'value': currentAvatarSettings.animGraphOverrideUrl }) + } } } @@ -282,7 +288,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See MyAvatar.setDominantHand(message.settings.dominantHand); MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; - MyAvatar.setAnimGraphUrl(message.settings.animGraphUrl); + MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); settings = getMyAvatarSettings(); break; diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 9485b8b49a..ebb45130e5 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -268,7 +268,6 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See break; case 'refreshConnections': print('Refreshing Connections...'); - getConnectionData(false); UserActivityLogger.palAction("refresh_connections", ""); break; case 'removeConnection': @@ -281,7 +280,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See print("Error: unable to remove connection", connectionUserName, error || response.status); return; } - getConnectionData(false); + sendToQml({ method: 'refreshConnections' }); }); break; @@ -361,8 +360,9 @@ function getProfilePicture(username, callback) { // callback(url) if successfull callback(matched[1]); }); } +var SAFETY_LIMIT = 400; function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise) - var url = METAVERSE_BASE + '/api/v1/users?per_page=400&'; + var url = METAVERSE_BASE + '/api/v1/users?per_page=' + SAFETY_LIMIT + '&'; if (domain) { url += 'status=' + domain.slice(1, -1); // without curly braces } else { @@ -373,8 +373,10 @@ function getAvailableConnections(domain, callback) { // callback([{usename, loca }); } function getInfoAboutUser(specificUsername, callback) { - var url = METAVERSE_BASE + '/api/v1/users?filter=connections'; + var url = METAVERSE_BASE + '/api/v1/users?filter=connections&per_page=' + SAFETY_LIMIT + '&search=' + encodeURIComponent(specificUsername); requestJSON(url, function (connectionsData) { + // You could have (up to SAFETY_LIMIT connections whose usernames contain the specificUsername. + // Search returns all such matches. for (user in connectionsData.users) { if (connectionsData.users[user].username === specificUsername) { callback(connectionsData.users[user]); @@ -406,16 +408,14 @@ function getConnectionData(specificUsername, domain) { // Update all the usernam print('Error: Unable to find information about ' + specificUsername + ' in connectionsData!'); } }); - } else { + } else if (domain) { getAvailableConnections(domain, function (users) { - if (domain) { - users.forEach(function (user) { - updateUser(frob(user)); - }); - } else { - sendToQml({ method: 'connections', params: users.map(frob) }); - } + users.forEach(function (user) { + updateUser(frob(user)); + }); }); + } else { + print("Error: unrecognized getConnectionData()"); } }