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()) {
// we're done with this sound object - so set our pointer back to NULL
// and our sent bytes back to zero
_avatarSound = NULL;
_avatarSound.clear();
_numAvatarSoundSentBytes = 0;
}
}

View file

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

View file

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

View file

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

View file

@ -867,6 +867,14 @@ function saveSettings() {
// grab a JSON representation of the form via form2js
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);
// 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();
const QVariant* settingsPasswordVariant = valueForKeyPath(settingsMap, BASIC_AUTH_PASSWORD_KEY_PATH);
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;
}
}

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
_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_FILE_NAME_KEY = "persistFilename";
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

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;
}
.property.checkbox {
width: auto;
}
.property label {
display: table-cell;
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;
bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY;
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 (queryIsDue || viewIsDifferentEnough) {
_lastQueriedTime = now;
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) {
queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions);
}
@ -3561,7 +3559,7 @@ int Application::sendNackPackets() {
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) {
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);
nodeList->eachNode([&](const SharedNodePointer& node){
nodeList->eachNode([&](const SharedNodePointer& node) {
// only send to the NodeTypes that are serverType
if (node->getActiveSocket() && node->getType() == serverType) {
@ -3717,6 +3715,16 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
_octreeQuery.setMaxQueryPacketsPerSecond(0);
}
// if asked to forceResend, then set the query's position/orientation to be degenerate in a manner
// 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
int packetSize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
queryPacket->setPayloadSize(packetSize);
@ -4133,6 +4141,7 @@ void Application::clearDomainOctreeDetails() {
auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME);
_recentlyClearedDomain = true;
}
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) {
// 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) {
// These are here because connecting NodeList::nodeKilled to OctreePacketProcessor::nodeKilled doesn't work:
// OctreePacketProcessor::nodeKilled is not being called when NodeList::nodeKilled is emitted.
// 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 updateWindowTitle() const;
void nodeAdded(SharedNodePointer node) const;
void nodeActivated(SharedNodePointer node) const;
void nodeActivated(SharedNodePointer node);
void nodeKilled(SharedNodePointer node);
static void packetSent(quint64 length);
void updateDisplayMode();
@ -331,7 +331,7 @@ private:
void updateThreads(float deltaTime);
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);
glm::vec3 getSunDirection() const;
@ -520,6 +520,8 @@ private:
std::atomic<uint32_t> _processOctreeStatsCounter { 0 };
bool _keyboardDeviceHasFocus { true };
bool _recentlyClearedDomain { false };
};
#endif // hifi_Application_h

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,18 +20,16 @@
class Sound : public Resource {
Q_OBJECT
Q_PROPERTY(bool downloaded READ isReady)
Q_PROPERTY(float duration READ getDuration)
public:
Sound(const QUrl& url, bool isStereo = false);
bool isStereo() const { return _isStereo; }
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:
void ready();
@ -50,13 +48,28 @@ private:
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)
QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, SharedSoundPointer const& in);
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);
QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in);
void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer& out);
#endif // hifi_Sound_h

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,7 +17,6 @@
void registerAudioMetaTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, injectorOptionsToScriptValue, injectorOptionsFromScriptValue);
qScriptRegisterMetaType(engine, soundSharedPointerToScriptValue, soundSharedPointerFromScriptValue);
qScriptRegisterMetaType(engine, soundPointerToScriptValue, soundPointerFromScriptValue);
}
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()) {
ScriptAudioInjector* injector = NULL;
QMetaObject::invokeMethod(this, "playSound", Qt::BlockingQueuedConnection,
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;
}

View file

@ -27,7 +27,7 @@ public:
protected:
// 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);

View file

@ -154,7 +154,11 @@ ScriptEngine::~ScriptEngine() {
void ScriptEngine::disconnectNonEssentialSignals() {
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() {
@ -578,7 +582,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
_registeredHandlers[entityID] = RegisteredEventHandlers();
}
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().
}
@ -795,7 +799,7 @@ void ScriptEngine::timerFired() {
// call the associated JS function, if it exists
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
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
CallbackData timerData = {function, currentEntityIdentifier};
CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL};
_timerFunctionMap.insert(newTimer, timerData);
newTimer->start(intervalMS);
@ -885,19 +889,49 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
return; // bail early
}
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) {
QUrl thisURL { resolvePath(file) };
if (!_includedURLs.contains(thisURL)) {
urls.append(thisURL);
_includedURLs << thisURL;
}
else {
if (!currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") &&
(
(currentSandboxURL.scheme() != "file") ||
(
!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;
}
}
BatchLoader* loader = new BatchLoader(urls);
EntityItemID capturedEntityIdentifier = currentEntityIdentifier;
QUrl capturedSandboxURL = currentSandboxURL;
auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {
auto parentURL = _parentURL;
@ -912,13 +946,13 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
auto operation = [&]() {
evaluate(contents, url.toString());
};
doWithEnvironment(capturedEntityIdentifier, operation);
doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation);
}
}
_parentURL = parentURL;
if (callback.isFunction()) {
callWithEnvironment(capturedEntityIdentifier, QScriptValue(callback), QScriptValue(), QScriptValueList());
callWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, QScriptValue(callback), QScriptValue(), QScriptValueList());
}
loader->deleteLater();
@ -996,10 +1030,11 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
CallbackList handlersForEvent = handlersOnEntity[eventName];
if (!handlersForEvent.isEmpty()) {
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.
// 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()) {
qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID << "\n"
" NOT CONSTRUCTOR\n"
" SCRIPT:" << scriptOrURL;
QString testConstructorType = QString(testConstructor.toVariant().typeName());
if (testConstructorType == "") {
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) {
scriptCache->addScriptToBadScriptList(scriptOrURL);
@ -1106,13 +1151,14 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch();
}
QScriptValue entityScriptConstructor, entityScriptObject;
QUrl sandboxURL = currentSandboxURL.isEmpty() ? scriptOrURL : currentSandboxURL;
auto initialization = [&]{
entityScriptConstructor = evaluate(contents, fileName);
entityScriptObject = entityScriptConstructor.construct();
};
doWithEnvironment(entityID, initialization);
doWithEnvironment(entityID, sandboxURL, initialization);
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified };
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified, sandboxURL };
_entityScripts[entityID] = newDetails;
if (isURL) {
setParentURL("");
@ -1201,9 +1247,11 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
// 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
// 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;
QUrl oldSandboxURL = currentSandboxURL;
currentEntityIdentifier = entityID;
currentSandboxURL = sandboxURL;
#if DEBUG_CURRENT_ENTITY
QScriptValue oldData = this->globalObject().property("debugEntityID");
@ -1215,12 +1263,13 @@ void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, std::function
#endif
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 = [&]() {
function.call(thisObject, args);
};
doWithEnvironment(entityID, operation);
doWithEnvironment(entityID, sandboxURL, operation);
}
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;
args << entityID.toScriptValue(this);
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;
args << entityID.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 << otherID.toScriptValue(this);
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:
QScriptValue function;
EntityItemID definingEntityIdentifier;
QUrl definingSandboxURL;
};
typedef QList<CallbackData> CallbackList;
@ -57,6 +58,7 @@ public:
QString scriptText;
QScriptValue scriptObject;
int64_t lastModified;
QUrl definingSandboxURL;
};
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);
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);
void callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty.
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;
static std::atomic<bool> _stoppingAllScripts;

View file

@ -43,6 +43,11 @@ public:
auto result = static_cast<MenuUserData*>(object->userData(USER_DATA_ID));
if (!result) {
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 result;