Merge branch 'master' of github.com:highfidelity/hifi into update-collision-hulls-of-avatar-children

This commit is contained in:
Seth Alves 2016-04-07 09:59:49 -07:00
commit 8158d34b20
31 changed files with 691 additions and 92 deletions

View file

@ -397,7 +397,7 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) {
if (_numAvatarSoundSentBytes == soundByteArray.size()) { if (_numAvatarSoundSentBytes == soundByteArray.size()) {
// we're done with this sound object - so set our pointer back to NULL // we're done with this sound object - so set our pointer back to NULL
// and our sent bytes back to zero // and our sent bytes back to zero
_avatarSound = NULL; _avatarSound.clear();
_numAvatarSoundSentBytes = 0; _numAvatarSoundSentBytes = 0;
} }
} }

View file

@ -56,7 +56,7 @@ public:
public slots: public slots:
void run(); void run();
void playAvatarSound(Sound* avatarSound) { setAvatarSound(avatarSound); } void playAvatarSound(SharedSoundPointer avatarSound) { setAvatarSound(avatarSound); }
private slots: private slots:
void requestScript(); void requestScript();
@ -77,7 +77,7 @@ private:
MixedAudioStream _receivedAudioStream; MixedAudioStream _receivedAudioStream;
float _lastReceivedAudioLoudness; float _lastReceivedAudioLoudness;
void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; } void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; }
void sendAvatarIdentityPacket(); void sendAvatarIdentityPacket();
void sendAvatarBillboardPacket(); void sendAvatarBillboardPacket();
@ -85,7 +85,7 @@ private:
QString _scriptContents; QString _scriptContents;
QTimer* _scriptRequestTimeout { nullptr }; QTimer* _scriptRequestTimeout { nullptr };
bool _isListeningToAudioStream = false; bool _isListeningToAudioStream = false;
Sound* _avatarSound = nullptr; SharedSoundPointer _avatarSound;
int _numAvatarSoundSentBytes = 0; int _numAvatarSoundSentBytes = 0;
bool _isAvatar = false; bool _isAvatar = false;
QTimer* _avatarIdentityTimer = nullptr; QTimer* _avatarIdentityTimer = nullptr;

View file

@ -1,5 +1,5 @@
{ {
"version": 1.1, "version": 1.2,
"settings": [ "settings": [
{ {
"name": "metaverse", "name": "metaverse",
@ -249,7 +249,7 @@
"label": "X end", "label": "X end",
"can_set": true, "can_set": true,
"placeholder": "16384.0" "placeholder": "16384.0"
}, },
{ {
"name": "y_min", "name": "y_min",
"label": "Y start", "label": "Y start",

View file

@ -103,4 +103,5 @@
<script src='js/sweetalert.min.js'></script> <script src='js/sweetalert.min.js'></script>
<script src='js/settings.js'></script> <script src='js/settings.js'></script>
<script src='js/form2js.min.js'></script> <script src='js/form2js.min.js'></script>
<script src='js/sha256.js'></script>
<!--#include virtual="page-end.html"--> <!--#include virtual="page-end.html"-->

View file

@ -867,6 +867,14 @@ function saveSettings() {
// grab a JSON representation of the form via form2js // grab a JSON representation of the form via form2js
var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true); var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true);
// check if we've set the basic http password - if so convert it to base64
if (formJSON["security"]) {
var password = formJSON["security"]["http_password"];
if (password.length > 0) {
formJSON["security"]["http_password"] = sha256_digest(password);
}
}
console.log(formJSON); console.log(formJSON);
// re-enable all inputs // re-enable all inputs

View file

@ -0,0 +1,247 @@
/*
* A JavaScript implementation of the SHA256 hash function.
*
* FILE: sha256.js
* VERSION: 0.8
* AUTHOR: Christoph Bichlmeier <informatik@zombiearena.de>
*
* NOTE: This version is not tested thoroughly!
*
* Copyright (c) 2003, Christoph Bichlmeier
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* ======================================================================
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* SHA256 logical functions */
function rotateRight(n,x) {
return ((x >>> n) | (x << (32 - n)));
}
function choice(x,y,z) {
return ((x & y) ^ (~x & z));
}
function majority(x,y,z) {
return ((x & y) ^ (x & z) ^ (y & z));
}
function sha256_Sigma0(x) {
return (rotateRight(2, x) ^ rotateRight(13, x) ^ rotateRight(22, x));
}
function sha256_Sigma1(x) {
return (rotateRight(6, x) ^ rotateRight(11, x) ^ rotateRight(25, x));
}
function sha256_sigma0(x) {
return (rotateRight(7, x) ^ rotateRight(18, x) ^ (x >>> 3));
}
function sha256_sigma1(x) {
return (rotateRight(17, x) ^ rotateRight(19, x) ^ (x >>> 10));
}
function sha256_expand(W, j) {
return (W[j&0x0f] += sha256_sigma1(W[(j+14)&0x0f]) + W[(j+9)&0x0f] +
sha256_sigma0(W[(j+1)&0x0f]));
}
/* Hash constant words K: */
var K256 = new Array(
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
);
/* global arrays */
var ihash, count, buffer;
var sha256_hex_digits = "0123456789abcdef";
/* Add 32-bit integers with 16-bit operations (bug in some JS-interpreters:
overflow) */
function safe_add(x, y)
{
var lsw = (x & 0xffff) + (y & 0xffff);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
/* Initialise the SHA256 computation */
function sha256_init() {
ihash = new Array(8);
count = new Array(2);
buffer = new Array(64);
count[0] = count[1] = 0;
ihash[0] = 0x6a09e667;
ihash[1] = 0xbb67ae85;
ihash[2] = 0x3c6ef372;
ihash[3] = 0xa54ff53a;
ihash[4] = 0x510e527f;
ihash[5] = 0x9b05688c;
ihash[6] = 0x1f83d9ab;
ihash[7] = 0x5be0cd19;
}
/* Transform a 512-bit message block */
function sha256_transform() {
var a, b, c, d, e, f, g, h, T1, T2;
var W = new Array(16);
/* Initialize registers with the previous intermediate value */
a = ihash[0];
b = ihash[1];
c = ihash[2];
d = ihash[3];
e = ihash[4];
f = ihash[5];
g = ihash[6];
h = ihash[7];
/* make 32-bit words */
for(var i=0; i<16; i++)
W[i] = ((buffer[(i<<2)+3]) | (buffer[(i<<2)+2] << 8) | (buffer[(i<<2)+1]
<< 16) | (buffer[i<<2] << 24));
for(var j=0; j<64; j++) {
T1 = h + sha256_Sigma1(e) + choice(e, f, g) + K256[j];
if(j < 16) T1 += W[j];
else T1 += sha256_expand(W, j);
T2 = sha256_Sigma0(a) + majority(a, b, c);
h = g;
g = f;
f = e;
e = safe_add(d, T1);
d = c;
c = b;
b = a;
a = safe_add(T1, T2);
}
/* Compute the current intermediate hash value */
ihash[0] += a;
ihash[1] += b;
ihash[2] += c;
ihash[3] += d;
ihash[4] += e;
ihash[5] += f;
ihash[6] += g;
ihash[7] += h;
}
/* Read the next chunk of data and update the SHA256 computation */
function sha256_update(data, inputLen) {
var i, index, curpos = 0;
/* Compute number of bytes mod 64 */
index = ((count[0] >> 3) & 0x3f);
var remainder = (inputLen & 0x3f);
/* Update number of bits */
if ((count[0] += (inputLen << 3)) < (inputLen << 3)) count[1]++;
count[1] += (inputLen >> 29);
/* Transform as many times as possible */
for(i=0; i+63<inputLen; i+=64) {
for(var j=index; j<64; j++)
buffer[j] = data.charCodeAt(curpos++);
sha256_transform();
index = 0;
}
/* Buffer remaining input */
for(var j=0; j<remainder; j++)
buffer[j] = data.charCodeAt(curpos++);
}
/* Finish the computation by operations such as padding */
function sha256_final() {
var index = ((count[0] >> 3) & 0x3f);
buffer[index++] = 0x80;
if(index <= 56) {
for(var i=index; i<56; i++)
buffer[i] = 0;
} else {
for(var i=index; i<64; i++)
buffer[i] = 0;
sha256_transform();
for(var i=0; i<56; i++)
buffer[i] = 0;
}
buffer[56] = (count[1] >>> 24) & 0xff;
buffer[57] = (count[1] >>> 16) & 0xff;
buffer[58] = (count[1] >>> 8) & 0xff;
buffer[59] = count[1] & 0xff;
buffer[60] = (count[0] >>> 24) & 0xff;
buffer[61] = (count[0] >>> 16) & 0xff;
buffer[62] = (count[0] >>> 8) & 0xff;
buffer[63] = count[0] & 0xff;
sha256_transform();
}
/* Split the internal hash values into an array of bytes */
function sha256_encode_bytes() {
var j=0;
var output = new Array(32);
for(var i=0; i<8; i++) {
output[j++] = ((ihash[i] >>> 24) & 0xff);
output[j++] = ((ihash[i] >>> 16) & 0xff);
output[j++] = ((ihash[i] >>> 8) & 0xff);
output[j++] = (ihash[i] & 0xff);
}
return output;
}
/* Get the internal hash as a hex string */
function sha256_encode_hex() {
var output = new String();
for(var i=0; i<8; i++) {
for(var j=28; j>=0; j-=4)
output += sha256_hex_digits.charAt((ihash[i] >>> j) & 0x0f);
}
return output;
}
/* Main function: returns a hex string representing the SHA256 value of the
given data */
function sha256_digest(data) {
sha256_init();
sha256_update(data, data.length);
sha256_final();
return sha256_encode_hex();
}
/* test if the JS-interpreter is working properly */
function sha256_self_test() {
return sha256_digest("message digest") ==
"f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650";
}

View file

@ -1679,8 +1679,9 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
QString settingsUsername = valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)->toString(); QString settingsUsername = valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)->toString();
const QVariant* settingsPasswordVariant = valueForKeyPath(settingsMap, BASIC_AUTH_PASSWORD_KEY_PATH); const QVariant* settingsPasswordVariant = valueForKeyPath(settingsMap, BASIC_AUTH_PASSWORD_KEY_PATH);
QString settingsPassword = settingsPasswordVariant ? settingsPasswordVariant->toString() : ""; QString settingsPassword = settingsPasswordVariant ? settingsPasswordVariant->toString() : "";
QString hexHeaderPassword = QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
if (settingsUsername == headerUsername && headerPassword == settingsPassword) { if (settingsUsername == headerUsername && hexHeaderPassword == settingsPassword) {
return true; return true;
} }
} }

View file

@ -129,7 +129,9 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
// reload the master and user config so that the merged config is right // reload the master and user config so that the merged config is right
_configMap.loadMasterAndUserConfig(argumentList); _configMap.loadMasterAndUserConfig(argumentList);
} }
} else if (oldVersion < 1.1) { }
if (oldVersion < 1.1) {
static const QString ENTITY_SERVER_SETTINGS_KEY = "entity_server_settings"; static const QString ENTITY_SERVER_SETTINGS_KEY = "entity_server_settings";
static const QString ENTITY_FILE_NAME_KEY = "persistFilename"; static const QString ENTITY_FILE_NAME_KEY = "persistFilename";
static const QString ENTITY_FILE_PATH_KEYPATH = ENTITY_SERVER_SETTINGS_KEY + ".persistFilePath"; static const QString ENTITY_FILE_PATH_KEYPATH = ENTITY_SERVER_SETTINGS_KEY + ".persistFilePath";
@ -165,6 +167,28 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
} }
} }
if (oldVersion < 1.2) {
// This was prior to the base64 encoding of password for HTTP Basic Authentication.
// If we have a password in the previous settings file, make it base 64
static const QString BASIC_AUTH_PASSWORD_KEY_PATH { "security.http_password" };
QVariant* passwordVariant = valueForKeyPath(_configMap.getUserConfig(), BASIC_AUTH_PASSWORD_KEY_PATH);
if (passwordVariant && passwordVariant->canConvert(QMetaType::QString)) {
QString plaintextPassword = passwordVariant->toString();
qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings.";
*passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
// write the new settings to file
persistToFile();
// reload the master and user config so the merged config is correct
_configMap.loadMasterAndUserConfig(argumentList);
}
}
} }
// write the current description version to our settings // write the current description version to our settings

View file

@ -0,0 +1,78 @@
//
// largeHall.js
// examples
//
// Created by Freidrica on 4/1/16.
// Copyright 2016 High Fidelity, Inc.
//
// This entity script invokes reverb upon entering an entity acting as a trigger zone
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var _this = this;
print("EBL PRELOADING NEW VERSION ")
var audioOptions = new AudioEffectOptions({
bandwidth: 7000,
preDelay: 80,
lateDelay: 0,
reverbTime: 3,
earlyDiffusion: 100,
lateDiffusion: 100,
roomSize: 50,
density: 100,
bassMult: 1.5,
bassFreq: 250,
highGain: -12,
highFreq: 3000,
modRate: 2.3,
modDepth: 50,
earlyGain: -12,
lateGain: -12,
earlyMixLeft: 20,
earlyMixRight: 20,
lateMixLeft: 90,
lateMixRight: 90,
wetDryMix: 90,
});
function setter(name) {
return function(value) {
audioOptions[name] = value;
AudioDevice.setReverbOptions(audioOptions);
}
}
function getter(name) {
return function() {
return audioOptions[name];
}
}
function displayer(units) {
return function(value) {
return (value).toFixed(1) + units;
}
}
function scriptEnding() {
AudioDevice.setReverb(false);
print("Reverb is OFF.");
}
_this.enterEntity = function(entityID) {
print('EBL I am insiude');
AudioDevice.setReverbOptions(audioOptions);
AudioDevice.setReverb(true);
print("Reverb is ON.");
};
_this.leaveEntity = function(entityID) {
print('EBL I am outsidee');
AudioDevice.setReverb(false);
print("Reverb is OFF.");
// Messages.sendMessage('PlayBackOnAssignment', 'BowShootingGameWelcome');
};
});

View file

@ -0,0 +1,78 @@
//
// smallRoom.js
// examples
//
// Created by Freidrica on 4/1/16.
// Copyright 2016 High Fidelity, Inc.
//
// This entity script invokes reverb upon entering an entity acting as a trigger zone
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var _this = this;
print("EBL PRELOADING NEW VERSION ")
var audioOptions = new AudioEffectOptions({
bandwidth: 7000,
preDelay: 20,
lateDelay: 0,
reverbTime: 1.5,
earlyDiffusion: 100,
lateDiffusion: 100,
roomSize: 50,
density: 100,
bassMult: 1.5,
bassFreq: 250,
highGain: -12,
highFreq: 3000,
modRate: 2.3,
modDepth: 50,
earlyGain: -24,
lateGain: -24,
earlyMixLeft: 20,
earlyMixRight: 20,
lateMixLeft: 90,
lateMixRight: 90,
wetDryMix: 70,
});
function setter(name) {
return function(value) {
audioOptions[name] = value;
AudioDevice.setReverbOptions(audioOptions);
}
}
function getter(name) {
return function() {
return audioOptions[name];
}
}
function displayer(units) {
return function(value) {
return (value).toFixed(1) + units;
}
}
function scriptEnding() {
AudioDevice.setReverb(false);
print("Reverb is OFF.");
}
_this.enterEntity = function(entityID) {
print('EBL I am insiude');
// create a slider for each parameter
AudioDevice.setReverbOptions(audioOptions);
AudioDevice.setReverb(true);
print("Reverb is ON.");
};
_this.leaveEntity = function(entityID) {
print('EBL I am outside');
AudioDevice.setReverb(false);
print("Reverb is OFF.");
};
});

View file

@ -452,6 +452,10 @@ input[type=checkbox]:checked + label:hover {
min-height: 29px; min-height: 29px;
} }
.property.checkbox {
width: auto;
}
.property label { .property label {
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;

View file

@ -3462,11 +3462,9 @@ void Application::update(float deltaTime) {
const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND;
bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY;
bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum); bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum);
// if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it // if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it
if (queryIsDue || viewIsDifferentEnough) { if (queryIsDue || viewIsDifferentEnough) {
_lastQueriedTime = now; _lastQueriedTime = now;
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) { if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) {
queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions);
} }
@ -3561,7 +3559,7 @@ int Application::sendNackPackets() {
return packetsSent; return packetsSent;
} }
void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions) { void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend) {
if (!_settingsLoaded) { if (!_settingsLoaded) {
return; // bail early if settings are not loaded return; // bail early if settings are not loaded
@ -3648,7 +3646,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
auto queryPacket = NLPacket::create(packetType); auto queryPacket = NLPacket::create(packetType);
nodeList->eachNode([&](const SharedNodePointer& node){ nodeList->eachNode([&](const SharedNodePointer& node) {
// only send to the NodeTypes that are serverType // only send to the NodeTypes that are serverType
if (node->getActiveSocket() && node->getType() == serverType) { if (node->getActiveSocket() && node->getType() == serverType) {
@ -3717,6 +3715,16 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
_octreeQuery.setMaxQueryPacketsPerSecond(0); _octreeQuery.setMaxQueryPacketsPerSecond(0);
} }
// if asked to forceResend, then set the query's position/orientation to be degenerate in a manner
// that will cause our next query to be guarenteed to be different and the server will resend to us
if (forceResend) {
_octreeQuery.setCameraPosition(glm::vec3(-0.1, -0.1, -0.1));
const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0);
_octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE);
_octreeQuery.setCameraNearClip(0.1f);
_octreeQuery.setCameraFarClip(0.1f);
}
// encode the query data // encode the query data
int packetSize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload())); int packetSize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
queryPacket->setPayloadSize(packetSize); queryPacket->setPayloadSize(packetSize);
@ -4133,6 +4141,7 @@ void Application::clearDomainOctreeDetails() {
auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME); skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME);
_recentlyClearedDomain = true;
} }
void Application::domainChanged(const QString& domainHostname) { void Application::domainChanged(const QString& domainHostname) {
@ -4154,7 +4163,7 @@ void Application::nodeAdded(SharedNodePointer node) const {
} }
} }
void Application::nodeActivated(SharedNodePointer node) const { void Application::nodeActivated(SharedNodePointer node) {
if (node->getType() == NodeType::AssetServer) { if (node->getType() == NodeType::AssetServer) {
// asset server just connected - check if we have the asset browser showing // asset server just connected - check if we have the asset browser showing
@ -4173,10 +4182,20 @@ void Application::nodeActivated(SharedNodePointer node) const {
} }
} }
} }
// If we get a new EntityServer activated, do a "forceRedraw" query. This will send a degenerate
// query so that the server will think our next non-degenerate query is "different enough" to send
// us a full scene
if (_recentlyClearedDomain && node->getType() == NodeType::EntityServer) {
_recentlyClearedDomain = false;
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) {
queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions, true);
}
}
} }
void Application::nodeKilled(SharedNodePointer node) { void Application::nodeKilled(SharedNodePointer node) {
// These are here because connecting NodeList::nodeKilled to OctreePacketProcessor::nodeKilled doesn't work: // These are here because connecting NodeList::nodeKilled to OctreePacketProcessor::nodeKilled doesn't work:
// OctreePacketProcessor::nodeKilled is not being called when NodeList::nodeKilled is emitted. // OctreePacketProcessor::nodeKilled is not being called when NodeList::nodeKilled is emitted.
// This may have to do with GenericThread::threadRoutine() blocking the QThread event loop // This may have to do with GenericThread::threadRoutine() blocking the QThread event loop

View file

@ -312,7 +312,7 @@ private slots:
void domainChanged(const QString& domainHostname); void domainChanged(const QString& domainHostname);
void updateWindowTitle() const; void updateWindowTitle() const;
void nodeAdded(SharedNodePointer node) const; void nodeAdded(SharedNodePointer node) const;
void nodeActivated(SharedNodePointer node) const; void nodeActivated(SharedNodePointer node);
void nodeKilled(SharedNodePointer node); void nodeKilled(SharedNodePointer node);
static void packetSent(quint64 length); static void packetSent(quint64 length);
void updateDisplayMode(); void updateDisplayMode();
@ -331,7 +331,7 @@ private:
void updateThreads(float deltaTime); void updateThreads(float deltaTime);
void updateDialogs(float deltaTime) const; void updateDialogs(float deltaTime) const;
void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions); void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend = false);
static void loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum); static void loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum);
glm::vec3 getSunDirection() const; glm::vec3 getSunDirection() const;
@ -520,6 +520,8 @@ private:
std::atomic<uint32_t> _processOctreeStatsCounter { 0 }; std::atomic<uint32_t> _processOctreeStatsCounter { 0 };
bool _keyboardDeviceHasFocus { true }; bool _keyboardDeviceHasFocus { true };
bool _recentlyClearedDomain { false };
}; };
#endif // hifi_Application_h #endif // hifi_Application_h

View file

@ -219,12 +219,12 @@ Menu::Menu() {
// View > First Person // View > First Person
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::FirstPerson, 0, // QML Qt:: Key_P MenuOption::FirstPerson, 0, // QML Qt:: Key_P
false, qApp, SLOT(cameraMenuChanged()))); true, qApp, SLOT(cameraMenuChanged())));
// View > Third Person // View > Third Person
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
MenuOption::ThirdPerson, 0, MenuOption::ThirdPerson, 0,
true, qApp, SLOT(cameraMenuChanged()))); false, qApp, SLOT(cameraMenuChanged())));
// View > Mirror // View > Mirror
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,

View file

@ -32,8 +32,8 @@ AudioInjector::AudioInjector(QObject* parent) :
} }
AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions) : AudioInjector::AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions) :
_audioData(sound->getByteArray()), _audioData(sound.getByteArray()),
_options(injectorOptions) _options(injectorOptions)
{ {

View file

@ -45,7 +45,7 @@ public:
}; };
AudioInjector(QObject* parent); AudioInjector(QObject* parent);
AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions); AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions);
AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions); AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions);
bool isFinished() const { return _state == State::Finished; } bool isFinished() const { return _state == State::Finished; }

View file

@ -23,13 +23,15 @@
#include "AudioRingBuffer.h" #include "AudioRingBuffer.h"
static const QString RING_BUFFER_OVERFLOW_DEBUG { "AudioRingBuffer::writeData has overflown the buffer. Overwriting old data." };
AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode, int numFramesCapacity) : AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode, int numFramesCapacity) :
_frameCapacity(numFramesCapacity), _frameCapacity(numFramesCapacity),
_sampleCapacity(numFrameSamples * numFramesCapacity), _sampleCapacity(numFrameSamples * numFramesCapacity),
_bufferLength(numFrameSamples * (numFramesCapacity + 1)), _bufferLength(numFrameSamples * (numFramesCapacity + 1)),
_numFrameSamples(numFrameSamples), _numFrameSamples(numFrameSamples),
_randomAccessMode(randomAccessMode), _randomAccessMode(randomAccessMode),
_overflowCount(0) _overflowCount(0)
{ {
if (numFrameSamples) { if (numFrameSamples) {
_buffer = new int16_t[_bufferLength]; _buffer = new int16_t[_bufferLength];
@ -41,6 +43,8 @@ _overflowCount(0)
_nextOutput = NULL; _nextOutput = NULL;
_endOfLastWrite = NULL; _endOfLastWrite = NULL;
} }
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG);
}; };
AudioRingBuffer::~AudioRingBuffer() { AudioRingBuffer::~AudioRingBuffer() {
@ -131,8 +135,6 @@ int AudioRingBuffer::writeData(const char* data, int maxSize) {
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete);
_overflowCount++; _overflowCount++;
const QString RING_BUFFER_OVERFLOW_DEBUG { "AudioRingBuffer::writeData has overflown the buffer. Overwriting old data." };
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG);
qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG); qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG);
} }
@ -179,7 +181,12 @@ int AudioRingBuffer::addSilentSamples(int silentSamples) {
if (silentSamples > samplesRoomFor) { if (silentSamples > samplesRoomFor) {
// there's not enough room for this write. write as many silent samples as we have room for // there's not enough room for this write. write as many silent samples as we have room for
silentSamples = samplesRoomFor; silentSamples = samplesRoomFor;
qCDebug(audio) << "Dropping some silent samples to prevent ring buffer overflow";
static const QString DROPPED_SILENT_DEBUG {
"AudioRingBuffer::addSilentSamples dropping silent samples to prevent overflow."
};
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(DROPPED_SILENT_DEBUG);
qCDebug(audio) << qPrintable(DROPPED_SILENT_DEBUG);
} }
// memset zeroes into the buffer, accomodate a wrap around the end // memset zeroes into the buffer, accomodate a wrap around the end
@ -243,7 +250,7 @@ int AudioRingBuffer::writeSamples(ConstIterator source, int maxSamples) {
int samplesToDelete = samplesToCopy - samplesRoomFor; int samplesToDelete = samplesToCopy - samplesRoomFor;
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete);
_overflowCount++; _overflowCount++;
qCDebug(audio) << "Overflowed ring buffer! Overwriting old data"; qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG);
} }
int16_t* bufferLast = _buffer + _bufferLength - 1; int16_t* bufferLast = _buffer + _bufferLength - 1;
@ -264,7 +271,7 @@ int AudioRingBuffer::writeSamplesWithFade(ConstIterator source, int maxSamples,
int samplesToDelete = samplesToCopy - samplesRoomFor; int samplesToDelete = samplesToCopy - samplesRoomFor;
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete);
_overflowCount++; _overflowCount++;
qCDebug(audio) << "Overflowed ring buffer! Overwriting old data"; qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG);
} }
int16_t* bufferLast = _buffer + _bufferLength - 1; int16_t* bufferLast = _buffer + _bufferLength - 1;

View file

@ -27,22 +27,18 @@
#include "AudioLogging.h" #include "AudioLogging.h"
#include "Sound.h" #include "Sound.h"
static int soundMetaTypeId = qRegisterMetaType<Sound*>(); QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) {
return engine->newQObject(new SoundScriptingInterface(in), QScriptEngine::ScriptOwnership);
QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, SharedSoundPointer const& in) {
return engine->newQObject(in.data());
} }
void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer &out) { void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer& out) {
out = SharedSoundPointer(qobject_cast<Sound*>(object.toQObject())); if (auto soundInterface = qobject_cast<SoundScriptingInterface*>(object.toQObject())) {
out = soundInterface->getSound();
}
} }
QScriptValue soundPointerToScriptValue(QScriptEngine* engine, Sound* const& in) { SoundScriptingInterface::SoundScriptingInterface(SharedSoundPointer sound) : _sound(sound) {
return engine->newQObject(in); QObject::connect(sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready);
}
void soundPointerFromScriptValue(const QScriptValue &object, Sound* &out) {
out = qobject_cast<Sound*>(object.toQObject());
} }
Sound::Sound(const QUrl& url, bool isStereo) : Sound::Sound(const QUrl& url, bool isStereo) :

View file

@ -20,18 +20,16 @@
class Sound : public Resource { class Sound : public Resource {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool downloaded READ isReady)
Q_PROPERTY(float duration READ getDuration)
public: public:
Sound(const QUrl& url, bool isStereo = false); Sound(const QUrl& url, bool isStereo = false);
bool isStereo() const { return _isStereo; } bool isStereo() const { return _isStereo; }
bool isReady() const { return _isReady; } bool isReady() const { return _isReady; }
float getDuration() { return _duration; } float getDuration() const { return _duration; }
const QByteArray& getByteArray() { return _byteArray; } const QByteArray& getByteArray() const { return _byteArray; }
signals: signals:
void ready(); void ready();
@ -50,13 +48,28 @@ private:
typedef QSharedPointer<Sound> SharedSoundPointer; typedef QSharedPointer<Sound> SharedSoundPointer;
class SoundScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(bool downloaded READ isReady)
Q_PROPERTY(float duration READ getDuration)
public:
SoundScriptingInterface(SharedSoundPointer sound);
SharedSoundPointer getSound() { return _sound; }
bool isReady() const { return _sound->isReady(); }
float getDuration() { return _sound->getDuration(); }
signals:
void ready();
private:
SharedSoundPointer _sound;
};
Q_DECLARE_METATYPE(SharedSoundPointer) Q_DECLARE_METATYPE(SharedSoundPointer)
QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, SharedSoundPointer const& in); QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in);
void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer &out); void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer& out);
Q_DECLARE_METATYPE(Sound*)
QScriptValue soundPointerToScriptValue(QScriptEngine* engine, Sound* const& in);
void soundPointerFromScriptValue(const QScriptValue& object, Sound* &out);
#endif // hifi_Sound_h #endif // hifi_Sound_h

View file

@ -1033,7 +1033,7 @@ Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) {
Route::Pointer route = parseRoute(channelIt); Route::Pointer route = parseRoute(channelIt);
if (!route) { if (!route) {
qWarning() << "Couldn't parse route"; qWarning() << "Couldn't parse route:" << mapping->name << channelIt;
continue; continue;
} }

View file

@ -71,8 +71,8 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
} }
EntityTreeRenderer::~EntityTreeRenderer() { EntityTreeRenderer::~EntityTreeRenderer() {
// NOTE: we don't need to delete _entitiesScriptEngine because it is registered with the application and has a // NOTE: We don't need to delete _entitiesScriptEngine because
// signal tied to call it's deleteLater on doneRunning // it is registered with ScriptEngines, which will call deleteLater for us.
} }
void EntityTreeRenderer::clear() { void EntityTreeRenderer::clear() {

View file

@ -77,6 +77,12 @@ void GLTextureTransferHelper::setup() {
#endif #endif
} }
void GLTextureTransferHelper::shutdown() {
_canvas->doneCurrent();
_canvas->moveToThreadWithContext(qApp->thread());
}
bool GLTextureTransferHelper::processQueueItems(const Queue& messages) { bool GLTextureTransferHelper::processQueueItems(const Queue& messages) {
for (auto package : messages) { for (auto package : messages) {
glWaitSync(package.fence, 0, GL_TIMEOUT_IGNORED); glWaitSync(package.fence, 0, GL_TIMEOUT_IGNORED);

View file

@ -28,6 +28,7 @@ public:
protected: protected:
void setup() override; void setup() override;
void shutdown() override;
bool processQueueItems(const Queue& messages) override; bool processQueueItems(const Queue& messages) override;
void transferTextureSynchronous(const gpu::Texture& texture); void transferTextureSynchronous(const gpu::Texture& texture);

View file

@ -380,7 +380,7 @@ void Resource::allReferencesCleared() {
_cache->addUnusedResource(self); _cache->addUnusedResource(self);
} else { } else {
delete this; deleteLater();
} }
} }

View file

@ -0,0 +1,52 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
// sdf_text.frag
// fragment shader
//
// Created by Bradley Austin Davis on 2015-02-04
// Based on fragment shader code from
// https://github.com/paulhoux/Cinder-Samples/blob/master/TextRendering/include/text/Text.cpp
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
uniform sampler2D Font;
uniform bool Outline;
uniform vec4 Color;
// the interpolated normal
in vec3 _normal;
in vec2 _texCoord0;
layout(location = 0) out vec4 _fragColor0;
const float gamma = 2.2;
const float smoothing = 32.0;
const float interiorCutoff = 0.8;
const float outlineExpansion = 0.2;
void main() {
// retrieve signed distance
float sdf = texture(Font, _texCoord0).g;
if (Outline) {
if (sdf > interiorCutoff) {
sdf = 1.0 - sdf;
} else {
sdf += outlineExpansion;
}
}
// perform adaptive anti-aliasing of the edges
// The larger we're rendering, the less anti-aliasing we need
float s = smoothing * length(fwidth(_texCoord0));
float w = clamp( s, 0.0, 0.5);
float a = smoothstep(0.5 - w, 0.5 + w, sdf);
// gamma correction for linear attenuation
a = pow(a, 1.0 / gamma);
// discard if unvisible
if (a < 0.01) {
discard;
}
_fragColor0 = vec4(Color.rgb, a);
}

View file

@ -9,6 +9,7 @@
#include "sdf_text3D_vert.h" #include "sdf_text3D_vert.h"
#include "sdf_text3D_frag.h" #include "sdf_text3D_frag.h"
#include "sdf_text3D_overlay_frag.h"
#include "../RenderUtilsLogging.h" #include "../RenderUtilsLogging.h"
#include "FontFamilies.h" #include "FontFamilies.h"
@ -220,10 +221,13 @@ void Font::setupGPU() {
{ {
auto vertexShader = gpu::Shader::createVertex(std::string(sdf_text3D_vert)); auto vertexShader = gpu::Shader::createVertex(std::string(sdf_text3D_vert));
auto pixelShader = gpu::Shader::createPixel(std::string(sdf_text3D_frag)); auto pixelShader = gpu::Shader::createPixel(std::string(sdf_text3D_frag));
auto pixelShaderOverlay = gpu::Shader::createPixel(std::string(sdf_text3D_overlay_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShader, pixelShader); gpu::ShaderPointer program = gpu::Shader::createProgram(vertexShader, pixelShader);
gpu::ShaderPointer programOverlay = gpu::Shader::createProgram(vertexShader, pixelShaderOverlay);
gpu::Shader::BindingSet slotBindings; gpu::Shader::BindingSet slotBindings;
gpu::Shader::makeProgram(*program, slotBindings); gpu::Shader::makeProgram(*program, slotBindings);
gpu::Shader::makeProgram(*programOverlay, slotBindings);
_fontLoc = program->getTextures().findLocation("Font"); _fontLoc = program->getTextures().findLocation("Font");
_outlineLoc = program->getUniforms().findLocation("Outline"); _outlineLoc = program->getUniforms().findLocation("Outline");
@ -237,9 +241,10 @@ void Font::setupGPU() {
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
_pipeline = gpu::Pipeline::create(program, state); _pipeline = gpu::Pipeline::create(program, state);
auto layeredState = std::make_shared<gpu::State>(state->getValues()); auto layeredState = std::make_shared<gpu::State>();
layeredState->setDepthTest(false); layeredState->setCullMode(gpu::State::CULL_BACK);
_layeredPipeline = gpu::Pipeline::create(program, layeredState); layeredState->setDepthTest(true, true, gpu::LESS_EQUAL);
_layeredPipeline = gpu::Pipeline::create(programOverlay, layeredState);
} }
// Sanity checks // Sanity checks

View file

@ -17,7 +17,6 @@
void registerAudioMetaTypes(QScriptEngine* engine) { void registerAudioMetaTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, injectorOptionsToScriptValue, injectorOptionsFromScriptValue); qScriptRegisterMetaType(engine, injectorOptionsToScriptValue, injectorOptionsFromScriptValue);
qScriptRegisterMetaType(engine, soundSharedPointerToScriptValue, soundSharedPointerFromScriptValue); qScriptRegisterMetaType(engine, soundSharedPointerToScriptValue, soundSharedPointerFromScriptValue);
qScriptRegisterMetaType(engine, soundPointerToScriptValue, soundPointerFromScriptValue);
} }
AudioScriptingInterface& AudioScriptingInterface::getInstance() { AudioScriptingInterface& AudioScriptingInterface::getInstance() {
@ -31,13 +30,14 @@ AudioScriptingInterface::AudioScriptingInterface() :
} }
ScriptAudioInjector* AudioScriptingInterface::playSound(Sound* sound, const AudioInjectorOptions& injectorOptions) { ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions) {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
ScriptAudioInjector* injector = NULL; ScriptAudioInjector* injector = NULL;
QMetaObject::invokeMethod(this, "playSound", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(this, "playSound", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(ScriptAudioInjector*, injector), Q_RETURN_ARG(ScriptAudioInjector*, injector),
Q_ARG(Sound*, sound), Q_ARG(const AudioInjectorOptions&, injectorOptions)); Q_ARG(SharedSoundPointer, sound),
Q_ARG(const AudioInjectorOptions&, injectorOptions));
return injector; return injector;
} }

View file

@ -27,7 +27,7 @@ public:
protected: protected:
// this method is protected to stop C++ callers from calling, but invokable from script // this method is protected to stop C++ callers from calling, but invokable from script
Q_INVOKABLE ScriptAudioInjector* playSound(Sound* sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); Q_INVOKABLE ScriptAudioInjector* playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions());
Q_INVOKABLE void setStereoInput(bool stereo); Q_INVOKABLE void setStereoInput(bool stereo);

View file

@ -154,7 +154,11 @@ ScriptEngine::~ScriptEngine() {
void ScriptEngine::disconnectNonEssentialSignals() { void ScriptEngine::disconnectNonEssentialSignals() {
disconnect(); disconnect();
connect(this, &ScriptEngine::doneRunning, thread(), &QThread::quit); QThread* receiver;
// Ensure the thread should be running, and does exist
if (_isRunning && _isThreaded && (receiver = thread())) {
connect(this, &ScriptEngine::doneRunning, receiver, &QThread::quit);
}
} }
void ScriptEngine::runInThread() { void ScriptEngine::runInThread() {
@ -578,7 +582,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
_registeredHandlers[entityID] = RegisteredEventHandlers(); _registeredHandlers[entityID] = RegisteredEventHandlers();
} }
CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName]; CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName];
CallbackData handlerData = {handler, currentEntityIdentifier}; CallbackData handlerData = {handler, currentEntityIdentifier, currentSandboxURL};
handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler(). handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().
} }
@ -795,7 +799,7 @@ void ScriptEngine::timerFired() {
// call the associated JS function, if it exists // call the associated JS function, if it exists
if (timerData.function.isValid()) { if (timerData.function.isValid()) {
callWithEnvironment(timerData.definingEntityIdentifier, timerData.function, timerData.function, QScriptValueList()); callWithEnvironment(timerData.definingEntityIdentifier, timerData.definingSandboxURL, timerData.function, timerData.function, QScriptValueList());
} }
} }
@ -810,7 +814,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int
// make sure the timer stops when the script does // make sure the timer stops when the script does
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop); connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
CallbackData timerData = {function, currentEntityIdentifier}; CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL};
_timerFunctionMap.insert(newTimer, timerData); _timerFunctionMap.insert(newTimer, timerData);
newTimer->start(intervalMS); newTimer->start(intervalMS);
@ -885,19 +889,49 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
return; // bail early return; // bail early
} }
QList<QUrl> urls; QList<QUrl> urls;
bool knowsSensitivity = false;
Qt::CaseSensitivity sensitivity;
auto getSensitivity = [&]() {
if (!knowsSensitivity) {
QString path = currentSandboxURL.path();
QFileInfo upperFI(path.toUpper());
QFileInfo lowerFI(path.toLower());
sensitivity = (upperFI == lowerFI) ? Qt::CaseInsensitive : Qt::CaseSensitive;
knowsSensitivity = true;
}
return sensitivity;
};
// Guard against meaningless query and fragment parts.
// Do NOT use PreferLocalFile as its behavior is unpredictable (e.g., on defaultScriptsLocation())
const auto strippingFlags = QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment;
for (QString file : includeFiles) { for (QString file : includeFiles) {
QUrl thisURL { resolvePath(file) }; QUrl thisURL { resolvePath(file) };
if (!_includedURLs.contains(thisURL)) { if (!_includedURLs.contains(thisURL)) {
urls.append(thisURL); if (!currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") &&
_includedURLs << thisURL; (
} (currentSandboxURL.scheme() != "file") ||
else { (
!thisURL.toString(strippingFlags).startsWith(defaultScriptsLocation().toString(), getSensitivity()) &&
!thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity())
)
)
) {
qCWarning(scriptengine) << "Script.include() ignoring file path" << thisURL << "outside of original entity script" << currentSandboxURL;
} else {
// We could also check here for CORS, but we don't yet.
// It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here.
urls.append(thisURL);
_includedURLs << thisURL;
}
} else {
qCDebug(scriptengine) << "Script.include() ignoring previously included url:" << thisURL; qCDebug(scriptengine) << "Script.include() ignoring previously included url:" << thisURL;
} }
} }
BatchLoader* loader = new BatchLoader(urls); BatchLoader* loader = new BatchLoader(urls);
EntityItemID capturedEntityIdentifier = currentEntityIdentifier; EntityItemID capturedEntityIdentifier = currentEntityIdentifier;
QUrl capturedSandboxURL = currentSandboxURL;
auto evaluateScripts = [=](const QMap<QUrl, QString>& data) { auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {
auto parentURL = _parentURL; auto parentURL = _parentURL;
@ -912,13 +946,13 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
auto operation = [&]() { auto operation = [&]() {
evaluate(contents, url.toString()); evaluate(contents, url.toString());
}; };
doWithEnvironment(capturedEntityIdentifier, operation); doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation);
} }
} }
_parentURL = parentURL; _parentURL = parentURL;
if (callback.isFunction()) { if (callback.isFunction()) {
callWithEnvironment(capturedEntityIdentifier, QScriptValue(callback), QScriptValue(), QScriptValueList()); callWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, QScriptValue(callback), QScriptValue(), QScriptValueList());
} }
loader->deleteLater(); loader->deleteLater();
@ -996,10 +1030,11 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
CallbackList handlersForEvent = handlersOnEntity[eventName]; CallbackList handlersForEvent = handlersOnEntity[eventName];
if (!handlersForEvent.isEmpty()) { if (!handlersForEvent.isEmpty()) {
for (int i = 0; i < handlersForEvent.count(); ++i) { for (int i = 0; i < handlersForEvent.count(); ++i) {
// handlersForEvent[i] can tonain many handlers that may have each been added by different interface or entity scripts, // handlersForEvent[i] can contain many handlers that may have each been added by different interface or entity scripts,
// and the entity scripts may be for entities other than the one this is a handler for. // and the entity scripts may be for entities other than the one this is a handler for.
// Fortunately, the definingEntityIdentifier captured the entity script id (if any) when the handler was added. // Fortunately, the definingEntityIdentifier captured the entity script id (if any) when the handler was added.
callWithEnvironment(handlersForEvent[i].definingEntityIdentifier, handlersForEvent[i].function, QScriptValue(), eventHandlerArgs); CallbackData& handler = handlersForEvent[i];
callWithEnvironment(handler.definingEntityIdentifier, handler.definingSandboxURL, handler.function, QScriptValue(), eventHandlerArgs);
} }
} }
} }
@ -1089,9 +1124,19 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
} }
if (!testConstructor.isFunction()) { if (!testConstructor.isFunction()) {
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID << "\n" QString testConstructorType = QString(testConstructor.toVariant().typeName());
" NOT CONSTRUCTOR\n" if (testConstructorType == "") {
" SCRIPT:" << scriptOrURL; testConstructorType = "empty";
}
QString testConstructorValue = testConstructor.toString();
const int maxTestConstructorValueSize = 80;
if (testConstructorValue.size() > maxTestConstructorValueSize) {
testConstructorValue = testConstructorValue.mid(0, maxTestConstructorValueSize) + "...";
}
qCDebug(scriptengine) << "Error -- ScriptEngine::loadEntityScript() entity:" << entityID
<< "failed to load entity script -- expected a function, got " + testConstructorType
<< "," << testConstructorValue
<< "," << scriptOrURL;
if (!isFileUrl) { if (!isFileUrl) {
scriptCache->addScriptToBadScriptList(scriptOrURL); scriptCache->addScriptToBadScriptList(scriptOrURL);
@ -1106,13 +1151,14 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch();
} }
QScriptValue entityScriptConstructor, entityScriptObject; QScriptValue entityScriptConstructor, entityScriptObject;
QUrl sandboxURL = currentSandboxURL.isEmpty() ? scriptOrURL : currentSandboxURL;
auto initialization = [&]{ auto initialization = [&]{
entityScriptConstructor = evaluate(contents, fileName); entityScriptConstructor = evaluate(contents, fileName);
entityScriptObject = entityScriptConstructor.construct(); entityScriptObject = entityScriptConstructor.construct();
}; };
doWithEnvironment(entityID, initialization); doWithEnvironment(entityID, sandboxURL, initialization);
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified, sandboxURL };
_entityScripts[entityID] = newDetails; _entityScripts[entityID] = newDetails;
if (isURL) { if (isURL) {
setParentURL(""); setParentURL("");
@ -1201,9 +1247,11 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
// Even if entityID is supplied as currentEntityIdentifier, this still documents the source // Even if entityID is supplied as currentEntityIdentifier, this still documents the source
// of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different // of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different
// global values for different entity scripts). // global values for different entity scripts).
void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, std::function<void()> operation) { void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation) {
EntityItemID oldIdentifier = currentEntityIdentifier; EntityItemID oldIdentifier = currentEntityIdentifier;
QUrl oldSandboxURL = currentSandboxURL;
currentEntityIdentifier = entityID; currentEntityIdentifier = entityID;
currentSandboxURL = sandboxURL;
#if DEBUG_CURRENT_ENTITY #if DEBUG_CURRENT_ENTITY
QScriptValue oldData = this->globalObject().property("debugEntityID"); QScriptValue oldData = this->globalObject().property("debugEntityID");
@ -1215,12 +1263,13 @@ void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, std::function
#endif #endif
currentEntityIdentifier = oldIdentifier; currentEntityIdentifier = oldIdentifier;
currentSandboxURL = oldSandboxURL;
} }
void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args) { void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args) {
auto operation = [&]() { auto operation = [&]() {
function.call(thisObject, args); function.call(thisObject, args);
}; };
doWithEnvironment(entityID, operation); doWithEnvironment(entityID, sandboxURL, operation);
} }
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) { void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) {
@ -1249,7 +1298,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
QScriptValueList args; QScriptValueList args;
args << entityID.toScriptValue(this); args << entityID.toScriptValue(this);
args << qScriptValueFromSequence(this, params); args << qScriptValueFromSequence(this, params);
callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args); callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args);
} }
} }
@ -1281,7 +1330,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
QScriptValueList args; QScriptValueList args;
args << entityID.toScriptValue(this); args << entityID.toScriptValue(this);
args << event.toScriptValue(this); args << event.toScriptValue(this);
callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args); callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args);
} }
} }
} }
@ -1315,7 +1364,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
args << entityID.toScriptValue(this); args << entityID.toScriptValue(this);
args << otherID.toScriptValue(this); args << otherID.toScriptValue(this);
args << collisionToScriptValue(this, collision); args << collisionToScriptValue(this, collision);
callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args); callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args);
} }
} }
} }

View file

@ -47,6 +47,7 @@ class CallbackData {
public: public:
QScriptValue function; QScriptValue function;
EntityItemID definingEntityIdentifier; EntityItemID definingEntityIdentifier;
QUrl definingSandboxURL;
}; };
typedef QList<CallbackData> CallbackList; typedef QList<CallbackData> CallbackList;
@ -57,6 +58,7 @@ public:
QString scriptText; QString scriptText;
QScriptValue scriptObject; QScriptValue scriptObject;
int64_t lastModified; int64_t lastModified;
QUrl definingSandboxURL;
}; };
class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider { class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider {
@ -214,8 +216,9 @@ protected:
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success); Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success);
EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution. EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution.
void doWithEnvironment(const EntityItemID& entityID, std::function<void()> operation); QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty.
void callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args); void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation);
void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
friend class ScriptEngines; friend class ScriptEngines;
static std::atomic<bool> _stoppingAllScripts; static std::atomic<bool> _stoppingAllScripts;

View file

@ -43,6 +43,11 @@ public:
auto result = static_cast<MenuUserData*>(object->userData(USER_DATA_ID)); auto result = static_cast<MenuUserData*>(object->userData(USER_DATA_ID));
if (!result) { if (!result) {
qWarning() << "Unable to find MenuUserData for object " << object; qWarning() << "Unable to find MenuUserData for object " << object;
if (auto action = dynamic_cast<QAction*>(object)) {
qWarning() << action->text();
} else if (auto menu = dynamic_cast<QMenu*>(object)) {
qWarning() << menu->title();
}
return nullptr; return nullptr;
} }
return result; return result;