mirror of
https://github.com/overte-org/overte.git
synced 2025-04-15 12:28:51 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into meta
This commit is contained in:
commit
05304c72e0
20 changed files with 483 additions and 82 deletions
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Binary file not shown.
|
@ -28,7 +28,7 @@ Item {
|
|||
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
|
||||
|
@ -55,18 +55,112 @@ Item {
|
|||
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
|
||||
|
@ -83,7 +177,7 @@ Item {
|
|||
// Size
|
||||
width: parent.width
|
||||
// Anchors
|
||||
anchors.top: displayNameText.bottom
|
||||
anchors.top: isMyCard ? myDisplayNameSpacer.bottom : displayNameText.bottom
|
||||
// Text Size
|
||||
size: thisNameCard.usernameTextHeight
|
||||
// Text Positioning
|
||||
|
@ -105,7 +199,7 @@ Item {
|
|||
Rectangle {
|
||||
id: nameCardVUMeter
|
||||
// Size
|
||||
width: ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
|
||||
width: isMyCard ? myDisplayName.width - 20 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
|
||||
height: 8
|
||||
// Anchors
|
||||
anchors.top: spacer.bottom
|
||||
|
|
|
@ -35,6 +35,17 @@ Rectangle {
|
|||
// Keep a local list of per-avatar gainSliderValueDBs. Far faster than fetching this data from the server.
|
||||
// NOTE: if another script modifies the per-avatar gain, this value won't be accurate!
|
||||
property var gainSliderValueDB: ({});
|
||||
|
||||
// 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 {
|
||||
|
@ -176,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
|
||||
|
@ -337,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
|
||||
|
@ -402,9 +406,6 @@ Rectangle {
|
|||
onExited: adminHelpText.color = hifi.colors.redHighlight
|
||||
}
|
||||
}
|
||||
LetterboxMessage {
|
||||
id: letterboxMessage
|
||||
}
|
||||
}
|
||||
|
||||
function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml
|
||||
|
@ -475,8 +476,6 @@ Rectangle {
|
|||
// Set the userName appropriately
|
||||
userModel.setProperty(userIndex, "userName", userName);
|
||||
userModelData[userIndex].userName = userName; // Defensive programming
|
||||
} else {
|
||||
console.log("updateUsername() called with unknown UUID: ", userId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -492,8 +491,6 @@ Rectangle {
|
|||
if (userIndex != -1) {
|
||||
userModel.setProperty(userIndex, "audioLevel", audioLevel);
|
||||
userModelData[userIndex].audioLevel = audioLevel; // Defensive programming
|
||||
} else {
|
||||
console.log("updateUsername() called with unknown UUID: ", userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -502,6 +499,10 @@ Rectangle {
|
|||
ignored = {};
|
||||
gainSliderValueDB = {};
|
||||
break;
|
||||
case 'avatarDisconnected':
|
||||
var sessionID = message.params[0];
|
||||
delete ignored[sessionID];
|
||||
break;
|
||||
default:
|
||||
console.log('Unrecognized message:', JSON.stringify(message));
|
||||
}
|
||||
|
|
|
@ -312,10 +312,11 @@ Item {
|
|||
readonly property string error: "="
|
||||
readonly property string settings: "@"
|
||||
readonly property string trash: "{"
|
||||
readonly property string objectGroup: ""
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1238,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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -847,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();
|
||||
|
|
|
@ -90,6 +90,8 @@ public:
|
|||
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
||||
void setRequestsDomainListData(bool isRequesting);
|
||||
|
||||
void removeFromIgnoreMuteSets(const QUuid& nodeID);
|
||||
|
||||
public slots:
|
||||
void reset();
|
||||
void sendDomainServerCheckIn();
|
||||
|
|
|
@ -211,7 +211,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
|||
HandControllerJoints,
|
||||
HasKillAvatarReason,
|
||||
SessionDisplayName,
|
||||
Unignore
|
||||
Unignore,
|
||||
ImmediateSessionDisplayNameUpdates
|
||||
};
|
||||
|
||||
enum class DomainConnectRequestVersion : PacketVersion {
|
||||
|
|
|
@ -139,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);
|
||||
|
|
|
@ -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, ¤tProcessAffinity, ¤tSystemAffinity);
|
||||
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
|
||||
}
|
||||
|
|
|
@ -231,4 +231,8 @@ struct ProcessorInfo {
|
|||
|
||||
bool getProcessorInfo(ProcessorInfo& info);
|
||||
|
||||
const QString& getInterfaceSharedMemoryName();
|
||||
|
||||
void setMaxCores(uint8_t maxCores);
|
||||
|
||||
#endif // hifi_SharedUtil_h
|
||||
|
|
19
scripts/developer/tests/ambientSoundTest.js
Normal file
19
scripts/developer/tests/ambientSoundTest.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
var WAVE = 'http://cdn.rawgit.com/ambisonictoolkit/atk-sounds/aa31005c/stereo/Aurora_Surgit-Lux_Aeterna.wav';
|
||||
var uuid = Entities.addEntity({
|
||||
type: "Shape",
|
||||
shape: "Icosahedron",
|
||||
dimensions: Vec3.HALF,
|
||||
script: Script.resolvePath('../../tutorials/entity_scripts/ambientSound.js'),
|
||||
position: Vec3.sum(Vec3.multiply(5, Quat.getFront(MyAvatar.orientation)), MyAvatar.position),
|
||||
userData: JSON.stringify({
|
||||
soundURL: WAVE,
|
||||
maxVolume: 0.1,
|
||||
range: 25,
|
||||
disabled: true,
|
||||
grabbableKey: { wantsTrigger: true },
|
||||
}),
|
||||
lifetime: 600,
|
||||
});
|
||||
Script.scriptEnding.connect(function() {
|
||||
Entities.deleteEntity(uuid);
|
||||
});
|
|
@ -237,6 +237,11 @@ pal.fromQml.connect(function (message) { // messages are {method, params}, like
|
|||
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));
|
||||
}
|
||||
|
@ -493,7 +498,6 @@ function getAudioLevel(id) {
|
|||
var audioLevel = 0.0;
|
||||
var data = id ? ExtendedOverlay.get(id) : myData;
|
||||
if (!data) {
|
||||
print('no data for', id);
|
||||
return audioLevel;
|
||||
}
|
||||
|
||||
|
@ -580,7 +584,10 @@ function onClicked() {
|
|||
}
|
||||
pal.setVisible(!pal.visible);
|
||||
}
|
||||
|
||||
function avatarDisconnected(nodeID) {
|
||||
// remove from the pal list
|
||||
pal.sendToQml({method: 'avatarDisconnected', params: [nodeID]});
|
||||
}
|
||||
//
|
||||
// Button state.
|
||||
//
|
||||
|
@ -593,6 +600,8 @@ button.clicked.connect(onClicked);
|
|||
pal.visibleChanged.connect(onVisibleChanged);
|
||||
pal.closed.connect(off);
|
||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||
Users.avatarDisconnected.connect(avatarDisconnected);
|
||||
|
||||
function clearLocalQMLDataAndClosePAL() {
|
||||
pal.sendToQml({ method: 'clearLocalQMLData' });
|
||||
if (pal.visible) {
|
||||
|
@ -615,6 +624,7 @@ Script.scriptEnding.connect(function () {
|
|||
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
|
||||
Messages.unsubscribe(CHANNEL);
|
||||
Messages.messageReceived.disconnect(receiveMessage);
|
||||
Users.avatarDisconnected.disconnect(avatarDisconnected);
|
||||
off();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,34 +1,48 @@
|
|||
// ambientSound.js
|
||||
//
|
||||
// This entity script will allow you to create an ambient sound that loops when a person is within a given
|
||||
// range of this entity. Great way to add one or more ambisonic soundfields to your environment.
|
||||
// range of this entity. Great way to add one or more ambisonic soundfields to your environment.
|
||||
//
|
||||
// In the userData section for the entity, add/edit three values:
|
||||
// userData.soundURL should be a string giving the URL to the sound file. Defaults to 100 meters if not set.
|
||||
// In the userData section for the entity, add/edit three values:
|
||||
// userData.soundURL should be a string giving the URL to the sound file. Defaults to 100 meters if not set.
|
||||
// userData.range should be an integer for the max distance away from the entity where the sound will be audible.
|
||||
// userData.volume is the max volume at which the clip should play. Defaults to 1.0 full volume)
|
||||
// userData.maxVolume is the max volume at which the clip should play. Defaults to 1.0 full volume.
|
||||
// userData.disabled is an optionanl boolean flag which can be used to disable the ambient sound. Defaults to false.
|
||||
//
|
||||
// The rotation of the entity is copied to the ambisonic field, so by rotating the entity you will rotate the
|
||||
// direction in-which a certain sound comes from.
|
||||
//
|
||||
// The rotation of the entity is copied to the ambisonic field, so by rotating the entity you will rotate the
|
||||
// direction in-which a certain sound comes from.
|
||||
//
|
||||
// Remember that the entity has to be visible to the user for the sound to play at all, so make sure the entity is
|
||||
// large enough to be loaded at the range you set, particularly for large ranges.
|
||||
//
|
||||
// large enough to be loaded at the range you set, particularly for large ranges.
|
||||
//
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function(){
|
||||
(function(){
|
||||
// This sample clip and range will be used if you don't add userData to the entity (see above)
|
||||
var DEFAULT_RANGE = 100;
|
||||
var DEFAULT_URL = "http://hifi-content.s3.amazonaws.com/ken/samples/forest_ambiX.wav";
|
||||
var DEFAULT_VOLUME = 1.0;
|
||||
|
||||
var DEFAULT_USERDATA = {
|
||||
soundURL: DEFAULT_URL,
|
||||
range: DEFAULT_RANGE,
|
||||
maxVolume: DEFAULT_VOLUME,
|
||||
disabled: true,
|
||||
grabbableKey: { wantsTrigger: true },
|
||||
};
|
||||
|
||||
var soundURL = "";
|
||||
var soundOptions = {
|
||||
loop: true,
|
||||
localOnly: true,
|
||||
};
|
||||
var range = DEFAULT_RANGE;
|
||||
var maxVolume = DEFAULT_VOLUME;
|
||||
var disabled = false;
|
||||
var UPDATE_INTERVAL_MSECS = 100;
|
||||
var rotation;
|
||||
|
||||
|
@ -39,14 +53,13 @@
|
|||
var checkTimer = false;
|
||||
var _this;
|
||||
|
||||
var WANT_COLOR_CHANGE = false;
|
||||
var COLOR_OFF = { red: 128, green: 128, blue: 128 };
|
||||
var COLOR_ON = { red: 255, green: 0, blue: 0 };
|
||||
|
||||
var WANT_DEBUG = false;
|
||||
function debugPrint(string) {
|
||||
if (WANT_DEBUG) {
|
||||
print(string);
|
||||
print("ambientSound | " + string);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,84 +68,201 @@
|
|||
var oldSoundURL = soundURL;
|
||||
var props = Entities.getEntityProperties(entity, [ "userData" ]);
|
||||
if (props.userData) {
|
||||
var data = JSON.parse(props.userData);
|
||||
try {
|
||||
var data = JSON.parse(props.userData);
|
||||
} catch(e) {
|
||||
debugPrint("unable to parse userData JSON string: " + props.userData);
|
||||
this.cleanup();
|
||||
return;
|
||||
}
|
||||
if (data.soundURL && !(soundURL === data.soundURL)) {
|
||||
soundURL = data.soundURL;
|
||||
debugPrint("Read ambient sound URL: " + soundURL);
|
||||
} else if (!data.soundURL) {
|
||||
soundURL = DEFAULT_URL;
|
||||
debugPrint("Read ambient sound URL: " + soundURL);
|
||||
}
|
||||
if (data.range && !(range === data.range)) {
|
||||
range = data.range;
|
||||
debugPrint("Read ambient sound range: " + range);
|
||||
}
|
||||
if (data.volume && !(maxVolume === data.volume)) {
|
||||
maxVolume = data.volume;
|
||||
// Check known aliases for the "volume" setting (which allows for inplace upgrade of existing marketplace entities)
|
||||
data.maxVolume = data.maxVolume || data.soundVolume || data.volume;
|
||||
if (data.maxVolume && !(maxVolume === data.maxVolume)) {
|
||||
maxVolume = data.maxVolume;
|
||||
debugPrint("Read ambient sound volume: " + maxVolume);
|
||||
}
|
||||
if ("disabled" in data && !(disabled === data.disabled)) {
|
||||
disabled = data.disabled;
|
||||
debugPrint("Read ambient disabled state: " + disabled);
|
||||
this._updateColor(disabled);
|
||||
}
|
||||
}
|
||||
if (disabled) {
|
||||
this.cleanup();
|
||||
soundURL = "";
|
||||
return;
|
||||
} else if (!checkTimer) {
|
||||
checkTimer = Script.setInterval(_this.maybeUpdate, UPDATE_INTERVAL_MSECS);
|
||||
}
|
||||
if (!(soundURL === oldSoundURL) || (soundURL === "")) {
|
||||
debugPrint("Loading ambient sound into cache");
|
||||
ambientSound = SoundCache.getSound(soundURL);
|
||||
if (soundURL) {
|
||||
debugPrint("Loading ambient sound into cache");
|
||||
// Use prefetch to detect URL loading errors
|
||||
var resource = SoundCache.prefetch(soundURL);
|
||||
function onStateChanged() {
|
||||
if (resource.state === Resource.State.FINISHED) {
|
||||
resource.stateChanged.disconnect(onStateChanged);
|
||||
ambientSound = SoundCache.getSound(soundURL);
|
||||
} else if (resource.state === Resource.State.FAILED) {
|
||||
resource.stateChanged.disconnect(onStateChanged);
|
||||
debugPrint("Failed to download ambient sound: " + soundURL);
|
||||
}
|
||||
}
|
||||
resource.stateChanged.connect(onStateChanged);
|
||||
onStateChanged(resource.state);
|
||||
}
|
||||
if (soundPlaying && soundPlaying.playing) {
|
||||
debugPrint("URL changed, stopping current ambient sound");
|
||||
soundPlaying.stop();
|
||||
soundPlaying = false;
|
||||
if (WANT_COLOR_CHANGE) {
|
||||
Entities.editEntity(entity, { color: COLOR_OFF });
|
||||
}
|
||||
debugPrint("Restarting ambient sound");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.preload = function(entityID) {
|
||||
this.clickDownOnEntity = function(entityID, mouseEvent) {
|
||||
if (mouseEvent.isPrimaryButton) {
|
||||
this._toggle("primary click");
|
||||
}
|
||||
};
|
||||
|
||||
this.startFarTrigger = function() {
|
||||
this._toggle("far click");
|
||||
};
|
||||
|
||||
this._toggle = function(hint) {
|
||||
// Toggle between ON/OFF state, but only if not in edit mode
|
||||
if (Settings.getValue("io.highfidelity.isEditting")) {
|
||||
return;
|
||||
}
|
||||
var props = Entities.getEntityProperties(entity, [ "userData" ]);
|
||||
if (!props.userData) {
|
||||
debugPrint("userData is empty; ignoring " + hint);
|
||||
this.cleanup();
|
||||
return;
|
||||
}
|
||||
var data = JSON.parse(props.userData);
|
||||
data.disabled = !data.disabled;
|
||||
|
||||
debugPrint(hint + " -- triggering ambient sound " + (data.disabled ? "OFF" : "ON") + " (" + data.soundURL + ")");
|
||||
|
||||
this.cleanup();
|
||||
|
||||
// Save the userData and notify nearby listeners of the change
|
||||
Entities.editEntity(entity, {
|
||||
userData: JSON.stringify(data)
|
||||
});
|
||||
Messages.sendMessage(entity, "toggled");
|
||||
};
|
||||
|
||||
this._updateColor = function(disabled) {
|
||||
// Update Shape or Text Entity color based on ON/OFF status
|
||||
var props = Entities.getEntityProperties(entity, [ "color", "textColor" ]);
|
||||
var targetColor = disabled ? COLOR_OFF : COLOR_ON;
|
||||
var currentColor = props.textColor || props.color;
|
||||
var newProps = props.textColor ? { textColor: targetColor } : { color: targetColor };
|
||||
|
||||
if (currentColor.red !== targetColor.red ||
|
||||
currentColor.green !== targetColor.green ||
|
||||
currentColor.blue !== targetColor.blue) {
|
||||
Entities.editEntity(entity, newProps);
|
||||
}
|
||||
};
|
||||
|
||||
this.preload = function(entityID) {
|
||||
// Load the sound and range from the entity userData fields, and note the position of the entity.
|
||||
debugPrint("Ambient sound preload");
|
||||
debugPrint("Ambient sound preload " + entityID);
|
||||
entity = entityID;
|
||||
_this = this;
|
||||
checkTimer = Script.setInterval(this.maybeUpdate, UPDATE_INTERVAL_MSECS);
|
||||
};
|
||||
|
||||
var props = Entities.getEntityProperties(entity, [ "userData" ]);
|
||||
var data = {};
|
||||
if (props.userData) {
|
||||
data = JSON.parse(props.userData);
|
||||
}
|
||||
var changed = false;
|
||||
for(var p in DEFAULT_USERDATA) {
|
||||
if (!(p in data)) {
|
||||
data[p] = DEFAULT_USERDATA[p];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (!data.grabbableKey.wantsTrigger) {
|
||||
data.grabbableKey.wantsTrigger = true;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
debugPrint("applying default values to userData");
|
||||
Entities.editEntity(entity, { userData: JSON.stringify(data) });
|
||||
}
|
||||
this._updateColor(data.disabled);
|
||||
this.updateSettings();
|
||||
|
||||
// Subscribe to toggle notifications using entity ID as a channel name
|
||||
Messages.subscribe(entity);
|
||||
Messages.messageReceived.connect(this, "_onMessageReceived");
|
||||
};
|
||||
|
||||
this._onMessageReceived = function(channel, message, sender, local) {
|
||||
// Handle incoming toggle notifications
|
||||
if (channel === entity && message === "toggled") {
|
||||
debugPrint("received " + message + " from " + sender);
|
||||
this.updateSettings();
|
||||
}
|
||||
};
|
||||
|
||||
this.maybeUpdate = function() {
|
||||
// Every UPDATE_INTERVAL_MSECS, update the volume of the ambient sound based on distance from my avatar
|
||||
_this.updateSettings();
|
||||
var HYSTERESIS_FRACTION = 0.1;
|
||||
var props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
|
||||
if (disabled || !props.position) {
|
||||
_this.cleanup();
|
||||
return;
|
||||
}
|
||||
center = props.position;
|
||||
rotation = props.rotation;
|
||||
var distance = Vec3.length(Vec3.subtract(MyAvatar.position, center));
|
||||
if (distance <= range) {
|
||||
var volume = (1.0 - distance / range) * maxVolume;
|
||||
if (!soundPlaying && ambientSound.downloaded) {
|
||||
soundPlaying = Audio.playSound(ambientSound, { loop: true,
|
||||
localOnly: true,
|
||||
orientation: rotation,
|
||||
volume: volume });
|
||||
debugPrint("Starting ambient sound, volume: " + volume);
|
||||
if (WANT_COLOR_CHANGE) {
|
||||
Entities.editEntity(entity, { color: COLOR_ON });
|
||||
}
|
||||
|
||||
soundOptions.orientation = rotation;
|
||||
soundOptions.volume = volume;
|
||||
if (!soundPlaying && ambientSound && ambientSound.downloaded) {
|
||||
debugPrint("Starting ambient sound: " + soundURL + " (duration: " + ambientSound.duration + ")");
|
||||
soundPlaying = Audio.playSound(ambientSound, soundOptions);
|
||||
} else if (soundPlaying && soundPlaying.playing) {
|
||||
soundPlaying.setOptions( { volume: volume, orientation: rotation } );
|
||||
soundPlaying.setOptions(soundOptions);
|
||||
}
|
||||
|
||||
} else if (soundPlaying && soundPlaying.playing && (distance > range * HYSTERESIS_FRACTION)) {
|
||||
soundPlaying.stop();
|
||||
soundPlaying = false;
|
||||
Entities.editEntity(entity, { color: { red: 128, green: 128, blue: 128 }});
|
||||
debugPrint("Out of range, stopping ambient sound");
|
||||
debugPrint("Out of range, stopping ambient sound: " + soundURL);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.unload = function(entityID) {
|
||||
this.unload = function(entityID) {
|
||||
debugPrint("Ambient sound unload");
|
||||
this.cleanup();
|
||||
Messages.unsubscribe(entity);
|
||||
Messages.messageReceived.disconnect(this, "_onMessageReceived");
|
||||
};
|
||||
|
||||
this.cleanup = function() {
|
||||
if (checkTimer) {
|
||||
Script.clearInterval(checkTimer);
|
||||
checkTimer = false;
|
||||
}
|
||||
if (soundPlaying && soundPlaying.playing) {
|
||||
soundPlaying.stop();
|
||||
soundPlaying = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue