fixing merge conflict

This commit is contained in:
Dante Ruiz 2017-11-20 09:51:10 -08:00
commit 76935c1457
49 changed files with 1078 additions and 379 deletions

View file

@ -29,6 +29,7 @@
#include <UUID.h> #include <UUID.h>
#include <CPUDetect.h> #include <CPUDetect.h>
#include "AudioLogging.h"
#include "AudioHelpers.h" #include "AudioHelpers.h"
#include "AudioRingBuffer.h" #include "AudioRingBuffer.h"
#include "AudioMixerClientData.h" #include "AudioMixerClientData.h"
@ -130,7 +131,7 @@ void AudioMixer::queueReplicatedAudioPacket(QSharedPointer<ReceivedMessage> mess
PacketType rewrittenType = PacketTypeEnum::getReplicatedPacketMapping().key(message->getType()); PacketType rewrittenType = PacketTypeEnum::getReplicatedPacketMapping().key(message->getType());
if (rewrittenType == PacketType::Unknown) { if (rewrittenType == PacketType::Unknown) {
qDebug() << "Cannot unwrap replicated packet type not present in REPLICATED_PACKET_WRAPPING"; qCDebug(audio) << "Cannot unwrap replicated packet type not present in REPLICATED_PACKET_WRAPPING";
} }
auto replicatedMessage = QSharedPointer<ReceivedMessage>::create(audioData, rewrittenType, auto replicatedMessage = QSharedPointer<ReceivedMessage>::create(audioData, rewrittenType,
@ -345,7 +346,7 @@ void AudioMixer::sendStatsPacket() {
void AudioMixer::run() { void AudioMixer::run() {
qDebug() << "Waiting for connection to domain to request settings from domain-server."; qCDebug(audio) << "Waiting for connection to domain to request settings from domain-server.";
// wait until we have the domain-server settings, otherwise we bail // wait until we have the domain-server settings, otherwise we bail
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler(); DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
@ -502,14 +503,14 @@ void AudioMixer::throttle(std::chrono::microseconds duration, int frame) {
int proportionalTerm = 1 + (_trailingMixRatio - TARGET) / 0.1f; int proportionalTerm = 1 + (_trailingMixRatio - TARGET) / 0.1f;
_throttlingRatio += THROTTLE_RATE * proportionalTerm; _throttlingRatio += THROTTLE_RATE * proportionalTerm;
_throttlingRatio = std::min(_throttlingRatio, 1.0f); _throttlingRatio = std::min(_throttlingRatio, 1.0f);
qDebug("audio-mixer is struggling (%f mix/sleep) - throttling %f of streams", qCDebug(audio) << "audio-mixer is struggling (" << _trailingMixRatio << "mix/sleep) - throttling"
(double)_trailingMixRatio, (double)_throttlingRatio); << _throttlingRatio << "of streams";
} else if (_throttlingRatio > 0.0f && _trailingMixRatio <= BACKOFF_TARGET) { } else if (_throttlingRatio > 0.0f && _trailingMixRatio <= BACKOFF_TARGET) {
int proportionalTerm = 1 + (TARGET - _trailingMixRatio) / 0.2f; int proportionalTerm = 1 + (TARGET - _trailingMixRatio) / 0.2f;
_throttlingRatio -= BACKOFF_RATE * proportionalTerm; _throttlingRatio -= BACKOFF_RATE * proportionalTerm;
_throttlingRatio = std::max(_throttlingRatio, 0.0f); _throttlingRatio = std::max(_throttlingRatio, 0.0f);
qDebug("audio-mixer is recovering (%f mix/sleep) - throttling %f of streams", qCDebug(audio) << "audio-mixer is recovering (" << _trailingMixRatio << "mix/sleep) - throttling"
(double)_trailingMixRatio, (double)_throttlingRatio); << _throttlingRatio << "of streams";
} }
} }
} }
@ -534,7 +535,7 @@ void AudioMixer::clearDomainSettings() {
} }
void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
qDebug() << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled"); qCDebug(audio) << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled");
if (settingsObject.contains(AUDIO_THREADING_GROUP_KEY)) { if (settingsObject.contains(AUDIO_THREADING_GROUP_KEY)) {
QJsonObject audioThreadingGroupObject = settingsObject[AUDIO_THREADING_GROUP_KEY].toObject(); QJsonObject audioThreadingGroupObject = settingsObject[AUDIO_THREADING_GROUP_KEY].toObject();
@ -557,7 +558,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "dynamic_jitter_buffer"; const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "dynamic_jitter_buffer";
bool enableDynamicJitterBuffer = audioBufferGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool(); bool enableDynamicJitterBuffer = audioBufferGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool();
if (enableDynamicJitterBuffer) { if (enableDynamicJitterBuffer) {
qDebug() << "Enabling dynamic jitter buffers."; qCDebug(audio) << "Enabling dynamic jitter buffers.";
bool ok; bool ok;
const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static_desired_jitter_buffer_frames"; const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static_desired_jitter_buffer_frames";
@ -565,9 +566,9 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
if (!ok) { if (!ok) {
_numStaticJitterFrames = InboundAudioStream::DEFAULT_STATIC_JITTER_FRAMES; _numStaticJitterFrames = InboundAudioStream::DEFAULT_STATIC_JITTER_FRAMES;
} }
qDebug() << "Static desired jitter buffer frames:" << _numStaticJitterFrames; qCDebug(audio) << "Static desired jitter buffer frames:" << _numStaticJitterFrames;
} else { } else {
qDebug() << "Disabling dynamic jitter buffers."; qCDebug(audio) << "Disabling dynamic jitter buffers.";
_numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES; _numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES;
} }
@ -621,7 +622,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
if (audioEnvGroupObject[CODEC_PREFERENCE_ORDER].isString()) { if (audioEnvGroupObject[CODEC_PREFERENCE_ORDER].isString()) {
QString codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString(); QString codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString();
_codecPreferenceOrder = codecPreferenceOrder.split(","); _codecPreferenceOrder = codecPreferenceOrder.split(",");
qDebug() << "Codec preference order changed to" << _codecPreferenceOrder; qCDebug(audio) << "Codec preference order changed to" << _codecPreferenceOrder;
} }
const QString ATTENATION_PER_DOULING_IN_DISTANCE = "attenuation_per_doubling_in_distance"; const QString ATTENATION_PER_DOULING_IN_DISTANCE = "attenuation_per_doubling_in_distance";
@ -630,7 +631,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
float attenuation = audioEnvGroupObject[ATTENATION_PER_DOULING_IN_DISTANCE].toString().toFloat(&ok); float attenuation = audioEnvGroupObject[ATTENATION_PER_DOULING_IN_DISTANCE].toString().toFloat(&ok);
if (ok) { if (ok) {
_attenuationPerDoublingInDistance = attenuation; _attenuationPerDoublingInDistance = attenuation;
qDebug() << "Attenuation per doubling in distance changed to" << _attenuationPerDoublingInDistance; qCDebug(audio) << "Attenuation per doubling in distance changed to" << _attenuationPerDoublingInDistance;
} }
} }
@ -640,7 +641,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
float noiseMutingThreshold = audioEnvGroupObject[NOISE_MUTING_THRESHOLD].toString().toFloat(&ok); float noiseMutingThreshold = audioEnvGroupObject[NOISE_MUTING_THRESHOLD].toString().toFloat(&ok);
if (ok) { if (ok) {
_noiseMutingThreshold = noiseMutingThreshold; _noiseMutingThreshold = noiseMutingThreshold;
qDebug() << "Noise muting threshold changed to" << _noiseMutingThreshold; qCDebug(audio) << "Noise muting threshold changed to" << _noiseMutingThreshold;
} }
} }
@ -680,8 +681,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
glm::vec3 dimensions(xMax - xMin, yMax - yMin, zMax - zMin); glm::vec3 dimensions(xMax - xMin, yMax - yMin, zMax - zMin);
AABox zoneAABox(corner, dimensions); AABox zoneAABox(corner, dimensions);
_audioZones.insert(zone, zoneAABox); _audioZones.insert(zone, zoneAABox);
qDebug() << "Added zone:" << zone << "(corner:" << corner qCDebug(audio) << "Added zone:" << zone << "(corner:" << corner << ", dimensions:" << dimensions << ")";
<< ", dimensions:" << dimensions << ")";
} }
} }
} }
@ -712,7 +712,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
_audioZones.contains(settings.source) && _audioZones.contains(settings.listener)) { _audioZones.contains(settings.source) && _audioZones.contains(settings.listener)) {
_zoneSettings.push_back(settings); _zoneSettings.push_back(settings);
qDebug() << "Added Coefficient:" << settings.source << settings.listener << settings.coefficient; qCDebug(audio) << "Added Coefficient:" << settings.source << settings.listener << settings.coefficient;
} }
} }
} }
@ -745,7 +745,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
_zoneReverbSettings.push_back(settings); _zoneReverbSettings.push_back(settings);
qDebug() << "Added Reverb:" << zone << reverbTime << wetLevel; qCDebug(audio) << "Added Reverb:" << zone << reverbTime << wetLevel;
} }
} }
} }

View file

@ -19,6 +19,7 @@
#include "InjectedAudioStream.h" #include "InjectedAudioStream.h"
#include "AudioLogging.h"
#include "AudioHelpers.h" #include "AudioHelpers.h"
#include "AudioMixer.h" #include "AudioMixer.h"
#include "AudioMixerClientData.h" #include "AudioMixerClientData.h"
@ -132,7 +133,7 @@ void AudioMixerClientData::optionallyReplicatePacket(ReceivedMessage& message, c
if (PacketTypeEnum::getReplicatedPacketMapping().key(message.getType()) != PacketType::Unknown) { if (PacketTypeEnum::getReplicatedPacketMapping().key(message.getType()) != PacketType::Unknown) {
mirroredType = message.getType(); mirroredType = message.getType();
} else { } else {
qDebug() << "Packet passed to optionallyReplicatePacket was not a replicatable type - returning"; qCDebug(audio) << "Packet passed to optionallyReplicatePacket was not a replicatable type - returning";
return; return;
} }
} }
@ -189,8 +190,16 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const
uint8_t packedGain; uint8_t packedGain;
message.readPrimitive(&packedGain); message.readPrimitive(&packedGain);
float gain = unpackFloatGainFromByte(packedGain); float gain = unpackFloatGainFromByte(packedGain);
if (avatarUuid.isNull()) {
// set the MASTER avatar gain
setMasterAvatarGain(gain);
qCDebug(audio) << "Setting MASTER avatar gain for " << uuid << " to " << gain;
} else {
// set the per-source avatar gain
hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain); hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain);
qDebug() << "Setting gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain; qCDebug(audio) << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain;
}
} }
void AudioMixerClientData::parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) { void AudioMixerClientData::parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) {
@ -276,7 +285,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
auto avatarAudioStream = new AvatarAudioStream(isStereo, AudioMixer::getStaticJitterFrames()); auto avatarAudioStream = new AvatarAudioStream(isStereo, AudioMixer::getStaticJitterFrames());
avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO); avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO);
qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName; qCDebug(audio) << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec, connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec,
this, &AudioMixerClientData::handleMismatchAudioFormat); this, &AudioMixerClientData::handleMismatchAudioFormat);
@ -315,7 +324,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
#if INJECTORS_SUPPORT_CODECS #if INJECTORS_SUPPORT_CODECS
injectorStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO); injectorStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
qDebug() << "creating new injectorStream... codec:" << _selectedCodecName; qCDebug(audio) << "creating new injectorStream... codec:" << _selectedCodecName;
#endif #endif
auto emplaced = _audioStreams.emplace( auto emplaced = _audioStreams.emplace(
@ -339,8 +348,8 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
auto parseResult = matchingStream->parseData(message); auto parseResult = matchingStream->parseData(message);
if (matchingStream->getOverflowCount() > overflowBefore) { if (matchingStream->getOverflowCount() > overflowBefore) {
qDebug() << "Just overflowed on stream from" << message.getSourceID() << "at" << message.getSenderSockAddr(); qCDebug(audio) << "Just overflowed on stream from" << message.getSourceID() << "at" << message.getSenderSockAddr();
qDebug() << "This stream is for" << (isMicStream ? "microphone audio" : "injected audio"); qCDebug(audio) << "This stream is for" << (isMicStream ? "microphone audio" : "injected audio");
} }
return parseResult; return parseResult;
@ -689,7 +698,7 @@ void AudioMixerClientData::setupCodecForReplicatedAgent(QSharedPointer<ReceivedM
auto codecString = message->readString(); auto codecString = message->readString();
if (codecString != _selectedCodecName) { if (codecString != _selectedCodecName) {
qDebug() << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID()) qCDebug(audio) << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID())
<< "-" << codecString; << "-" << codecString;
const std::pair<QString, CodecPluginPointer> codec = AudioMixer::negotiateCodec({ codecString }); const std::pair<QString, CodecPluginPointer> codec = AudioMixer::negotiateCodec({ codecString });

View file

@ -83,6 +83,9 @@ public:
// uses randomization to have the AudioMixer send a stats packet to this node around every second // uses randomization to have the AudioMixer send a stats packet to this node around every second
bool shouldSendStats(int frameNumber); bool shouldSendStats(int frameNumber);
float getMasterAvatarGain() const { return _masterAvatarGain; }
void setMasterAvatarGain(float gain) { _masterAvatarGain = gain; }
AudioLimiter audioLimiter; AudioLimiter audioLimiter;
void setupCodec(CodecPluginPointer codec, const QString& codecName); void setupCodec(CodecPluginPointer codec, const QString& codecName);
@ -175,6 +178,8 @@ private:
int _frameToSendStats { 0 }; int _frameToSendStats { 0 };
float _masterAvatarGain { 1.0f }; // per-listener mixing gain, applied only to avatars
CodecPluginPointer _codec; CodecPluginPointer _codec;
QString _selectedCodecName; QString _selectedCodecName;
Encoder* _encoder{ nullptr }; // for outbound mixed stream Encoder* _encoder{ nullptr }; // for outbound mixed stream

View file

@ -48,8 +48,8 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData&
// mix helpers // mix helpers
inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd, inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition); const glm::vec3& relativePosition);
inline float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd, inline float computeGain(const AudioMixerClientData& listenerNodeData, const AvatarAudioStream& listeningNodeStream,
const glm::vec3& relativePosition, bool isEcho); const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, bool isEcho);
inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd, inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition); const glm::vec3& relativePosition);
@ -266,7 +266,7 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU
glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition(); glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition();
float distance = glm::max(glm::length(relativePosition), EPSILON); float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = computeGain(listeningNodeStream, streamToAdd, relativePosition, isEcho); float gain = computeGain(listenerNodeData, listeningNodeStream, streamToAdd, relativePosition, isEcho);
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition); float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
const int HRTF_DATASET_INDEX = 1; const int HRTF_DATASET_INDEX = 1;
@ -484,10 +484,12 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi
// when throttling, as close streams are expected to be heard by a user // when throttling, as close streams are expected to be heard by a user
float distance = glm::length(relativePosition); float distance = glm::length(relativePosition);
return gain / distance; return gain / distance;
// avatar: skip master gain - it is constant for all streams
} }
float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd, float computeGain(const AudioMixerClientData& listenerNodeData, const AvatarAudioStream& listeningNodeStream,
const glm::vec3& relativePosition, bool isEcho) { const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, bool isEcho) {
float gain = 1.0f; float gain = 1.0f;
// injector: apply attenuation // injector: apply attenuation
@ -507,6 +509,9 @@ float computeGain(const AvatarAudioStream& listeningNodeStream, const Positional
float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION + (angleOfDelivery * (OFF_AXIS_ATTENUATION_STEP / PI_OVER_TWO)); float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION + (angleOfDelivery * (OFF_AXIS_ATTENUATION_STEP / PI_OVER_TWO));
gain *= offAxisCoefficient; gain *= offAxisCoefficient;
// apply master gain, only to avatars
gain *= listenerNodeData.getMasterAvatarGain();
} }
auto& audioZones = AudioMixer::getAudioZones(); auto& audioZones = AudioMixer::getAudioZones();

View file

@ -80,12 +80,24 @@ span.port {
display: none; display: none;
} }
@media (min-width: 768px) {
#setup-sidebar.affix { #setup-sidebar.affix {
/* This overrides a case where going to the bottom of the page, /* This overrides a case where going to the bottom of the page,
* then scrolling up, causes `position: relative` to be added to the style * then scrolling up, causes `position: relative` to be added to the style
*/ */
position: fixed !important; position: fixed !important;
} }
}
@media (max-width: 767px) {
#setup-sidebar.affix {
position: static !important;
}
#setup-sidebar {
margin-bottom: 20px;
}
}
#setup-sidebar button { #setup-sidebar button {
width: 100%; width: 100%;
@ -302,6 +314,7 @@ table .headers + .headers td {
} }
.account-connected-header { .account-connected-header {
vertical-align: middle;
color: #6FCF97; color: #6FCF97;
font-size: 30px; font-size: 30px;
margin-right: 20px; margin-right: 20px;

View file

@ -62,26 +62,25 @@ var Strings = {
// dialog with new path still set, allowing them to retry immediately, and without // dialog with new path still set, allowing them to retry immediately, and without
// having to type the new path in again. // having to type the new path in again.
EDIT_PLACE_TITLE: "Modify Viewpoint or Path", EDIT_PLACE_TITLE: "Modify Viewpoint or Path",
EDIT_PLACE_ERROR: "Failed to update place path. Please try again.", EDIT_PLACE_ERROR: "Failed to update Viewpoint or Path for this Place Name. Please try again.",
EDIT_PLACE_CONFIRM_BUTTON: "Save", EDIT_PLACE_CONFIRM_BUTTON: "Save",
EDIT_PLACE_CONFIRM_BUTTON_PENDING: "Saving...", EDIT_PLACE_CONFIRM_BUTTON_PENDING: "Saving...",
EDIT_PLACE_CANCEL_BUTTON: "Cancel", EDIT_PLACE_CANCEL_BUTTON: "Cancel",
REMOVE_PLACE_TITLE: "Are you sure you want to remove <strong>{{place}}</strong>?", REMOVE_PLACE_TITLE: "Are you sure you want to remove <strong>{{place}}</strong> and its path information?",
REMOVE_PLACE_ERROR: "Failed to remove place. Please try again.", REMOVE_PLACE_ERROR: "Failed to remove Place Name and its Path information.",
REMOVE_PLACE_DELETE_BUTTON: "Delete", REMOVE_PLACE_DELETE_BUTTON: "This action removes your Place Name",
REMOVE_PLACE_DELETE_BUTTON_PENDING: "Deleting...", REMOVE_PLACE_DELETE_BUTTON_PENDING: "Deleting...",
REMOVE_PLACE_CANCEL_BUTTON: "Cancel", REMOVE_PLACE_CANCEL_BUTTON: "Cancel",
ADD_PLACE_TITLE: "Choose a place", ADD_PLACE_TITLE: "Choose a place",
ADD_PLACE_MESSAGE: "Choose the High Fidelity place to point at this domain server.", ADD_PLACE_MESSAGE: "Choose a Place Name that you own or register a new Place Name.",
ADD_PLACE_CONFIRM_BUTTON: "Choose place", ADD_PLACE_CONFIRM_BUTTON: "Save",
ADD_PLACE_CONFIRM_BUTTON_PENDING: "Saving...", ADD_PLACE_CONFIRM_BUTTON_PENDING: "Saving...",
ADD_PLACE_CANCEL_BUTTON: "Cancel", ADD_PLACE_CANCEL_BUTTON: "Cancel",
ADD_PLACE_UNKNOWN_ERROR: "There was an error adding this place name.", ADD_PLACE_UNKNOWN_ERROR: "There was an error adding this Place Name. Try saving again",
ADD_PLACE_NO_PLACES_MESSAGE: "<p>You do not have any places in your High Fidelity account." ADD_PLACE_NO_PLACES_MESSAGE: "You don't have any Place Names registered. Once you have a Place Name, reopen this window to select it.",
+ "<br/><br/>Go to your <a href='https://metaverse.highfidelity.com/user/places/new'>places page</a> to create a new one. Once your place is created re-open this dialog to select it.</p>",
ADD_PLACE_NO_PLACES_BUTTON: "Create new place", ADD_PLACE_NO_PLACES_BUTTON: "Create new place",
ADD_PLACE_UNABLE_TO_LOAD_ERROR: "We were unable to load your place names. Please try again later.", ADD_PLACE_UNABLE_TO_LOAD_ERROR: "We were unable to load your place names. Please try again later.",
ADD_PLACE_LOADING_DIALOG: "Loading your places...", ADD_PLACE_LOADING_DIALOG: "Loading your places...",
@ -236,7 +235,7 @@ function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAd
if (forcePathTo === undefined || forcePathTo === null) { if (forcePathTo === undefined || forcePathTo === null) {
var path = "<div class='form-group'>"; var path = "<div class='form-group'>";
path += "<label for='place-path-input' class='control-label'>Path</label>"; path += "<label for='place-path-input' class='control-label'>Path or Viewpoint</label>";
path += "<input type='text' id='place-path-input' class='form-control' value='/'>"; path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
path += "</div>"; path += "</div>";
modal_body.append($(path)); modal_body.append($(path));
@ -339,7 +338,6 @@ function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAd
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON); $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
$('.add-place-cancel-button').removeAttr('disabled'); $('.add-place-cancel-button').removeAttr('disabled');
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR); bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
bootbox.alert("FAIL");
}); });
} }
} }
@ -363,7 +361,8 @@ function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAd
title: Strings.ADD_PLACE_TITLE, title: Strings.ADD_PLACE_TITLE,
message: modal_body, message: modal_body,
closeButton: false, closeButton: false,
buttons: modal_buttons buttons: modal_buttons,
onEscape: true
}); });
} else { } else {
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR); bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);

View file

@ -36,11 +36,6 @@
</div> </div>
<div class="col-md-9 col-sm-9 col-xs-12"> <div class="col-md-9 col-sm-9 col-xs-12">
<div id="xs-advanced-container" class="col-xs-12 hidden-sm hidden-md hidden-lg">
<button id="advanced-toggle-button-xs" class="btn btn-info advanced-toggle">Show advanced</button>
</div>
<div class="col-xs-12"> <div class="col-xs-12">
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;"> <div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">

View file

@ -503,7 +503,7 @@ function showDomainCreationAlert(justConnected) {
swal({ swal({
title: 'Create new domain ID', title: 'Create new domain ID',
type: 'input', type: 'input',
text: 'Enter a short description for this machine.</br></br>This will help you identify which domain ID belongs to which machine.</br></br>', text: 'Enter a label this machine.</br></br>This will help you identify which domain ID belongs to which machine.</br></br>',
showCancelButton: true, showCancelButton: true,
confirmButtonText: "Create", confirmButtonText: "Create",
closeOnConfirm: false, closeOnConfirm: false,
@ -528,12 +528,11 @@ function createNewDomainID(label, justConnected) {
// get the JSON object ready that we'll use to create a new domain // get the JSON object ready that we'll use to create a new domain
var domainJSON = { var domainJSON = {
"label": label "label": label
//"access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val()
} }
$.post("/api/domains", domainJSON, function(data){ $.post("/api/domains", domainJSON, function(data){
// we successfully created a domain ID, set it on that field // we successfully created a domain ID, set it on that field
var domainID = data.domain_id; var domainID = data.domain.id;
console.log("Setting domain id to ", data, domainID); console.log("Setting domain id to ", data, domainID);
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
@ -620,18 +619,14 @@ function parseJSONResponse(xhr) {
function showOrHideLabel() { function showOrHideLabel() {
var type = getCurrentDomainIDType(); var type = getCurrentDomainIDType();
if (!accessTokenIsSet() || (type !== DOMAIN_ID_TYPE_FULL && type !== DOMAIN_ID_TYPE_UNKNOWN)) { var shouldShow = accessTokenIsSet() && (type === DOMAIN_ID_TYPE_FULL || type === DOMAIN_ID_TYPE_UNKNOWN);
$(".panel#label").hide(); $(".panel#label").toggle(shouldShow);
return false; $("li a[href='#label']").parent().toggle(shouldShow);
} return shouldShow;
$(".panel#label").show();
return true;
} }
function setupDomainLabelSetting() { function setupDomainLabelSetting() {
if (!showOrHideLabel()) { showOrHideLabel();
return;
}
var html = "<div>" var html = "<div>"
html += "<label class='control-label'>Specify a label for your domain</label> <a class='domain-loading-hide' href='#'>Edit</a>"; html += "<label class='control-label'>Specify a label for your domain</label> <a class='domain-loading-hide' href='#'>Edit</a>";
@ -654,6 +649,7 @@ function setupDomainLabelSetting() {
title: 'Edit Label', title: 'Edit Label',
message: modal_body, message: modal_body,
closeButton: false, closeButton: false,
onEscape: true,
buttons: [ buttons: [
{ {
label: 'Cancel', label: 'Cancel',
@ -742,7 +738,7 @@ function setupDomainNetworkingSettings() {
var includeAddress = autoNetworkingSetting === 'disabled'; var includeAddress = autoNetworkingSetting === 'disabled';
if (includeAddress) { if (includeAddress) {
var label = "Network Address and Port"; var label = "Network Address:Port";
} else { } else {
var label = "Network Port"; var label = "Network Port";
} }
@ -777,6 +773,7 @@ function setupDomainNetworkingSettings() {
title: 'Edit Network', title: 'Edit Network',
message: modal_body, message: modal_body,
closeButton: false, closeButton: false,
onEscape: true,
buttons: [ buttons: [
{ {
label: 'Cancel', label: 'Cancel',
@ -924,6 +921,7 @@ function placeTableRow(name, path, isTemporary, placeID) {
var dialog = bootbox.dialog({ var dialog = bootbox.dialog({
message: confirmString, message: confirmString,
closeButton: false, closeButton: false,
onEscape: true,
buttons: [ buttons: [
{ {
label: Strings.REMOVE_PLACE_CANCEL_BUTTON, label: Strings.REMOVE_PLACE_CANCEL_BUTTON,
@ -1025,7 +1023,9 @@ function reloadDomainInfo() {
} }
} }
if (accessTokenIsSet()) {
appendAddButtonToPlacesTable(); appendAddButtonToPlacesTable();
}
} else { } else {
$('.domain-loading-error').show(); $('.domain-loading-error').show();
@ -1098,6 +1098,7 @@ function editHighFidelityPlace(placeID, name, path) {
dialog = bootbox.dialog({ dialog = bootbox.dialog({
title: Strings.EDIT_PLACE_TITLE, title: Strings.EDIT_PLACE_TITLE,
closeButton: false, closeButton: false,
onEscape: true,
message: modal_body, message: modal_body,
buttons: modal_buttons buttons: modal_buttons
}) })
@ -1180,6 +1181,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
bootbox.dialog({ bootbox.dialog({
title: "Choose matching domain", title: "Choose matching domain",
onEscape: true,
message: modal_body, message: modal_body,
buttons: modal_buttons buttons: modal_buttons
}) })

View file

@ -1,7 +1,7 @@
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtWebChannel 1.0 import QtWebChannel 1.0
import QtWebEngine 1.2 import QtWebEngine 1.5
import "controls-uit" import "controls-uit"
import "styles" as HifiStyles import "styles" as HifiStyles

View file

@ -8,7 +8,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
import QtQuick 2.5 import QtQuick 2.7
import QtWebEngine 1.5 import QtWebEngine 1.5
WebEngineView { WebEngineView {

View file

@ -135,4 +135,10 @@ Item {
playing: visible playing: visible
z: 10000 z: 10000
} }
Keys.onPressed: {
if ((event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) {
webViewCore.focus = false;
}
}
} }

View file

@ -51,19 +51,23 @@ FocusScope {
// The VR version of the primary menu // The VR version of the primary menu
property var rootMenu: Menu { property var rootMenu: Menu {
id: rootMenuId
objectName: "rootMenu" objectName: "rootMenu"
// for some reasons it is not possible to use just '({})' here as it gets empty when passed to TableRoot/DesktopRoot property var exclusionGroups: ({});
property var exclusionGroupsByMenuItem : ListModel {} property Component exclusiveGroupMaker: Component {
ExclusiveGroup {
function addExclusionGroup(menuItem, exclusionGroup)
{
exclusionGroupsByMenuItem.append(
{
'menuItem' : menuItem.toString(),
'exclusionGroup' : exclusionGroup.toString()
} }
); }
function addExclusionGroup(qmlAction, exclusionGroup) {
var exclusionGroupId = exclusionGroup.toString();
if(!exclusionGroups[exclusionGroupId]) {
exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(rootMenuId);
}
qmlAction.exclusiveGroup = exclusionGroups[exclusionGroupId]
} }
} }

View file

@ -1,6 +1,6 @@
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtWebEngine 1.1; import QtWebEngine 1.5;
import Qt.labs.settings 1.0 import Qt.labs.settings 1.0
import "../desktop" as OriginalDesktop import "../desktop" as OriginalDesktop

View file

@ -442,7 +442,7 @@ Item {
Rectangle { Rectangle {
id: nameCardVUMeter id: nameCardVUMeter
// Size // Size
width: isMyCard ? myDisplayName.width - 20 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * (gainSlider.width); width: ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * (gainSlider.width);
height: 8 height: 8
// Anchors // Anchors
anchors.bottom: isMyCard ? avatarImage.bottom : parent.bottom; anchors.bottom: isMyCard ? avatarImage.bottom : parent.bottom;
@ -526,17 +526,15 @@ Item {
anchors.verticalCenter: nameCardVUMeter.verticalCenter; anchors.verticalCenter: nameCardVUMeter.verticalCenter;
anchors.left: nameCardVUMeter.left; anchors.left: nameCardVUMeter.left;
// Properties // Properties
visible: !isMyCard && selected && pal.activeTab == "nearbyTab" && isPresent; visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent;
value: Users.getAvatarGain(uuid) value: Users.getAvatarGain(uuid)
minimumValue: -60.0 minimumValue: -60.0
maximumValue: 20.0 maximumValue: 20.0
stepSize: 5 stepSize: 5
updateValueWhileDragging: true updateValueWhileDragging: true
onValueChanged: { onValueChanged: {
if (uuid !== "") {
updateGainFromQML(uuid, value, false); updateGainFromQML(uuid, value, false);
} }
}
onPressedChanged: { onPressedChanged: {
if (!pressed) { if (!pressed) {
updateGainFromQML(uuid, value, true) updateGainFromQML(uuid, value, true)
@ -575,6 +573,18 @@ Item {
implicitHeight: 16 implicitHeight: 16
} }
} }
RalewayRegular {
// The slider for my card is special, it controls the master gain
id: gainSliderText;
visible: isMyCard;
text: "master volume";
size: hifi.fontSizes.tabularData;
anchors.left: parent.right;
anchors.leftMargin: 8;
color: hifi.colors.baseGrayHighlight;
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignTop;
}
} }
function updateGainFromQML(avatarUuid, sliderValue, isReleased) { function updateGainFromQML(avatarUuid, sliderValue, isReleased) {

View file

@ -1,3 +1,4 @@
// //
// WebBrowser.qml // WebBrowser.qml
// //
@ -9,12 +10,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
import QtQuick 2.5 import QtQuick 2.7
import QtQuick.Controls 1.5 as QQControls import QtQuick.Controls 2.2 as QQControls
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.0
import QtWebEngine 1.2 import QtWebEngine 1.5
import QtWebChannel 1.0 import QtWebChannel 1.0
import "../styles-uit" import "../styles-uit"
@ -22,6 +23,8 @@ import "../controls-uit" as HifiControls
import "../windows" import "../windows"
import "../controls" import "../controls"
import HifiWeb 1.0
Rectangle { Rectangle {
id: root; id: root;
@ -32,130 +35,296 @@ Rectangle {
property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false
property bool keyboardRaised: false property bool keyboardRaised: false
property bool punctuationMode: false property bool punctuationMode: false
property var suggestionsList: []
readonly property string searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q=";
WebBrowserSuggestionsEngine {
id: searchEngine
onSuggestions: {
if (suggestions.length > 0) {
suggestionsList = []
suggestionsList.push(addressBarInput.text); //do not overwrite edit text
for(var i = 0; i < suggestions.length; i++) {
suggestionsList.push(suggestions[i]);
}
addressBar.model = suggestionsList
if (!addressBar.popup.visible) {
addressBar.popup.open();
}
}
}
}
Timer {
id: suggestionRequestTimer
interval: 200
repeat: false
onTriggered: {
if (addressBar.editText !== "") {
searchEngine.querySuggestions(addressBarInput.text);
}
}
}
color: hifi.colors.baseGray; color: hifi.colors.baseGray;
// only show the title if loaded through a "loader" function goTo(url) {
//must be valid attempt to open an site with dot
var urlNew = url
if (url.indexOf(".") > 0) {
if (url.indexOf("http") < 0) {
urlNew = "http://" + url;
}
} else {
urlNew = searchUrlTemplate + url
}
addressBar.model = []
//need to rebind if binfing was broken by selecting from suggestions
addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; });
webStack.currentItem.webEngineView.url = urlNew
suggestionRequestTimer.stop();
addressBar.popup.close();
}
Column { Column {
spacing: 2 spacing: 2
width: parent.width; width: parent.width;
RowLayout { RowLayout {
id: addressBarRow
width: parent.width; width: parent.width;
height: 48 height: 48
HifiControls.WebGlyphButton { HifiControls.WebGlyphButton {
enabled: webEngineView.canGoBack enabled: webStack.currentItem.webEngineView.canGoBack || webStack.depth > 1
glyph: hifi.glyphs.backward; glyph: hifi.glyphs.backward;
anchors.verticalCenter: parent.verticalCenter; anchors.verticalCenter: parent.verticalCenter;
size: 38; size: 38;
onClicked: { onClicked: {
webEngineView.goBack() if (webStack.currentItem.webEngineView.canGoBack) {
webStack.currentItem.webEngineView.goBack();
} else if (webStack.depth > 1) {
webStack.pop();
}
} }
} }
HifiControls.WebGlyphButton { HifiControls.WebGlyphButton {
enabled: webEngineView.canGoForward enabled: webStack.currentItem.webEngineView.canGoForward
glyph: hifi.glyphs.forward; glyph: hifi.glyphs.forward;
anchors.verticalCenter: parent.verticalCenter; anchors.verticalCenter: parent.verticalCenter;
size: 38; size: 38;
onClicked: { onClicked: {
webEngineView.goForward() webStack.currentItem.webEngineView.goForward();
} }
} }
QQControls.TextField { QQControls.ComboBox {
id: addressBar id: addressBar
//selectByMouse: true
focus: true
editable: true
//flat: true
indicator: Item {}
background: Item {}
onActivated: {
goTo(textAt(index));
}
onHighlightedIndexChanged: {
if (highlightedIndex >= 0) {
addressBar.editText = textAt(highlightedIndex)
}
}
popup.height: webStack.height
onFocusChanged: {
if (focus) {
addressBarInput.selectAll();
}
}
contentItem: QQControls.TextField {
id: addressBarInput
leftPadding: 26
rightPadding: hifi.dimensions.controlLineHeight + 5
text: addressBar.editText
placeholderText: qsTr("Enter URL")
font: addressBar.font
selectByMouse: true
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
onFocusChanged: {
if (focus) {
selectAll();
}
}
Keys.onDeletePressed: {
addressBarInput.text = ""
}
Keys.onPressed: {
if (event.key === Qt.Key_Return) {
goTo(addressBarInput.text);
event.accepted = true;
}
}
Image { Image {
anchors.verticalCenter: addressBar.verticalCenter; anchors.verticalCenter: parent.verticalCenter;
x: 5 x: 5
z: 2 z: 2
id: faviconImage id: faviconImage
width: 16; height: 16 width: 16; height: 16
sourceSize: Qt.size(width, height) sourceSize: Qt.size(width, height)
source: webEngineView.icon source: webStack.currentItem.webEngineView.icon
} }
HifiControls.WebGlyphButton { HifiControls.WebGlyphButton {
glyph: webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; glyph: webStack.currentItem.webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall;
anchors.verticalCenter: parent.verticalCenter; anchors.verticalCenter: parent.verticalCenter;
width: hifi.dimensions.controlLineHeight width: hifi.dimensions.controlLineHeight
z: 2 z: 2
x: addressBar.width - 28 x: addressBarInput.width - implicitWidth
onClicked: { onClicked: {
if (webEngineView.loading) { if (webStack.currentItem.webEngineView.loading) {
webEngineView.stop() webStack.currentItem.webEngineView.stop();
} else { } else {
reloadTimer.start() webStack.currentItem.reloadTimer.start();
}
} }
} }
} }
style: TextFieldStyle { Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i");
padding {
left: 26; Keys.onPressed: {
right: 26 if (event.key === Qt.Key_Return) {
goTo(addressBarInput.text);
event.accepted = true;
} }
} }
focus: true
onEditTextChanged: {
if (addressBar.editText !== "" && addressBar.editText !== webStack.currentItem.webEngineView.url.toString()) {
suggestionRequestTimer.restart();
} else {
addressBar.model = []
addressBar.popup.close();
}
}
Layout.fillWidth: true Layout.fillWidth: true
text: webEngineView.url editText: webStack.currentItem.webEngineView.url
onAccepted: webEngineView.url = text onAccepted: goTo(addressBarInput.text);
} }
HifiControls.WebGlyphButton { HifiControls.WebGlyphButton {
checkable: true checkable: true
//only QtWebEngine 1.3 checked: webStack.currentItem.webEngineView.audioMuted
//checked: webEngineView.audioMuted
glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted
anchors.verticalCenter: parent.verticalCenter; anchors.verticalCenter: parent.verticalCenter;
width: hifi.dimensions.controlLineHeight width: hifi.dimensions.controlLineHeight
onClicked: { onClicked: {
webEngineView.triggerWebAction(WebEngineView.ToggleMediaMute) webStack.currentItem.webEngineView.audioMuted = !webStack.currentItem.webEngineView.audioMuted
} }
} }
} }
QQControls.ProgressBar { QQControls.ProgressBar {
id: loadProgressBar id: loadProgressBar
style: ProgressBarStyle {
background: Rectangle { background: Rectangle {
implicitHeight: 2
color: "#6A6A6A" color: "#6A6A6A"
} }
progress: Rectangle{
contentItem: Item {
implicitHeight: 2
Rectangle {
width: loadProgressBar.visualPosition * parent.width
height: parent.height
color: "#00B4EF" color: "#00B4EF"
} }
} }
width: parent.width; width: parent.width;
minimumValue: 0 from: 0
maximumValue: 100 to: 100
value: webEngineView.loadProgress value: webStack.currentItem.webEngineView.loadProgress
height: 2 height: 2
} }
Component {
id: webViewComponent
Rectangle {
property alias webEngineView: webEngineView
property alias reloadTimer: reloadTimer
property WebEngineNewViewRequest request: null
property bool isDialog: QQControls.StackView.index > 0
property real margins: isDialog ? 10 : 0
color: "#d1d1d1"
QQControls.StackView.onActivated: {
addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; });
}
onRequestChanged: {
if (isDialog && request !== null && request !== undefined) {//is Dialog ?
request.openIn(webEngineView);
}
}
HifiControls.BaseWebView { HifiControls.BaseWebView {
id: webEngineView id: webEngineView
anchors.fill: parent
anchors.margins: parent.margins
layer.enabled: parent.isDialog
layer.effect: DropShadow {
verticalOffset: 8
horizontalOffset: 8
color: "#330066ff"
samples: 10
spread: 0.5
}
focus: true focus: true
objectName: "tabletWebEngineView" objectName: "tabletWebEngineView"
url: "http://www.highfidelity.com" //profile: HFWebEngineProfile;
property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 profile.httpUserAgent: "Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0"
width: parent.width;
height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight
profile: HFWebEngineProfile;
property string userScriptUrl: "" property string userScriptUrl: ""
onLoadingChanged: {
if (!loading) {
addressBarInput.cursorPosition = 0 //set input field cursot to beginning
suggestionRequestTimer.stop();
addressBar.popup.close();
}
}
onLinkHovered: {
//TODO: change cursor shape?
}
// creates a global EventBridge object. // creates a global EventBridge object.
WebEngineScript { WebEngineScript {
id: createGlobalEventBridge id: createGlobalEventBridge
sourceCode: eventBridgeJavaScriptToInject sourceCode: eventBridgeJavaScriptToInject
injectionPoint: WebEngineScript.DocumentCreation injectionPoint: WebEngineScript.Deferred
worldId: WebEngineScript.MainWorld worldId: WebEngineScript.MainWorld
} }
@ -181,10 +350,9 @@ Rectangle {
settings.javascriptEnabled: true settings.javascriptEnabled: true
settings.errorPageEnabled: true settings.errorPageEnabled: true
settings.pluginsEnabled: true settings.pluginsEnabled: true
settings.fullScreenSupportEnabled: false settings.fullScreenSupportEnabled: true
//from WebEngine 1.3 settings.autoLoadIconsForPage: true
// settings.autoLoadIconsForPage: false settings.touchIconsEnabled: true
// settings.touchIconsEnabled: false
onCertificateError: { onCertificateError: {
error.defer(); error.defer();
@ -193,7 +361,6 @@ Rectangle {
Component.onCompleted: { Component.onCompleted: {
webChannel.registerObject("eventBridge", eventBridge); webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)";
} }
onFeaturePermissionRequested: { onFeaturePermissionRequested: {
@ -201,8 +368,10 @@ Rectangle {
} }
onNewViewRequested: { onNewViewRequested: {
if (!request.userInitiated) { if (request.destination == WebEngineView.NewViewInDialog) {
print("Warning: Blocked a popup window."); webStack.push(webViewComponent, {"request": request});
} else {
request.openIn(webEngineView);
} }
} }
@ -223,13 +392,23 @@ Rectangle {
break; break;
} }
print("Render process exited with code " + exitCode + " " + status); console.error("Render process exited with code " + exitCode + " " + status);
reloadTimer.running = true; reloadTimer.running = true;
} }
onWindowCloseRequested: { onFullScreenRequested: {
if (request.toggleOn) {
webEngineView.state = "FullScreen";
} else {
webEngineView.state = "";
}
request.accept();
} }
onWindowCloseRequested: {
webStack.pop();
}
}
Timer { Timer {
id: reloadTimer id: reloadTimer
interval: 0 interval: 0
@ -240,6 +419,17 @@ Rectangle {
} }
} }
QQControls.StackView {
id: webStack
width: parent.width;
property real webViewHeight: root.height - loadProgressBar.height - 48 - 4
height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight
Component.onCompleted: webStack.push(webViewComponent, {"webEngineView.url": "https://www.highfidelity.com"});
}
}
HifiControls.Keyboard { HifiControls.Keyboard {
id: keyboard id: keyboard
raised: parent.keyboardEnabled && parent.keyboardRaised raised: parent.keyboardEnabled && parent.keyboardRaised

View file

@ -5,7 +5,9 @@ import TabletScriptingInterface 1.0
Item { Item {
id: tabletButton id: tabletButton
property string captionColorOverride: "" property color defaultCaptionColor: "#ffffff"
property color captionColor: defaultCaptionColor
property var uuid; property var uuid;
property string icon: "icons/tablet-icons/edit-i.svg" property string icon: "icons/tablet-icons/edit-i.svg"
property string hoverIcon: tabletButton.icon property string hoverIcon: tabletButton.icon
@ -105,7 +107,7 @@ Item {
Text { Text {
id: text id: text
color: captionColorOverride !== "" ? captionColorOverride: "#ffffff" color: captionColor
text: tabletButton.text text: tabletButton.text
font.bold: true font.bold: true
font.pixelSize: 18 font.pixelSize: 18
@ -171,7 +173,7 @@ Item {
PropertyChanges { PropertyChanges {
target: text target: text
color: captionColorOverride !== "" ? captionColorOverride: "#ffffff" color: captionColor
text: tabletButton.hoverText text: tabletButton.hoverText
} }
@ -197,7 +199,7 @@ Item {
PropertyChanges { PropertyChanges {
target: text target: text
color: captionColorOverride !== "" ? captionColorOverride: "#333333" color: captionColor !== defaultCaptionColor ? captionColor : "#333333"
text: tabletButton.activeText text: tabletButton.activeText
} }
@ -228,7 +230,7 @@ Item {
PropertyChanges { PropertyChanges {
target: text target: text
color: captionColorOverride !== "" ? captionColorOverride: "#333333" color: captionColor !== defaultCaptionColor ? captionColor : "#333333"
text: tabletButton.activeHoverText text: tabletButton.activeHoverText
} }

View file

@ -40,37 +40,29 @@ Item {
CheckBox { CheckBox {
id: checkbox id: checkbox
// FIXME: Should use radio buttons if source.exclusiveGroup.
width: 20 width: 20
visible: source !== null ? visible: source !== null ?
source.visible && source.type === 1 && source.checkable && !source.exclusiveGroup : source.visible && source.type === 1 && source.checkable && !source.exclusiveGroup :
false false
checked: setChecked()
function setChecked() { Binding on checked {
if (!source || source.type !== 1 || !source.checkable) { value: source.checked;
return false; when: source && source.type === 1 && source.checkable && !source.exclusiveGroup;
}
// FIXME this works for native QML menus but I don't think it will
// for proxied QML menus
return source.checked;
} }
} }
RadioButton { RadioButton {
id: radiobutton id: radiobutton
// FIXME: Should use radio buttons if source.exclusiveGroup.
width: 20 width: 20
visible: source !== null ? visible: source !== null ?
source.visible && source.type === 1 && source.checkable && source.exclusiveGroup : source.visible && source.type === 1 && source.checkable && source.exclusiveGroup :
false false
checked: setChecked()
function setChecked() { Binding on checked {
if (!source || source.type !== 1 || !source.checkable) { value: source.checked;
return false; when: source && source.type === 1 && source.checkable && source.exclusiveGroup;
}
// FIXME this works for native QML menus but I don't think it will
// for proxied QML menus
return source.checked;
} }
} }
} }

View file

@ -66,7 +66,6 @@ Item {
function toModel(items, newMenu) { function toModel(items, newMenu) {
var result = modelMaker.createObject(tabletMenu); var result = modelMaker.createObject(tabletMenu);
var exclusionGroups = {};
for (var i = 0; i < items.length; ++i) { for (var i = 0; i < items.length; ++i) {
var item = items[i]; var item = items[i];
@ -78,28 +77,6 @@ Item {
if (item.text !== "Users Online") { if (item.text !== "Users Online") {
result.append({"name": item.text, "item": item}) result.append({"name": item.text, "item": item})
} }
for(var j = 0; j < tabletMenu.rootMenu.exclusionGroupsByMenuItem.count; ++j)
{
var entry = tabletMenu.rootMenu.exclusionGroupsByMenuItem.get(j);
if(entry.menuItem == item.toString())
{
var exclusionGroupId = entry.exclusionGroup;
console.debug('item exclusionGroupId: ', exclusionGroupId)
if(!exclusionGroups[exclusionGroupId])
{
exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(newMenu);
console.debug('new exclusion group created: ', exclusionGroups[exclusionGroupId])
}
var exclusionGroup = exclusionGroups[exclusionGroupId];
item.exclusiveGroup = exclusionGroup
console.debug('item.exclusiveGroup: ', item.exclusiveGroup)
}
}
break; break;
case MenuItemType.Separator: case MenuItemType.Separator:
result.append({"name": "", "item": item}) result.append({"name": "", "item": item})

View file

@ -4,7 +4,9 @@ import QtQuick.Controls 1.4
StateImage { StateImage {
id: button id: button
property string captionColorOverride: "" property color defaultCaptionColor: "#ffffff"
property color captionColor: defaultCaptionColor
property bool buttonEnabled: true property bool buttonEnabled: true
property bool isActive: false property bool isActive: false
property bool isEntered: false property bool isEntered: false
@ -98,7 +100,7 @@ StateImage {
Text { Text {
id: caption id: caption
color: captionColorOverride !== "" ? captionColorOverride: (button.isActive ? "#000000" : "#ffffff") color: button.isActive ? "#000000" : captionColor
text: button.isActive ? (button.isEntered ? button.activeHoverText : button.activeText) : (button.isEntered ? button.hoverText : button.text) text: button.isActive ? (button.isEntered ? button.activeHoverText : button.activeText) : (button.isEntered ? button.hoverText : button.text)
font.bold: false font.bold: false
font.pixelSize: 9 font.pixelSize: 9

View file

@ -208,6 +208,8 @@
#include "commerce/Wallet.h" #include "commerce/Wallet.h"
#include "commerce/QmlCommerce.h" #include "commerce/QmlCommerce.h"
#include "webbrowser/WebBrowserSuggestionsEngine.h"
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken. // FIXME seems to be broken.
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
@ -2243,6 +2245,7 @@ void Application::initializeUi() {
QmlCommerce::registerType(); QmlCommerce::registerType();
qmlRegisterType<ResourceImageItem>("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType<ResourceImageItem>("Hifi", 1, 0, "ResourceImageItem");
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference"); qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
qmlRegisterType<WebBrowserSuggestionsEngine>("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine");
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create(); offscreenUi->create();
@ -7110,6 +7113,7 @@ DisplayPluginPointer Application::getActiveDisplayPlugin() const {
return _displayPlugin; return _displayPlugin;
} }
static const char* EXCLUSION_GROUP_KEY = "exclusionGroup";
static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) { static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) {
auto menu = Menu::getInstance(); auto menu = Menu::getInstance();
@ -7145,6 +7149,8 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
action->setCheckable(true); action->setCheckable(true);
action->setChecked(active); action->setChecked(active);
displayPluginGroup->addAction(action); displayPluginGroup->addAction(action);
action->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(displayPluginGroup));
Q_ASSERT(menu->menuItemExists(MenuOption::OutputMenu, name)); Q_ASSERT(menu->menuItemExists(MenuOption::OutputMenu, name));
} }

View file

@ -56,7 +56,7 @@ Menu* Menu::getInstance() {
return dynamic_cast<Menu*>(qApp->getWindow()->menuBar()); return dynamic_cast<Menu*>(qApp->getWindow()->menuBar());
} }
const char* exclusionGroupKey = "exclusionGroup"; const char* EXCLUSION_GROUP_KEY = "exclusionGroup";
Menu::Menu() { Menu::Menu() {
auto dialogsManager = DependencyManager::get<DialogsManager>(); auto dialogsManager = DependencyManager::get<DialogsManager>();
@ -228,21 +228,21 @@ Menu::Menu() {
viewMenu, MenuOption::FirstPerson, Qt::CTRL | Qt::Key_F, viewMenu, MenuOption::FirstPerson, Qt::CTRL | Qt::Key_F,
true, qApp, SLOT(cameraMenuChanged()))); true, qApp, SLOT(cameraMenuChanged())));
firstPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); firstPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Third Person // View > Third Person
auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::ThirdPerson, Qt::CTRL | Qt::Key_G, viewMenu, MenuOption::ThirdPerson, Qt::CTRL | Qt::Key_G,
false, qApp, SLOT(cameraMenuChanged()))); false, qApp, SLOT(cameraMenuChanged())));
thirdPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); thirdPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Mirror // View > Mirror
auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::FullscreenMirror, Qt::CTRL | Qt::Key_H, viewMenu, MenuOption::FullscreenMirror, Qt::CTRL | Qt::Key_H,
false, qApp, SLOT(cameraMenuChanged()))); false, qApp, SLOT(cameraMenuChanged())));
viewMirrorAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Independent [advanced] // View > Independent [advanced]
auto viewIndependentAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, auto viewIndependentAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
@ -250,7 +250,7 @@ Menu::Menu() {
false, qApp, SLOT(cameraMenuChanged()), false, qApp, SLOT(cameraMenuChanged()),
UNSPECIFIED_POSITION, "Advanced")); UNSPECIFIED_POSITION, "Advanced"));
viewIndependentAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); viewIndependentAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Entity Camera [advanced] // View > Entity Camera [advanced]
auto viewEntityCameraAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, auto viewEntityCameraAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
@ -258,7 +258,7 @@ Menu::Menu() {
false, qApp, SLOT(cameraMenuChanged()), false, qApp, SLOT(cameraMenuChanged()),
UNSPECIFIED_POSITION, "Advanced")); UNSPECIFIED_POSITION, "Advanced"));
viewEntityCameraAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); viewEntityCameraAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
viewMenu->addSeparator(); viewMenu->addSeparator();

View file

@ -216,7 +216,7 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
if (property == "localRotation" || property == "localOrientation") { if (property == "localRotation" || property == "localOrientation") {
return quatToVariant(getLocalOrientation()); return quatToVariant(getLocalOrientation());
} }
if (property == "isSolid" || property == "isFilled" || property == "solid" || property == "filed") { if (property == "isSolid" || property == "isFilled" || property == "solid" || property == "filled" || property == "filed") {
return _isSolid; return _isSolid;
} }
if (property == "isWire" || property == "wire") { if (property == "isWire" || property == "wire") {

View file

@ -59,7 +59,8 @@ ContextOverlayInterface::ContextOverlayInterface() {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
glm::quat cameraOrientation = qApp->getCamera().getOrientation(); glm::quat cameraOrientation = qApp->getCamera().getOrientation();
QVariantMap props; QVariantMap props;
props.insert("position", vec3toVariant(myAvatar->getEyePosition() + glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_OFFSET, 0.0f))) * (CONTEXT_OVERLAY_TABLET_DISTANCE * (cameraOrientation * Vectors::FRONT)))); float sensorToWorldScale = myAvatar->getSensorToWorldScale();
props.insert("position", vec3toVariant(myAvatar->getEyePosition() + glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_OFFSET, 0.0f))) * ((CONTEXT_OVERLAY_TABLET_DISTANCE * sensorToWorldScale) * (cameraOrientation * Vectors::FRONT))));
props.insert("orientation", quatToVariant(cameraOrientation * glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_ORIENTATION, 0.0f))))); props.insert("orientation", quatToVariant(cameraOrientation * glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_ORIENTATION, 0.0f)))));
qApp->getOverlays().editOverlay(tabletFrameID, props); qApp->getOverlays().editOverlay(tabletFrameID, props);
_contextOverlayJustClicked = false; _contextOverlayJustClicked = false;

View file

@ -0,0 +1,88 @@
//
// WebBrowserSuggestionsEngine.cpp
// interface/src/webbrowser
//
// Created by Vlad Stelmahovsky on 30/10/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "WebBrowserSuggestionsEngine.h"
#include "qregexp.h"
#include <qbuffer.h>
#include <qcoreapplication.h>
#include <qlocale.h>
#include <qnetworkrequest.h>
#include <qnetworkreply.h>
#include <qregexp.h>
#include <qstringlist.h>
#include <QUrlQuery>
#include <QJsonDocument>
#include <NetworkAccessManager.h>
static const QString GoogleSuggestionsUrl = "https://suggestqueries.google.com/complete/search?output=firefox&q=%1";
static const int SUGGESTIONS_LIST_INDEX = 1;
WebBrowserSuggestionsEngine::WebBrowserSuggestionsEngine(QObject* parent)
: QObject(parent)
, _suggestionsReply(0) {
_currentNAM = &NetworkAccessManager::getInstance();
connect(_currentNAM, &QNetworkAccessManager::finished, this, &WebBrowserSuggestionsEngine::suggestionsFinished);
}
WebBrowserSuggestionsEngine::~WebBrowserSuggestionsEngine() {
disconnect(_currentNAM, &QNetworkAccessManager::finished, this, &WebBrowserSuggestionsEngine::suggestionsFinished);
}
void WebBrowserSuggestionsEngine::querySuggestions(const QString &searchString) {
if (_suggestionsReply) {
_suggestionsReply->disconnect(this);
_suggestionsReply->abort();
_suggestionsReply->deleteLater();
_suggestionsReply = 0;
}
QString url = QString(GoogleSuggestionsUrl).arg(searchString);
_suggestionsReply = _currentNAM->get(QNetworkRequest(url));
}
void WebBrowserSuggestionsEngine::suggestionsFinished(QNetworkReply *reply) {
if (reply != _suggestionsReply) {
return; //invalid reply. ignore
}
const QByteArray response = _suggestionsReply->readAll();
_suggestionsReply->close();
_suggestionsReply->deleteLater();
_suggestionsReply = 0;
QJsonParseError err;
QJsonDocument json = QJsonDocument::fromJson(response, &err);
const QVariant res = json.toVariant();
if (err.error != QJsonParseError::NoError || res.type() != QVariant::List) {
return;
}
const QVariantList list = res.toList();
if (list.size() <= SUGGESTIONS_LIST_INDEX) {
return;
}
QStringList out;
const QVariantList& suggList = list.at(SUGGESTIONS_LIST_INDEX).toList();
foreach (const QVariant &v, suggList) {
out.append(v.toString());
}
emit suggestions(out);
}

View file

@ -0,0 +1,46 @@
//
// WebBrowserSuggestionsEngine.h
// interface/src/webbrowser
//
// Created by Vlad Stelmahovsky on 30/10/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef WEBBROWSERSUGGESTIONSENGINE_H
#define WEBBROWSERSUGGESTIONSENGINE_H
#include <qpair.h>
#include <qimage.h>
#include <qmap.h>
#include <qnetworkaccessmanager.h>
#include <qstring.h>
#include <qurl.h>
class QNetworkReply;
class WebBrowserSuggestionsEngine : public QObject {
Q_OBJECT
public:
WebBrowserSuggestionsEngine(QObject* parent = 0);
virtual ~WebBrowserSuggestionsEngine();
public slots:
void querySuggestions(const QString& searchString);
signals:
void suggestions(const QStringList& suggestions);
private slots:
void suggestionsFinished(QNetworkReply *reply);
private:
QNetworkReply* _suggestionsReply;
QNetworkAccessManager* _currentNAM;
};
#endif // WEBBROWSERSUGGESTIONSENGINE_H

View file

@ -673,8 +673,8 @@ static void crossfade_4x2(float* src, float* dst, const float* win, int numFrame
// linear interpolation with gain // linear interpolation with gain
static void interpolate(float* dst, const float* src0, const float* src1, float frac, float gain) { static void interpolate(float* dst, const float* src0, const float* src1, float frac, float gain) {
float f0 = HRTF_GAIN * gain * (1.0f - frac); float f0 = gain * (1.0f - frac);
float f1 = HRTF_GAIN * gain * frac; float f1 = gain * frac;
for (int k = 0; k < HRTF_TAPS; k++) { for (int k = 0; k < HRTF_TAPS; k++) {
dst[k] = f0 * src0[k] + f1 * src1[k]; dst[k] = f0 * src0[k] + f1 * src1[k];

View file

@ -9,7 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#if defined(__AVX512F__) #ifdef __AVX512F__
#include <assert.h> #include <assert.h>
#include <immintrin.h> #include <immintrin.h>
@ -87,15 +87,4 @@ void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* ds
_mm256_zeroupper(); _mm256_zeroupper();
} }
// FIXME: this fallback can be removed, once we require VS2017
#elif defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__)
#include "../AudioHRTF.h"
void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames);
void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) {
FIR_1x4_AVX2(src, dst0, dst1, dst2, dst3, coef, numFrames);
}
#endif #endif

View file

@ -65,7 +65,9 @@ QHash<QString, QString> HTTPConnection::parseUrlEncodedForm() {
QUrlQuery form { _requestContent }; QUrlQuery form { _requestContent };
QHash<QString, QString> pairs; QHash<QString, QString> pairs;
for (auto pair : form.queryItems()) { for (auto pair : form.queryItems()) {
pairs[QUrl::fromPercentEncoding(pair.first.toLatin1())] = QUrl::fromPercentEncoding(pair.second.toLatin1()); auto key = QUrl::fromPercentEncoding(pair.first.toLatin1().replace('+', ' '));
auto value = QUrl::fromPercentEncoding(pair.second.toLatin1().replace('+', ' '));
pairs[key] = value;
} }
return pairs; return pairs;

View file

@ -12,24 +12,25 @@
#include "EntityTreeRenderer.h" #include "EntityTreeRenderer.h"
#include <glm/gtx/quaternion.hpp> #include <glm/gtx/quaternion.hpp>
#include <queue>
#include <QEventLoop> #include <QEventLoop>
#include <QScriptSyntaxCheckResult> #include <QScriptSyntaxCheckResult>
#include <QThreadPool> #include <QThreadPool>
#include <shared/QtHelpers.h> #include <shared/QtHelpers.h>
#include <ColorUtils.h>
#include <AbstractScriptingServicesInterface.h> #include <AbstractScriptingServicesInterface.h>
#include <AbstractViewStateInterface.h> #include <AbstractViewStateInterface.h>
#include <AddressManager.h>
#include <ColorUtils.h>
#include <Model.h> #include <Model.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <PerfStat.h> #include <PerfStat.h>
#include <PrioritySortUtil.h>
#include <Rig.h>
#include <SceneScriptingInterface.h> #include <SceneScriptingInterface.h>
#include <ScriptEngine.h> #include <ScriptEngine.h>
#include <AddressManager.h>
#include <Rig.h>
#include <EntitySimulation.h> #include <EntitySimulation.h>
#include <AddressManager.h>
#include <ZoneRenderer.h> #include <ZoneRenderer.h>
#include "EntitiesRendererLogging.h" #include "EntitiesRendererLogging.h"
@ -204,6 +205,7 @@ void EntityTreeRenderer::clear() {
qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown"; qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown";
} }
_entitiesInScene.clear(); _entitiesInScene.clear();
_renderablesToUpdate.clear();
// reset the zone to the default (while we load the next scene) // reset the zone to the default (while we load the next scene)
_layeredZones.clear(); _layeredZones.clear();
@ -293,7 +295,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r
} }
} }
void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, render::Transaction& transaction) { void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction) {
PROFILE_RANGE_EX(simulation_physics, "Change", 0xffff00ff, (uint64_t)_changedEntities.size()); PROFILE_RANGE_EX(simulation_physics, "Change", 0xffff00ff, (uint64_t)_changedEntities.size());
PerformanceTimer pt("change"); PerformanceTimer pt("change");
std::unordered_set<EntityItemID> changedEntities; std::unordered_set<EntityItemID> changedEntities;
@ -307,21 +309,91 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene
#endif #endif
}); });
{
PROFILE_RANGE_EX(simulation_physics, "CopyRenderables", 0xffff00ff, (uint64_t)changedEntities.size());
for (const auto& entityId : changedEntities) { for (const auto& entityId : changedEntities) {
auto renderable = renderableForEntityId(entityId); auto renderable = renderableForEntityId(entityId);
if (!renderable) { if (renderable) {
continue; // only add valid renderables _renderablesToUpdate
}
_renderablesToUpdate.insert({ entityId, renderable }); _renderablesToUpdate.insert({ entityId, renderable });
} }
}
}
if (!_renderablesToUpdate.empty()) { float expectedUpdateCost = _avgRenderableUpdateCost * _renderablesToUpdate.size();
if (expectedUpdateCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET) {
// we expect to update all renderables within available time budget
PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size());
uint64_t updateStart = usecTimestampNow();
for (const auto& entry : _renderablesToUpdate) { for (const auto& entry : _renderablesToUpdate) {
const auto& renderable = entry.second; const auto& renderable = entry.second;
assert(renderable); // only valid renderables are added to _renderablesToUpdate
renderable->updateInScene(scene, transaction); renderable->updateInScene(scene, transaction);
} }
size_t numRenderables = _renderablesToUpdate.size() + 1; // add one to avoid divide by zero
_renderablesToUpdate.clear(); _renderablesToUpdate.clear();
// compute average per-renderable update cost
float cost = (float)(usecTimestampNow() - updateStart) / (float)(numRenderables);
const float blend = 0.1f;
_avgRenderableUpdateCost = (1.0f - blend) * _avgRenderableUpdateCost + blend * cost;
} else {
// we expect the cost to updating all renderables to exceed available time budget
// so we first sort by priority and update in order until out of time
class SortableRenderer: public PrioritySortUtil::Sortable {
public:
SortableRenderer(const EntityRendererPointer& renderer) : _renderer(renderer) { }
glm::vec3 getPosition() const override { return _renderer->getEntity()->getPosition(); }
float getRadius() const override { return 0.5f * _renderer->getEntity()->getQueryAACube().getScale(); }
uint64_t getTimestamp() const override { return _renderer->getUpdateTime(); }
const EntityRendererPointer& getRenderer() const { return _renderer; }
private:
EntityRendererPointer _renderer;
};
// prioritize and sort the renderables
uint64_t sortStart = usecTimestampNow();
PrioritySortUtil::PriorityQueue<SortableRenderer> sortedRenderables(view);
{
PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size());
std::unordered_map<EntityItemID, EntityRendererPointer>::iterator itr = _renderablesToUpdate.begin();
while (itr != _renderablesToUpdate.end()) {
assert(itr->second); // only valid renderables are added to _renderablesToUpdate
sortedRenderables.push(SortableRenderer(itr->second));
++itr;
}
}
{
PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, sortedRenderables.size());
// compute remaining time budget
uint64_t updateStart = usecTimestampNow();
uint64_t timeBudget = MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET;
uint64_t sortCost = updateStart - sortStart;
if (sortCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET - MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET) {
timeBudget = MAX_UPDATE_RENDERABLES_TIME_BUDGET - sortCost;
}
uint64_t expiry = updateStart + timeBudget;
// process the sorted renderables
std::unordered_map<EntityItemID, EntityRendererPointer>::iterator itr;
size_t numSorted = sortedRenderables.size();
while (!sortedRenderables.empty() && usecTimestampNow() < expiry) {
const EntityRendererPointer& renderable = sortedRenderables.top().getRenderer();
renderable->updateInScene(scene, transaction);
_renderablesToUpdate.erase(renderable->getEntity()->getID());
sortedRenderables.pop();
}
// compute average per-renderable update cost
size_t numUpdated = numSorted - sortedRenderables.size() + 1; // add one to avoid divide by zero
float cost = (float)(usecTimestampNow() - updateStart) / (float)(numUpdated);
const float blend = 0.1f;
_avgRenderableUpdateCost = (1.0f - blend) * _avgRenderableUpdateCost + blend * cost;
}
} }
} }
@ -340,7 +412,9 @@ void EntityTreeRenderer::update(bool simulate) {
if (scene) { if (scene) {
render::Transaction transaction; render::Transaction transaction;
addPendingEntities(scene, transaction); addPendingEntities(scene, transaction);
updateChangedEntities(scene, transaction); ViewFrustum view;
_viewState->copyCurrentViewFrustum(view);
updateChangedEntities(scene, view, transaction);
scene->enqueueTransaction(transaction); scene->enqueueTransaction(transaction);
} }
} }
@ -768,7 +842,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
} }
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
// If it's in the pending queue, remove it // If it's in a pending queue, remove it
_renderablesToUpdate.erase(entityID);
_entitiesToAdd.erase(entityID); _entitiesToAdd.erase(entityID);
auto itr = _entitiesInScene.find(entityID); auto itr = _entitiesInScene.find(entityID);

View file

@ -144,7 +144,7 @@ protected:
private: private:
void addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction); void addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction);
void updateChangedEntities(const render::ScenePointer& scene, render::Transaction& transaction); void updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction);
EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); } EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); }
render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); } render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); }
@ -235,11 +235,12 @@ private:
NetworkTexturePointer _skyboxTexture; NetworkTexturePointer _skyboxTexture;
QString _ambientTextureURL; QString _ambientTextureURL;
QString _skyboxTextureURL; QString _skyboxTextureURL;
float _avgRenderableUpdateCost { 0.0f };
bool _pendingAmbientTexture { false }; bool _pendingAmbientTexture { false };
bool _pendingSkyboxTexture { false }; bool _pendingSkyboxTexture { false };
quint64 _lastZoneCheck { 0 }; uint64_t _lastZoneCheck { 0 };
const quint64 ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz const uint64_t ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz
const float ZONE_CHECK_DISTANCE = 0.001f; const float ZONE_CHECK_DISTANCE = 0.001f;
ReadWriteLockable _changedEntitiesGuard; ReadWriteLockable _changedEntitiesGuard;

View file

@ -59,7 +59,7 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St
const QUuid& myNodeID = nodeList->getSessionUUID(); const QUuid& myNodeID = nodeList->getSessionUUID();
statusGetters.push_back([entity]() -> render::Item::Status::Value { statusGetters.push_back([entity]() -> render::Item::Status::Value {
quint64 delta = usecTimestampNow() - entity->getLastEditedFromRemote(); uint64_t delta = usecTimestampNow() - entity->getLastEditedFromRemote();
const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND); const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND);
float normalizedDelta = delta * WAIT_THRESHOLD_INV; float normalizedDelta = delta * WAIT_THRESHOLD_INV;
// Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD
@ -71,7 +71,7 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St
}); });
statusGetters.push_back([entity] () -> render::Item::Status::Value { statusGetters.push_back([entity] () -> render::Item::Status::Value {
quint64 delta = usecTimestampNow() - entity->getLastBroadcast(); uint64_t delta = usecTimestampNow() - entity->getLastBroadcast();
const float WAIT_THRESHOLD_INV = 1.0f / (0.4f * USECS_PER_SECOND); const float WAIT_THRESHOLD_INV = 1.0f / (0.4f * USECS_PER_SECOND);
float normalizedDelta = delta * WAIT_THRESHOLD_INV; float normalizedDelta = delta * WAIT_THRESHOLD_INV;
// Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD
@ -278,6 +278,7 @@ void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& trans
if (!isValidRenderItem()) { if (!isValidRenderItem()) {
return; return;
} }
_updateTime = usecTimestampNow();
// FIXME is this excessive? // FIXME is this excessive?
if (!needsRenderUpdate()) { if (!needsRenderUpdate()) {

View file

@ -52,6 +52,8 @@ public:
void clearSubRenderItemIDs(); void clearSubRenderItemIDs();
void setSubRenderItemIDs(const render::ItemIDs& ids); void setSubRenderItemIDs(const render::ItemIDs& ids);
const uint64_t& getUpdateTime() const { return _updateTime; }
protected: protected:
virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
virtual void onAddToScene(const EntityItemPointer& entity); virtual void onAddToScene(const EntityItemPointer& entity);
@ -100,7 +102,6 @@ protected:
return result; return result;
} }
signals: signals:
void requestRenderUpdate(); void requestRenderUpdate();
@ -113,14 +114,15 @@ protected:
static std::function<bool()> _entitiesShouldFadeFunction; static std::function<bool()> _entitiesShouldFadeFunction;
const Transform& getModelTransform() const; const Transform& getModelTransform() const;
Item::Bound _bound;
SharedSoundPointer _collisionSound; SharedSoundPointer _collisionSound;
QUuid _changeHandlerId; QUuid _changeHandlerId;
ItemID _renderItemID{ Item::INVALID_ITEM_ID }; ItemID _renderItemID{ Item::INVALID_ITEM_ID };
ItemIDs _subRenderItemIDs; ItemIDs _subRenderItemIDs;
quint64 _fadeStartTime{ usecTimestampNow() }; uint64_t _fadeStartTime{ usecTimestampNow() };
uint64_t _updateTime{ usecTimestampNow() }; // used when sorting/throttling render updates
bool _isFading{ _entitiesShouldFadeFunction() }; bool _isFading{ _entitiesShouldFadeFunction() };
bool _prevIsTransparent { false }; bool _prevIsTransparent { false };
Item::Bound _bound;
bool _visible { false }; bool _visible { false };
bool _moving { false }; bool _moving { false };
// Only touched on the rendering thread // Only touched on the rendering thread

View file

@ -93,6 +93,11 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0); QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
if (type == PacketType::EntityAdd) {
auto MAX_ADD_DATA_SIZE = NLPacket::maxPayloadSize(type) * 10; // a really big buffer
bufferOut.resize(MAX_ADD_DATA_SIZE);
}
OctreeElement::AppendState encodeResult = OctreeElement::PARTIAL; // start the loop assuming there's more to send OctreeElement::AppendState encodeResult = OctreeElement::PARTIAL; // start the loop assuming there's more to send
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
@ -115,6 +120,7 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
qCDebug(entities) << " id:" << entityItemID; qCDebug(entities) << " id:" << entityItemID;
qCDebug(entities) << " properties:" << properties; qCDebug(entities) << " properties:" << properties;
#endif #endif
queueOctreeEditMessage(type, bufferOut); queueOctreeEditMessage(type, bufferOut);
if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) { if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) {
emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get<AddressManager>()->getPlaceName()); emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get<AddressManager>()->getPlaceName());

View file

@ -979,8 +979,8 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
} }
void NodeList::setAvatarGain(const QUuid& nodeID, float gain) { void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
// cannot set gain of yourself or nobody // cannot set gain of yourself
if (!nodeID.isNull() && _sessionUUID != nodeID) { if (_sessionUUID != nodeID) {
auto audioMixer = soloNodeOfType(NodeType::AudioMixer); auto audioMixer = soloNodeOfType(NodeType::AudioMixer);
if (audioMixer) { if (audioMixer) {
// setup the packet // setup the packet
@ -988,10 +988,15 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
// write the node ID to the packet // write the node ID to the packet
setAvatarGainPacket->write(nodeID.toRfc4122()); 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; // We need to convert the gain in dB (from the script) to an amplitude before packing it.
setAvatarGainPacket->writePrimitive(packFloatGainToByte(fastExp2f(gain / 6.02059991f)));
if (nodeID.isNull()) {
qCDebug(networking) << "Sending Set MASTER Avatar Gain packet with Gain:" << gain;
} else {
qCDebug(networking) << "Sending Set Avatar Gain packet with UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
}
sendPacket(std::move(setAvatarGainPacket), *audioMixer); sendPacket(std::move(setAvatarGainPacket), *audioMixer);
QWriteLocker{ &_avatarGainMapLock }; QWriteLocker{ &_avatarGainMapLock };
@ -1001,7 +1006,7 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
qWarning() << "Couldn't find audio mixer to send set gain request"; qWarning() << "Couldn't find audio mixer to send set gain request";
} }
} else { } else {
qWarning() << "NodeList::setAvatarGain called with an invalid ID or an ID which matches the current session ID:" << nodeID; qWarning() << "NodeList::setAvatarGain called with an ID which matches the current session ID:" << nodeID;
} }
} }

View file

@ -40,6 +40,9 @@ const quint64 DOMAIN_SERVER_CHECK_IN_MSECS = 1 * 1000;
const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5;
using PacketOrPacketList = std::pair<std::unique_ptr<NLPacket>, std::unique_ptr<NLPacketList>>;
using NodePacketOrPacketListPair = std::pair<SharedNodePointer, PacketOrPacketList>;
using NodePacketPair = std::pair<SharedNodePointer, std::unique_ptr<NLPacket>>; using NodePacketPair = std::pair<SharedNodePointer, std::unique_ptr<NLPacket>>;
using NodeSharedPacketPair = std::pair<SharedNodePointer, QSharedPointer<NLPacket>>; using NodeSharedPacketPair = std::pair<SharedNodePointer, QSharedPointer<NLPacket>>;
using NodeSharedReceivedMessagePair = std::pair<SharedNodePointer, QSharedPointer<ReceivedMessage>>; using NodeSharedReceivedMessagePair = std::pair<SharedNodePointer, QSharedPointer<ReceivedMessage>>;

View file

@ -53,7 +53,19 @@ void PacketSender::queuePacketForSending(const SharedNodePointer& destinationNod
_totalBytesQueued += packet->getDataSize(); _totalBytesQueued += packet->getDataSize();
lock(); lock();
_packets.push_back({destinationNode, std::move(packet)}); _packets.push_back({destinationNode, PacketOrPacketList { std::move(packet), nullptr} });
unlock();
// Make sure to wake our actual processing thread because we now have packets for it to process.
_hasPackets.wakeAll();
}
void PacketSender::queuePacketListForSending(const SharedNodePointer& destinationNode, std::unique_ptr<NLPacketList> packetList) {
_totalPacketsQueued += packetList->getNumPackets();
_totalBytesQueued += packetList->getMessageSize();
lock();
_packets.push_back({ destinationNode, PacketOrPacketList { nullptr, std::move(packetList)} });
unlock(); unlock();
// Make sure to wake our actual processing thread because we now have packets for it to process. // Make sure to wake our actual processing thread because we now have packets for it to process.
@ -178,8 +190,8 @@ bool PacketSender::nonThreadedProcess() {
float averagePacketsPerCall = 0; // might be less than 1, if our caller calls us more frequently than the target PPS float averagePacketsPerCall = 0; // might be less than 1, if our caller calls us more frequently than the target PPS
int packetsSentThisCall = 0; size_t packetsSentThisCall = 0;
int packetsToSendThisCall = 0; size_t packetsToSendThisCall = 0;
// Since we're in non-threaded mode, we need to determine how many packets to send per call to process // Since we're in non-threaded mode, we need to determine how many packets to send per call to process
// based on how often we get called... We do this by keeping a running average of our call times, and we determine // based on how often we get called... We do this by keeping a running average of our call times, and we determine
@ -265,24 +277,32 @@ bool PacketSender::nonThreadedProcess() {
while ((packetsSentThisCall < packetsToSendThisCall) && (packetsLeft > 0)) { while ((packetsSentThisCall < packetsToSendThisCall) && (packetsLeft > 0)) {
lock(); lock();
NodePacketPair packetPair = std::move(_packets.front()); NodePacketOrPacketListPair packetPair = std::move(_packets.front());
_packets.pop_front(); _packets.pop_front();
packetsLeft = _packets.size(); packetsLeft = _packets.size();
unlock(); unlock();
// send the packet through the NodeList... // send the packet through the NodeList...
DependencyManager::get<NodeList>()->sendUnreliablePacket(*packetPair.second, *packetPair.first); //PacketOrPacketList packetOrList = packetPair.second;
bool sendAsPacket = packetPair.second.first.get();
size_t packetSize = sendAsPacket ? packetPair.second.first->getDataSize() : packetPair.second.second->getMessageSize();
size_t packetCount = sendAsPacket ? 1 : packetPair.second.second->getNumPackets();
packetsSentThisCall++; if (sendAsPacket) {
_packetsOverCheckInterval++; DependencyManager::get<NodeList>()->sendUnreliablePacket(*packetPair.second.first, *packetPair.first);
_totalPacketsSent++; } else {
DependencyManager::get<NodeList>()->sendPacketList(std::move(packetPair.second.second), *packetPair.first);
}
packetsSentThisCall += packetCount;
_packetsOverCheckInterval += packetCount;
_totalPacketsSent += packetCount;
int packetSize = packetPair.second->getDataSize();
_totalBytesSent += packetSize; _totalBytesSent += packetSize;
emit packetSent(packetSize); emit packetSent(packetSize); // FIXME should include number of packets?
_lastSendTime = now; _lastSendTime = now;
} }
return isStillRunning(); return isStillRunning();

View file

@ -39,6 +39,7 @@ public:
/// Add packet to outbound queue. /// Add packet to outbound queue.
void queuePacketForSending(const SharedNodePointer& destinationNode, std::unique_ptr<NLPacket> packet); void queuePacketForSending(const SharedNodePointer& destinationNode, std::unique_ptr<NLPacket> packet);
void queuePacketListForSending(const SharedNodePointer& destinationNode, std::unique_ptr<NLPacketList> packetList);
void setPacketsPerSecond(int packetsPerSecond); void setPacketsPerSecond(int packetsPerSecond);
int getPacketsPerSecond() const { return _packetsPerSecond; } int getPacketsPerSecond() const { return _packetsPerSecond; }
@ -99,14 +100,14 @@ protected:
SimpleMovingAverage _averageProcessCallTime; SimpleMovingAverage _averageProcessCallTime;
private: private:
std::list<NodePacketPair> _packets; std::list<NodePacketOrPacketListPair> _packets;
quint64 _lastSendTime; quint64 _lastSendTime;
bool threadedProcess(); bool threadedProcess();
bool nonThreadedProcess(); bool nonThreadedProcess();
quint64 _lastPPSCheck; quint64 _lastPPSCheck;
int _packetsOverCheckInterval; size_t _packetsOverCheckInterval;
quint64 _started; quint64 _started;
quint64 _totalPacketsSent; quint64 _totalPacketsSent;

View file

@ -53,7 +53,6 @@ ReceivedMessage::ReceivedMessage(QByteArray byteArray, PacketType packetType, Pa
_senderSockAddr(senderSockAddr), _senderSockAddr(senderSockAddr),
_isComplete(true) _isComplete(true)
{ {
} }
void ReceivedMessage::setFailed() { void ReceivedMessage::setFailed() {

View file

@ -115,6 +115,22 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, std::uniqu
}); });
} }
// This method is called when the edit packet layer has determined that it has a fully formed packet destined for
// a known nodeID.
void OctreeEditPacketSender::queuePacketListToNode(const QUuid& nodeUUID, std::unique_ptr<NLPacketList> packetList) {
DependencyManager::get<NodeList>()->eachNode([&](const SharedNodePointer& node) {
// only send to the NodeTypes that are getMyNodeType()
if (node->getType() == getMyNodeType()
&& ((node->getUUID() == nodeUUID) || (nodeUUID.isNull()))
&& node->getActiveSocket()) {
// NOTE: unlike packets, the packet lists don't get rewritten sequence numbers.
// or do history for resend
queuePacketListForSending(node, std::move(packetList));
}
});
}
void OctreeEditPacketSender::processPreServerExistsPackets() { void OctreeEditPacketSender::processPreServerExistsPackets() {
assert(serversExist()); // we should only be here if we have jurisdictions assert(serversExist()); // we should only be here if we have jurisdictions
@ -247,7 +263,37 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray&
}); });
} }
if (isMyJurisdiction) { if (isMyJurisdiction) {
std::unique_ptr<NLPacket>& bufferedPacket = _pendingEditPackets[nodeUUID];
// for edit messages, we will attempt to combine multiple edit commands where possible, we
// don't do this for add because we send those reliably
if (type == PacketType::EntityAdd) {
auto newPacket = NLPacketList::create(type, QByteArray(), true, true);
auto nodeClockSkew = node->getClockSkewUsec();
// pack sequence number
quint16 sequence = _outgoingSequenceNumbers[nodeUUID]++;
newPacket->writePrimitive(sequence);
// pack in timestamp
quint64 now = usecTimestampNow() + nodeClockSkew;
newPacket->writePrimitive(now);
// We call this virtual function that allows our specific type of EditPacketSender to
// fixup the buffer for any clock skew
if (nodeClockSkew != 0) {
adjustEditPacketForClockSkew(type, editMessage, nodeClockSkew);
}
newPacket->write(editMessage);
// release the new packet
releaseQueuedPacketList(nodeUUID, std::move(newPacket));
} else {
std::unique_ptr<NLPacket>& bufferedPacket = _pendingEditPackets[nodeUUID].first; //only a NLPacket for now
if (!bufferedPacket) { if (!bufferedPacket) {
bufferedPacket = initializePacket(type, node->getClockSkewUsec()); bufferedPacket = initializePacket(type, node->getClockSkewUsec());
@ -274,6 +320,8 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray&
} }
bufferedPacket->write(editMessage); bufferedPacket->write(editMessage);
}
} }
} }
}); });
@ -291,15 +339,24 @@ void OctreeEditPacketSender::releaseQueuedMessages() {
} else { } else {
_packetsQueueLock.lock(); _packetsQueueLock.lock();
for (auto& i : _pendingEditPackets) { for (auto& i : _pendingEditPackets) {
if (i.second) { if (i.second.first) {
// construct a null unique_ptr to an NL packet // construct a null unique_ptr to an NL packet
std::unique_ptr<NLPacket> releasedPacket; std::unique_ptr<NLPacket> releasedPacket;
// swap the null ptr with the packet we want to release // swap the null ptr with the packet we want to release
i.second.swap(releasedPacket); i.second.first.swap(releasedPacket);
// move and release the queued packet // move and release the queued packet
releaseQueuedPacket(i.first, std::move(releasedPacket)); releaseQueuedPacket(i.first, std::move(releasedPacket));
} else if (i.second.second) {
// construct a null unique_ptr to an NLPacketList
std::unique_ptr<NLPacketList> releasedPacketList;
// swap the null ptr with the NLPacketList we want to release
i.second.second.swap(releasedPacketList);
// move and release the queued NLPacketList
releaseQueuedPacketList(i.first, std::move(releasedPacketList));
} }
} }
@ -315,6 +372,14 @@ void OctreeEditPacketSender::releaseQueuedPacket(const QUuid& nodeID, std::uniqu
_releaseQueuedPacketMutex.unlock(); _releaseQueuedPacketMutex.unlock();
} }
void OctreeEditPacketSender::releaseQueuedPacketList(const QUuid& nodeID, std::unique_ptr<NLPacketList> packetList) {
_releaseQueuedPacketMutex.lock();
if (packetList->getMessageSize() > 0 && packetList->getType() != PacketType::Unknown) {
queuePacketListToNode(nodeID, std::move(packetList));
}
_releaseQueuedPacketMutex.unlock();
}
std::unique_ptr<NLPacket> OctreeEditPacketSender::initializePacket(PacketType type, qint64 nodeClockSkew) { std::unique_ptr<NLPacket> OctreeEditPacketSender::initializePacket(PacketType type, qint64 nodeClockSkew) {
auto newPacket = NLPacket::create(type); auto newPacket = NLPacket::create(type);

View file

@ -87,15 +87,18 @@ protected:
bool _shouldSend; bool _shouldSend;
void queuePacketToNode(const QUuid& nodeID, std::unique_ptr<NLPacket> packet); void queuePacketToNode(const QUuid& nodeID, std::unique_ptr<NLPacket> packet);
void queuePacketListToNode(const QUuid& nodeUUID, std::unique_ptr<NLPacketList> packetList);
void queuePendingPacketToNodes(std::unique_ptr<NLPacket> packet); void queuePendingPacketToNodes(std::unique_ptr<NLPacket> packet);
void queuePacketToNodes(std::unique_ptr<NLPacket> packet); void queuePacketToNodes(std::unique_ptr<NLPacket> packet);
std::unique_ptr<NLPacket> initializePacket(PacketType type, qint64 nodeClockSkew); std::unique_ptr<NLPacket> initializePacket(PacketType type, qint64 nodeClockSkew);
void releaseQueuedPacket(const QUuid& nodeUUID, std::unique_ptr<NLPacket> packetBuffer); // releases specific queued packet void releaseQueuedPacket(const QUuid& nodeUUID, std::unique_ptr<NLPacket> packetBuffer); // releases specific queued packet
void releaseQueuedPacketList(const QUuid& nodeID, std::unique_ptr<NLPacketList> packetList);
void processPreServerExistsPackets(); void processPreServerExistsPackets();
// These are packets which are destined from know servers but haven't been released because they're still too small // These are packets which are destined from know servers but haven't been released because they're still too small
std::unordered_map<QUuid, std::unique_ptr<NLPacket>> _pendingEditPackets; std::unordered_map<QUuid, PacketOrPacketList> _pendingEditPackets;
// These are packets that are waiting to be processed because we don't yet know if there are servers // These are packets that are waiting to be processed because we don't yet know if there are servers
int _maxPendingMessages; int _maxPendingMessages;

View file

@ -35,7 +35,13 @@ OctreePacketData::OctreePacketData(bool enableCompression, int targetSize) {
void OctreePacketData::changeSettings(bool enableCompression, unsigned int targetSize) { void OctreePacketData::changeSettings(bool enableCompression, unsigned int targetSize) {
_enableCompression = enableCompression; _enableCompression = enableCompression;
_targetSize = std::min(MAX_OCTREE_UNCOMRESSED_PACKET_SIZE, targetSize); _targetSize = targetSize;
_uncompressedByteArray.resize(_targetSize);
_compressedByteArray.resize(_targetSize);
_uncompressed = (unsigned char*)_uncompressedByteArray.data();
_compressed = (unsigned char*)_compressedByteArray.data();
reset(); reset();
} }
@ -689,6 +695,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto
uint16_t length; uint16_t length;
memcpy(&length, dataBytes, sizeof(uint16_t)); memcpy(&length, dataBytes, sizeof(uint16_t));
dataBytes += sizeof(length); dataBytes += sizeof(length);
// FIXME - this size check is wrong if we allow larger packets
if (length * sizeof(glm::vec3) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { if (length * sizeof(glm::vec3) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
result.resize(0); result.resize(0);
return sizeof(uint16_t); return sizeof(uint16_t);
@ -702,6 +710,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto
uint16_t length; uint16_t length;
memcpy(&length, dataBytes, sizeof(uint16_t)); memcpy(&length, dataBytes, sizeof(uint16_t));
dataBytes += sizeof(length); dataBytes += sizeof(length);
// FIXME - this size check is wrong if we allow larger packets
if (length * sizeof(glm::quat) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { if (length * sizeof(glm::quat) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
result.resize(0); result.resize(0);
return sizeof(uint16_t); return sizeof(uint16_t);
@ -720,6 +730,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto
uint16_t length; uint16_t length;
memcpy(&length, dataBytes, sizeof(uint16_t)); memcpy(&length, dataBytes, sizeof(uint16_t));
dataBytes += sizeof(length); dataBytes += sizeof(length);
// FIXME - this size check is wrong if we allow larger packets
if (length * sizeof(float) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { if (length * sizeof(float) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
result.resize(0); result.resize(0);
return sizeof(uint16_t); return sizeof(uint16_t);
@ -733,6 +745,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto
uint16_t length; uint16_t length;
memcpy(&length, dataBytes, sizeof(uint16_t)); memcpy(&length, dataBytes, sizeof(uint16_t));
dataBytes += sizeof(length); dataBytes += sizeof(length);
// FIXME - this size check is wrong if we allow larger packets
if (length / 8 > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { if (length / 8 > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
result.resize(0); result.resize(0);
return sizeof(uint16_t); return sizeof(uint16_t);

View file

@ -279,7 +279,8 @@ private:
unsigned int _targetSize; unsigned int _targetSize;
bool _enableCompression; bool _enableCompression;
unsigned char _uncompressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; QByteArray _uncompressedByteArray;
unsigned char* _uncompressed { nullptr };
int _bytesInUse; int _bytesInUse;
int _bytesAvailable; int _bytesAvailable;
int _subTreeAt; int _subTreeAt;
@ -288,7 +289,8 @@ private:
bool compressContent(); bool compressContent();
unsigned char _compressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; QByteArray _compressedByteArray;
unsigned char* _compressed { nullptr };
int _compressedBytes; int _compressedBytes;
int _bytesInUseLastCheck; int _bytesInUseLastCheck;
bool _dirty; bool _dirty;

View file

@ -87,7 +87,7 @@ namespace indexed_container {
if (index < (Index) _elements.size()) { if (index < (Index) _elements.size()) {
_elements[index] = e; _elements[index] = e;
} else { } else {
assert(index == _elements.size()); assert(index == (Index)_elements.size());
_elements.emplace_back(e); _elements.emplace_back(e);
} }
} }

View file

@ -65,7 +65,7 @@ public slots:
* Sets an avatar's gain for you and you only. * Sets an avatar's gain for you and you only.
* Units are Decibels (dB) * Units are Decibels (dB)
* @function Users.setAvatarGain * @function Users.setAvatarGain
* @param {nodeID} nodeID The node or session ID of the user whose gain you want to modify. * @param {nodeID} nodeID The node or session ID of the user whose gain you want to modify, or null to set the master gain.
* @param {float} gain The gain of the avatar you'd like to set. Units are dB. * @param {float} gain The gain of the avatar you'd like to set. Units are dB.
*/ */
void setAvatarGain(const QUuid& nodeID, float gain); void setAvatarGain(const QUuid& nodeID, float gain);
@ -73,7 +73,7 @@ public slots:
/**jsdoc /**jsdoc
* Gets an avatar's gain for you and you only. * Gets an avatar's gain for you and you only.
* @function Users.getAvatarGain * @function Users.getAvatarGain
* @param {nodeID} nodeID The node or session ID of the user whose gain you want to get. * @param {nodeID} nodeID The node or session ID of the user whose gain you want to get, or null to get the master gain.
* @return {float} gain (in dB) * @return {float} gain (in dB)
*/ */
float getAvatarGain(const QUuid& nodeID); float getAvatarGain(const QUuid& nodeID);

View file

@ -0,0 +1,141 @@
//
// PrioritySortUtil.h
//
// Created by Andrew Meadows on 2017-11-08
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_PrioritySortUtil_h
#define hifi_PrioritySortUtil_h
#include <glm/glm.hpp>
#include "ViewFrustum.h"
/* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use:
(1) Derive a class from pure-virtual PrioritySortUtil::Sortable that wraps a copy of
the Thing you want to prioritize and sort:
class SortableWrapper: public PrioritySortUtil::Sortable {
public:
SortableWrapper(const Thing& thing) : _thing(thing) { }
glm::vec3 getPosition() const override { return _thing->getPosition(); }
float getRadius() const override { return 0.5f * _thing->getBoundingRadius(); }
uint64_t getTimestamp() const override { return _thing->getLastTime(); }
const Thing& getThing() const { return _thing; }
private:
Thing _thing;
};
(2) Make a PrioritySortUtil::PriorityQueue<Thing> and add them to the queue:
PrioritySortUtil::Prioritizer prioritizer(viewFrustum);
std::priority_queue< PrioritySortUtil::Sortable<Thing> > sortedThings;
for (thing in things) {
float priority = prioritizer.computePriority(PrioritySortUtil::PrioritizableThing(thing));
sortedThings.push(PrioritySortUtil::Sortable<Thing> entry(thing, priority));
}
(3) Loop over your priority queue and do timeboxed work:
uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET;
while (!sortedThings.empty()) {
const Thing& thing = sortedThings.top();
// ...do work on thing...
sortedThings.pop();
if (usecTimestampNow() > cutoffTime) {
break;
}
}
*/
namespace PrioritySortUtil {
constexpr float DEFAULT_ANGULAR_COEF { 1.0f };
constexpr float DEFAULT_CENTER_COEF { 0.5f };
constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) };
class Sortable {
public:
virtual glm::vec3 getPosition() const = 0;
virtual float getRadius() const = 0;
virtual uint64_t getTimestamp() const = 0;
void setPriority(float priority) { _priority = priority; }
bool operator<(const Sortable& other) const { return _priority < other._priority; }
private:
float _priority { 0.0f };
};
template <typename T>
class PriorityQueue {
public:
PriorityQueue() = delete;
PriorityQueue(const ViewFrustum& view) : _view(view) { }
PriorityQueue(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight)
: _view(view), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight)
{ }
void setView(const ViewFrustum& view) { _view = view; }
void setWeights(float angularWeight, float centerWeight, float ageWeight) {
_angularWeight = angularWeight;
_centerWeight = centerWeight;
_ageWeight = ageWeight;
}
size_t size() const { return _queue.size(); }
void push(T thing) {
thing.setPriority(computePriority(thing));
_queue.push(thing);
}
const T& top() const { return _queue.top(); }
void pop() { return _queue.pop(); }
bool empty() const { return _queue.empty(); }
private:
float computePriority(const T& thing) const {
// priority = weighted linear combination of multiple values:
// (a) angular size
// (b) proximity to center of view
// (c) time since last update
// where the relative "weights" are tuned to scale the contributing values into units of "priority".
glm::vec3 position = thing.getPosition();
glm::vec3 offset = position - _view.getPosition();
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
float radius = thing.getRadius();
float priority = _angularWeight * (radius / distance)
+ _centerWeight * (glm::dot(offset, _view.getDirection()) / distance)
+ _ageWeight * (float)(usecTimestampNow() - thing.getTimestamp());
// decrement priority of things outside keyhole
if (distance - radius > _view.getCenterRadius()) {
if (!_view.sphereIntersectsFrustum(position, radius)) {
constexpr float OUT_OF_VIEW_PENALTY = -10.0f;
priority += OUT_OF_VIEW_PENALTY;
}
}
return priority;
}
ViewFrustum _view;
std::priority_queue<T> _queue;
float _angularWeight { DEFAULT_ANGULAR_COEF };
float _centerWeight { DEFAULT_CENTER_COEF };
float _ageWeight { DEFAULT_AGE_COEF };
};
} // namespace PrioritySortUtil
// for now we're keeping hard-coded sorted time budgets in one spot
const uint64_t MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2000; // usec
const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1000; // usec
#endif // hifi_PrioritySortUtil_h

View file

@ -328,7 +328,8 @@ void TabletProxy::initialScreen(const QVariant& url) {
pushOntoStack(url); pushOntoStack(url);
} else { } else {
_initialScreen = true; _initialScreen = true;
_initialPath = url; _initialPath.first = url;
_initialPath.second = State::QML;
} }
} }
@ -416,10 +417,18 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) {
}); });
if (_initialScreen) { if (_initialScreen) {
if (!_showRunningScripts) { if (!_showRunningScripts && _initialPath.second == State::QML) {
pushOntoStack(_initialPath); pushOntoStack(_initialPath.first);
} else if (_initialPath.second == State::Web) {
QVariant webUrl = _initialPath.first;
QVariant scriptUrl = _initialWebPathParams.first;
gotoWebScreen(webUrl.toString(), scriptUrl.toString(), _initialWebPathParams.second);
} }
_initialScreen = false; _initialScreen = false;
_initialPath.first = "";
_initialPath.second = State::Uninitialized;
_initialWebPathParams.first = "";
_initialWebPathParams.second = false;
} }
if (_showRunningScripts) { if (_showRunningScripts) {
@ -685,6 +694,14 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS
QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(false))); QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(false)));
} }
QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl)));
} else {
// tablet is not initialized yet, save information and load when
// the tablet root is set
_initialPath.first = url;
_initialPath.second = State::Web;
_initialWebPathParams.first = injectedJavaScriptUrl;
_initialWebPathParams.second = loadOtherBase;
_initialScreen = true;
} }
_state = State::Web; _state = State::Web;
emit screenChanged(QVariant("Web"), QVariant(url)); emit screenChanged(QVariant("Web"), QVariant(url));

View file

@ -248,7 +248,6 @@ protected:
void removeButtonsFromToolbar(); void removeButtonsFromToolbar();
bool _initialScreen { false }; bool _initialScreen { false };
QVariant _initialPath { "" };
QVariant _currentPathLoaded { "" }; QVariant _currentPathLoaded { "" };
QString _name; QString _name;
std::vector<QSharedPointer<TabletButtonProxy>> _tabletButtonProxies; std::vector<QSharedPointer<TabletButtonProxy>> _tabletButtonProxies;
@ -260,6 +259,8 @@ protected:
enum class State { Uninitialized, Home, Web, Menu, QML }; enum class State { Uninitialized, Home, Web, Menu, QML };
State _state { State::Uninitialized }; State _state { State::Uninitialized };
std::pair<QVariant, State> _initialPath { "", State::Uninitialized };
std::pair<QVariant, bool> _initialWebPathParams;
bool _landscape { false }; bool _landscape { false };
bool _showRunningScripts { false }; bool _showRunningScripts { false };
}; };

View file

@ -419,7 +419,7 @@ var toolBar = (function () {
var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON); var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON);
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
activeButton = tablet.addButton({ activeButton = tablet.addButton({
captionColorOverride: hasRezPermissions ? "" : "#888888", captionColor: hasRezPermissions ? "#ffffff" : "#888888",
icon: createButtonIconRsrc, icon: createButtonIconRsrc,
activeIcon: "icons/tablet-icons/edit-a.svg", activeIcon: "icons/tablet-icons/edit-a.svg",
text: "CREATE", text: "CREATE",
@ -792,7 +792,7 @@ function handleDomainChange() {
var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified());
createButton.editProperties({ createButton.editProperties({
icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON),
captionColorOverride: (hasRezPermissions ? "" : "#888888"), captionColor: (hasRezPermissions ? "#ffffff" : "#888888"),
}); });
} }

View file

@ -18,7 +18,7 @@
APP_ICON_INACTIVE = Script.resolvePath("./assets/shapes-i.svg"), APP_ICON_INACTIVE = Script.resolvePath("./assets/shapes-i.svg"),
APP_ICON_ACTIVE = Script.resolvePath("./assets/shapes-a.svg"), APP_ICON_ACTIVE = Script.resolvePath("./assets/shapes-a.svg"),
APP_ICON_DISABLED = Script.resolvePath("./assets/shapes-d.svg"), APP_ICON_DISABLED = Script.resolvePath("./assets/shapes-d.svg"),
ENABLED_CAPTION_COLOR_OVERRIDE = "", ENABLED_CAPTION_COLOR_OVERRIDE = "#ffffff",
DISABLED_CAPTION_COLOR_OVERRIDE = "#888888", DISABLED_CAPTION_COLOR_OVERRIDE = "#888888",
START_DELAY = 2000, // ms START_DELAY = 2000, // ms
@ -1865,7 +1865,7 @@
} }
button.editProperties({ button.editProperties({
icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED, icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED,
captionColorOverride: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, captionColor: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE,
isActive: isAppActive isActive: isAppActive
}); });
} }
@ -1880,7 +1880,7 @@
} }
button.editProperties({ button.editProperties({
icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED, icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED,
captionColorOverride: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, captionColor: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE,
isActive: isAppActive isActive: isAppActive
}); });
} }
@ -1945,7 +1945,7 @@
hasRezPermissions = Entities.canRez() || Entities.canRezTmp(); hasRezPermissions = Entities.canRez() || Entities.canRezTmp();
button = tablet.addButton({ button = tablet.addButton({
icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED, icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED,
captionColorOverride: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, captionColor: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE,
activeIcon: APP_ICON_ACTIVE, activeIcon: APP_ICON_ACTIVE,
text: APP_NAME, text: APP_NAME,
isActive: isAppActive isActive: isAppActive