Merge branch 'master' of https://github.com/highfidelity/hifi into minimum-edit-entity-filter

This commit is contained in:
howard-stearns 2017-01-18 10:02:27 -08:00
commit d9182cd38d
36 changed files with 760 additions and 169 deletions

View file

@ -12,6 +12,8 @@
#include "AssetServer.h"
#include <thread>
#include <QtCore/QCoreApplication>
#include <QtCore/QCryptographicHash>
#include <QtCore/QDateTime>
@ -21,6 +23,7 @@
#include <QtCore/QJsonDocument>
#include <QtCore/QString>
#include <SharedUtil.h>
#include <ServerPathUtils.h>
#include "NetworkLogging.h"
@ -28,8 +31,44 @@
#include "SendAssetTask.h"
#include "UploadAssetTask.h"
static const uint8_t MIN_CORES_FOR_MULTICORE = 4;
static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2;
static const uint8_t CPU_AFFINITY_COUNT_LOW = 1;
static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000;
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
bool interfaceRunning() {
bool result = false;
#ifdef Q_OS_WIN
QSharedMemory sharedMemory { getInterfaceSharedMemoryName() };
result = sharedMemory.attach(QSharedMemory::ReadOnly);
if (result) {
sharedMemory.detach();
}
#endif
return result;
}
void updateConsumedCores() {
static bool wasInterfaceRunning = false;
bool isInterfaceRunning = interfaceRunning();
// If state is unchanged, return early
if (isInterfaceRunning == wasInterfaceRunning) {
return;
}
wasInterfaceRunning = isInterfaceRunning;
auto coreCount = std::thread::hardware_concurrency();
if (isInterfaceRunning) {
coreCount = coreCount > MIN_CORES_FOR_MULTICORE ? CPU_AFFINITY_COUNT_HIGH : CPU_AFFINITY_COUNT_LOW;
}
qDebug() << "Setting max consumed cores to " << coreCount;
setMaxCores(coreCount);
}
AssetServer::AssetServer(ReceivedMessage& message) :
ThreadedAssignment(message),
_taskPool(this)
@ -45,6 +84,20 @@ AssetServer::AssetServer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo");
packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload");
packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation");
#ifdef Q_OS_WIN
updateConsumedCores();
QTimer* timer = new QTimer(this);
auto timerConnection = connect(timer, &QTimer::timeout, [] {
updateConsumedCores();
});
connect(qApp, &QCoreApplication::aboutToQuit, [this, timerConnection] {
disconnect(timerConnection);
});
timer->setInterval(INTERFACE_RUNNING_CHECK_FREQUENCY_MS);
timer->setTimerType(Qt::CoarseTimer);
timer->start();
#endif
}
void AssetServer::run() {

View file

@ -28,6 +28,7 @@
#include <StDev.h>
#include <UUID.h>
#include "AudioHelpers.h"
#include "AudioRingBuffer.h"
#include "AudioMixerClientData.h"
#include "AvatarAudioStream.h"
@ -68,7 +69,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket");
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
packetReceiver.registerListener(PacketType::PerAvatarGainSet, this, "handlePerAvatarGainSetDataPacket");
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
}
@ -186,7 +188,8 @@ void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) {
nodeList->eachNode([&killedNode](const SharedNodePointer& node) {
auto clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
if (clientData) {
clientData->removeHRTFsForNode(killedNode->getUUID());
QUuid killedUUID = killedNode->getUUID();
clientData->removeHRTFsForNode(killedUUID);
}
});
}
@ -240,6 +243,20 @@ void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> p
sendingNode->parseIgnoreRequestMessage(packet);
}
void AudioMixer::handlePerAvatarGainSetDataPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
auto clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
if (clientData) {
QUuid listeningNodeUUID = sendingNode->getUUID();
// parse the UUID from the packet
QUuid audioSourceUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
uint8_t packedGain;
packet->readPrimitive(&packedGain);
float gain = unpackFloatGainFromByte(packedGain);
clientData->hrtfForStream(audioSourceUUID, QUuid()).setGainAdjustment(gain);
qDebug() << "Setting gain adjustment for hrtf[" << listeningNodeUUID << "][" << audioSourceUUID << "] to " << gain;
}
}
void AudioMixer::handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
sendingNode->parseIgnoreRadiusRequestMessage(packet);
}

View file

@ -66,6 +66,7 @@ private slots:
void handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleNodeMuteRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handlePerAvatarGainSetDataPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void start();
void removeHRTFsForFinishedInjector(const QUuid& streamID);

View file

@ -262,8 +262,12 @@ void AvatarMixer::broadcastAvatarData() {
// setup a PacketList for the avatarPackets
auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
if (avatar.getSessionDisplayName().isEmpty() && // We haven't set it yet...
nodeData->getReceivedIdentity()) { // ... but we have processed identity (with possible displayName).
if (nodeData->getAvatarSessionDisplayNameMustChange()) {
const QString& existingBaseDisplayName = nodeData->getBaseDisplayName();
if (--_sessionDisplayNames[existingBaseDisplayName].second <= 0) {
_sessionDisplayNames.remove(existingBaseDisplayName);
}
QString baseName = avatar.getDisplayName().trimmed();
const QRegularExpression curses{ "fuck|shit|damn|cock|cunt" }; // POC. We may eventually want something much more elaborate (subscription?).
baseName = baseName.replace(curses, "*"); // Replace rather than remove, so that people have a clue that the person's a jerk.
@ -276,11 +280,14 @@ void AvatarMixer::broadcastAvatarData() {
QPair<int, int>& soFar = _sessionDisplayNames[baseName]; // Inserts and answers 0, 0 if not already present, which is what we want.
int& highWater = soFar.first;
nodeData->setBaseDisplayName(baseName);
avatar.setSessionDisplayName((highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName);
QString sessionDisplayName = (highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName;
avatar.setSessionDisplayName(sessionDisplayName);
highWater++;
soFar.second++; // refcount
nodeData->flagIdentityChange();
sendIdentityPacket(nodeData, node); // Tell new node about its sessionUUID. Others will find out below.
nodeData->setAvatarSessionDisplayNameMustChange(false);
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. Others will find out below.
qDebug() << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID();
}
// this is an AGENT we have received head data from
@ -584,7 +591,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
if (avatar.processAvatarIdentity(identity)) {
QMutexLocker nodeDataLocker(&nodeData->getMutex());
nodeData->flagIdentityChange();
nodeData->setReceivedIdentity();
nodeData->setAvatarSessionDisplayNameMustChange(true);
}
}
}

View file

@ -53,8 +53,8 @@ public:
HRCTime getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
void flagIdentityChange() { _identityChangeTimestamp = p_high_resolution_clock::now(); }
bool getReceivedIdentity() const { return _gotIdentity; }
void setReceivedIdentity() { _gotIdentity = true; }
bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; }
void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; }
void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; }
float getFullRateDistance() const { return _fullRateDistance; }
@ -112,7 +112,7 @@ private:
std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
HRCTime _identityChangeTimestamp;
bool _gotIdentity { false };
bool _avatarSessionDisplayNameMustChange{ false };
float _fullRateDistance = FLT_MAX;
float _maxAvatarDistance = FLT_MAX;

View file

@ -0,0 +1,32 @@
include(ExternalProject)
include(SelectLibraryConfigurations)
set(EXTERNAL_NAME LibOVRPlatform)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/OVRPlatformSDK_v1.10.0.zip
URL_MD5 e6c8264af16d904e6506acd5172fa0a9
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD 1
)
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform64_1.lib CACHE TYPE INTERNAL)
else()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/Windows/LibOVRPlatform32_1.lib CACHE TYPE INTERNAL)
endif()
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/Include CACHE TYPE INTERNAL)
endif ()
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")

View file

@ -0,0 +1,44 @@
#
# FindLibOVRPlatform.cmake
#
# Try to find the LibOVRPlatform library to use the Oculus Platform SDK
#
# You must provide a LIBOVRPLATFORM_ROOT_DIR which contains Windows and Include directories
#
# Once done this will define
#
# LIBOVRPLATFORM_FOUND - system found Oculus Platform SDK
# LIBOVRPLATFORM_INCLUDE_DIRS - the Oculus Platform include directory
# LIBOVRPLATFORM_LIBRARIES - Link this to use Oculus Platform
#
# Created on December 16, 2016 by Stephen Birarda
# 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
#
if (WIN32)
# setup hints for LIBOVRPLATFORM search
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
hifi_library_search_hints("LibOVRPlatform")
find_path(LIBOVRPLATFORM_INCLUDE_DIRS OVR_Platform.h PATH_SUFFIXES Include HINTS ${LIBOVRPLATFORM_SEARCH_DIRS})
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
set(_LIB_NAME LibOVRPlatform64_1.lib)
else()
set(_LIB_NAME LibOVRPlatform32_1.lib)
endif()
find_library(LIBOVRPLATFORM_LIBRARY_RELEASE NAMES ${_LIB_NAME} PATH_SUFFIXES Windows HINTS ${LIBOVRPLATFORM_SEARCH_DIRS})
include(SelectLibraryConfigurations)
select_library_configurations(LIBOVRPLATFORM)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LIBOVRPLATFORM DEFAULT_MSG LIBOVRPLATFORM_INCLUDE_DIRS LIBOVRPLATFORM_LIBRARIES)
mark_as_advanced(LIBOVRPLATFORM_INCLUDE_DIRS LIBOVRPLATFORM_LIBRARIES LIBOVRPLATFORM_SEARCH_DIRS)
endif ()

View file

@ -40,7 +40,7 @@
{
"name": "local_port",
"label": "Local UDP Port",
"help": "This is the local port your domain-server binds to for UDP connections.<br/>Depending on your router, this may need to be changed to run multiple full automatic networking domain-servers in the same network.",
"help": "This is the local port your domain-server binds to for UDP connections.<br/>Depending on your router, this may need to be changed to unique values for each domain-server in order to run multiple full automatic networking domain-servers in the same network. You can use the value 0 to have the domain-server select a random port, which will help in preventing port collisions.",
"default": "40102",
"type": "int",
"advanced": true

View file

@ -780,12 +780,12 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
// This function processes the "Get Username from ID" request.
void DomainServerSettingsManager::processUsernameFromIDRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
// Before we do any processing on this packet, make sure it comes from a node that is allowed to kick (is an admin)
if (sendingNode->getCanKick()) {
// From the packet, pull the UUID we're identifying
QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
if (!nodeUUID.isNull()) {
// From the packet, pull the UUID we're identifying
QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
if (!nodeUUID.isNull()) {
// Before we do any processing on this packet, make sure it comes from a node that is allowed to kick (is an admin)
// OR from a node whose UUID matches the one in the packet
if (sendingNode->getCanKick() || nodeUUID == sendingNode->getUUID()) {
// First, make sure we actually have a node with this UUID
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
auto matchingNode = limitedNodeList->nodeWithUUID(nodeUUID);
@ -813,12 +813,12 @@ void DomainServerSettingsManager::processUsernameFromIDRequestPacket(QSharedPoin
qWarning() << "Node username request received for unknown node. Refusing to process.";
}
} else {
qWarning() << "Node username request received for invalid node ID. Refusing to process.";
qWarning() << "Refusing to process a username request packet from node" << uuidStringWithoutCurlyBraces(sendingNode->getUUID())
<< ". Either node doesn't have kick permissions or is requesting a username not from their UUID.";
}
} else {
qWarning() << "Refusing to process a username request packet from node" << uuidStringWithoutCurlyBraces(sendingNode->getUUID())
<< "that does not have kick permissions.";
qWarning() << "Node username request received for invalid node ID. Refusing to process.";
}
}

View file

@ -10,35 +10,35 @@
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import "../styles-uit"
Row {
Item {
id: thisNameCard
// Spacing
spacing: 10
// Anchors
anchors.top: parent.top
anchors {
topMargin: (parent.height - contentHeight)/2
bottomMargin: (parent.height - contentHeight)/2
verticalCenter: parent.verticalCenter
leftMargin: 10
rightMargin: 10
}
// Properties
property int contentHeight: 50
property string uuid: ""
property string displayName: ""
property string userName: ""
property int displayTextHeight: 18
property real displayNameTextPixelSize: 18
property int usernameTextHeight: 12
property real audioLevel: 0.0
property bool isMyCard: false
property bool selected: false
/* User image commented out for now - will probably be re-introduced later.
Column {
id: avatarImage
// Size
height: contentHeight
height: parent.height
width: height
Image {
id: userImage
@ -49,22 +49,118 @@ Row {
}
}
*/
Column {
Item {
id: textContainer
// Size
width: parent.width - /*avatarImage.width - */parent.anchors.leftMargin - parent.anchors.rightMargin - parent.spacing
height: contentHeight
width: parent.width - /*avatarImage.width - parent.spacing - */parent.anchors.leftMargin - parent.anchors.rightMargin
height: childrenRect.height
anchors.verticalCenter: parent.verticalCenter
// DisplayName Text
// DisplayName field for my card
Rectangle {
id: myDisplayName
visible: isMyCard
// Size
width: parent.width + 70
height: 35
// Anchors
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: -10
// Style
color: hifi.colors.textFieldLightBackground
border.color: hifi.colors.blueHighlight
border.width: 0
TextInput {
id: myDisplayNameText
// Properties
text: thisNameCard.displayName
maximumLength: 256
clip: true
// Size
width: parent.width
height: parent.height
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin
// Style
color: hifi.colors.darkGray
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
font.family: firaSansSemiBold.name
font.pixelSize: displayNameTextPixelSize
selectionColor: hifi.colors.blueHighlight
selectedTextColor: "black"
// Text Positioning
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignLeft
// Signals
onEditingFinished: {
pal.sendToScript({method: 'displayNameUpdate', params: text})
cursorPosition = 0
focus = false
myDisplayName.border.width = 0
color = hifi.colors.darkGray
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: {
myDisplayName.border.width = 1
myDisplayNameText.focus ? myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX, mouseY, TextInput.CursorOnCharacter) : myDisplayNameText.selectAll();
myDisplayNameText.focus = true
myDisplayNameText.color = "black"
}
onDoubleClicked: {
myDisplayNameText.selectAll();
myDisplayNameText.focus = true;
}
onEntered: myDisplayName.color = hifi.colors.lightGrayText
onExited: myDisplayName.color = hifi.colors.textFieldLightBackground
}
// Edit pencil glyph
HiFiGlyphs {
id: editGlyph
text: hifi.glyphs.editPencil
// Text Size
size: displayNameTextPixelSize*1.5
// Anchors
anchors.right: parent.right
anchors.rightMargin: 5
anchors.verticalCenter: parent.verticalCenter
// Style
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: hifi.colors.baseGray
}
}
// Spacer for DisplayName for my card
Rectangle {
id: myDisplayNameSpacer
width: myDisplayName.width
// Anchors
anchors.top: myDisplayName.bottom
height: 5
visible: isMyCard
opacity: 0
}
// DisplayName Text for others' cards
FiraSansSemiBold {
id: displayNameText
// Properties
text: thisNameCard.displayName
elide: Text.ElideRight
visible: !isMyCard
// Size
width: parent.width
// Anchors
anchors.top: parent.top
// Text Size
size: thisNameCard.displayTextHeight
size: displayNameTextPixelSize
// Text Positioning
verticalAlignment: Text.AlignVCenter
// Style
@ -80,6 +176,8 @@ Row {
visible: thisNameCard.displayName
// Size
width: parent.width
// Anchors
anchors.top: isMyCard ? myDisplayNameSpacer.bottom : displayNameText.bottom
// Text Size
size: thisNameCard.usernameTextHeight
// Text Positioning
@ -90,25 +188,56 @@ Row {
// Spacer
Item {
id: spacer
height: 4
width: parent.width
// Anchors
anchors.top: userNameText.bottom
}
// VU Meter
Rectangle { // CHANGEME to the appropriate type!
Rectangle {
id: nameCardVUMeter
// Size
width: parent.width
width: isMyCard ? myDisplayName.width - 20 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
height: 8
// Anchors
anchors.top: spacer.bottom
// Style
radius: 4
color: "#c5c5c5"
// Rectangle for the zero-gain point on the VU meter
Rectangle {
id: vuMeterZeroGain
visible: gainSlider.visible
// Size
width: 4
height: 18
// Style
color: hifi.colors.darkGray
// Anchors
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: (-gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue) * gainSlider.width - 4
}
// Rectangle for the VU meter line
Rectangle {
id: vuMeterLine
width: gainSlider.width
visible: gainSlider.visible
// Style
color: vuMeterBase.color
radius: nameCardVUMeter.radius
height: nameCardVUMeter.height / 2
anchors.verticalCenter: nameCardVUMeter.verticalCenter
}
// Rectangle for the VU meter base
Rectangle {
id: vuMeterBase
// Anchors
anchors.fill: parent
// Style
color: "#c5c5c5"
color: parent.color
radius: parent.radius
}
// Rectangle for the VU meter audio level
@ -117,7 +246,7 @@ Row {
// Size
width: (thisNameCard.audioLevel) * parent.width
// Style
color: "#c5c5c5"
color: parent.color
radius: parent.radius
// Anchors
anchors.bottom: parent.bottom
@ -138,5 +267,66 @@ Row {
}
}
}
// Per-Avatar Gain Slider
Slider {
id: gainSlider
// Size
width: parent.width
height: 14
// Anchors
anchors.verticalCenter: nameCardVUMeter.verticalCenter
// Properties
visible: !isMyCard && selected
value: pal.gainSliderValueDB[uuid] ? pal.gainSliderValueDB[uuid] : 0.0
minimumValue: -60.0
maximumValue: 20.0
stepSize: 5
updateValueWhileDragging: true
onValueChanged: updateGainFromQML(uuid, value)
MouseArea {
anchors.fill: parent
onWheel: {
// Do nothing.
}
onDoubleClicked: {
gainSlider.value = 0.0
}
onPressed: {
// Pass through to Slider
mouse.accepted = false
}
onReleased: {
// Pass through to Slider
mouse.accepted = false
}
}
style: SliderStyle {
groove: Rectangle {
color: "#c5c5c5"
implicitWidth: gainSlider.width
implicitHeight: 4
radius: 2
opacity: 0
}
handle: Rectangle {
anchors.centerIn: parent
color: (control.pressed || control.hovered) ? "#00b4ef" : "#8F8F8F"
implicitWidth: 10
implicitHeight: 16
}
}
}
}
function updateGainFromQML(avatarUuid, sliderValue) {
if (pal.gainSliderValueDB[avatarUuid] !== sliderValue) {
pal.gainSliderValueDB[avatarUuid] = sliderValue;
var data = {
sessionId: avatarUuid,
gain: sliderValue
};
pal.sendToScript({method: 'updateGain', params: data});
}
}
}

View file

@ -24,7 +24,7 @@ Rectangle {
// Style
color: "#E3E3E3"
// Properties
property int myCardHeight: 70
property int myCardHeight: 90
property int rowHeight: 70
property int actionButtonWidth: 75
property int nameCardWidth: palContainer.width - actionButtonWidth*(iAmAdmin ? 4 : 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth
@ -32,6 +32,20 @@ Rectangle {
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities.
property bool iAmAdmin: false
// 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: ({});
// The letterbox used for popup messages
LetterboxMessage {
id: letterboxMessage
z: 999 // Force the popup on top of everything else
}
function letterbox(message) {
letterboxMessage.text = message
letterboxMessage.visible = true
letterboxMessage.popupRadius = 0
}
// This is the container for the PAL
Rectangle {
@ -51,7 +65,7 @@ Rectangle {
id: myInfo
// Size
width: palContainer.width
height: myCardHeight + 20
height: myCardHeight
// Style
color: pal.color
// Anchors
@ -65,6 +79,7 @@ Rectangle {
displayName: myData.displayName
userName: myData.userName
audioLevel: myData.audioLevel
isMyCard: true
// Size
width: nameCardWidth
height: parent.height
@ -172,8 +187,6 @@ Rectangle {
TableViewColumn {
visible: iAmAdmin
role: "kick"
// The hacky spaces used to center text over the button, since I don't know how to apply a margin
// to column header text.
title: "BAN"
width: actionButtonWidth
movable: false
@ -206,6 +219,8 @@ Rectangle {
userName: model && model.userName
audioLevel: model && model.audioLevel
visible: !isCheckBox && !isButton
uuid: model && model.sessionId
selected: styleData.selected
// Size
width: nameCardWidth
height: parent.height
@ -331,11 +346,6 @@ Rectangle {
visible: iAmAdmin
color: hifi.colors.lightGrayText
}
function letterbox(message) {
letterboxMessage.text = message;
letterboxMessage.visible = true
}
// This Rectangle refers to the [?] popup button next to "NAMES"
Rectangle {
color: hifi.colors.tableBackgroundLight
@ -396,9 +406,6 @@ Rectangle {
onExited: adminHelpText.color = hifi.colors.redHighlight
}
}
LetterboxMessage {
id: letterboxMessage
}
}
function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml
@ -492,8 +499,13 @@ Rectangle {
}
}
break;
case 'clearIgnored':
case 'clearLocalQMLData':
ignored = {};
gainSliderValueDB = {};
break;
case 'avatarDisconnected':
var sessionID = message.params[0];
delete ignored[sessionID];
break;
default:
console.log('Unrecognized message:', JSON.stringify(message));

View file

@ -312,10 +312,11 @@ Item {
readonly property string error: "="
readonly property string settings: "@"
readonly property string trash: "{"
readonly property string objectGroup: "&#xe000;"
readonly property string objectGroup: "\ue000"
readonly property string cm: "}"
readonly property string msvg79: "~"
readonly property string deg: "\\"
readonly property string px: "|"
readonly property string editPencil: "\ue00d"
}
}

View file

@ -252,7 +252,7 @@ public:
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 30 * USECS_PER_SECOND;
static const int WARNING_ELAPSED_HEARTBEAT = 500 * USECS_PER_MSEC; // warn if elapsed heartbeat average is large
static const int HEARTBEAT_SAMPLES = 100000; // ~5 seconds worth of samples
// Set the heartbeat on launch
DeadlockWatchdogThread() {
setObjectName("Deadlock Watchdog");
@ -616,7 +616,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_window->setWindowTitle("Interface");
Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
auto nodeList = DependencyManager::get<NodeList>();
// Set up a watchdog thread to intentionally crash the application on deadlocks
@ -637,6 +637,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
qCDebug(interfaceapp) << "[VERSION] We will use DEVELOPMENT global services.";
#endif
// set the OCULUS_STORE property so the oculus plugin can know if we ran from the Oculus Store
static const QString OCULUS_STORE_ARG = "--oculus-store";
setProperty(hifi::properties::OCULUS_STORE, arguments().indexOf(OCULUS_STORE_ARG) != -1);
static const QString NO_UPDATER_ARG = "--no-updater";
static const bool noUpdater = arguments().indexOf(NO_UPDATER_ARG) != -1;
@ -697,7 +700,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
audioIO->setPositionGetter([]{
auto avatarManager = DependencyManager::get<AvatarManager>();
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO;
});
audioIO->setOrientationGetter([]{
@ -880,7 +883,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
#ifdef Q_OS_MAC
auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget
#else
// On windows and linux, hiding the top level cursor also means it's invisible when hovering over the
// On windows and linux, hiding the top level cursor also means it's invisible when hovering over the
// window menu, which is a pain, so only hide it for the GL surface
auto cursorTarget = _glWidget;
#endif
@ -1125,7 +1128,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
loadSettings();
// Now that we've loaded the menu and thus switched to the previous display plugin
// we can unlock the desktop repositioning code, since all the positions will be
// we can unlock the desktop repositioning code, since all the positions will be
// relative to the desktop size for this plugin
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getDesktop()->setProperty("repositionLocked", false);
@ -1235,8 +1238,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// Add periodic checks to send user activity data
static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000;
static int SEND_STATS_INTERVAL_MS = 10000;
static int NEARBY_AVATAR_RADIUS_METERS = 10;
// setup the stats interval depending on if the 1s faster hearbeat was requested
static const QString FAST_STATS_ARG = "--fast-heartbeat";
static int SEND_STATS_INTERVAL_MS = arguments().indexOf(FAST_STATS_ARG) != -1 ? 1000 : 10000;
static glm::vec3 lastAvatarPosition = myAvatar->getPosition();
static glm::mat4 lastHMDHeadPose = getHMDSensorPose();
@ -1598,7 +1604,7 @@ void Application::checkChangeCursor() {
#ifdef Q_OS_MAC
auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget
#else
// On windows and linux, hiding the top level cursor also means it's invisible when hovering over the
// On windows and linux, hiding the top level cursor also means it's invisible when hovering over the
// window menu, which is a pain, so only hide it for the GL surface
auto cursorTarget = _glWidget;
#endif
@ -1785,7 +1791,7 @@ Application::~Application() {
#endif
// The window takes ownership of the menu, so this has the side effect of destroying it.
_window->setMenuBar(nullptr);
_window->deleteLater();
// Can't log to file passed this point, FileLogger about to be deleted
@ -1811,10 +1817,10 @@ void Application::initializeGL() {
_glWidget->makeCurrent();
gpu::Context::init<gpu::gl::GLBackend>();
qApp->setProperty(hifi::properties::gl::MAKE_PROGRAM_CALLBACK,
qApp->setProperty(hifi::properties::gl::MAKE_PROGRAM_CALLBACK,
QVariant::fromValue((void*)(&gpu::gl::GLBackend::makeProgram)));
_gpuContext = std::make_shared<gpu::Context>();
// The gpu context can make child contexts for transfers, so
// The gpu context can make child contexts for transfers, so
// we need to restore primary rendering context
_glWidget->makeCurrent();
@ -2034,7 +2040,7 @@ void Application::paintGL() {
// FIXME not needed anymore?
_offscreenContext->makeCurrent();
// If a display plugin loses it's underlying support, it
// If a display plugin loses it's underlying support, it
// needs to be able to signal us to not use it
if (!displayPlugin->beginFrameRender(_frameCount)) {
_inPaint = false;
@ -2846,7 +2852,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
if (isMirrorChecked) {
// if we got here without coming in from a non-Full Screen mirror case, then our
// _returnFromFullScreenMirrorTo is unknown. In that case we'll go to the old
// _returnFromFullScreenMirrorTo is unknown. In that case we'll go to the old
// behavior of returning to ThirdPerson
if (_returnFromFullScreenMirrorTo.isEmpty()) {
_returnFromFullScreenMirrorTo = MenuOption::ThirdPerson;
@ -3020,7 +3026,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
maybeToggleMenuVisible(event);
auto& compositor = getApplicationCompositor();
// if this is a real mouse event, and we're in HMD mode, then we should use it to move the
// if this is a real mouse event, and we're in HMD mode, then we should use it to move the
// compositor reticle
// handleRealMouseMoveEvent() will return true, if we shouldn't process the event further
if (!compositor.fakeEventActive() && compositor.handleRealMouseMoveEvent()) {
@ -4109,7 +4115,7 @@ void Application::setKeyboardFocusEntity(EntityItemID entityItemID) {
}
_lastAcceptedKeyPress = usecTimestampNow();
setKeyboardFocusHighlight(entity->getPosition(), entity->getRotation(),
setKeyboardFocusHighlight(entity->getPosition(), entity->getRotation(),
entity->getDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR);
}
}
@ -4700,7 +4706,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
_octreeQuery.setMaxQueryPacketsPerSecond(0);
}
// if asked to forceResend, then set the query's position/orientation to be degenerate in a manner
// if asked to forceResend, then set the query's position/orientation to be degenerate in a manner
// that will cause our next query to be guarenteed to be different and the server will resend to us
if (forceResend) {
_octreeQuery.setCameraPosition(glm::vec3(-0.1, -0.1, -0.1));
@ -5294,15 +5300,17 @@ bool Application::nearbyEntitiesAreReadyForPhysics() {
if (_nearbyEntitiesStabilityCount >= MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT) {
// We've seen the same number of nearby entities for several stats packets in a row. assume we've got all
// the local entities.
bool result = true;
foreach (EntityItemPointer entity, entities) {
if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) {
static QString repeatedMessage =
LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*");
qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName();
return false;
// don't break here because we want all the relevant entities to start their downloads
result = false;
}
}
return true;
return result;
}
return false;
}
@ -5833,7 +5841,7 @@ void Application::addAssetToWorldWithNewMapping(QString filePath, QString mappin
mapping = mapping.insert(mapping.lastIndexOf("."), "-" + QString::number(copy));
addAssetToWorldWithNewMapping(filePath, mapping, copy);
} else {
QString errorInfo = "Too many copies of asset name: "
QString errorInfo = "Too many copies of asset name: "
+ mapping.left(mapping.length() - QString::number(copy).length() - 1);
qWarning(interfaceapp) << "Error downloading model: " + errorInfo;
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
@ -5900,7 +5908,7 @@ void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) {
// Note: Model dimensions are not available here; model is scaled per FBX mesh in RenderableModelEntityItem::update() later
// on. But FBX dimensions may be in cm, so we monitor for the dimension change and rescale again if warranted.
if (entityID == QUuid()) {
QString errorInfo = "Could not add model " + mapping + " to world.";
qWarning(interfaceapp) << "Could not add model to world: " + errorInfo;
@ -6364,7 +6372,7 @@ glm::uvec2 Application::getCanvasSize() const {
}
QRect Application::getRenderingGeometry() const {
auto geometry = _glWidget->geometry();
auto geometry = _glWidget->geometry();
auto topLeft = geometry.topLeft();
auto topLeftScreen = _glWidget->mapToGlobal(topLeft);
geometry.moveTopLeft(topLeftScreen);
@ -6727,8 +6735,8 @@ bool Application::makeRenderingContextCurrent() {
return _offscreenContext->makeCurrent();
}
bool Application::isForeground() const {
return _isForeground && !_window->isMinimized();
bool Application::isForeground() const {
return _isForeground && !_window->isMinimized();
}
void Application::sendMousePressOnEntity(QUuid id, PointerEvent event) {

View file

@ -260,6 +260,10 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
}
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble || removalReason == YourAvatarEnteredTheirBubble) {
DependencyManager::get<NodeList>()->radiusIgnoreNodeBySessionID(avatar->getSessionUUID(), true);
} else if (removalReason == KillAvatarReason::AvatarDisconnected) {
// remove from node sets, if present
DependencyManager::get<NodeList>()->removeFromIgnoreMuteSets(avatar->getSessionUUID());
DependencyManager::get<UsersScriptingInterface>()->avatarDisconnected(avatar->getSessionUUID());
}
_avatarFades.push_back(removedAvatar);
}

View file

@ -56,7 +56,7 @@ int main(int argc, const char* argv[]) {
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME");
const QString& applicationName = getInterfaceSharedMemoryName();
bool instanceMightBeRunning = true;

View file

@ -45,7 +45,7 @@ public:
void renderSilent(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames);
//
// HRTF local gain adjustment
// HRTF local gain adjustment in amplitude (1.0 == unity)
//
void setGainAdjustment(float gain) { _gainAdjust = HRTF_GAIN * gain; };

View file

@ -1047,7 +1047,7 @@ bool AvatarData::processAvatarIdentity(const Identity& identity) {
}
if (identity.displayName != _displayName) {
setDisplayName(identity.displayName);
_displayName = identity.displayName;
hasIdentityChanged = true;
}
maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName);
@ -1094,6 +1094,9 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
void AvatarData::setDisplayName(const QString& displayName) {
_displayName = displayName;
_sessionDisplayName = "";
sendIdentityPacket();
qCDebug(avatars) << "Changing display name for avatar to" << displayName;
}

View file

@ -646,6 +646,12 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
// the model is still being downloaded.
return false;
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
if (!_model) {
EntityTreePointer tree = getTree();
if (tree) {
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID()));
}
}
return (_model && _model->isLoaded());
}
return true;

View file

@ -972,7 +972,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
static const QVariant EMISSIVE = QByteArray("Emissive");
static const QVariant AMBIENT_FACTOR = QByteArray("AmbientFactor");
static const QVariant SHININESS = QByteArray("Shininess");
static const QVariant OPACITY = QByteArray("Shininess");
static const QVariant OPACITY = QByteArray("Opacity");
static const QVariant MAYA_USE_NORMAL_MAP = QByteArray("Maya|use_normal_map");
static const QVariant MAYA_BASE_COLOR = QByteArray("Maya|base_color");
static const QVariant MAYA_USE_COLOR_MAP = QByteArray("Maya|use_color_map");

View file

@ -26,6 +26,7 @@
#include "AccountManager.h"
#include "AddressManager.h"
#include "Assignment.h"
#include "AudioHelpers.h"
#include "HifiSockAddr.h"
#include "FingerprintUtils.h"
@ -846,6 +847,16 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) {
}
}
void NodeList::removeFromIgnoreMuteSets(const QUuid& nodeID) {
// don't remove yourself, or nobody
if (!nodeID.isNull() && _sessionUUID != nodeID) {
QWriteLocker ignoredSetLocker{ &_ignoredSetLock };
QWriteLocker personalMutedSetLocker{ &_personalMutedSetLock };
_ignoredNodeIDs.unsafe_erase(nodeID);
_personalMutedNodeIDs.unsafe_erase(nodeID);
}
}
bool NodeList::isIgnoringNode(const QUuid& nodeID) const {
QReadLocker ignoredSetLocker{ &_ignoredSetLock };
return _ignoredNodeIDs.find(nodeID) != _ignoredNodeIDs.cend();
@ -951,6 +962,30 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
}
}
void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
// cannot set gain of yourself or nobody
if (!nodeID.isNull() && _sessionUUID != nodeID) {
auto audioMixer = soloNodeOfType(NodeType::AudioMixer);
if (audioMixer) {
// setup the packet
auto setAvatarGainPacket = NLPacket::create(PacketType::PerAvatarGainSet, NUM_BYTES_RFC4122_UUID + sizeof(float), true);
// write the node ID to the packet
setAvatarGainPacket->write(nodeID.toRfc4122());
// We need to convert the gain in dB (from the script) to an amplitude before packing it.
setAvatarGainPacket->writePrimitive(packFloatGainToByte(fastExp2f(gain / 6.0206f)));
qCDebug(networking) << "Sending Set Avatar Gain packet UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
sendPacket(std::move(setAvatarGainPacket), *audioMixer);
} else {
qWarning() << "Couldn't find audio mixer to send set gain request";
}
} else {
qWarning() << "NodeList::setAvatarGain called with an invalid ID or an ID which matches the current session ID:" << nodeID;
}
}
void NodeList::kickNodeBySessionID(const QUuid& nodeID) {
// send a request to domain-server to kick the node with the given session ID
// the domain-server will handle the persistence of the kick (via username or IP)

View file

@ -82,6 +82,7 @@ public:
bool isIgnoringNode(const QUuid& nodeID) const;
void personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled);
bool isPersonalMutingNode(const QUuid& nodeID) const;
void setAvatarGain(const QUuid& nodeID, float gain);
void kickNodeBySessionID(const QUuid& nodeID);
void muteNodeBySessionID(const QUuid& nodeID);
@ -89,6 +90,8 @@ public:
bool getRequestsDomainListData() { return _requestsDomainListData; }
void setRequestsDomainListData(bool isRequesting);
void removeFromIgnoreMuteSets(const QUuid& nodeID);
public slots:
void reset();
void sendDomainServerCheckIn();

View file

@ -106,7 +106,8 @@ public:
ViewFrustum,
RequestsDomainListData,
ExitingSpaceBubble,
LAST_PACKET_TYPE = ExitingSpaceBubble
PerAvatarGainSet,
LAST_PACKET_TYPE = PerAvatarGainSet
};
};
@ -210,7 +211,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
HandControllerJoints,
HasKillAvatarReason,
SessionDisplayName,
Unignore
Unignore,
ImmediateSessionDisplayNameUpdates
};
enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -359,8 +359,11 @@ void ModelMeshPartPayload::notifyLocationChanged() {
}
void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const QVector<glm::mat4>& clusterMatrices) {
void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform,
const QVector<glm::mat4>& clusterMatrices,
const QVector<glm::mat4>& cauterizedClusterMatrices) {
_transform = transform;
_cauterizedTransform = transform;
if (clusterMatrices.size() > 0) {
_worldBound = AABox();
@ -373,6 +376,11 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transf
_worldBound.transform(transform);
if (clusterMatrices.size() == 1) {
_transform = _transform.worldTransform(Transform(clusterMatrices[0]));
if (cauterizedClusterMatrices.size() != 0) {
_cauterizedTransform = _cauterizedTransform.worldTransform(Transform(cauterizedClusterMatrices[0]));
} else {
_cauterizedTransform = _transform;
}
}
}
}
@ -527,9 +535,14 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline:
} else {
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer);
}
batch.setModelTransform(_transform);
} else {
if (canCauterize && _model->getCauterizeBones()) {
batch.setModelTransform(_cauterizedTransform);
} else {
batch.setModelTransform(_transform);
}
}
batch.setModelTransform(_transform);
}
void ModelMeshPartPayload::startFade() {

View file

@ -85,7 +85,9 @@ public:
typedef Payload::DataPointer Pointer;
void notifyLocationChanged() override;
void updateTransformForSkinnedMesh(const Transform& transform, const QVector<glm::mat4>& clusterMatrices);
void updateTransformForSkinnedMesh(const Transform& transform,
const QVector<glm::mat4>& clusterMatrices,
const QVector<glm::mat4>& cauterizedClusterMatrices);
// Entity fade in
void startFade();
@ -106,6 +108,7 @@ public:
Model* _model;
Transform _cauterizedTransform;
int _meshIndex;
int _shapeID;

View file

@ -238,7 +238,7 @@ void Model::updateRenderItems() {
// update the model transform and bounding box for this render item.
const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex);
data.updateTransformForSkinnedMesh(modelTransform, state.clusterMatrices);
data.updateTransformForSkinnedMesh(modelTransform, state.clusterMatrices, state.cauterizedClusterMatrices);
}
}
});

View file

@ -42,6 +42,11 @@ bool UsersScriptingInterface::getPersonalMuteStatus(const QUuid& nodeID) {
return DependencyManager::get<NodeList>()->isPersonalMutingNode(nodeID);
}
void UsersScriptingInterface::setAvatarGain(const QUuid& nodeID, float gain) {
// ask the NodeList to set the gain of the specified avatar
DependencyManager::get<NodeList>()->setAvatarGain(nodeID, gain);
}
void UsersScriptingInterface::kick(const QUuid& nodeID) {
// ask the NodeList to kick the user with the given session ID
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);

View file

@ -61,6 +61,15 @@ public slots:
*/
bool getPersonalMuteStatus(const QUuid& nodeID);
/**jsdoc
* Sets an avatar's gain for you and you only.
* Units are Decibels (dB)
* @function Users.setAvatarGain
* @param {nodeID} nodeID The node or session ID of the user whose gain you want to modify.
* @param {float} gain The gain of the avatar you'd like to set. Units are dB.
*/
void setAvatarGain(const QUuid& nodeID, float gain);
/**jsdoc
* Kick another user.
* @function Users.kick
@ -130,6 +139,13 @@ signals:
*/
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint);
/**jsdoc
* Notifies scripts that a user has disconnected from the domain
* @function Users.avatar.avatarDisconnected
* @param {nodeID} NodeID The session ID of the avatar that has disconnected
*/
void avatarDisconnected(const QUuid& nodeID);
private:
bool getRequestsDomainListData();
void setRequestsDomainListData(bool requests);

View file

@ -18,6 +18,8 @@
#include <cctype>
#include <time.h>
#include <mutex>
#include <thread>
#include <set>
#include <glm/glm.hpp>
@ -1022,4 +1024,54 @@ bool getProcessorInfo(ProcessorInfo& info) {
#endif
return false;
}
}
const QString& getInterfaceSharedMemoryName() {
static const QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME");
return applicationName;
}
const std::vector<uint8_t>& getAvailableCores() {
static std::vector<uint8_t> availableCores;
#ifdef Q_OS_WIN
static std::once_flag once;
std::call_once(once, [&] {
DWORD_PTR defaultProcessAffinity = 0, defaultSystemAffinity = 0;
HANDLE process = GetCurrentProcess();
GetProcessAffinityMask(process, &defaultProcessAffinity, &defaultSystemAffinity);
for (uint64_t i = 0; i < sizeof(DWORD_PTR) * BITS_IN_BYTE; ++i) {
DWORD_PTR coreMask = 1;
coreMask <<= i;
if (0 != (defaultSystemAffinity & coreMask)) {
availableCores.push_back(i);
}
}
});
#endif
return availableCores;
}
void setMaxCores(uint8_t maxCores) {
#ifdef Q_OS_WIN
HANDLE process = GetCurrentProcess();
auto availableCores = getAvailableCores();
if (availableCores.size() <= maxCores) {
DWORD_PTR currentProcessAffinity = 0, currentSystemAffinity = 0;
GetProcessAffinityMask(process, &currentProcessAffinity, &currentSystemAffinity);
SetProcessAffinityMask(GetCurrentProcess(), currentSystemAffinity);
return;
}
DWORD_PTR newProcessAffinity = 0;
while (maxCores) {
int index = randIntInRange(0, (int)availableCores.size() - 1);
DWORD_PTR coreMask = 1;
coreMask <<= availableCores[index];
newProcessAffinity |= coreMask;
availableCores.erase(availableCores.begin() + index);
maxCores--;
}
SetProcessAffinityMask(process, newProcessAffinity);
#endif
}

View file

@ -231,4 +231,8 @@ struct ProcessorInfo {
bool getProcessorInfo(ProcessorInfo& info);
const QString& getInterfaceSharedMemoryName();
void setMaxCores(uint8_t maxCores);
#endif // hifi_SharedUtil_h

View file

@ -13,6 +13,7 @@ namespace hifi { namespace properties {
const char* CRASHED = "com.highfidelity.crashed";
const char* STEAM = "com.highfidelity.launchedFromSteam";
const char* LOGGER = "com.highfidelity.logger";
const char* OCULUS_STORE = "com.highfidelity.oculusStore";
const char* TEST = "com.highfidelity.test";
const char* TRACING = "com.highfidelity.tracing";

View file

@ -15,6 +15,7 @@ namespace hifi { namespace properties {
extern const char* CRASHED;
extern const char* STEAM;
extern const char* LOGGER;
extern const char* OCULUS_STORE;
extern const char* TEST;
extern const char* TRACING;

View file

@ -8,21 +8,31 @@
if (WIN32)
# we're using static GLEW, so define GLEW_STATIC
add_definitions(-DGLEW_STATIC)
# we're using static GLEW, so define GLEW_STATIC
add_definitions(-DGLEW_STATIC)
set(TARGET_NAME oculus)
setup_hifi_plugin(Multimedia)
link_hifi_libraries(shared gl gpu gpu-gl controllers ui
plugins ui-plugins display-plugins input-plugins
audio-client networking render-utils)
include_hifi_library_headers(octree)
add_dependency_external_projects(LibOVR)
find_package(LibOVR REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${LIBOVR_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES})
target_link_libraries(${TARGET_NAME} Winmm.lib)
# if we were passed an Oculus App ID for entitlement checks, send that along
if (DEFINED ENV{OCULUS_APP_ID})
add_definitions(-DOCULUS_APP_ID="$ENV{OCULUS_APP_ID}")
endif ()
set(TARGET_NAME oculus)
setup_hifi_plugin(Multimedia)
link_hifi_libraries(
shared gl gpu gpu-gl controllers ui
plugins ui-plugins display-plugins input-plugins
audio-client networking render-utils
)
include_hifi_library_headers(octree)
add_dependency_external_projects(LibOVR)
find_package(LibOVR REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${LIBOVR_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES})
target_link_libraries(${TARGET_NAME} Winmm.lib)
add_dependency_external_projects(LibOVRPlatform)
find_package(LibOVRPlatform REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${LIBOVRPLATFORM_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${LIBOVRPLATFORM_LIBRARIES})
endif()

View file

@ -15,8 +15,12 @@
#include <QtCore/QDir>
#include <QtCore/QProcessEnvironment>
#define OVRPL_DISABLED
#include <OVR_Platform.h>
#include <controllers/Input.h>
#include <controllers/Pose.h>
#include <shared/GlobalAppProperties.h>
#include <NumericalConstants.h>
Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display")
@ -89,6 +93,18 @@ ovrSession acquireOculusSession() {
return session;
}
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
if (ovr_PlatformInitializeWindows(OCULUS_APP_ID) != ovrPlatformInitialize_Success) {
// we were unable to initialize the platform for entitlement check - fail the check
_quitRequested = true;
} else {
qCDebug(oculus) << "Performing Oculus Platform entitlement check";
ovr_Entitlement_GetIsViewerEntitled();
}
}
#endif
Q_ASSERT(0 == refCount);
ovrGraphicsLuid luid;
if (!OVR_SUCCESS(ovr_Create(&session, &luid))) {
@ -127,6 +143,35 @@ void handleOVREvents() {
_quitRequested = status.ShouldQuit;
_reorientRequested = status.ShouldRecenter;
#ifdef OCULUS_APP_ID
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
// pop messages to see if we got a return for an entitlement check
ovrMessageHandle message = ovr_PopMessage();
while (message) {
switch (ovr_Message_GetType(message)) {
case ovrMessage_Entitlement_GetIsViewerEntitled: {
if (!ovr_Message_IsError(message)) {
// this viewer is entitled, no need to flag anything
qCDebug(oculus) << "Oculus Platform entitlement check succeeded, proceeding normally";
} else {
// we failed the entitlement check, set our flag so the app can stop
qCDebug(oculus) << "Oculus Platform entitlement check failed, app will now quit" << OCULUS_APP_ID;
_quitRequested = true;
}
}
}
// free the message handle to cleanup and not leak
ovr_FreeMessage(message);
// pop the next message to check, if there is one
message = ovr_PopMessage();
}
}
#endif
}
bool quitRequested() {
@ -217,4 +262,4 @@ controller::Pose ovrControllerPoseToHandPose(
pose.velocity = toGlm(handPose.LinearVelocity);
pose.valid = true;
return pose;
}
}

View file

@ -3082,7 +3082,7 @@ var handleHandMessages = function(channel, message, sender) {
Messages.messageReceived.connect(handleHandMessages);
var TARGET_UPDATE_HZ = 50; // 50hz good enough (no change in logic)
var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update
var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ;
var lastInterval = Date.now();
@ -3095,7 +3095,7 @@ var updateTotalWork = 0;
var UPDATE_PERFORMANCE_DEBUGGING = false;
var updateIntervalTimer = Script.setInterval(function(){
function updateWrapper(){
intervalCount++;
var thisInterval = Date.now();
@ -3141,11 +3141,12 @@ var updateIntervalTimer = Script.setInterval(function(){
updateTotalWork = 0;
}
}, BASIC_TIMER_INTERVAL_MS);
}
Script.update.connect(updateWrapper);
function cleanup() {
Menu.removeMenuItem("Developer", "Show Grab Sphere");
Script.clearInterval(updateIntervalTimer);
Script.update.disconnect(updateWrapper);
rightController.cleanup();
leftController.cleanup();
Controller.disableMapping(MAPPING_NAME);

View file

@ -233,6 +233,15 @@ pal.fromQml.connect(function (message) { // messages are {method, params}, like
removeOverlays();
populateUserList();
break;
case 'updateGain':
data = message.params;
Users.setAvatarGain(data['sessionId'], data['gain']);
break;
case 'displayNameUpdate':
if (MyAvatar.displayName != message.params) {
MyAvatar.displayName = message.params;
}
break;
default:
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
}
@ -448,50 +457,6 @@ triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Cont
triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
//
// Manage the connection between the button and the window.
//
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var buttonName = "pal";
var button = toolBar.addButton({
objectName: buttonName,
imageURL: Script.resolvePath("assets/images/tools/people.svg"),
visible: true,
hoverState: 2,
defaultState: 1,
buttonState: 1,
alpha: 0.9
});
var isWired = false;
function off() {
if (isWired) { // It is not ok to disconnect these twice, hence guard.
Script.update.disconnect(updateOverlays);
Controller.mousePressEvent.disconnect(handleMouseEvent);
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
isWired = false;
}
triggerMapping.disable(); // It's ok if we disable twice.
triggerPressMapping.disable(); // see above
removeOverlays();
Users.requestsDomainListData = false;
}
function onClicked() {
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();
} else {
off();
}
pal.setVisible(!pal.visible);
}
//
// Message from other scripts, such as edit.js
//
@ -523,6 +488,7 @@ var LOUDNESS_SCALE = 2.8 / 5.0;
var LOG2 = Math.log(2.0);
var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too)
var myData = {}; // we're not includied in ExtendedOverlay.get.
var audioInterval;
function getAudioLevel(id) {
// the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged
@ -555,21 +521,74 @@ function getAudioLevel(id) {
return audioLevel;
}
function createAudioInterval() {
// we will update the audioLevels periodically
// TODO: tune for efficiency - expecially with large numbers of avatars
return Script.setInterval(function () {
if (pal.visible) {
var param = {};
AvatarList.getAvatarIdentifiers().forEach(function (id) {
var level = getAudioLevel(id);
// qml didn't like an object with null/empty string for a key, so...
var userId = id || 0;
param[userId] = level;
});
pal.sendToQml({method: 'updateAudioLevel', params: param});
}
}, AUDIO_LEVEL_UPDATE_INTERVAL_MS);
}
// we will update the audioLevels periodically
// TODO: tune for efficiency - expecially with large numbers of avatars
Script.setInterval(function () {
if (pal.visible) {
var param = {};
AvatarList.getAvatarIdentifiers().forEach(function (id) {
var level = getAudioLevel(id);
// qml didn't like an object with null/empty string for a key, so...
var userId = id || 0;
param[userId] = level;
});
pal.sendToQml({method: 'updateAudioLevel', params: param});
//
// Manage the connection between the button and the window.
//
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var buttonName = "pal";
var button = toolBar.addButton({
objectName: buttonName,
imageURL: Script.resolvePath("assets/images/tools/people.svg"),
visible: true,
hoverState: 2,
defaultState: 1,
buttonState: 1,
alpha: 0.9
});
var isWired = false;
function off() {
if (isWired) { // It is not ok to disconnect these twice, hence guard.
Script.update.disconnect(updateOverlays);
Controller.mousePressEvent.disconnect(handleMouseEvent);
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
isWired = false;
}
}, AUDIO_LEVEL_UPDATE_INTERVAL_MS);
triggerMapping.disable(); // It's ok if we disable twice.
triggerPressMapping.disable(); // see above
removeOverlays();
Users.requestsDomainListData = false;
if (audioInterval) {
Script.clearInterval(audioInterval);
}
}
function onClicked() {
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();
createAudioInterval();
} else {
off();
}
pal.setVisible(!pal.visible);
}
function avatarDisconnected(nodeID) {
// remove from the pal list
pal.sendToQml({method: 'avatarDisconnected', params: [nodeID]});
}
//
// Button state.
//
@ -582,14 +601,16 @@ button.clicked.connect(onClicked);
pal.visibleChanged.connect(onVisibleChanged);
pal.closed.connect(off);
Users.usernameFromIDReply.connect(usernameFromIDReply);
function clearIgnoredInQMLAndClosePAL() {
pal.sendToQml({ method: 'clearIgnored' });
Users.avatarDisconnected.connect(avatarDisconnected);
function clearLocalQMLDataAndClosePAL() {
pal.sendToQml({ method: 'clearLocalQMLData' });
if (pal.visible) {
onClicked(); // Close the PAL
}
}
Window.domainChanged.connect(clearIgnoredInQMLAndClosePAL);
Window.domainConnectionRefused.connect(clearIgnoredInQMLAndClosePAL);
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
//
// Cleanup.
@ -600,10 +621,11 @@ Script.scriptEnding.connect(function () {
pal.visibleChanged.disconnect(onVisibleChanged);
pal.closed.disconnect(off);
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
Window.domainChanged.disconnect(clearIgnoredInQMLAndClosePAL);
Window.domainConnectionRefused.disconnect(clearIgnoredInQMLAndClosePAL);
Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
Messages.unsubscribe(CHANNEL);
Messages.messageReceived.disconnect(receiveMessage);
Users.avatarDisconnected.disconnect(avatarDisconnected);
off();
});