Merge branch 'master' of https://github.com/highfidelity/hifi into editHandleTriggerValues

This commit is contained in:
David Back 2018-07-31 09:28:14 -07:00
commit 03f0116b79
14 changed files with 218 additions and 85 deletions

View file

@ -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<SoundCache>().data());
_scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());

View file

@ -33,19 +33,6 @@
#include "entities/EntityTreeHeadlessViewer.h"
#include "avatars/ScriptableAvatar.h"
/**jsdoc
* @namespace Agent
*
* @hifi-assignment-client
*
* @property {boolean} isAvatar
* @property {boolean} isPlayingAvatarSound <em>Read-only.</em>
* @property {boolean} isListeningToAudioStream
* @property {boolean} isNoiseGateEnabled
* @property {number} lastReceivedAudioLoudness <em>Read-only.</em>
* @property {Uuid} sessionUUID <em>Read-only.</em>
*/
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:

View file

@ -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)
{ }

View file

@ -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 <QObject>
#include "Agent.h"
/**jsdoc
* @namespace Agent
*
* @hifi-assignment-client
*
* @property {boolean} isAvatar
* @property {boolean} isPlayingAvatarSound <em>Read-only.</em>
* @property {boolean} isListeningToAudioStream
* @property {boolean} isNoiseGateEnabled
* @property {number} lastReceivedAudioLoudness <em>Read-only.</em>
* @property {Uuid} sessionUUID <em>Read-only.</em>
*/
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

View file

@ -244,7 +244,7 @@ Rectangle {
var avatarSettings = {
dominantHand : settings.dominantHandIsLeft ? 'left' : 'right',
collisionsEnabled : settings.avatarCollisionsOn,
animGraphUrl : settings.avatarAnimationJSON,
animGraphOverrideUrl : settings.avatarAnimationOverrideJSON,
collisionSoundUrl : settings.avatarCollisionSoundUrl
};

View file

@ -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];

View file

@ -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;

View file

@ -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.

View file

@ -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()));

View file

@ -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();

View file

@ -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:

View file

@ -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.');
}

View file

@ -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;

View file

@ -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()");
}
}