mirror of
https://github.com/overte-org/overte.git
synced 2025-04-14 04:07:11 +02:00
fixing merge conflict
This commit is contained in:
commit
76935c1457
49 changed files with 1078 additions and 379 deletions
|
@ -29,6 +29,7 @@
|
|||
#include <UUID.h>
|
||||
#include <CPUDetect.h>
|
||||
|
||||
#include "AudioLogging.h"
|
||||
#include "AudioHelpers.h"
|
||||
#include "AudioRingBuffer.h"
|
||||
#include "AudioMixerClientData.h"
|
||||
|
@ -130,7 +131,7 @@ void AudioMixer::queueReplicatedAudioPacket(QSharedPointer<ReceivedMessage> mess
|
|||
PacketType rewrittenType = PacketTypeEnum::getReplicatedPacketMapping().key(message->getType());
|
||||
|
||||
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,
|
||||
|
@ -345,7 +346,7 @@ void AudioMixer::sendStatsPacket() {
|
|||
|
||||
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
|
||||
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;
|
||||
_throttlingRatio += THROTTLE_RATE * proportionalTerm;
|
||||
_throttlingRatio = std::min(_throttlingRatio, 1.0f);
|
||||
qDebug("audio-mixer is struggling (%f mix/sleep) - throttling %f of streams",
|
||||
(double)_trailingMixRatio, (double)_throttlingRatio);
|
||||
qCDebug(audio) << "audio-mixer is struggling (" << _trailingMixRatio << "mix/sleep) - throttling"
|
||||
<< _throttlingRatio << "of streams";
|
||||
} else if (_throttlingRatio > 0.0f && _trailingMixRatio <= BACKOFF_TARGET) {
|
||||
int proportionalTerm = 1 + (TARGET - _trailingMixRatio) / 0.2f;
|
||||
_throttlingRatio -= BACKOFF_RATE * proportionalTerm;
|
||||
_throttlingRatio = std::max(_throttlingRatio, 0.0f);
|
||||
qDebug("audio-mixer is recovering (%f mix/sleep) - throttling %f of streams",
|
||||
(double)_trailingMixRatio, (double)_throttlingRatio);
|
||||
qCDebug(audio) << "audio-mixer is recovering (" << _trailingMixRatio << "mix/sleep) - throttling"
|
||||
<< _throttlingRatio << "of streams";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -534,7 +535,7 @@ void AudioMixer::clearDomainSettings() {
|
|||
}
|
||||
|
||||
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)) {
|
||||
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";
|
||||
bool enableDynamicJitterBuffer = audioBufferGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool();
|
||||
if (enableDynamicJitterBuffer) {
|
||||
qDebug() << "Enabling dynamic jitter buffers.";
|
||||
qCDebug(audio) << "Enabling dynamic jitter buffers.";
|
||||
|
||||
bool ok;
|
||||
const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static_desired_jitter_buffer_frames";
|
||||
|
@ -565,9 +566,9 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
if (!ok) {
|
||||
_numStaticJitterFrames = InboundAudioStream::DEFAULT_STATIC_JITTER_FRAMES;
|
||||
}
|
||||
qDebug() << "Static desired jitter buffer frames:" << _numStaticJitterFrames;
|
||||
qCDebug(audio) << "Static desired jitter buffer frames:" << _numStaticJitterFrames;
|
||||
} else {
|
||||
qDebug() << "Disabling dynamic jitter buffers.";
|
||||
qCDebug(audio) << "Disabling dynamic jitter buffers.";
|
||||
_numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES;
|
||||
}
|
||||
|
||||
|
@ -621,7 +622,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
if (audioEnvGroupObject[CODEC_PREFERENCE_ORDER].isString()) {
|
||||
QString codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString();
|
||||
_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";
|
||||
|
@ -630,7 +631,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
float attenuation = audioEnvGroupObject[ATTENATION_PER_DOULING_IN_DISTANCE].toString().toFloat(&ok);
|
||||
if (ok) {
|
||||
_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);
|
||||
if (ok) {
|
||||
_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);
|
||||
AABox zoneAABox(corner, dimensions);
|
||||
_audioZones.insert(zone, zoneAABox);
|
||||
qDebug() << "Added zone:" << zone << "(corner:" << corner
|
||||
<< ", dimensions:" << dimensions << ")";
|
||||
qCDebug(audio) << "Added zone:" << zone << "(corner:" << corner << ", dimensions:" << dimensions << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -712,7 +712,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
_audioZones.contains(settings.source) && _audioZones.contains(settings.listener)) {
|
||||
|
||||
_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);
|
||||
|
||||
qDebug() << "Added Reverb:" << zone << reverbTime << wetLevel;
|
||||
qCDebug(audio) << "Added Reverb:" << zone << reverbTime << wetLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "InjectedAudioStream.h"
|
||||
|
||||
#include "AudioLogging.h"
|
||||
#include "AudioHelpers.h"
|
||||
#include "AudioMixer.h"
|
||||
#include "AudioMixerClientData.h"
|
||||
|
@ -132,7 +133,7 @@ void AudioMixerClientData::optionallyReplicatePacket(ReceivedMessage& message, c
|
|||
if (PacketTypeEnum::getReplicatedPacketMapping().key(message.getType()) != PacketType::Unknown) {
|
||||
mirroredType = message.getType();
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
@ -189,8 +190,16 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const
|
|||
uint8_t packedGain;
|
||||
message.readPrimitive(&packedGain);
|
||||
float gain = unpackFloatGainFromByte(packedGain);
|
||||
hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain);
|
||||
qDebug() << "Setting gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain;
|
||||
|
||||
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);
|
||||
qCDebug(audio) << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain;
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO);
|
||||
qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
|
||||
qCDebug(audio) << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
|
||||
|
||||
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec,
|
||||
this, &AudioMixerClientData::handleMismatchAudioFormat);
|
||||
|
@ -315,7 +324,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
|
||||
#if INJECTORS_SUPPORT_CODECS
|
||||
injectorStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
qDebug() << "creating new injectorStream... codec:" << _selectedCodecName;
|
||||
qCDebug(audio) << "creating new injectorStream... codec:" << _selectedCodecName;
|
||||
#endif
|
||||
|
||||
auto emplaced = _audioStreams.emplace(
|
||||
|
@ -339,8 +348,8 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
auto parseResult = matchingStream->parseData(message);
|
||||
|
||||
if (matchingStream->getOverflowCount() > overflowBefore) {
|
||||
qDebug() << "Just overflowed on stream from" << message.getSourceID() << "at" << message.getSenderSockAddr();
|
||||
qDebug() << "This stream is for" << (isMicStream ? "microphone audio" : "injected audio");
|
||||
qCDebug(audio) << "Just overflowed on stream from" << message.getSourceID() << "at" << message.getSenderSockAddr();
|
||||
qCDebug(audio) << "This stream is for" << (isMicStream ? "microphone audio" : "injected audio");
|
||||
}
|
||||
|
||||
return parseResult;
|
||||
|
@ -689,7 +698,7 @@ void AudioMixerClientData::setupCodecForReplicatedAgent(QSharedPointer<ReceivedM
|
|||
auto codecString = message->readString();
|
||||
|
||||
if (codecString != _selectedCodecName) {
|
||||
qDebug() << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID())
|
||||
qCDebug(audio) << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID())
|
||||
<< "-" << codecString;
|
||||
|
||||
const std::pair<QString, CodecPluginPointer> codec = AudioMixer::negotiateCodec({ codecString });
|
||||
|
|
|
@ -83,6 +83,9 @@ public:
|
|||
// uses randomization to have the AudioMixer send a stats packet to this node around every second
|
||||
bool shouldSendStats(int frameNumber);
|
||||
|
||||
float getMasterAvatarGain() const { return _masterAvatarGain; }
|
||||
void setMasterAvatarGain(float gain) { _masterAvatarGain = gain; }
|
||||
|
||||
AudioLimiter audioLimiter;
|
||||
|
||||
void setupCodec(CodecPluginPointer codec, const QString& codecName);
|
||||
|
@ -175,6 +178,8 @@ private:
|
|||
|
||||
int _frameToSendStats { 0 };
|
||||
|
||||
float _masterAvatarGain { 1.0f }; // per-listener mixing gain, applied only to avatars
|
||||
|
||||
CodecPluginPointer _codec;
|
||||
QString _selectedCodecName;
|
||||
Encoder* _encoder{ nullptr }; // for outbound mixed stream
|
||||
|
|
|
@ -48,8 +48,8 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData&
|
|||
// mix helpers
|
||||
inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition);
|
||||
inline float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition, bool isEcho);
|
||||
inline float computeGain(const AudioMixerClientData& listenerNodeData, const AvatarAudioStream& listeningNodeStream,
|
||||
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, bool isEcho);
|
||||
inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition);
|
||||
|
||||
|
@ -266,7 +266,7 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU
|
|||
glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition();
|
||||
|
||||
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);
|
||||
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
|
||||
float distance = glm::length(relativePosition);
|
||||
return gain / distance;
|
||||
|
||||
// avatar: skip master gain - it is constant for all streams
|
||||
}
|
||||
|
||||
float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
|
||||
const glm::vec3& relativePosition, bool isEcho) {
|
||||
float computeGain(const AudioMixerClientData& listenerNodeData, const AvatarAudioStream& listeningNodeStream,
|
||||
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, bool isEcho) {
|
||||
float gain = 1.0f;
|
||||
|
||||
// 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));
|
||||
|
||||
gain *= offAxisCoefficient;
|
||||
|
||||
// apply master gain, only to avatars
|
||||
gain *= listenerNodeData.getMasterAvatarGain();
|
||||
}
|
||||
|
||||
auto& audioZones = AudioMixer::getAudioZones();
|
||||
|
|
|
@ -80,11 +80,23 @@ span.port {
|
|||
display: none;
|
||||
}
|
||||
|
||||
#setup-sidebar.affix {
|
||||
/* This overrides a case where going to the bottom of the page,
|
||||
* then scrolling up, causes `position: relative` to be added to the style
|
||||
*/
|
||||
position: fixed !important;
|
||||
@media (min-width: 768px) {
|
||||
#setup-sidebar.affix {
|
||||
/* This overrides a case where going to the bottom of the page,
|
||||
* then scrolling up, causes `position: relative` to be added to the style
|
||||
*/
|
||||
position: fixed !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
#setup-sidebar.affix {
|
||||
position: static !important;
|
||||
}
|
||||
|
||||
#setup-sidebar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
#setup-sidebar button {
|
||||
|
@ -302,6 +314,7 @@ table .headers + .headers td {
|
|||
}
|
||||
|
||||
.account-connected-header {
|
||||
vertical-align: middle;
|
||||
color: #6FCF97;
|
||||
font-size: 30px;
|
||||
margin-right: 20px;
|
||||
|
|
|
@ -62,26 +62,25 @@ var Strings = {
|
|||
// dialog with new path still set, allowing them to retry immediately, and without
|
||||
// having to type the new path in again.
|
||||
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_PENDING: "Saving...",
|
||||
EDIT_PLACE_CANCEL_BUTTON: "Cancel",
|
||||
|
||||
REMOVE_PLACE_TITLE: "Are you sure you want to remove <strong>{{place}}</strong>?",
|
||||
REMOVE_PLACE_ERROR: "Failed to remove place. Please try again.",
|
||||
REMOVE_PLACE_DELETE_BUTTON: "Delete",
|
||||
REMOVE_PLACE_TITLE: "Are you sure you want to remove <strong>{{place}}</strong> and its path information?",
|
||||
REMOVE_PLACE_ERROR: "Failed to remove Place Name and its Path information.",
|
||||
REMOVE_PLACE_DELETE_BUTTON: "This action removes your Place Name",
|
||||
REMOVE_PLACE_DELETE_BUTTON_PENDING: "Deleting...",
|
||||
REMOVE_PLACE_CANCEL_BUTTON: "Cancel",
|
||||
|
||||
ADD_PLACE_TITLE: "Choose a place",
|
||||
ADD_PLACE_MESSAGE: "Choose the High Fidelity place to point at this domain server.",
|
||||
ADD_PLACE_CONFIRM_BUTTON: "Choose place",
|
||||
ADD_PLACE_MESSAGE: "Choose a Place Name that you own or register a new Place Name.",
|
||||
ADD_PLACE_CONFIRM_BUTTON: "Save",
|
||||
ADD_PLACE_CONFIRM_BUTTON_PENDING: "Saving...",
|
||||
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."
|
||||
+ "<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_MESSAGE: "You don't have any Place Names registered. Once you have a Place Name, reopen this window to select it.",
|
||||
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_LOADING_DIALOG: "Loading your places...",
|
||||
|
@ -236,7 +235,7 @@ function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAd
|
|||
|
||||
if (forcePathTo === undefined || forcePathTo === null) {
|
||||
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 += "</div>";
|
||||
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-cancel-button').removeAttr('disabled');
|
||||
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
|
||||
bootbox.alert("FAIL");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -363,7 +361,8 @@ function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAd
|
|||
title: Strings.ADD_PLACE_TITLE,
|
||||
message: modal_body,
|
||||
closeButton: false,
|
||||
buttons: modal_buttons
|
||||
buttons: modal_buttons,
|
||||
onEscape: true
|
||||
});
|
||||
} else {
|
||||
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
|
||||
|
|
|
@ -36,11 +36,6 @@
|
|||
</div>
|
||||
|
||||
<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 id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
|
||||
|
|
|
@ -503,7 +503,7 @@ function showDomainCreationAlert(justConnected) {
|
|||
swal({
|
||||
title: 'Create new domain ID',
|
||||
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,
|
||||
confirmButtonText: "Create",
|
||||
closeOnConfirm: false,
|
||||
|
@ -527,13 +527,12 @@ function showDomainCreationAlert(justConnected) {
|
|||
function createNewDomainID(label, justConnected) {
|
||||
// get the JSON object ready that we'll use to create a new domain
|
||||
var domainJSON = {
|
||||
"label": label
|
||||
//"access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val()
|
||||
"label": label
|
||||
}
|
||||
|
||||
$.post("/api/domains", domainJSON, function(data){
|
||||
// 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);
|
||||
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
|
||||
|
||||
|
@ -620,18 +619,14 @@ function parseJSONResponse(xhr) {
|
|||
|
||||
function showOrHideLabel() {
|
||||
var type = getCurrentDomainIDType();
|
||||
if (!accessTokenIsSet() || (type !== DOMAIN_ID_TYPE_FULL && type !== DOMAIN_ID_TYPE_UNKNOWN)) {
|
||||
$(".panel#label").hide();
|
||||
return false;
|
||||
}
|
||||
$(".panel#label").show();
|
||||
return true;
|
||||
var shouldShow = accessTokenIsSet() && (type === DOMAIN_ID_TYPE_FULL || type === DOMAIN_ID_TYPE_UNKNOWN);
|
||||
$(".panel#label").toggle(shouldShow);
|
||||
$("li a[href='#label']").parent().toggle(shouldShow);
|
||||
return shouldShow;
|
||||
}
|
||||
|
||||
function setupDomainLabelSetting() {
|
||||
if (!showOrHideLabel()) {
|
||||
return;
|
||||
}
|
||||
showOrHideLabel();
|
||||
|
||||
var html = "<div>"
|
||||
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',
|
||||
message: modal_body,
|
||||
closeButton: false,
|
||||
onEscape: true,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Cancel',
|
||||
|
@ -742,7 +738,7 @@ function setupDomainNetworkingSettings() {
|
|||
var includeAddress = autoNetworkingSetting === 'disabled';
|
||||
|
||||
if (includeAddress) {
|
||||
var label = "Network Address and Port";
|
||||
var label = "Network Address:Port";
|
||||
} else {
|
||||
var label = "Network Port";
|
||||
}
|
||||
|
@ -777,6 +773,7 @@ function setupDomainNetworkingSettings() {
|
|||
title: 'Edit Network',
|
||||
message: modal_body,
|
||||
closeButton: false,
|
||||
onEscape: true,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Cancel',
|
||||
|
@ -924,6 +921,7 @@ function placeTableRow(name, path, isTemporary, placeID) {
|
|||
var dialog = bootbox.dialog({
|
||||
message: confirmString,
|
||||
closeButton: false,
|
||||
onEscape: true,
|
||||
buttons: [
|
||||
{
|
||||
label: Strings.REMOVE_PLACE_CANCEL_BUTTON,
|
||||
|
@ -1025,7 +1023,9 @@ function reloadDomainInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
appendAddButtonToPlacesTable();
|
||||
if (accessTokenIsSet()) {
|
||||
appendAddButtonToPlacesTable();
|
||||
}
|
||||
|
||||
} else {
|
||||
$('.domain-loading-error').show();
|
||||
|
@ -1098,6 +1098,7 @@ function editHighFidelityPlace(placeID, name, path) {
|
|||
dialog = bootbox.dialog({
|
||||
title: Strings.EDIT_PLACE_TITLE,
|
||||
closeButton: false,
|
||||
onEscape: true,
|
||||
message: modal_body,
|
||||
buttons: modal_buttons
|
||||
})
|
||||
|
@ -1180,6 +1181,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
|||
|
||||
bootbox.dialog({
|
||||
title: "Choose matching domain",
|
||||
onEscape: true,
|
||||
message: modal_body,
|
||||
buttons: modal_buttons
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.2
|
||||
import QtWebChannel 1.0
|
||||
import QtWebEngine 1.2
|
||||
import QtWebEngine 1.5
|
||||
|
||||
import "controls-uit"
|
||||
import "styles" as HifiStyles
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// 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
|
||||
|
||||
WebEngineView {
|
||||
|
|
|
@ -135,4 +135,10 @@ Item {
|
|||
playing: visible
|
||||
z: 10000
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if ((event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) {
|
||||
webViewCore.focus = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,19 +51,23 @@ FocusScope {
|
|||
|
||||
// The VR version of the primary menu
|
||||
property var rootMenu: Menu {
|
||||
id: rootMenuId
|
||||
objectName: "rootMenu"
|
||||
|
||||
// for some reasons it is not possible to use just '({})' here as it gets empty when passed to TableRoot/DesktopRoot
|
||||
property var exclusionGroupsByMenuItem : ListModel {}
|
||||
property var exclusionGroups: ({});
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtWebEngine 1.1;
|
||||
import QtWebEngine 1.5;
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import "../desktop" as OriginalDesktop
|
||||
|
|
|
@ -442,7 +442,7 @@ Item {
|
|||
Rectangle {
|
||||
id: nameCardVUMeter
|
||||
// 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
|
||||
// Anchors
|
||||
anchors.bottom: isMyCard ? avatarImage.bottom : parent.bottom;
|
||||
|
@ -526,16 +526,14 @@ Item {
|
|||
anchors.verticalCenter: nameCardVUMeter.verticalCenter;
|
||||
anchors.left: nameCardVUMeter.left;
|
||||
// Properties
|
||||
visible: !isMyCard && selected && pal.activeTab == "nearbyTab" && isPresent;
|
||||
visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent;
|
||||
value: Users.getAvatarGain(uuid)
|
||||
minimumValue: -60.0
|
||||
maximumValue: 20.0
|
||||
stepSize: 5
|
||||
updateValueWhileDragging: true
|
||||
onValueChanged: {
|
||||
if (uuid !== "") {
|
||||
updateGainFromQML(uuid, value, false);
|
||||
}
|
||||
updateGainFromQML(uuid, value, false);
|
||||
}
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
|
@ -575,7 +573,19 @@ Item {
|
|||
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) {
|
||||
Users.setAvatarGain(avatarUuid, sliderValue);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
//
|
||||
// WebBrowser.qml
|
||||
//
|
||||
|
@ -9,12 +10,12 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.5 as QQControls
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.2 as QQControls
|
||||
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 "../styles-uit"
|
||||
|
@ -22,6 +23,8 @@ import "../controls-uit" as HifiControls
|
|||
import "../windows"
|
||||
import "../controls"
|
||||
|
||||
import HifiWeb 1.0
|
||||
|
||||
Rectangle {
|
||||
id: root;
|
||||
|
||||
|
@ -32,214 +35,401 @@ Rectangle {
|
|||
property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false
|
||||
property bool keyboardRaised: 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;
|
||||
|
||||
// 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 {
|
||||
spacing: 2
|
||||
width: parent.width;
|
||||
|
||||
RowLayout {
|
||||
id: addressBarRow
|
||||
width: parent.width;
|
||||
height: 48
|
||||
|
||||
HifiControls.WebGlyphButton {
|
||||
enabled: webEngineView.canGoBack
|
||||
enabled: webStack.currentItem.webEngineView.canGoBack || webStack.depth > 1
|
||||
glyph: hifi.glyphs.backward;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
size: 38;
|
||||
onClicked: {
|
||||
webEngineView.goBack()
|
||||
if (webStack.currentItem.webEngineView.canGoBack) {
|
||||
webStack.currentItem.webEngineView.goBack();
|
||||
} else if (webStack.depth > 1) {
|
||||
webStack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.WebGlyphButton {
|
||||
enabled: webEngineView.canGoForward
|
||||
enabled: webStack.currentItem.webEngineView.canGoForward
|
||||
glyph: hifi.glyphs.forward;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
size: 38;
|
||||
onClicked: {
|
||||
webEngineView.goForward()
|
||||
webStack.currentItem.webEngineView.goForward();
|
||||
}
|
||||
}
|
||||
|
||||
QQControls.TextField {
|
||||
QQControls.ComboBox {
|
||||
id: addressBar
|
||||
|
||||
Image {
|
||||
anchors.verticalCenter: addressBar.verticalCenter;
|
||||
x: 5
|
||||
z: 2
|
||||
id: faviconImage
|
||||
width: 16; height: 16
|
||||
sourceSize: Qt.size(width, height)
|
||||
source: webEngineView.icon
|
||||
//selectByMouse: true
|
||||
focus: true
|
||||
|
||||
editable: true
|
||||
//flat: true
|
||||
indicator: Item {}
|
||||
background: Item {}
|
||||
onActivated: {
|
||||
goTo(textAt(index));
|
||||
}
|
||||
|
||||
HifiControls.WebGlyphButton {
|
||||
glyph: webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
width: hifi.dimensions.controlLineHeight
|
||||
z: 2
|
||||
x: addressBar.width - 28
|
||||
onClicked: {
|
||||
if (webEngineView.loading) {
|
||||
webEngineView.stop()
|
||||
} else {
|
||||
reloadTimer.start()
|
||||
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 {
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
x: 5
|
||||
z: 2
|
||||
id: faviconImage
|
||||
width: 16; height: 16
|
||||
sourceSize: Qt.size(width, height)
|
||||
source: webStack.currentItem.webEngineView.icon
|
||||
}
|
||||
|
||||
HifiControls.WebGlyphButton {
|
||||
glyph: webStack.currentItem.webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
width: hifi.dimensions.controlLineHeight
|
||||
z: 2
|
||||
x: addressBarInput.width - implicitWidth
|
||||
onClicked: {
|
||||
if (webStack.currentItem.webEngineView.loading) {
|
||||
webStack.currentItem.webEngineView.stop();
|
||||
} else {
|
||||
webStack.currentItem.reloadTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style: TextFieldStyle {
|
||||
padding {
|
||||
left: 26;
|
||||
right: 26
|
||||
Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i");
|
||||
|
||||
Keys.onPressed: {
|
||||
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
|
||||
text: webEngineView.url
|
||||
onAccepted: webEngineView.url = text
|
||||
editText: webStack.currentItem.webEngineView.url
|
||||
onAccepted: goTo(addressBarInput.text);
|
||||
}
|
||||
|
||||
HifiControls.WebGlyphButton {
|
||||
checkable: true
|
||||
//only QtWebEngine 1.3
|
||||
//checked: webEngineView.audioMuted
|
||||
checked: webStack.currentItem.webEngineView.audioMuted
|
||||
glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
width: hifi.dimensions.controlLineHeight
|
||||
onClicked: {
|
||||
webEngineView.triggerWebAction(WebEngineView.ToggleMediaMute)
|
||||
webStack.currentItem.webEngineView.audioMuted = !webStack.currentItem.webEngineView.audioMuted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQControls.ProgressBar {
|
||||
id: loadProgressBar
|
||||
style: ProgressBarStyle {
|
||||
background: Rectangle {
|
||||
color: "#6A6A6A"
|
||||
}
|
||||
progress: Rectangle{
|
||||
background: Rectangle {
|
||||
implicitHeight: 2
|
||||
color: "#6A6A6A"
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
implicitHeight: 2
|
||||
|
||||
Rectangle {
|
||||
width: loadProgressBar.visualPosition * parent.width
|
||||
height: parent.height
|
||||
color: "#00B4EF"
|
||||
}
|
||||
}
|
||||
|
||||
width: parent.width;
|
||||
minimumValue: 0
|
||||
maximumValue: 100
|
||||
value: webEngineView.loadProgress
|
||||
from: 0
|
||||
to: 100
|
||||
value: webStack.currentItem.webEngineView.loadProgress
|
||||
height: 2
|
||||
}
|
||||
|
||||
HifiControls.BaseWebView {
|
||||
id: webEngineView
|
||||
focus: true
|
||||
objectName: "tabletWebEngineView"
|
||||
Component {
|
||||
id: webViewComponent
|
||||
Rectangle {
|
||||
property alias webEngineView: webEngineView
|
||||
property alias reloadTimer: reloadTimer
|
||||
|
||||
url: "http://www.highfidelity.com"
|
||||
property real webViewHeight: root.height - loadProgressBar.height - 48 - 4
|
||||
property WebEngineNewViewRequest request: null
|
||||
|
||||
width: parent.width;
|
||||
height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight
|
||||
property bool isDialog: QQControls.StackView.index > 0
|
||||
property real margins: isDialog ? 10 : 0
|
||||
|
||||
profile: HFWebEngineProfile;
|
||||
color: "#d1d1d1"
|
||||
|
||||
property string userScriptUrl: ""
|
||||
|
||||
// creates a global EventBridge object.
|
||||
WebEngineScript {
|
||||
id: createGlobalEventBridge
|
||||
sourceCode: eventBridgeJavaScriptToInject
|
||||
injectionPoint: WebEngineScript.DocumentCreation
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
// detects when to raise and lower virtual keyboard
|
||||
WebEngineScript {
|
||||
id: raiseAndLowerKeyboard
|
||||
injectionPoint: WebEngineScript.Deferred
|
||||
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
// User script.
|
||||
WebEngineScript {
|
||||
id: userScript
|
||||
sourceUrl: webEngineView.userScriptUrl
|
||||
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
||||
|
||||
settings.autoLoadImages: true
|
||||
settings.javascriptEnabled: true
|
||||
settings.errorPageEnabled: true
|
||||
settings.pluginsEnabled: true
|
||||
settings.fullScreenSupportEnabled: false
|
||||
//from WebEngine 1.3
|
||||
// settings.autoLoadIconsForPage: false
|
||||
// settings.touchIconsEnabled: false
|
||||
|
||||
onCertificateError: {
|
||||
error.defer();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
webChannel.registerObject("eventBridge", eventBridge);
|
||||
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
||||
webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)";
|
||||
}
|
||||
|
||||
onFeaturePermissionRequested: {
|
||||
grantFeaturePermission(securityOrigin, feature, true);
|
||||
}
|
||||
|
||||
onNewViewRequested: {
|
||||
if (!request.userInitiated) {
|
||||
print("Warning: Blocked a popup window.");
|
||||
}
|
||||
}
|
||||
|
||||
onRenderProcessTerminated: {
|
||||
var status = "";
|
||||
switch (terminationStatus) {
|
||||
case WebEngineView.NormalTerminationStatus:
|
||||
status = "(normal exit)";
|
||||
break;
|
||||
case WebEngineView.AbnormalTerminationStatus:
|
||||
status = "(abnormal exit)";
|
||||
break;
|
||||
case WebEngineView.CrashedTerminationStatus:
|
||||
status = "(crashed)";
|
||||
break;
|
||||
case WebEngineView.KilledTerminationStatus:
|
||||
status = "(killed)";
|
||||
break;
|
||||
QQControls.StackView.onActivated: {
|
||||
addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; });
|
||||
}
|
||||
|
||||
print("Render process exited with code " + exitCode + " " + status);
|
||||
reloadTimer.running = true;
|
||||
}
|
||||
onRequestChanged: {
|
||||
if (isDialog && request !== null && request !== undefined) {//is Dialog ?
|
||||
request.openIn(webEngineView);
|
||||
}
|
||||
}
|
||||
|
||||
onWindowCloseRequested: {
|
||||
}
|
||||
HifiControls.BaseWebView {
|
||||
id: webEngineView
|
||||
anchors.fill: parent
|
||||
anchors.margins: parent.margins
|
||||
|
||||
Timer {
|
||||
id: reloadTimer
|
||||
interval: 0
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: webEngineView.reload()
|
||||
layer.enabled: parent.isDialog
|
||||
layer.effect: DropShadow {
|
||||
verticalOffset: 8
|
||||
horizontalOffset: 8
|
||||
color: "#330066ff"
|
||||
samples: 10
|
||||
spread: 0.5
|
||||
}
|
||||
|
||||
focus: true
|
||||
objectName: "tabletWebEngineView"
|
||||
|
||||
//profile: HFWebEngineProfile;
|
||||
profile.httpUserAgent: "Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0"
|
||||
|
||||
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.
|
||||
WebEngineScript {
|
||||
id: createGlobalEventBridge
|
||||
sourceCode: eventBridgeJavaScriptToInject
|
||||
injectionPoint: WebEngineScript.Deferred
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
// detects when to raise and lower virtual keyboard
|
||||
WebEngineScript {
|
||||
id: raiseAndLowerKeyboard
|
||||
injectionPoint: WebEngineScript.Deferred
|
||||
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
// User script.
|
||||
WebEngineScript {
|
||||
id: userScript
|
||||
sourceUrl: webEngineView.userScriptUrl
|
||||
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
||||
|
||||
settings.autoLoadImages: true
|
||||
settings.javascriptEnabled: true
|
||||
settings.errorPageEnabled: true
|
||||
settings.pluginsEnabled: true
|
||||
settings.fullScreenSupportEnabled: true
|
||||
settings.autoLoadIconsForPage: true
|
||||
settings.touchIconsEnabled: true
|
||||
|
||||
onCertificateError: {
|
||||
error.defer();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
webChannel.registerObject("eventBridge", eventBridge);
|
||||
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
||||
}
|
||||
|
||||
onFeaturePermissionRequested: {
|
||||
grantFeaturePermission(securityOrigin, feature, true);
|
||||
}
|
||||
|
||||
onNewViewRequested: {
|
||||
if (request.destination == WebEngineView.NewViewInDialog) {
|
||||
webStack.push(webViewComponent, {"request": request});
|
||||
} else {
|
||||
request.openIn(webEngineView);
|
||||
}
|
||||
}
|
||||
|
||||
onRenderProcessTerminated: {
|
||||
var status = "";
|
||||
switch (terminationStatus) {
|
||||
case WebEngineView.NormalTerminationStatus:
|
||||
status = "(normal exit)";
|
||||
break;
|
||||
case WebEngineView.AbnormalTerminationStatus:
|
||||
status = "(abnormal exit)";
|
||||
break;
|
||||
case WebEngineView.CrashedTerminationStatus:
|
||||
status = "(crashed)";
|
||||
break;
|
||||
case WebEngineView.KilledTerminationStatus:
|
||||
status = "(killed)";
|
||||
break;
|
||||
}
|
||||
|
||||
console.error("Render process exited with code " + exitCode + " " + status);
|
||||
reloadTimer.running = true;
|
||||
}
|
||||
|
||||
onFullScreenRequested: {
|
||||
if (request.toggleOn) {
|
||||
webEngineView.state = "FullScreen";
|
||||
} else {
|
||||
webEngineView.state = "";
|
||||
}
|
||||
request.accept();
|
||||
}
|
||||
|
||||
onWindowCloseRequested: {
|
||||
webStack.pop();
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
id: reloadTimer
|
||||
interval: 0
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: webEngineView.reload()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
id: keyboard
|
||||
raised: parent.keyboardEnabled && parent.keyboardRaised
|
||||
|
|
|
@ -5,7 +5,9 @@ import TabletScriptingInterface 1.0
|
|||
Item {
|
||||
id: tabletButton
|
||||
|
||||
property string captionColorOverride: ""
|
||||
property color defaultCaptionColor: "#ffffff"
|
||||
property color captionColor: defaultCaptionColor
|
||||
|
||||
property var uuid;
|
||||
property string icon: "icons/tablet-icons/edit-i.svg"
|
||||
property string hoverIcon: tabletButton.icon
|
||||
|
@ -105,7 +107,7 @@ Item {
|
|||
|
||||
Text {
|
||||
id: text
|
||||
color: captionColorOverride !== "" ? captionColorOverride: "#ffffff"
|
||||
color: captionColor
|
||||
text: tabletButton.text
|
||||
font.bold: true
|
||||
font.pixelSize: 18
|
||||
|
@ -171,7 +173,7 @@ Item {
|
|||
|
||||
PropertyChanges {
|
||||
target: text
|
||||
color: captionColorOverride !== "" ? captionColorOverride: "#ffffff"
|
||||
color: captionColor
|
||||
text: tabletButton.hoverText
|
||||
}
|
||||
|
||||
|
@ -197,7 +199,7 @@ Item {
|
|||
|
||||
PropertyChanges {
|
||||
target: text
|
||||
color: captionColorOverride !== "" ? captionColorOverride: "#333333"
|
||||
color: captionColor !== defaultCaptionColor ? captionColor : "#333333"
|
||||
text: tabletButton.activeText
|
||||
}
|
||||
|
||||
|
@ -228,7 +230,7 @@ Item {
|
|||
|
||||
PropertyChanges {
|
||||
target: text
|
||||
color: captionColorOverride !== "" ? captionColorOverride: "#333333"
|
||||
color: captionColor !== defaultCaptionColor ? captionColor : "#333333"
|
||||
text: tabletButton.activeHoverText
|
||||
}
|
||||
|
||||
|
|
|
@ -40,37 +40,29 @@ Item {
|
|||
|
||||
CheckBox {
|
||||
id: checkbox
|
||||
// FIXME: Should use radio buttons if source.exclusiveGroup.
|
||||
|
||||
width: 20
|
||||
visible: source !== null ?
|
||||
source.visible && source.type === 1 && source.checkable && !source.exclusiveGroup :
|
||||
false
|
||||
checked: setChecked()
|
||||
function setChecked() {
|
||||
if (!source || source.type !== 1 || !source.checkable) {
|
||||
return false;
|
||||
}
|
||||
// FIXME this works for native QML menus but I don't think it will
|
||||
// for proxied QML menus
|
||||
return source.checked;
|
||||
|
||||
Binding on checked {
|
||||
value: source.checked;
|
||||
when: source && source.type === 1 && source.checkable && !source.exclusiveGroup;
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
id: radiobutton
|
||||
// FIXME: Should use radio buttons if source.exclusiveGroup.
|
||||
|
||||
width: 20
|
||||
visible: source !== null ?
|
||||
source.visible && source.type === 1 && source.checkable && source.exclusiveGroup :
|
||||
false
|
||||
checked: setChecked()
|
||||
function setChecked() {
|
||||
if (!source || source.type !== 1 || !source.checkable) {
|
||||
return false;
|
||||
}
|
||||
// FIXME this works for native QML menus but I don't think it will
|
||||
// for proxied QML menus
|
||||
return source.checked;
|
||||
|
||||
Binding on checked {
|
||||
value: source.checked;
|
||||
when: source && source.type === 1 && source.checkable && source.exclusiveGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,6 @@ Item {
|
|||
|
||||
function toModel(items, newMenu) {
|
||||
var result = modelMaker.createObject(tabletMenu);
|
||||
var exclusionGroups = {};
|
||||
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
var item = items[i];
|
||||
|
@ -78,28 +77,6 @@ Item {
|
|||
if (item.text !== "Users Online") {
|
||||
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;
|
||||
case MenuItemType.Separator:
|
||||
result.append({"name": "", "item": item})
|
||||
|
|
|
@ -4,7 +4,9 @@ import QtQuick.Controls 1.4
|
|||
StateImage {
|
||||
id: button
|
||||
|
||||
property string captionColorOverride: ""
|
||||
property color defaultCaptionColor: "#ffffff"
|
||||
property color captionColor: defaultCaptionColor
|
||||
|
||||
property bool buttonEnabled: true
|
||||
property bool isActive: false
|
||||
property bool isEntered: false
|
||||
|
@ -98,7 +100,7 @@ StateImage {
|
|||
|
||||
Text {
|
||||
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)
|
||||
font.bold: false
|
||||
font.pixelSize: 9
|
||||
|
|
|
@ -208,6 +208,8 @@
|
|||
#include "commerce/Wallet.h"
|
||||
#include "commerce/QmlCommerce.h"
|
||||
|
||||
#include "webbrowser/WebBrowserSuggestionsEngine.h"
|
||||
|
||||
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
|
||||
// FIXME seems to be broken.
|
||||
#if defined(Q_OS_WIN)
|
||||
|
@ -2243,6 +2245,7 @@ void Application::initializeUi() {
|
|||
QmlCommerce::registerType();
|
||||
qmlRegisterType<ResourceImageItem>("Hifi", 1, 0, "ResourceImageItem");
|
||||
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
|
||||
qmlRegisterType<WebBrowserSuggestionsEngine>("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine");
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->create();
|
||||
|
@ -7110,6 +7113,7 @@ DisplayPluginPointer Application::getActiveDisplayPlugin() const {
|
|||
return _displayPlugin;
|
||||
}
|
||||
|
||||
static const char* EXCLUSION_GROUP_KEY = "exclusionGroup";
|
||||
|
||||
static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) {
|
||||
auto menu = Menu::getInstance();
|
||||
|
@ -7145,6 +7149,8 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
|
|||
action->setCheckable(true);
|
||||
action->setChecked(active);
|
||||
displayPluginGroup->addAction(action);
|
||||
|
||||
action->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(displayPluginGroup));
|
||||
Q_ASSERT(menu->menuItemExists(MenuOption::OutputMenu, name));
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ Menu* Menu::getInstance() {
|
|||
return dynamic_cast<Menu*>(qApp->getWindow()->menuBar());
|
||||
}
|
||||
|
||||
const char* exclusionGroupKey = "exclusionGroup";
|
||||
const char* EXCLUSION_GROUP_KEY = "exclusionGroup";
|
||||
|
||||
Menu::Menu() {
|
||||
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
||||
|
@ -228,21 +228,21 @@ Menu::Menu() {
|
|||
viewMenu, MenuOption::FirstPerson, Qt::CTRL | Qt::Key_F,
|
||||
true, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
firstPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup));
|
||||
firstPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Third Person
|
||||
auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::ThirdPerson, Qt::CTRL | Qt::Key_G,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
thirdPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup));
|
||||
thirdPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Mirror
|
||||
auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::FullscreenMirror, Qt::CTRL | Qt::Key_H,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
viewMirrorAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup));
|
||||
viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Independent [advanced]
|
||||
auto viewIndependentAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
|
||||
|
@ -250,7 +250,7 @@ Menu::Menu() {
|
|||
false, qApp, SLOT(cameraMenuChanged()),
|
||||
UNSPECIFIED_POSITION, "Advanced"));
|
||||
|
||||
viewIndependentAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup));
|
||||
viewIndependentAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
// View > Entity Camera [advanced]
|
||||
auto viewEntityCameraAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
|
||||
|
@ -258,7 +258,7 @@ Menu::Menu() {
|
|||
false, qApp, SLOT(cameraMenuChanged()),
|
||||
UNSPECIFIED_POSITION, "Advanced"));
|
||||
|
||||
viewEntityCameraAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup));
|
||||
viewEntityCameraAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
|
||||
|
||||
viewMenu->addSeparator();
|
||||
|
||||
|
|
|
@ -216,7 +216,7 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
|
|||
if (property == "localRotation" || property == "localOrientation") {
|
||||
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;
|
||||
}
|
||||
if (property == "isWire" || property == "wire") {
|
||||
|
|
|
@ -59,7 +59,8 @@ ContextOverlayInterface::ContextOverlayInterface() {
|
|||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
glm::quat cameraOrientation = qApp->getCamera().getOrientation();
|
||||
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)))));
|
||||
qApp->getOverlays().editOverlay(tabletFrameID, props);
|
||||
_contextOverlayJustClicked = false;
|
||||
|
|
88
interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp
Normal file
88
interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp
Normal 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);
|
||||
}
|
46
interface/src/webbrowser/WebBrowserSuggestionsEngine.h
Normal file
46
interface/src/webbrowser/WebBrowserSuggestionsEngine.h
Normal 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
|
||||
|
|
@ -673,8 +673,8 @@ static void crossfade_4x2(float* src, float* dst, const float* win, int numFrame
|
|||
// linear interpolation with 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 f1 = HRTF_GAIN * gain * frac;
|
||||
float f0 = gain * (1.0f - frac);
|
||||
float f1 = gain * frac;
|
||||
|
||||
for (int k = 0; k < HRTF_TAPS; k++) {
|
||||
dst[k] = f0 * src0[k] + f1 * src1[k];
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#if defined(__AVX512F__)
|
||||
#ifdef __AVX512F__
|
||||
|
||||
#include <assert.h>
|
||||
#include <immintrin.h>
|
||||
|
@ -87,15 +87,4 @@ void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* ds
|
|||
_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
|
||||
|
|
|
@ -65,7 +65,9 @@ QHash<QString, QString> HTTPConnection::parseUrlEncodedForm() {
|
|||
QUrlQuery form { _requestContent };
|
||||
QHash<QString, QString> pairs;
|
||||
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;
|
||||
|
|
|
@ -12,24 +12,25 @@
|
|||
#include "EntityTreeRenderer.h"
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <queue>
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QScriptSyntaxCheckResult>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include <shared/QtHelpers.h>
|
||||
#include <ColorUtils.h>
|
||||
#include <AbstractScriptingServicesInterface.h>
|
||||
#include <AbstractViewStateInterface.h>
|
||||
#include <AddressManager.h>
|
||||
#include <ColorUtils.h>
|
||||
#include <Model.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <PerfStat.h>
|
||||
#include <PrioritySortUtil.h>
|
||||
#include <Rig.h>
|
||||
#include <SceneScriptingInterface.h>
|
||||
#include <ScriptEngine.h>
|
||||
#include <AddressManager.h>
|
||||
#include <Rig.h>
|
||||
#include <EntitySimulation.h>
|
||||
#include <AddressManager.h>
|
||||
#include <ZoneRenderer.h>
|
||||
|
||||
#include "EntitiesRendererLogging.h"
|
||||
|
@ -204,6 +205,7 @@ void EntityTreeRenderer::clear() {
|
|||
qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown";
|
||||
}
|
||||
_entitiesInScene.clear();
|
||||
_renderablesToUpdate.clear();
|
||||
|
||||
// reset the zone to the default (while we load the next scene)
|
||||
_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());
|
||||
PerformanceTimer pt("change");
|
||||
std::unordered_set<EntityItemID> changedEntities;
|
||||
|
@ -307,21 +309,91 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene
|
|||
#endif
|
||||
});
|
||||
|
||||
for (const auto& entityId : changedEntities) {
|
||||
auto renderable = renderableForEntityId(entityId);
|
||||
if (!renderable) {
|
||||
continue;
|
||||
{
|
||||
PROFILE_RANGE_EX(simulation_physics, "CopyRenderables", 0xffff00ff, (uint64_t)changedEntities.size());
|
||||
for (const auto& entityId : changedEntities) {
|
||||
auto renderable = renderableForEntityId(entityId);
|
||||
if (renderable) {
|
||||
// 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());
|
||||
uint64_t updateStart = usecTimestampNow();
|
||||
for (const auto& entry : _renderablesToUpdate) {
|
||||
const auto& renderable = entry.second;
|
||||
assert(renderable); // only valid renderables are added to _renderablesToUpdate
|
||||
renderable->updateInScene(scene, transaction);
|
||||
}
|
||||
size_t numRenderables = _renderablesToUpdate.size() + 1; // add one to avoid divide by zero
|
||||
_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) {
|
||||
render::Transaction transaction;
|
||||
addPendingEntities(scene, transaction);
|
||||
updateChangedEntities(scene, transaction);
|
||||
ViewFrustum view;
|
||||
_viewState->copyCurrentViewFrustum(view);
|
||||
updateChangedEntities(scene, view, transaction);
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
@ -768,7 +842,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
auto itr = _entitiesInScene.find(entityID);
|
||||
|
@ -776,7 +851,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
|||
// Not in the scene, and no longer potentially in the pending queue, we're done
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (_tree && !_shuttingDown && _entitiesScriptEngine) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID, true);
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ protected:
|
|||
|
||||
private:
|
||||
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()); }
|
||||
render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); }
|
||||
|
||||
|
@ -235,11 +235,12 @@ private:
|
|||
NetworkTexturePointer _skyboxTexture;
|
||||
QString _ambientTextureURL;
|
||||
QString _skyboxTextureURL;
|
||||
float _avgRenderableUpdateCost { 0.0f };
|
||||
bool _pendingAmbientTexture { false };
|
||||
bool _pendingSkyboxTexture { false };
|
||||
|
||||
quint64 _lastZoneCheck { 0 };
|
||||
const quint64 ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz
|
||||
uint64_t _lastZoneCheck { 0 };
|
||||
const uint64_t ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz
|
||||
const float ZONE_CHECK_DISTANCE = 0.001f;
|
||||
|
||||
ReadWriteLockable _changedEntitiesGuard;
|
||||
|
|
|
@ -59,7 +59,7 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St
|
|||
const QUuid& myNodeID = nodeList->getSessionUUID();
|
||||
|
||||
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);
|
||||
float normalizedDelta = delta * WAIT_THRESHOLD_INV;
|
||||
// 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 {
|
||||
quint64 delta = usecTimestampNow() - entity->getLastBroadcast();
|
||||
uint64_t delta = usecTimestampNow() - entity->getLastBroadcast();
|
||||
const float WAIT_THRESHOLD_INV = 1.0f / (0.4f * USECS_PER_SECOND);
|
||||
float normalizedDelta = delta * WAIT_THRESHOLD_INV;
|
||||
// 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()) {
|
||||
return;
|
||||
}
|
||||
_updateTime = usecTimestampNow();
|
||||
|
||||
// FIXME is this excessive?
|
||||
if (!needsRenderUpdate()) {
|
||||
|
|
|
@ -52,6 +52,8 @@ public:
|
|||
void clearSubRenderItemIDs();
|
||||
void setSubRenderItemIDs(const render::ItemIDs& ids);
|
||||
|
||||
const uint64_t& getUpdateTime() const { return _updateTime; }
|
||||
|
||||
protected:
|
||||
virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
|
||||
virtual void onAddToScene(const EntityItemPointer& entity);
|
||||
|
@ -100,7 +102,6 @@ protected:
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
signals:
|
||||
void requestRenderUpdate();
|
||||
|
||||
|
@ -113,14 +114,15 @@ protected:
|
|||
static std::function<bool()> _entitiesShouldFadeFunction;
|
||||
const Transform& getModelTransform() const;
|
||||
|
||||
Item::Bound _bound;
|
||||
SharedSoundPointer _collisionSound;
|
||||
QUuid _changeHandlerId;
|
||||
ItemID _renderItemID{ Item::INVALID_ITEM_ID };
|
||||
ItemIDs _subRenderItemIDs;
|
||||
quint64 _fadeStartTime{ usecTimestampNow() };
|
||||
uint64_t _fadeStartTime{ usecTimestampNow() };
|
||||
uint64_t _updateTime{ usecTimestampNow() }; // used when sorting/throttling render updates
|
||||
bool _isFading{ _entitiesShouldFadeFunction() };
|
||||
bool _prevIsTransparent { false };
|
||||
Item::Bound _bound;
|
||||
bool _visible { false };
|
||||
bool _moving { false };
|
||||
// Only touched on the rendering thread
|
||||
|
|
|
@ -93,6 +93,11 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
|||
|
||||
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
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
@ -115,6 +120,7 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
|||
qCDebug(entities) << " id:" << entityItemID;
|
||||
qCDebug(entities) << " properties:" << properties;
|
||||
#endif
|
||||
|
||||
queueOctreeEditMessage(type, bufferOut);
|
||||
if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) {
|
||||
emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get<AddressManager>()->getPlaceName());
|
||||
|
|
|
@ -979,8 +979,8 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
|
|||
}
|
||||
|
||||
void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
|
||||
// cannot set gain of yourself or nobody
|
||||
if (!nodeID.isNull() && _sessionUUID != nodeID) {
|
||||
// cannot set gain of yourself
|
||||
if (_sessionUUID != nodeID) {
|
||||
auto audioMixer = soloNodeOfType(NodeType::AudioMixer);
|
||||
if (audioMixer) {
|
||||
// setup the packet
|
||||
|
@ -988,10 +988,15 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
|
|||
|
||||
// write the node ID to the packet
|
||||
setAvatarGainPacket->write(nodeID.toRfc4122());
|
||||
// We need to convert the gain in dB (from the script) to an amplitude before packing it.
|
||||
setAvatarGainPacket->writePrimitive(packFloatGainToByte(fastExp2f(gain / 6.0206f)));
|
||||
|
||||
qCDebug(networking) << "Sending Set Avatar Gain packet UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
|
||||
// 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);
|
||||
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";
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,9 @@ const quint64 DOMAIN_SERVER_CHECK_IN_MSECS = 1 * 1000;
|
|||
|
||||
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 NodeSharedPacketPair = std::pair<SharedNodePointer, QSharedPointer<NLPacket>>;
|
||||
using NodeSharedReceivedMessagePair = std::pair<SharedNodePointer, QSharedPointer<ReceivedMessage>>;
|
||||
|
|
|
@ -53,7 +53,19 @@ void PacketSender::queuePacketForSending(const SharedNodePointer& destinationNod
|
|||
_totalBytesQueued += packet->getDataSize();
|
||||
|
||||
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();
|
||||
|
||||
// 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
|
||||
int packetsSentThisCall = 0;
|
||||
int packetsToSendThisCall = 0;
|
||||
size_t packetsSentThisCall = 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
|
||||
// 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)) {
|
||||
lock();
|
||||
|
||||
NodePacketPair packetPair = std::move(_packets.front());
|
||||
NodePacketOrPacketListPair packetPair = std::move(_packets.front());
|
||||
_packets.pop_front();
|
||||
packetsLeft = _packets.size();
|
||||
|
||||
unlock();
|
||||
|
||||
// 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++;
|
||||
_packetsOverCheckInterval++;
|
||||
_totalPacketsSent++;
|
||||
if (sendAsPacket) {
|
||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(*packetPair.second.first, *packetPair.first);
|
||||
} 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;
|
||||
emit packetSent(packetSize);
|
||||
|
||||
emit packetSent(packetSize); // FIXME should include number of packets?
|
||||
_lastSendTime = now;
|
||||
}
|
||||
return isStillRunning();
|
||||
|
|
|
@ -39,6 +39,7 @@ public:
|
|||
|
||||
/// Add packet to outbound queue.
|
||||
void queuePacketForSending(const SharedNodePointer& destinationNode, std::unique_ptr<NLPacket> packet);
|
||||
void queuePacketListForSending(const SharedNodePointer& destinationNode, std::unique_ptr<NLPacketList> packetList);
|
||||
|
||||
void setPacketsPerSecond(int packetsPerSecond);
|
||||
int getPacketsPerSecond() const { return _packetsPerSecond; }
|
||||
|
@ -99,14 +100,14 @@ protected:
|
|||
SimpleMovingAverage _averageProcessCallTime;
|
||||
|
||||
private:
|
||||
std::list<NodePacketPair> _packets;
|
||||
std::list<NodePacketOrPacketListPair> _packets;
|
||||
quint64 _lastSendTime;
|
||||
|
||||
bool threadedProcess();
|
||||
bool nonThreadedProcess();
|
||||
|
||||
quint64 _lastPPSCheck;
|
||||
int _packetsOverCheckInterval;
|
||||
size_t _packetsOverCheckInterval;
|
||||
|
||||
quint64 _started;
|
||||
quint64 _totalPacketsSent;
|
||||
|
|
|
@ -53,7 +53,6 @@ ReceivedMessage::ReceivedMessage(QByteArray byteArray, PacketType packetType, Pa
|
|||
_senderSockAddr(senderSockAddr),
|
||||
_isComplete(true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ReceivedMessage::setFailed() {
|
||||
|
|
|
@ -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() {
|
||||
assert(serversExist()); // we should only be here if we have jurisdictions
|
||||
|
||||
|
@ -247,33 +263,65 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray&
|
|||
});
|
||||
}
|
||||
if (isMyJurisdiction) {
|
||||
std::unique_ptr<NLPacket>& bufferedPacket = _pendingEditPackets[nodeUUID];
|
||||
|
||||
if (!bufferedPacket) {
|
||||
bufferedPacket = initializePacket(type, node->getClockSkewUsec());
|
||||
} else {
|
||||
// If we're switching type, then we send the last one and start over
|
||||
if ((type != bufferedPacket->getType() && bufferedPacket->getPayloadSize() > 0) ||
|
||||
(editMessage.size() >= bufferedPacket->bytesAvailableForWrite())) {
|
||||
// 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) {
|
||||
|
||||
// create the new packet and swap it with the packet in _pendingEditPackets
|
||||
auto packetToRelease = initializePacket(type, node->getClockSkewUsec());
|
||||
bufferedPacket.swap(packetToRelease);
|
||||
auto newPacket = NLPacketList::create(type, QByteArray(), true, true);
|
||||
auto nodeClockSkew = node->getClockSkewUsec();
|
||||
|
||||
// release the previously buffered packet
|
||||
releaseQueuedPacket(nodeUUID, std::move(packetToRelease));
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// This is really the first time we know which server/node this particular edit message
|
||||
// is going to, so we couldn't adjust for clock skew till now. But here's our chance.
|
||||
// We call this virtual function that allows our specific type of EditPacketSender to
|
||||
// fixup the buffer for any clock skew
|
||||
if (node->getClockSkewUsec() != 0) {
|
||||
adjustEditPacketForClockSkew(type, editMessage, node->getClockSkewUsec());
|
||||
}
|
||||
newPacket->write(editMessage);
|
||||
|
||||
bufferedPacket->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) {
|
||||
bufferedPacket = initializePacket(type, node->getClockSkewUsec());
|
||||
} else {
|
||||
// If we're switching type, then we send the last one and start over
|
||||
if ((type != bufferedPacket->getType() && bufferedPacket->getPayloadSize() > 0) ||
|
||||
(editMessage.size() >= bufferedPacket->bytesAvailableForWrite())) {
|
||||
|
||||
// create the new packet and swap it with the packet in _pendingEditPackets
|
||||
auto packetToRelease = initializePacket(type, node->getClockSkewUsec());
|
||||
bufferedPacket.swap(packetToRelease);
|
||||
|
||||
// release the previously buffered packet
|
||||
releaseQueuedPacket(nodeUUID, std::move(packetToRelease));
|
||||
}
|
||||
}
|
||||
|
||||
// This is really the first time we know which server/node this particular edit message
|
||||
// is going to, so we couldn't adjust for clock skew till now. But here's our chance.
|
||||
// We call this virtual function that allows our specific type of EditPacketSender to
|
||||
// fixup the buffer for any clock skew
|
||||
if (node->getClockSkewUsec() != 0) {
|
||||
adjustEditPacketForClockSkew(type, editMessage, node->getClockSkewUsec());
|
||||
}
|
||||
|
||||
bufferedPacket->write(editMessage);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -291,15 +339,24 @@ void OctreeEditPacketSender::releaseQueuedMessages() {
|
|||
} else {
|
||||
_packetsQueueLock.lock();
|
||||
for (auto& i : _pendingEditPackets) {
|
||||
if (i.second) {
|
||||
if (i.second.first) {
|
||||
// construct a null unique_ptr to an NL packet
|
||||
std::unique_ptr<NLPacket> releasedPacket;
|
||||
|
||||
// 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
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
auto newPacket = NLPacket::create(type);
|
||||
|
||||
|
|
|
@ -87,15 +87,18 @@ protected:
|
|||
|
||||
bool _shouldSend;
|
||||
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 queuePacketToNodes(std::unique_ptr<NLPacket> packet);
|
||||
std::unique_ptr<NLPacket> initializePacket(PacketType type, qint64 nodeClockSkew);
|
||||
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();
|
||||
|
||||
// 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
|
||||
int _maxPendingMessages;
|
||||
|
|
|
@ -35,7 +35,13 @@ OctreePacketData::OctreePacketData(bool enableCompression, int targetSize) {
|
|||
|
||||
void OctreePacketData::changeSettings(bool enableCompression, unsigned int targetSize) {
|
||||
_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();
|
||||
}
|
||||
|
||||
|
@ -689,6 +695,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto
|
|||
uint16_t length;
|
||||
memcpy(&length, dataBytes, sizeof(uint16_t));
|
||||
dataBytes += sizeof(length);
|
||||
|
||||
// FIXME - this size check is wrong if we allow larger packets
|
||||
if (length * sizeof(glm::vec3) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
|
||||
result.resize(0);
|
||||
return sizeof(uint16_t);
|
||||
|
@ -702,6 +710,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto
|
|||
uint16_t length;
|
||||
memcpy(&length, dataBytes, sizeof(uint16_t));
|
||||
dataBytes += sizeof(length);
|
||||
|
||||
// FIXME - this size check is wrong if we allow larger packets
|
||||
if (length * sizeof(glm::quat) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
|
||||
result.resize(0);
|
||||
return sizeof(uint16_t);
|
||||
|
@ -720,6 +730,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto
|
|||
uint16_t length;
|
||||
memcpy(&length, dataBytes, sizeof(uint16_t));
|
||||
dataBytes += sizeof(length);
|
||||
|
||||
// FIXME - this size check is wrong if we allow larger packets
|
||||
if (length * sizeof(float) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
|
||||
result.resize(0);
|
||||
return sizeof(uint16_t);
|
||||
|
@ -733,6 +745,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto
|
|||
uint16_t length;
|
||||
memcpy(&length, dataBytes, sizeof(uint16_t));
|
||||
dataBytes += sizeof(length);
|
||||
|
||||
// FIXME - this size check is wrong if we allow larger packets
|
||||
if (length / 8 > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) {
|
||||
result.resize(0);
|
||||
return sizeof(uint16_t);
|
||||
|
|
|
@ -279,7 +279,8 @@ private:
|
|||
unsigned int _targetSize;
|
||||
bool _enableCompression;
|
||||
|
||||
unsigned char _uncompressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE];
|
||||
QByteArray _uncompressedByteArray;
|
||||
unsigned char* _uncompressed { nullptr };
|
||||
int _bytesInUse;
|
||||
int _bytesAvailable;
|
||||
int _subTreeAt;
|
||||
|
@ -288,7 +289,8 @@ private:
|
|||
|
||||
bool compressContent();
|
||||
|
||||
unsigned char _compressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE];
|
||||
QByteArray _compressedByteArray;
|
||||
unsigned char* _compressed { nullptr };
|
||||
int _compressedBytes;
|
||||
int _bytesInUseLastCheck;
|
||||
bool _dirty;
|
||||
|
|
|
@ -87,7 +87,7 @@ namespace indexed_container {
|
|||
if (index < (Index) _elements.size()) {
|
||||
_elements[index] = e;
|
||||
} else {
|
||||
assert(index == _elements.size());
|
||||
assert(index == (Index)_elements.size());
|
||||
_elements.emplace_back(e);
|
||||
}
|
||||
}
|
||||
|
@ -159,4 +159,4 @@ namespace indexed_container {
|
|||
};
|
||||
};
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -65,7 +65,7 @@ public slots:
|
|||
* Sets an avatar's gain for you and you only.
|
||||
* Units are Decibels (dB)
|
||||
* @function Users.setAvatarGain
|
||||
* @param {nodeID} nodeID The node or session ID of the user whose gain you want to modify.
|
||||
* @param {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.
|
||||
*/
|
||||
void setAvatarGain(const QUuid& nodeID, float gain);
|
||||
|
@ -73,7 +73,7 @@ public slots:
|
|||
/**jsdoc
|
||||
* Gets an avatar's gain for you and you only.
|
||||
* @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)
|
||||
*/
|
||||
float getAvatarGain(const QUuid& nodeID);
|
||||
|
|
141
libraries/shared/src/PrioritySortUtil.h
Normal file
141
libraries/shared/src/PrioritySortUtil.h
Normal 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
|
||||
|
|
@ -328,7 +328,8 @@ void TabletProxy::initialScreen(const QVariant& url) {
|
|||
pushOntoStack(url);
|
||||
} else {
|
||||
_initialScreen = true;
|
||||
_initialPath = url;
|
||||
_initialPath.first = url;
|
||||
_initialPath.second = State::QML;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -416,10 +417,18 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) {
|
|||
});
|
||||
|
||||
if (_initialScreen) {
|
||||
if (!_showRunningScripts) {
|
||||
pushOntoStack(_initialPath);
|
||||
if (!_showRunningScripts && _initialPath.second == State::QML) {
|
||||
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;
|
||||
_initialPath.first = "";
|
||||
_initialPath.second = State::Uninitialized;
|
||||
_initialWebPathParams.first = "";
|
||||
_initialWebPathParams.second = false;
|
||||
}
|
||||
|
||||
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, "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;
|
||||
emit screenChanged(QVariant("Web"), QVariant(url));
|
||||
|
|
|
@ -248,7 +248,6 @@ protected:
|
|||
void removeButtonsFromToolbar();
|
||||
|
||||
bool _initialScreen { false };
|
||||
QVariant _initialPath { "" };
|
||||
QVariant _currentPathLoaded { "" };
|
||||
QString _name;
|
||||
std::vector<QSharedPointer<TabletButtonProxy>> _tabletButtonProxies;
|
||||
|
@ -260,6 +259,8 @@ protected:
|
|||
|
||||
enum class State { Uninitialized, Home, Web, Menu, QML };
|
||||
State _state { State::Uninitialized };
|
||||
std::pair<QVariant, State> _initialPath { "", State::Uninitialized };
|
||||
std::pair<QVariant, bool> _initialWebPathParams;
|
||||
bool _landscape { false };
|
||||
bool _showRunningScripts { false };
|
||||
};
|
||||
|
|
|
@ -419,7 +419,7 @@ var toolBar = (function () {
|
|||
var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON);
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
activeButton = tablet.addButton({
|
||||
captionColorOverride: hasRezPermissions ? "" : "#888888",
|
||||
captionColor: hasRezPermissions ? "#ffffff" : "#888888",
|
||||
icon: createButtonIconRsrc,
|
||||
activeIcon: "icons/tablet-icons/edit-a.svg",
|
||||
text: "CREATE",
|
||||
|
@ -792,7 +792,7 @@ function handleDomainChange() {
|
|||
var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified());
|
||||
createButton.editProperties({
|
||||
icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON),
|
||||
captionColorOverride: (hasRezPermissions ? "" : "#888888"),
|
||||
captionColor: (hasRezPermissions ? "#ffffff" : "#888888"),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
APP_ICON_INACTIVE = Script.resolvePath("./assets/shapes-i.svg"),
|
||||
APP_ICON_ACTIVE = Script.resolvePath("./assets/shapes-a.svg"),
|
||||
APP_ICON_DISABLED = Script.resolvePath("./assets/shapes-d.svg"),
|
||||
ENABLED_CAPTION_COLOR_OVERRIDE = "",
|
||||
ENABLED_CAPTION_COLOR_OVERRIDE = "#ffffff",
|
||||
DISABLED_CAPTION_COLOR_OVERRIDE = "#888888",
|
||||
START_DELAY = 2000, // ms
|
||||
|
||||
|
@ -1865,7 +1865,7 @@
|
|||
}
|
||||
button.editProperties({
|
||||
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
|
||||
});
|
||||
}
|
||||
|
@ -1880,7 +1880,7 @@
|
|||
}
|
||||
button.editProperties({
|
||||
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
|
||||
});
|
||||
}
|
||||
|
@ -1945,7 +1945,7 @@
|
|||
hasRezPermissions = Entities.canRez() || Entities.canRezTmp();
|
||||
button = tablet.addButton({
|
||||
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,
|
||||
text: APP_NAME,
|
||||
isActive: isAppActive
|
||||
|
|
Loading…
Reference in a new issue